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