1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package android.hardware.camera2;
17 
18 import android.annotation.IntDef;
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.ServiceConnection;
25 import android.graphics.ImageFormat;
26 import android.hardware.camera2.extension.IAdvancedExtenderImpl;
27 import android.hardware.camera2.extension.ICameraExtensionsProxyService;
28 import android.hardware.camera2.extension.IImageCaptureExtenderImpl;
29 import android.hardware.camera2.extension.IInitializeSessionCallback;
30 import android.hardware.camera2.extension.IPreviewExtenderImpl;
31 import android.hardware.camera2.extension.LatencyRange;
32 import android.hardware.camera2.extension.SizeList;
33 import android.hardware.camera2.impl.CameraExtensionUtils;
34 import android.hardware.camera2.impl.CameraMetadataNative;
35 import android.hardware.camera2.params.ExtensionSessionConfiguration;
36 import android.hardware.camera2.params.StreamConfigurationMap;
37 import android.os.Binder;
38 import android.os.ConditionVariable;
39 import android.os.IBinder;
40 import android.os.RemoteException;
41 import android.os.SystemProperties;
42 import android.util.Log;
43 import android.util.Pair;
44 import android.util.Range;
45 import android.util.Size;
46 
47 import java.lang.annotation.Retention;
48 import java.lang.annotation.RetentionPolicy;
49 import java.util.ArrayList;
50 import java.util.Arrays;
51 import java.util.Collections;
52 import java.util.HashSet;
53 import java.util.List;
54 import java.util.Map;
55 import java.util.Objects;
56 import java.util.Set;
57 import java.util.concurrent.Future;
58 import java.util.concurrent.TimeUnit;
59 import java.util.concurrent.TimeoutException;
60 
61 /**
62  * <p>Allows clients to query availability and supported resolutions of camera extensions.</p>
63  *
64  * <p>Camera extensions give camera clients access to device-specific algorithms and sequences that
65  * can improve the overall image quality of snapshots in various cases such as low light, selfies,
66  * portraits, and scenes that can benefit from enhanced dynamic range. Often such sophisticated
67  * processing sequences will rely on multiple camera frames as input and will produce a single
68  * output.</p>
69  *
70  * <p>Camera extensions are not guaranteed to be present on all devices so camera clients must
71  * query for their availability via {@link CameraExtensionCharacteristics#getSupportedExtensions()}.
72  * </p>
73  *
74  * <p>In order to use any available camera extension, camera clients must create a corresponding
75  * {@link CameraExtensionSession} via
76  * {@link CameraDevice#createExtensionSession(ExtensionSessionConfiguration)}</p>
77  *
78  * <p>Camera clients must be aware that device-specific camera extensions may support only a
79  * subset of the available camera resolutions and must first query
80  * {@link CameraExtensionCharacteristics#getExtensionSupportedSizes(int, int)} for supported
81  * single high-quality request output sizes and
82  * {@link CameraExtensionCharacteristics#getExtensionSupportedSizes(int, Class)} for supported
83  * repeating request output sizes.</p>
84  *
85  * <p>The extension characteristics for a given device are expected to remain static under
86  * normal operating conditions.</p>
87  *
88  * @see CameraManager#getCameraExtensionCharacteristics(String)
89  */
90 public final class CameraExtensionCharacteristics {
91     private static final String TAG = "CameraExtensionCharacteristics";
92 
93     /**
94      * Device-specific extension implementation for automatic selection of particular extension
95      * such as HDR or NIGHT depending on the current lighting and environment conditions.
96      */
97     public static final int EXTENSION_AUTOMATIC = 0;
98 
99     /**
100      * Device-specific extension implementation which tends to smooth the skin and apply other
101      * cosmetic effects to people's faces.
102      */
103     public static final int EXTENSION_FACE_RETOUCH = 1;
104 
105     /**
106      * Device-specific extension implementation which tends to smooth the skin and apply other
107      * cosmetic effects to people's faces.
108      *
109      * @deprecated Use {@link #EXTENSION_FACE_RETOUCH} instead.
110      */
111     public @Deprecated static final int EXTENSION_BEAUTY = EXTENSION_FACE_RETOUCH;
112 
113     /**
114      * Device-specific extension implementation which can blur certain regions of the final image
115      * thereby "enhancing" focus for all remaining non-blurred parts.
116      */
117     public static final int EXTENSION_BOKEH = 2;
118 
119     /**
120      * Device-specific extension implementation for enhancing the dynamic range of the
121      * final image.
122      */
123     public static final int EXTENSION_HDR = 3;
124 
125     /**
126      * Device-specific extension implementation that aims to suppress noise and improve the
127      * overall image quality under low light conditions.
128      */
129     public static final int EXTENSION_NIGHT = 4;
130 
131     /**
132      * @hide
133      */
134     @Retention(RetentionPolicy.SOURCE)
135     @IntDef(flag = true, value = {EXTENSION_AUTOMATIC,
136                 EXTENSION_FACE_RETOUCH,
137                 EXTENSION_BOKEH,
138                 EXTENSION_HDR,
139                 EXTENSION_NIGHT})
140     public @interface Extension {
141     }
142 
143     /**
144      * Default camera output in case additional processing from CameraX extensions is not needed
145      *
146      * @hide
147      */
148     public static final int NON_PROCESSING_INPUT_FORMAT = ImageFormat.PRIVATE;
149 
150     /**
151      * CameraX extensions require YUV_420_888 as default input for processing at the moment
152      *
153      * @hide
154      */
155     public static final int PROCESSING_INPUT_FORMAT = ImageFormat.YUV_420_888;
156 
157     private static final @Extension
158     int[] EXTENSION_LIST = new int[]{
159             EXTENSION_AUTOMATIC,
160             EXTENSION_FACE_RETOUCH,
161             EXTENSION_BOKEH,
162             EXTENSION_HDR,
163             EXTENSION_NIGHT};
164 
165     private final Context mContext;
166     private final String mCameraId;
167     private final Map<String, CameraCharacteristics> mCharacteristicsMap;
168     private final Map<String, CameraMetadataNative> mCharacteristicsMapNative;
169 
170     /**
171      * @hide
172      */
CameraExtensionCharacteristics(Context context, String cameraId, Map<String, CameraCharacteristics> characteristicsMap)173     public CameraExtensionCharacteristics(Context context, String cameraId,
174             Map<String, CameraCharacteristics> characteristicsMap) {
175         mContext = context;
176         mCameraId = cameraId;
177         mCharacteristicsMap = characteristicsMap;
178         mCharacteristicsMapNative =
179                 CameraExtensionUtils.getCharacteristicsMapNative(characteristicsMap);
180     }
181 
getSupportedSizes(List<SizeList> sizesList, Integer format)182     private static ArrayList<Size> getSupportedSizes(List<SizeList> sizesList,
183             Integer format) {
184         ArrayList<Size> ret = new ArrayList<>();
185         if ((sizesList != null) && (!sizesList.isEmpty())) {
186             for (SizeList entry : sizesList) {
187                 if ((entry.format == format) && !entry.sizes.isEmpty()) {
188                     for (android.hardware.camera2.extension.Size sz : entry.sizes) {
189                         ret.add(new Size(sz.width, sz.height));
190                     }
191                     return ret;
192                 }
193             }
194         }
195 
196         return ret;
197     }
198 
generateSupportedSizes(List<SizeList> sizesList, Integer format, StreamConfigurationMap streamMap)199     private static List<Size> generateSupportedSizes(List<SizeList> sizesList,
200                                                      Integer format,
201                                                      StreamConfigurationMap streamMap) {
202         // Per API contract it is assumed that the extension is able to support all
203         // camera advertised sizes for a given format in case it doesn't return
204         // a valid non-empty size list.
205         ArrayList<Size> ret = getSupportedSizes(sizesList, format);
206         Size[] supportedSizes = streamMap.getOutputSizes(format);
207         if ((ret.isEmpty()) && (supportedSizes != null)) {
208             ret.addAll(Arrays.asList(supportedSizes));
209         }
210         return ret;
211     }
212 
generateJpegSupportedSizes(List<SizeList> sizesList, StreamConfigurationMap streamMap)213     private static List<Size> generateJpegSupportedSizes(List<SizeList> sizesList,
214             StreamConfigurationMap streamMap) {
215         ArrayList<Size> extensionSizes = getSupportedSizes(sizesList, ImageFormat.YUV_420_888);
216         HashSet<Size> supportedSizes = extensionSizes.isEmpty() ? new HashSet<>(Arrays.asList(
217                 streamMap.getOutputSizes(ImageFormat.YUV_420_888))) : new HashSet<>(extensionSizes);
218         HashSet<Size> supportedJpegSizes = new HashSet<>(Arrays.asList(streamMap.getOutputSizes(
219                 ImageFormat.JPEG)));
220         supportedSizes.retainAll(supportedJpegSizes);
221 
222         return new ArrayList<>(supportedSizes);
223     }
224 
225     /**
226      * A per-process global camera extension manager instance, to track and
227      * initialize/release extensions depending on client activity.
228      */
229     private static final class CameraExtensionManagerGlobal {
230         private static final String TAG = "CameraExtensionManagerGlobal";
231         private static final String PROXY_PACKAGE_NAME = "com.android.cameraextensions";
232         private static final String PROXY_SERVICE_NAME =
233                 "com.android.cameraextensions.CameraExtensionsProxyService";
234 
235         // Singleton instance
236         private static final CameraExtensionManagerGlobal GLOBAL_CAMERA_MANAGER =
237                 new CameraExtensionManagerGlobal();
238         private final Object mLock = new Object();
239         private final int PROXY_SERVICE_DELAY_MS = 2000;
240         private InitializerFuture mInitFuture = null;
241         private ServiceConnection mConnection = null;
242         private int mConnectionCount = 0;
243         private ICameraExtensionsProxyService mProxy = null;
244         private boolean mSupportsAdvancedExtensions = false;
245 
246         // Singleton, don't allow construction
CameraExtensionManagerGlobal()247         private CameraExtensionManagerGlobal() {}
248 
get()249         public static CameraExtensionManagerGlobal get() {
250             return GLOBAL_CAMERA_MANAGER;
251         }
252 
releaseProxyConnectionLocked(Context ctx)253         private void releaseProxyConnectionLocked(Context ctx) {
254             if (mConnection != null ) {
255                 ctx.unbindService(mConnection);
256                 mConnection = null;
257                 mProxy = null;
258                 mConnectionCount = 0;
259             }
260         }
261 
connectToProxyLocked(Context ctx)262         private void connectToProxyLocked(Context ctx) {
263             if (mConnection == null) {
264                 Intent intent = new Intent();
265                 intent.setClassName(PROXY_PACKAGE_NAME, PROXY_SERVICE_NAME);
266                 String vendorProxyPackage = SystemProperties.get(
267                     "ro.vendor.camera.extensions.package");
268                 String vendorProxyService = SystemProperties.get(
269                     "ro.vendor.camera.extensions.service");
270                 if (!vendorProxyPackage.isEmpty() && !vendorProxyService.isEmpty()) {
271                   Log.v(TAG,
272                       "Choosing the vendor camera extensions proxy package: "
273                       + vendorProxyPackage);
274                   Log.v(TAG,
275                       "Choosing the vendor camera extensions proxy service: "
276                       + vendorProxyService);
277                   intent.setClassName(vendorProxyPackage, vendorProxyService);
278                 }
279                 mInitFuture = new InitializerFuture();
280                 mConnection = new ServiceConnection() {
281                     @Override
282                     public void onServiceDisconnected(ComponentName component) {
283                         mConnection = null;
284                         mProxy = null;
285                     }
286 
287                     @Override
288                     public void onServiceConnected(ComponentName component, IBinder binder) {
289                         mProxy = ICameraExtensionsProxyService.Stub.asInterface(binder);
290                         if (mProxy == null) {
291                             throw new IllegalStateException("Camera Proxy service is null");
292                         }
293                         try {
294                             mSupportsAdvancedExtensions = mProxy.advancedExtensionsSupported();
295                         } catch (RemoteException e) {
296                             Log.e(TAG, "Remote IPC failed!");
297                         }
298                         mInitFuture.setStatus(true);
299                     }
300                 };
301                 ctx.bindService(intent, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT |
302                         Context.BIND_ABOVE_CLIENT | Context.BIND_NOT_VISIBLE,
303                         android.os.AsyncTask.THREAD_POOL_EXECUTOR, mConnection);
304 
305                 try {
306                     mInitFuture.get(PROXY_SERVICE_DELAY_MS, TimeUnit.MILLISECONDS);
307                 } catch (TimeoutException e) {
308                     Log.e(TAG, "Timed out while initializing proxy service!");
309                 }
310             }
311         }
312 
313         private static class InitializerFuture implements Future<Boolean> {
314             private volatile Boolean mStatus;
315             ConditionVariable mCondVar = new ConditionVariable(/*opened*/false);
316 
setStatus(boolean status)317             public void setStatus(boolean status) {
318                 mStatus = status;
319                 mCondVar.open();
320             }
321 
322             @Override
cancel(boolean mayInterruptIfRunning)323             public boolean cancel(boolean mayInterruptIfRunning) {
324                 return false; // don't allow canceling this task
325             }
326 
327             @Override
isCancelled()328             public boolean isCancelled() {
329                 return false; // can never cancel this task
330             }
331 
332             @Override
isDone()333             public boolean isDone() {
334                 return mStatus != null;
335             }
336 
337             @Override
get()338             public Boolean get() {
339                 mCondVar.block();
340                 return mStatus;
341             }
342 
343             @Override
get(long timeout, TimeUnit unit)344             public Boolean get(long timeout, TimeUnit unit) throws TimeoutException {
345                 long timeoutMs = unit.convert(timeout, TimeUnit.MILLISECONDS);
346                 if (!mCondVar.block(timeoutMs)) {
347                     throw new TimeoutException(
348                             "Failed to receive status after " + timeout + " " + unit);
349                 }
350 
351                 if (mStatus == null) {
352                     throw new AssertionError();
353                 }
354                 return mStatus;
355             }
356         }
357 
registerClient(Context ctx, IBinder token)358         public boolean registerClient(Context ctx, IBinder token) {
359             synchronized (mLock) {
360                 boolean ret = false;
361                 connectToProxyLocked(ctx);
362                 if (mProxy == null) {
363                     return false;
364                 }
365                 mConnectionCount++;
366 
367                 try {
368                     ret = mProxy.registerClient(token);
369                 } catch (RemoteException e) {
370                     Log.e(TAG, "Failed to initialize extension! Extension service does "
371                             + " not respond!");
372                 }
373                 if (!ret) {
374                     mConnectionCount--;
375                 }
376 
377                 if (mConnectionCount <= 0) {
378                     releaseProxyConnectionLocked(ctx);
379                 }
380 
381                 return ret;
382             }
383         }
384 
unregisterClient(Context ctx, IBinder token)385         public void unregisterClient(Context ctx, IBinder token) {
386             synchronized (mLock) {
387                 if (mProxy != null) {
388                     try {
389                         mProxy.unregisterClient(token);
390                     } catch (RemoteException e) {
391                         Log.e(TAG, "Failed to de-initialize extension! Extension service does"
392                                 + " not respond!");
393                     } finally {
394                         mConnectionCount--;
395                         if (mConnectionCount <= 0) {
396                             releaseProxyConnectionLocked(ctx);
397                         }
398                     }
399                 }
400             }
401         }
402 
initializeSession(IInitializeSessionCallback cb)403         public void initializeSession(IInitializeSessionCallback cb) throws RemoteException {
404             synchronized (mLock) {
405                 if (mProxy != null) {
406                     mProxy.initializeSession(cb);
407                 }
408             }
409         }
410 
releaseSession()411         public void releaseSession() {
412             synchronized (mLock) {
413                 if (mProxy != null) {
414                     try {
415                         mProxy.releaseSession();
416                     } catch (RemoteException e) {
417                         Log.e(TAG, "Failed to release session! Extension service does"
418                                 + " not respond!");
419                     }
420                 }
421             }
422         }
423 
areAdvancedExtensionsSupported()424         public boolean areAdvancedExtensionsSupported() {
425             return mSupportsAdvancedExtensions;
426         }
427 
initializePreviewExtension(int extensionType)428         public IPreviewExtenderImpl initializePreviewExtension(int extensionType)
429                 throws RemoteException {
430             synchronized (mLock) {
431                 if (mProxy != null) {
432                     return mProxy.initializePreviewExtension(extensionType);
433                 } else {
434                     return null;
435                 }
436             }
437         }
438 
initializeImageExtension(int extensionType)439         public IImageCaptureExtenderImpl initializeImageExtension(int extensionType)
440                 throws RemoteException {
441             synchronized (mLock) {
442                 if (mProxy != null) {
443                     return mProxy.initializeImageExtension(extensionType);
444                 } else {
445                     return null;
446                 }
447             }
448         }
449 
initializeAdvancedExtension(int extensionType)450         public IAdvancedExtenderImpl initializeAdvancedExtension(int extensionType)
451                 throws RemoteException {
452             synchronized (mLock) {
453                 if (mProxy != null) {
454                     return mProxy.initializeAdvancedExtension(extensionType);
455                 } else {
456                     return null;
457                 }
458             }
459         }
460     }
461 
462     /**
463      * @hide
464      */
registerClient(Context ctx, IBinder token)465     public static boolean registerClient(Context ctx, IBinder token) {
466         return CameraExtensionManagerGlobal.get().registerClient(ctx, token);
467     }
468 
469     /**
470      * @hide
471      */
unregisterClient(Context ctx, IBinder token)472     public static void unregisterClient(Context ctx, IBinder token) {
473         CameraExtensionManagerGlobal.get().unregisterClient(ctx, token);
474     }
475 
476     /**
477      * @hide
478      */
initializeSession(IInitializeSessionCallback cb)479     public static void initializeSession(IInitializeSessionCallback cb) throws RemoteException {
480         CameraExtensionManagerGlobal.get().initializeSession(cb);
481     }
482 
483     /**
484      * @hide
485      */
releaseSession()486     public static void releaseSession() {
487         CameraExtensionManagerGlobal.get().releaseSession();
488     }
489 
490     /**
491      * @hide
492      */
areAdvancedExtensionsSupported()493     public static boolean areAdvancedExtensionsSupported() {
494         return CameraExtensionManagerGlobal.get().areAdvancedExtensionsSupported();
495     }
496 
497     /**
498      * @hide
499      */
isExtensionSupported(String cameraId, int extensionType, Map<String, CameraMetadataNative> characteristicsMap)500     public static boolean isExtensionSupported(String cameraId, int extensionType,
501             Map<String, CameraMetadataNative> characteristicsMap) {
502         if (areAdvancedExtensionsSupported()) {
503             try {
504                 IAdvancedExtenderImpl extender = initializeAdvancedExtension(extensionType);
505                 return extender.isExtensionAvailable(cameraId, characteristicsMap);
506             } catch (RemoteException e) {
507                 Log.e(TAG, "Failed to query extension availability! Extension service does not"
508                         + " respond!");
509                 return false;
510             }
511         } else {
512             Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders;
513             try {
514                 extenders = initializeExtension(extensionType);
515             } catch (IllegalArgumentException e) {
516                 return false;
517             }
518 
519             try {
520                 return extenders.first.isExtensionAvailable(cameraId,
521                         characteristicsMap.get(cameraId))
522                         && extenders.second.isExtensionAvailable(cameraId,
523                         characteristicsMap.get(cameraId));
524             } catch (RemoteException e) {
525                 Log.e(TAG, "Failed to query extension availability! Extension service does not"
526                         + " respond!");
527                 return false;
528             }
529         }
530     }
531 
532     /**
533      * @hide
534      */
initializeAdvancedExtension(@xtension int extensionType)535     public static IAdvancedExtenderImpl initializeAdvancedExtension(@Extension int extensionType) {
536         IAdvancedExtenderImpl extender;
537         try {
538             extender = CameraExtensionManagerGlobal.get().initializeAdvancedExtension(
539                     extensionType);
540         } catch (RemoteException e) {
541             throw new IllegalStateException("Failed to initialize extension: " + extensionType);
542         }
543 
544         if (extender == null) {
545             throw new IllegalArgumentException("Unknown extension: " + extensionType);
546         }
547 
548         return extender;
549     }
550 
551     /**
552      * @hide
553      */
initializeExtension( @xtension int extensionType)554     public static Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> initializeExtension(
555             @Extension int extensionType) {
556         IPreviewExtenderImpl previewExtender;
557         IImageCaptureExtenderImpl imageExtender;
558         try {
559             previewExtender =
560                     CameraExtensionManagerGlobal.get().initializePreviewExtension(extensionType);
561             imageExtender =
562                     CameraExtensionManagerGlobal.get().initializeImageExtension(extensionType);
563         } catch (RemoteException e) {
564             throw new IllegalStateException("Failed to initialize extension: " + extensionType);
565         }
566         if ((imageExtender == null) || (previewExtender == null)) {
567             throw new IllegalArgumentException("Unknown extension: " + extensionType);
568         }
569 
570         return new Pair<>(previewExtender, imageExtender);
571     }
572 
isOutputSupportedFor(Class<T> klass)573     private static <T> boolean isOutputSupportedFor(Class<T> klass) {
574         Objects.requireNonNull(klass, "klass must not be null");
575 
576         if ((klass == android.graphics.SurfaceTexture.class) ||
577                 (klass == android.view.SurfaceView.class)) {
578             return true;
579         }
580 
581         return false;
582     }
583 
584     /**
585      * Return a list of supported device-specific extensions for a given camera device.
586      *
587      * @return non-modifiable list of available extensions
588      */
getSupportedExtensions()589     public @NonNull List<Integer> getSupportedExtensions() {
590         ArrayList<Integer> ret = new ArrayList<>();
591         final IBinder token = new Binder(TAG + "#getSupportedExtensions:" + mCameraId);
592         boolean success = registerClient(mContext, token);
593         if (!success) {
594             return Collections.unmodifiableList(ret);
595         }
596 
597         try {
598             for (int extensionType : EXTENSION_LIST) {
599                 if (isExtensionSupported(mCameraId, extensionType, mCharacteristicsMapNative)) {
600                     ret.add(extensionType);
601                 }
602             }
603         } finally {
604             unregisterClient(mContext, token);
605         }
606 
607         return Collections.unmodifiableList(ret);
608     }
609 
610     /**
611      * Checks for postview support of still capture.
612      *
613      * <p>A postview is a preview version of the still capture that is available before the final
614      * image. For example, it can be used as a temporary placeholder for the requested capture
615      * while the final image is being processed. The supported sizes for a still capture's postview
616      * can be retrieved using
617      * {@link CameraExtensionCharacteristics#getPostviewSupportedSizes(int, Size, int)}.
618      * The formats of the still capture and postview should be equivalent upon capture request.</p>
619      *
620      * @param extension the extension type
621      * @return {@code true} in case postview is supported, {@code false} otherwise
622      *
623      * @throws IllegalArgumentException in case the extension type is not a
624      * supported device-specific extension
625      */
isPostviewAvailable(@xtension int extension)626     public boolean isPostviewAvailable(@Extension int extension) {
627         final IBinder token = new Binder(TAG + "#isPostviewAvailable:" + mCameraId);
628         boolean success = registerClient(mContext, token);
629         if (!success) {
630             throw new IllegalArgumentException("Unsupported extensions");
631         }
632 
633         try {
634             if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) {
635                 throw new IllegalArgumentException("Unsupported extension");
636             }
637 
638             if (areAdvancedExtensionsSupported()) {
639                 IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
640                 extender.init(mCameraId, mCharacteristicsMapNative);
641                 return extender.isPostviewAvailable();
642             } else {
643                 Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
644                         initializeExtension(extension);
645                 extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId));
646                 return extenders.second.isPostviewAvailable();
647             }
648         } catch (RemoteException e) {
649             Log.e(TAG, "Failed to query the extension for postview availability! Extension "
650                     + "service does not respond!");
651         } finally {
652             unregisterClient(mContext, token);
653         }
654 
655         return false;
656     }
657 
658     /**
659      * Get a list of the postview sizes supported for a still capture, using its
660      * capture size {@code captureSize}, to use as an output for the postview request.
661      *
662      * <p>Available postview sizes will always be either equal to or less than the still
663      * capture size. When choosing the most applicable postview size for a usecase, it should
664      * be noted that lower resolution postviews will generally be available more quickly
665      * than larger resolution postviews. For example, when choosing a size for an optimized
666      * postview that will be displayed as a placeholder while the final image is processed,
667      * the resolution closest to the preview size may be most suitable.</p>
668      *
669      * <p>Note that device-specific extensions are allowed to support only a subset
670      * of the camera resolutions advertised by
671      * {@link StreamConfigurationMap#getOutputSizes}.</p>
672      *
673      * @param extension the extension type
674      * @param captureSize size of the still capture for which the postview is requested
675      * @param format device-specific extension output format of the still capture and
676      * postview
677      * @return non-modifiable list of available sizes or an empty list if the format and
678      * size is not supported.
679      * @throws IllegalArgumentException in case of unsupported extension or if postview
680      * feature is not supported by extension.
681      */
682     @NonNull
getPostviewSupportedSizes(@xtension int extension, @NonNull Size captureSize, int format)683     public List<Size> getPostviewSupportedSizes(@Extension int extension,
684             @NonNull Size captureSize, int format) {
685         final IBinder token = new Binder(TAG + "#getPostviewSupportedSizes:" + mCameraId);
686         boolean success = registerClient(mContext, token);
687         if (!success) {
688             throw new IllegalArgumentException("Unsupported extensions");
689         }
690 
691         try {
692             if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) {
693                 throw new IllegalArgumentException("Unsupported extension");
694             }
695 
696             android.hardware.camera2.extension.Size sz =
697                     new android.hardware.camera2.extension.Size();
698             sz.width = captureSize.getWidth();
699             sz.height = captureSize.getHeight();
700 
701             StreamConfigurationMap streamMap = mCharacteristicsMap.get(mCameraId).get(
702                     CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
703 
704             if (areAdvancedExtensionsSupported()) {
705                 switch(format) {
706                     case ImageFormat.YUV_420_888:
707                     case ImageFormat.JPEG:
708                         break;
709                     default:
710                         throw new IllegalArgumentException("Unsupported format: " + format);
711                 }
712                 IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
713                 extender.init(mCameraId, mCharacteristicsMapNative);
714                 return generateSupportedSizes(extender.getSupportedPostviewResolutions(
715                     sz), format, streamMap);
716             } else {
717                 Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
718                         initializeExtension(extension);
719                 extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId));
720                 if ((extenders.second.getCaptureProcessor() == null) ||
721                         !isPostviewAvailable(extension)) {
722                     // Extensions that don't implement any capture processor
723                     // and have processing occur in the HAL don't currently support the
724                     // postview feature
725                     throw new IllegalArgumentException("Extension does not support "
726                             + "postview feature");
727                 }
728 
729                 if (format == ImageFormat.YUV_420_888) {
730                     return generateSupportedSizes(
731                             extenders.second.getSupportedPostviewResolutions(sz),
732                             format, streamMap);
733                 } else if (format == ImageFormat.JPEG) {
734                     // The framework will perform the additional encoding pass on the
735                     // processed YUV_420 buffers.
736                     return generateJpegSupportedSizes(
737                             extenders.second.getSupportedPostviewResolutions(sz),
738                                     streamMap);
739                 } else {
740                     throw new IllegalArgumentException("Unsupported format: " + format);
741                 }
742             }
743         } catch (RemoteException e) {
744             Log.e(TAG, "Failed to query the extension postview supported sizes! Extension "
745                     + "service does not respond!");
746             return Collections.emptyList();
747         } finally {
748             unregisterClient(mContext, token);
749         }
750     }
751 
752     /**
753      * Get a list of sizes compatible with {@code klass} to use as an output for the
754      * repeating request
755      * {@link CameraExtensionSession#setRepeatingRequest}.
756      *
757      * <p>Note that device-specific extensions are allowed to support only a subset
758      * of the camera output surfaces and resolutions.
759      * The {@link android.graphics.SurfaceTexture} class is guaranteed at least one size for
760      * backward compatible cameras whereas other output classes are not guaranteed to be supported.
761      * </p>
762      *
763      * <p>Starting with Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}
764      * {@link android.view.SurfaceView} classes are also guaranteed to be supported and include
765      * the same resolutions as {@link android.graphics.SurfaceTexture}.
766      * Clients must set the desired SurfaceView resolution by calling
767      * {@link android.view.SurfaceHolder#setFixedSize}.</p>
768      *
769      * @param extension the extension type
770      * @param klass     a non-{@code null} {@link Class} object reference
771      * @return non-modifiable list of available sizes or an empty list if the Surface output is not
772      * supported
773      * @throws NullPointerException     if {@code klass} was {@code null}
774      * @throws IllegalArgumentException in case of  unsupported extension.
775      */
776     @NonNull
getExtensionSupportedSizes(@xtension int extension, @NonNull Class<T> klass)777     public <T> List<Size> getExtensionSupportedSizes(@Extension int extension,
778             @NonNull Class<T> klass) {
779         if (!isOutputSupportedFor(klass)) {
780             return new ArrayList<>();
781         }
782         // TODO: Revisit this code once the Extension preview processor output format
783         //       ambiguity is resolved in b/169799538.
784 
785         final IBinder token = new Binder(TAG + "#getExtensionSupportedSizes:" + mCameraId);
786         boolean success = registerClient(mContext, token);
787         if (!success) {
788             throw new IllegalArgumentException("Unsupported extensions");
789         }
790 
791         try {
792             if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) {
793                 throw new IllegalArgumentException("Unsupported extension");
794             }
795 
796             StreamConfigurationMap streamMap = mCharacteristicsMap.get(mCameraId).get(
797                     CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
798             if (areAdvancedExtensionsSupported()) {
799                 IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
800                 extender.init(mCameraId, mCharacteristicsMapNative);
801                 return generateSupportedSizes(
802                         extender.getSupportedPreviewOutputResolutions(mCameraId),
803                         ImageFormat.PRIVATE, streamMap);
804             } else {
805                 Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
806                         initializeExtension(extension);
807                 extenders.first.init(mCameraId,
808                         mCharacteristicsMapNative.get(mCameraId));
809                 return generateSupportedSizes(extenders.first.getSupportedResolutions(),
810                         ImageFormat.PRIVATE, streamMap);
811             }
812         } catch (RemoteException e) {
813             Log.e(TAG, "Failed to query the extension supported sizes! Extension service does"
814                     + " not respond!");
815             return new ArrayList<>();
816         } finally {
817             unregisterClient(mContext, token);
818         }
819     }
820 
821     /**
822      * Check whether a given extension is available and return the
823      * supported output surface resolutions that can be used for high-quality capture
824      * requests via {@link CameraExtensionSession#capture}.
825      *
826      * <p>Note that device-specific extensions are allowed to support only a subset
827      * of the camera resolutions advertised by
828      * {@link StreamConfigurationMap#getOutputSizes}.</p>
829      *
830      * <p>Device-specific extensions currently support at most two
831      * multi-frame capture surface formats. ImageFormat.JPEG will be supported by all
832      * extensions and ImageFormat.YUV_420_888 may or may not be supported.</p>
833      *
834      * @param extension the extension type
835      * @param format    device-specific extension output format
836      * @return non-modifiable list of available sizes or an empty list if the format is not
837      * supported.
838      * @throws IllegalArgumentException in case of format different from ImageFormat.JPEG /
839      *                                  ImageFormat.YUV_420_888; or unsupported extension.
840      */
841     public @NonNull
getExtensionSupportedSizes(@xtension int extension, int format)842     List<Size> getExtensionSupportedSizes(@Extension int extension, int format) {
843         try {
844             final IBinder token = new Binder(TAG + "#getExtensionSupportedSizes:" + mCameraId);
845             boolean success = registerClient(mContext, token);
846             if (!success) {
847                 throw new IllegalArgumentException("Unsupported extensions");
848             }
849 
850             try {
851                 if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) {
852                     throw new IllegalArgumentException("Unsupported extension");
853                 }
854 
855                 StreamConfigurationMap streamMap = mCharacteristicsMap.get(mCameraId).get(
856                         CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
857                 if (areAdvancedExtensionsSupported()) {
858                     switch(format) {
859                         case ImageFormat.YUV_420_888:
860                         case ImageFormat.JPEG:
861                             break;
862                         default:
863                             throw new IllegalArgumentException("Unsupported format: " + format);
864                     }
865                     IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
866                     extender.init(mCameraId, mCharacteristicsMapNative);
867                     return generateSupportedSizes(extender.getSupportedCaptureOutputResolutions(
868                             mCameraId), format, streamMap);
869                 } else {
870                     if (format == ImageFormat.YUV_420_888) {
871                         Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
872                                 initializeExtension(extension);
873                         extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId));
874                         if (extenders.second.getCaptureProcessor() == null) {
875                             // Extensions that don't implement any capture processor are limited to
876                             // JPEG only!
877                             return new ArrayList<>();
878                         }
879                         return generateSupportedSizes(extenders.second.getSupportedResolutions(),
880                                 format, streamMap);
881                     } else if (format == ImageFormat.JPEG) {
882                         Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
883                                 initializeExtension(extension);
884                         extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId));
885                         if (extenders.second.getCaptureProcessor() != null) {
886                             // The framework will perform the additional encoding pass on the
887                             // processed YUV_420 buffers.
888                             return generateJpegSupportedSizes(
889                                     extenders.second.getSupportedResolutions(), streamMap);
890                         } else {
891                             return generateSupportedSizes(null, format, streamMap);
892                         }
893                     } else {
894                         throw new IllegalArgumentException("Unsupported format: " + format);
895                     }
896                 }
897             } finally {
898                 unregisterClient(mContext, token);
899             }
900         } catch (RemoteException e) {
901             Log.e(TAG, "Failed to query the extension supported sizes! Extension service does"
902                     + " not respond!");
903             return new ArrayList<>();
904         }
905     }
906 
907     /**
908      * Returns the estimated capture latency range in milliseconds for the
909      * target capture resolution during the calls to {@link CameraExtensionSession#capture}. This
910      * includes the time spent processing the multi-frame capture request along with any additional
911      * time for encoding of the processed buffer if necessary.
912      *
913      * @param extension         the extension type
914      * @param captureOutputSize size of the capture output surface. If it is not in the supported
915      *                          output sizes, maximum capture output size is used for the estimation
916      * @param format            device-specific extension output format
917      * @return the range of estimated minimal and maximal capture latency in milliseconds
918      * or null if no capture latency info can be provided
919      * @throws IllegalArgumentException in case of format different from {@link ImageFormat#JPEG} /
920      *                                  {@link ImageFormat#YUV_420_888}; or unsupported extension.
921      */
getEstimatedCaptureLatencyRangeMillis(@xtension int extension, @NonNull Size captureOutputSize, @ImageFormat.Format int format)922     public @Nullable Range<Long> getEstimatedCaptureLatencyRangeMillis(@Extension int extension,
923             @NonNull Size captureOutputSize, @ImageFormat.Format int format) {
924         switch (format) {
925             case ImageFormat.YUV_420_888:
926             case ImageFormat.JPEG:
927                 //No op
928                 break;
929             default:
930                 throw new IllegalArgumentException("Unsupported format: " + format);
931         }
932 
933         final IBinder token = new Binder(TAG + "#getEstimatedCaptureLatencyRangeMillis:" + mCameraId);
934         boolean success = registerClient(mContext, token);
935         if (!success) {
936             throw new IllegalArgumentException("Unsupported extensions");
937         }
938 
939         try {
940             if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) {
941                 throw new IllegalArgumentException("Unsupported extension");
942             }
943 
944             android.hardware.camera2.extension.Size sz =
945                     new android.hardware.camera2.extension.Size();
946             sz.width = captureOutputSize.getWidth();
947             sz.height = captureOutputSize.getHeight();
948             if (areAdvancedExtensionsSupported()) {
949                 IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
950                 extender.init(mCameraId, mCharacteristicsMapNative);
951                 LatencyRange latencyRange = extender.getEstimatedCaptureLatencyRange(mCameraId,
952                         sz, format);
953                 if (latencyRange != null) {
954                     return new Range(latencyRange.min, latencyRange.max);
955                 }
956             } else {
957                 Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
958                         initializeExtension(extension);
959                 extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId));
960                 if ((format == ImageFormat.YUV_420_888) &&
961                         (extenders.second.getCaptureProcessor() == null) ){
962                     // Extensions that don't implement any capture processor are limited to
963                     // JPEG only!
964                     return null;
965                 }
966                 if ((format == ImageFormat.JPEG) &&
967                         (extenders.second.getCaptureProcessor() != null)) {
968                     // The framework will perform the additional encoding pass on the
969                     // processed YUV_420 buffers. Latency in this case is very device
970                     // specific and cannot be estimated accurately enough.
971                     return  null;
972                 }
973 
974                 LatencyRange latencyRange = extenders.second.getEstimatedCaptureLatencyRange(sz);
975                 if (latencyRange != null) {
976                     return new Range(latencyRange.min, latencyRange.max);
977                 }
978             }
979         } catch (RemoteException e) {
980             Log.e(TAG, "Failed to query the extension capture latency! Extension service does"
981                     + " not respond!");
982         } finally {
983             unregisterClient(mContext, token);
984         }
985 
986         return null;
987     }
988 
989     /**
990      * Retrieve support for capture progress callbacks via
991      *  {@link CameraExtensionSession.ExtensionCaptureCallback#onCaptureProcessProgressed}.
992      *
993      * @param extension         the extension type
994      * @return {@code true} in case progress callbacks are supported, {@code false} otherwise
995      *
996      * @throws IllegalArgumentException in case of an unsupported extension.
997      */
isCaptureProcessProgressAvailable(@xtension int extension)998     public boolean isCaptureProcessProgressAvailable(@Extension int extension) {
999         final IBinder token = new Binder(TAG + "#isCaptureProcessProgressAvailable:" + mCameraId);
1000         boolean success = registerClient(mContext, token);
1001         if (!success) {
1002             throw new IllegalArgumentException("Unsupported extensions");
1003         }
1004 
1005         try {
1006             if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) {
1007                 throw new IllegalArgumentException("Unsupported extension");
1008             }
1009 
1010             if (areAdvancedExtensionsSupported()) {
1011                 IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
1012                 extender.init(mCameraId, mCharacteristicsMapNative);
1013                 return extender.isCaptureProcessProgressAvailable();
1014             } else {
1015                 Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
1016                         initializeExtension(extension);
1017                 extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId));
1018                 return extenders.second.isCaptureProcessProgressAvailable();
1019             }
1020         } catch (RemoteException e) {
1021             Log.e(TAG, "Failed to query the extension progress callbacks! Extension service does"
1022                     + " not respond!");
1023         } finally {
1024             unregisterClient(mContext, token);
1025         }
1026 
1027         return false;
1028     }
1029 
1030     /**
1031      * Returns the set of keys supported by a {@link CaptureRequest} submitted in a
1032      * {@link CameraExtensionSession} with a given extension type.
1033      *
1034      * <p>The set returned is not modifiable, so any attempts to modify it will throw
1035      * a {@code UnsupportedOperationException}.</p>
1036      *
1037      * @param extension the extension type
1038      *
1039      * @return non-modifiable set of capture keys supported by camera extension session initialized
1040      *         with the given extension type.
1041      * @throws IllegalArgumentException in case of unsupported extension.
1042      */
1043     @NonNull
getAvailableCaptureRequestKeys(@xtension int extension)1044     public Set<CaptureRequest.Key> getAvailableCaptureRequestKeys(@Extension int extension) {
1045         final IBinder token = new Binder(TAG + "#getAvailableCaptureRequestKeys:" + mCameraId);
1046         boolean success = registerClient(mContext, token);
1047         if (!success) {
1048             throw new IllegalArgumentException("Unsupported extensions");
1049         }
1050 
1051         HashSet<CaptureRequest.Key> ret = new HashSet<>();
1052 
1053         try {
1054             if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) {
1055                 throw new IllegalArgumentException("Unsupported extension");
1056             }
1057 
1058             CameraMetadataNative captureRequestMeta = null;
1059             if (areAdvancedExtensionsSupported()) {
1060                 IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
1061                 extender.init(mCameraId, mCharacteristicsMapNative);
1062                 captureRequestMeta = extender.getAvailableCaptureRequestKeys(mCameraId);
1063             } else {
1064                 Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
1065                         initializeExtension(extension);
1066                 extenders.second.onInit(token, mCameraId,
1067                         mCharacteristicsMapNative.get(mCameraId));
1068                 extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId));
1069                 captureRequestMeta = extenders.second.getAvailableCaptureRequestKeys();
1070                 extenders.second.onDeInit(token);
1071             }
1072 
1073             if (captureRequestMeta != null) {
1074                 int[] requestKeys = captureRequestMeta.get(
1075                         CameraCharacteristics.REQUEST_AVAILABLE_REQUEST_KEYS);
1076                 if (requestKeys == null) {
1077                     throw new AssertionError(
1078                             "android.request.availableRequestKeys must be non-null"
1079                                     + " in the characteristics");
1080                 }
1081                 CameraCharacteristics requestChars = new CameraCharacteristics(
1082                         captureRequestMeta);
1083 
1084                 Object crKey = CaptureRequest.Key.class;
1085                 Class<CaptureRequest.Key<?>> crKeyTyped = (Class<CaptureRequest.Key<?>>) crKey;
1086 
1087                 ret.addAll(requestChars.getAvailableKeyList(CaptureRequest.class, crKeyTyped,
1088                         requestKeys, /*includeSynthetic*/ true));
1089             }
1090 
1091             // Jpeg quality and orientation must always be supported
1092             if (!ret.contains(CaptureRequest.JPEG_QUALITY)) {
1093                 ret.add(CaptureRequest.JPEG_QUALITY);
1094             }
1095             if (!ret.contains(CaptureRequest.JPEG_ORIENTATION)) {
1096                 ret.add(CaptureRequest.JPEG_ORIENTATION);
1097             }
1098         } catch (RemoteException e) {
1099             throw new IllegalStateException("Failed to query the available capture request keys!");
1100         } finally {
1101             unregisterClient(mContext, token);
1102         }
1103 
1104         return Collections.unmodifiableSet(ret);
1105     }
1106 
1107     /**
1108      * Returns the set of keys supported by a {@link CaptureResult} passed as an argument to
1109      * {@link CameraExtensionSession.ExtensionCaptureCallback#onCaptureResultAvailable}.
1110      *
1111      * <p>The set returned is not modifiable, so any attempts to modify it will throw
1112      * a {@code UnsupportedOperationException}.</p>
1113      *
1114      * <p>In case the set is empty, then the extension is not able to support any capture results
1115      * and the {@link CameraExtensionSession.ExtensionCaptureCallback#onCaptureResultAvailable}
1116      * callback will not be fired.</p>
1117      *
1118      * @param extension the extension type
1119      *
1120      * @return non-modifiable set of capture result keys supported by camera extension session
1121      *         initialized with the given extension type.
1122      * @throws IllegalArgumentException in case of unsupported extension.
1123      */
1124     @NonNull
getAvailableCaptureResultKeys(@xtension int extension)1125     public Set<CaptureResult.Key> getAvailableCaptureResultKeys(@Extension int extension) {
1126         final IBinder token = new Binder(TAG + "#getAvailableCaptureResultKeys:" + mCameraId);
1127         boolean success = registerClient(mContext, token);
1128         if (!success) {
1129             throw new IllegalArgumentException("Unsupported extensions");
1130         }
1131 
1132         HashSet<CaptureResult.Key> ret = new HashSet<>();
1133         try {
1134             if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) {
1135                 throw new IllegalArgumentException("Unsupported extension");
1136             }
1137 
1138             CameraMetadataNative captureResultMeta = null;
1139             if (areAdvancedExtensionsSupported()) {
1140                 IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
1141                 extender.init(mCameraId, mCharacteristicsMapNative);
1142                 captureResultMeta = extender.getAvailableCaptureResultKeys(mCameraId);
1143             } else {
1144                 Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
1145                         initializeExtension(extension);
1146                 extenders.second.onInit(token, mCameraId,
1147                         mCharacteristicsMapNative.get(mCameraId));
1148                 extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId));
1149                 captureResultMeta = extenders.second.getAvailableCaptureResultKeys();
1150                 extenders.second.onDeInit(token);
1151             }
1152 
1153             if (captureResultMeta != null) {
1154                 int[] resultKeys = captureResultMeta.get(
1155                         CameraCharacteristics.REQUEST_AVAILABLE_RESULT_KEYS);
1156                 if (resultKeys == null) {
1157                     throw new AssertionError("android.request.availableResultKeys must be non-null "
1158                             + "in the characteristics");
1159                 }
1160                 CameraCharacteristics resultChars = new CameraCharacteristics(captureResultMeta);
1161                 Object crKey = CaptureResult.Key.class;
1162                 Class<CaptureResult.Key<?>> crKeyTyped = (Class<CaptureResult.Key<?>>) crKey;
1163 
1164                 ret.addAll(resultChars.getAvailableKeyList(CaptureResult.class, crKeyTyped,
1165                         resultKeys, /*includeSynthetic*/ true));
1166 
1167                 // Jpeg quality, orientation and sensor timestamp must always be supported
1168                 if (!ret.contains(CaptureResult.JPEG_QUALITY)) {
1169                     ret.add(CaptureResult.JPEG_QUALITY);
1170                 }
1171                 if (!ret.contains(CaptureResult.JPEG_ORIENTATION)) {
1172                     ret.add(CaptureResult.JPEG_ORIENTATION);
1173                 }
1174                 if (!ret.contains(CaptureResult.SENSOR_TIMESTAMP)) {
1175                     ret.add(CaptureResult.SENSOR_TIMESTAMP);
1176                 }
1177             }
1178         } catch (RemoteException e) {
1179             throw new IllegalStateException("Failed to query the available capture result keys!");
1180         } finally {
1181             unregisterClient(mContext, token);
1182         }
1183 
1184         return Collections.unmodifiableSet(ret);
1185     }
1186 }
1187