| /* |
| * Copyright 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.server.camera; |
| |
| import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; |
| import static android.os.Build.VERSION_CODES.M; |
| |
| import android.annotation.IntDef; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.annotation.TestApi; |
| import android.app.ActivityManager; |
| import android.app.ActivityTaskManager; |
| import android.app.admin.DevicePolicyManager; |
| import android.app.compat.CompatChanges; |
| import android.compat.annotation.ChangeId; |
| import android.compat.annotation.Disabled; |
| import android.compat.annotation.Overridable; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.pm.ActivityInfo; |
| import android.content.pm.PackageManager; |
| import android.content.pm.ParceledListSlice; |
| import android.content.res.Configuration; |
| import android.graphics.Rect; |
| import android.hardware.CameraSessionStats; |
| import android.hardware.CameraStreamStats; |
| import android.hardware.ICameraService; |
| import android.hardware.ICameraServiceProxy; |
| import android.hardware.camera2.CameraCharacteristics; |
| import android.hardware.camera2.CameraMetadata; |
| import android.hardware.camera2.CaptureRequest; |
| import android.hardware.devicestate.DeviceStateManager; |
| import android.hardware.devicestate.DeviceStateManager.FoldStateListener; |
| import android.hardware.display.DisplayManager; |
| import android.hardware.usb.UsbDevice; |
| import android.hardware.usb.UsbManager; |
| import android.media.AudioManager; |
| import android.nfc.INfcAdapter; |
| import android.os.Binder; |
| import android.os.Handler; |
| import android.os.HandlerExecutor; |
| import android.os.IBinder; |
| import android.os.Message; |
| import android.os.Process; |
| import android.os.RemoteException; |
| import android.os.SystemClock; |
| import android.os.SystemProperties; |
| import android.os.UserHandle; |
| import android.os.UserManager; |
| import android.stats.camera.nano.CameraProtos.CameraStreamProto; |
| import android.util.ArrayMap; |
| import android.util.ArraySet; |
| import android.util.Log; |
| import android.util.Slog; |
| import android.view.Display; |
| import android.view.IDisplayWindowListener; |
| import android.view.Surface; |
| import android.view.WindowManagerGlobal; |
| |
| import com.android.framework.protobuf.nano.MessageNano; |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.internal.util.FrameworkStatsLog; |
| import com.android.server.LocalServices; |
| import com.android.server.ServiceThread; |
| import com.android.server.SystemService; |
| import com.android.server.wm.WindowManagerInternal; |
| |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.concurrent.ScheduledThreadPoolExecutor; |
| import java.util.concurrent.TimeUnit; |
| |
| /** |
| * CameraServiceProxy is the system_server analog to the camera service running in cameraserver. |
| * |
| * @hide |
| */ |
| public class CameraServiceProxy extends SystemService |
| implements Handler.Callback, IBinder.DeathRecipient { |
| private static final String TAG = "CameraService_proxy"; |
| private static final boolean DEBUG = false; |
| |
| /** |
| * This must match the ICameraService.aidl definition |
| */ |
| private static final String CAMERA_SERVICE_BINDER_NAME = "media.camera"; |
| |
| public static final String CAMERA_SERVICE_PROXY_BINDER_NAME = "media.camera.proxy"; |
| |
| /** |
| * When enabled this change id forces the packages it is applied to override the default |
| * camera rotate & crop behavior and always return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE . |
| * The default behavior along with all possible override combinations is discussed in the table |
| * below. |
| */ |
| @ChangeId |
| @Overridable |
| @Disabled |
| @TestApi |
| public static final long OVERRIDE_CAMERA_ROTATE_AND_CROP_DEFAULTS = 189229956L; // buganizer id |
| |
| /** |
| * When enabled this change id forces the packages it is applied to ignore the current value of |
| * 'android:resizeableActivity' as well as target SDK equal to or below M and consider the |
| * activity as non-resizeable. In this case, the value of camera rotate & crop will only depend |
| * on the needed compensation considering the current display rotation. |
| */ |
| @ChangeId |
| @Overridable |
| @Disabled |
| @TestApi |
| public static final long OVERRIDE_CAMERA_RESIZABLE_AND_SDK_CHECK = 191513214L; // buganizer id |
| |
| /** |
| * Possible override combinations |
| * |
| * |OVERRIDE |OVERRIDE_ |
| * |CAMERA_ |CAMERA_ |
| * |ROTATE_ |RESIZEABLE_ |
| * |AND_CROP_ |AND_SDK_ |
| * |DEFAULTS |CHECK |
| * _________________________________________________ |
| * Default Behavior | D |D |
| * _________________________________________________ |
| * Ignore SDK&Resize | D |E |
| * _________________________________________________ |
| * SCALER_ROTATE_AND_CROP_NONE | E |D, E |
| * _________________________________________________ |
| * Where: |
| * E -> Override enabled |
| * D -> Override disabled |
| * Default behavior -> Rotate&crop will be calculated depending on the required |
| * compensation necessary for the current display rotation. |
| * Additionally the app must either target M (or below) |
| * or is declared as non-resizeable. |
| * Ignore SDK&Resize -> The Rotate&crop value will depend on the required |
| * compensation for the current display rotation. |
| * SCALER_ROTATE_AND_CROP_NONE -> Always return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE |
| */ |
| |
| // Flags arguments to NFC adapter to enable/disable NFC |
| public static final int DISABLE_POLLING_FLAGS = 0x1000; |
| public static final int ENABLE_POLLING_FLAGS = 0x0000; |
| |
| // Handler message codes |
| private static final int MSG_SWITCH_USER = 1; |
| private static final int MSG_NOTIFY_DEVICE_STATE = 2; |
| |
| private static final int RETRY_DELAY_TIME = 20; //ms |
| private static final int RETRY_TIMES = 60; |
| |
| @IntDef(flag = true, prefix = { "DEVICE_STATE_" }, value = { |
| ICameraService.DEVICE_STATE_BACK_COVERED, |
| ICameraService.DEVICE_STATE_FRONT_COVERED, |
| ICameraService.DEVICE_STATE_FOLDED |
| }) |
| @Retention(RetentionPolicy.SOURCE) |
| @interface DeviceStateFlags {} |
| |
| // Maximum entries to keep in usage history before dumping out |
| private static final int MAX_USAGE_HISTORY = 20; |
| // Number of stream statistics being dumped for each camera session |
| // Must be equal to number of CameraStreamProto in CameraActionEvent |
| private static final int MAX_STREAM_STATISTICS = 5; |
| |
| private static final float MIN_PREVIEW_FPS = 30.0f; |
| private static final float MAX_PREVIEW_FPS = 60.0f; |
| |
| private final Context mContext; |
| private final ServiceThread mHandlerThread; |
| private final Handler mHandler; |
| private UserManager mUserManager; |
| |
| private final Object mLock = new Object(); |
| private Set<Integer> mEnabledCameraUsers; |
| private int mLastUser; |
| // The current set of device state flags. May be different from mLastReportedDeviceState if the |
| // native camera service has not been notified of the change. |
| @GuardedBy("mLock") |
| @DeviceStateFlags |
| private int mDeviceState; |
| // The most recent device state flags reported to the native camera server. |
| @GuardedBy("mLock") |
| @DeviceStateFlags |
| private int mLastReportedDeviceState; |
| |
| private ICameraService mCameraServiceRaw; |
| |
| // Map of currently active camera IDs |
| private final ArrayMap<String, CameraUsageEvent> mActiveCameraUsage = new ArrayMap<>(); |
| private final List<CameraUsageEvent> mCameraUsageHistory = new ArrayList<>(); |
| |
| private static final String NFC_NOTIFICATION_PROP = "ro.camera.notify_nfc"; |
| private static final String NFC_SERVICE_BINDER_NAME = "nfc"; |
| private static final IBinder nfcInterfaceToken = new Binder(); |
| |
| private final boolean mNotifyNfc; |
| |
| private ScheduledThreadPoolExecutor mLogWriterService = new ScheduledThreadPoolExecutor( |
| /*corePoolSize*/ 1); |
| |
| /** |
| * Structure to track camera usage |
| */ |
| private static class CameraUsageEvent { |
| public final String mCameraId; |
| public final int mCameraFacing; |
| public final String mClientName; |
| public final int mAPILevel; |
| public final boolean mIsNdk; |
| public final int mAction; |
| public final int mLatencyMs; |
| public final int mOperatingMode; |
| |
| private boolean mCompleted; |
| public int mInternalReconfigure; |
| public long mRequestCount; |
| public long mResultErrorCount; |
| public boolean mDeviceError; |
| public List<CameraStreamStats> mStreamStats; |
| public String mUserTag; |
| public int mVideoStabilizationMode; |
| |
| private long mDurationOrStartTimeMs; // Either start time, or duration once completed |
| |
| CameraUsageEvent(String cameraId, int facing, String clientName, int apiLevel, |
| boolean isNdk, int action, int latencyMs, int operatingMode) { |
| mCameraId = cameraId; |
| mCameraFacing = facing; |
| mClientName = clientName; |
| mAPILevel = apiLevel; |
| mDurationOrStartTimeMs = SystemClock.elapsedRealtime(); |
| mCompleted = false; |
| mIsNdk = isNdk; |
| mAction = action; |
| mLatencyMs = latencyMs; |
| mOperatingMode = operatingMode; |
| } |
| |
| public void markCompleted(int internalReconfigure, long requestCount, |
| long resultErrorCount, boolean deviceError, |
| List<CameraStreamStats> streamStats, String userTag, |
| int videoStabilizationMode) { |
| if (mCompleted) { |
| return; |
| } |
| mCompleted = true; |
| mDurationOrStartTimeMs = SystemClock.elapsedRealtime() - mDurationOrStartTimeMs; |
| mInternalReconfigure = internalReconfigure; |
| mRequestCount = requestCount; |
| mResultErrorCount = resultErrorCount; |
| mDeviceError = deviceError; |
| mStreamStats = streamStats; |
| mUserTag = userTag; |
| mVideoStabilizationMode = videoStabilizationMode; |
| if (CameraServiceProxy.DEBUG) { |
| Slog.v(TAG, "A camera facing " + cameraFacingToString(mCameraFacing) + |
| " was in use by " + mClientName + " for " + |
| mDurationOrStartTimeMs + " ms"); |
| } |
| } |
| |
| /** |
| * Return duration of camera usage event, or 0 if the event is not done |
| */ |
| public long getDuration() { |
| return mCompleted ? mDurationOrStartTimeMs : 0; |
| } |
| } |
| |
| private final class DisplayWindowListener extends IDisplayWindowListener.Stub { |
| |
| @Override |
| public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) { |
| ICameraService cs = getCameraServiceRawLocked(); |
| if (cs == null) return; |
| |
| try { |
| cs.notifyDisplayConfigurationChange(); |
| } catch (RemoteException e) { |
| Slog.w(TAG, "Could not notify cameraserver, remote exception: " + e); |
| // Not much we can do if camera service is dead. |
| } |
| } |
| |
| @Override |
| public void onDisplayAdded(int displayId) { } |
| |
| @Override |
| public void onDisplayRemoved(int displayId) { } |
| |
| @Override |
| public void onFixedRotationStarted(int displayId, int newRotation) { } |
| |
| @Override |
| public void onFixedRotationFinished(int displayId) { } |
| |
| @Override |
| public void onKeepClearAreasChanged(int displayId, List<Rect> restricted, |
| List<Rect> unrestricted) { } |
| } |
| |
| |
| private final DisplayWindowListener mDisplayWindowListener = new DisplayWindowListener(); |
| |
| public static final class TaskInfo { |
| public int frontTaskId; |
| public boolean isResizeable; |
| public boolean isFixedOrientationLandscape; |
| public boolean isFixedOrientationPortrait; |
| public int displayId; |
| public int userId; |
| } |
| |
| private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| final String action = intent.getAction(); |
| if (action == null) return; |
| |
| switch (action) { |
| case Intent.ACTION_USER_ADDED: |
| case Intent.ACTION_USER_REMOVED: |
| case Intent.ACTION_USER_INFO_CHANGED: |
| case Intent.ACTION_MANAGED_PROFILE_ADDED: |
| case Intent.ACTION_MANAGED_PROFILE_REMOVED: |
| synchronized(mLock) { |
| // Return immediately if we haven't seen any users start yet |
| if (mEnabledCameraUsers == null) return; |
| switchUserLocked(mLastUser); |
| } |
| break; |
| case UsbManager.ACTION_USB_DEVICE_ATTACHED: |
| case UsbManager.ACTION_USB_DEVICE_DETACHED: |
| synchronized (mLock) { |
| UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); |
| if (device != null) { |
| notifyUsbDeviceHotplugLocked(device, |
| action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)); |
| } |
| } |
| break; |
| default: |
| break; // do nothing |
| } |
| |
| } |
| }; |
| |
| private static boolean isMOrBelow(Context ctx, String packageName) { |
| try { |
| return ctx.getPackageManager().getPackageInfo( |
| packageName, 0).applicationInfo.targetSdkVersion <= M; |
| } catch (PackageManager.NameNotFoundException e) { |
| Slog.e(TAG,"Package name not found!"); |
| } |
| return false; |
| } |
| |
| /** |
| * Estimate the app crop-rotate-scale compensation value. |
| */ |
| public static int getCropRotateScale(@NonNull Context ctx, @NonNull String packageName, |
| @Nullable TaskInfo taskInfo, int displayRotation, int lensFacing, |
| boolean ignoreResizableAndSdkCheck) { |
| if (taskInfo == null) { |
| return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; |
| } |
| |
| // External cameras do not need crop-rotate-scale. |
| if (lensFacing != CameraMetadata.LENS_FACING_FRONT |
| && lensFacing != CameraMetadata.LENS_FACING_BACK) { |
| Log.v(TAG, "lensFacing=" + lensFacing + ". Crop-rotate-scale is disabled."); |
| return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; |
| } |
| |
| // In case the activity behavior is not explicitly overridden, enable the |
| // crop-rotate-scale workaround if the app targets M (or below) or is not |
| // resizeable. |
| if (!ignoreResizableAndSdkCheck && !isMOrBelow(ctx, packageName) && |
| taskInfo.isResizeable) { |
| Slog.v(TAG, |
| "The activity is N or above and claims to support resizeable-activity. " |
| + "Crop-rotate-scale is disabled."); |
| return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; |
| } |
| |
| if (!taskInfo.isFixedOrientationPortrait && !taskInfo.isFixedOrientationLandscape) { |
| Log.v(TAG, "Non-fixed orientation activity. Crop-rotate-scale is disabled."); |
| return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; |
| } |
| |
| int rotationDegree; |
| switch (displayRotation) { |
| case Surface.ROTATION_0: |
| rotationDegree = 0; |
| break; |
| case Surface.ROTATION_90: |
| rotationDegree = 90; |
| break; |
| case Surface.ROTATION_180: |
| rotationDegree = 180; |
| break; |
| case Surface.ROTATION_270: |
| rotationDegree = 270; |
| break; |
| default: |
| Log.e(TAG, "Unsupported display rotation: " + displayRotation); |
| return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; |
| } |
| |
| Slog.v(TAG, |
| "Display.getRotation()=" + rotationDegree |
| + " isFixedOrientationPortrait=" + taskInfo.isFixedOrientationPortrait |
| + " isFixedOrientationLandscape=" + |
| taskInfo.isFixedOrientationLandscape); |
| // We are trying to estimate the necessary rotation compensation for clients that |
| // don't handle various display orientations. |
| // The logic that is missing on client side is similar to the reference code |
| // in {@link android.hardware.Camera#setDisplayOrientation} where "info.orientation" |
| // is already applied in "CameraUtils::getRotationTransform". |
| // Care should be taken to reverse the rotation direction depending on the camera |
| // lens facing. |
| if (rotationDegree == 0) { |
| return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; |
| } |
| if (lensFacing == CameraCharacteristics.LENS_FACING_FRONT) { |
| // Switch direction for front facing cameras |
| rotationDegree = 360 - rotationDegree; |
| } |
| |
| switch (rotationDegree) { |
| case 90: |
| return CaptureRequest.SCALER_ROTATE_AND_CROP_90; |
| case 270: |
| return CaptureRequest.SCALER_ROTATE_AND_CROP_270; |
| case 180: |
| return CaptureRequest.SCALER_ROTATE_AND_CROP_180; |
| case 0: |
| default: |
| return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; |
| } |
| } |
| |
| private final ICameraServiceProxy.Stub mCameraServiceProxy = new ICameraServiceProxy.Stub() { |
| @Override |
| public int getRotateAndCropOverride(String packageName, int lensFacing, int userId) { |
| if (Binder.getCallingUid() != Process.CAMERASERVER_UID) { |
| Slog.e(TAG, "Calling UID: " + Binder.getCallingUid() + " doesn't match expected " + |
| " camera service UID!"); |
| return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; |
| } |
| |
| TaskInfo taskInfo = null; |
| ParceledListSlice<ActivityManager.RecentTaskInfo> recentTasks = null; |
| |
| try { |
| // Get 2 recent tasks in case we are running in split mode |
| recentTasks = ActivityTaskManager.getService().getRecentTasks(/*maxNum*/2, |
| /*flags*/ 0, userId); |
| } catch (RemoteException e) { |
| Log.e(TAG, "Failed to query recent tasks!"); |
| return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; |
| } |
| |
| if ((recentTasks != null) && (!recentTasks.getList().isEmpty())) { |
| for (ActivityManager.RecentTaskInfo task : recentTasks.getList()) { |
| if (packageName.equals(task.topActivityInfo.packageName)) { |
| taskInfo = new TaskInfo(); |
| taskInfo.frontTaskId = task.taskId; |
| taskInfo.isResizeable = |
| (task.topActivityInfo.resizeMode != RESIZE_MODE_UNRESIZEABLE); |
| taskInfo.displayId = task.displayId; |
| taskInfo.userId = task.userId; |
| taskInfo.isFixedOrientationLandscape = |
| ActivityInfo.isFixedOrientationLandscape( |
| task.topActivityInfo.screenOrientation); |
| taskInfo.isFixedOrientationPortrait = |
| ActivityInfo.isFixedOrientationPortrait( |
| task.topActivityInfo.screenOrientation); |
| break; |
| } |
| } |
| |
| if (taskInfo == null) { |
| Log.e(TAG, "Recent tasks don't include camera client package name: " + |
| packageName); |
| return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; |
| } |
| } else { |
| Log.e(TAG, "Recent task list is empty!"); |
| return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; |
| } |
| |
| // TODO: Modify the sensor orientation in camera characteristics along with any 3A |
| // regions in capture requests/results to account for thea physical rotation. The |
| // former is somewhat tricky as it assumes that camera clients always check for the |
| // current value by retrieving the camera characteristics from the camera device. |
| if ((taskInfo != null) && (CompatChanges.isChangeEnabled( |
| OVERRIDE_CAMERA_ROTATE_AND_CROP_DEFAULTS, packageName, |
| UserHandle.getUserHandleForUid(taskInfo.userId)))) { |
| Slog.v(TAG, "OVERRIDE_CAMERA_ROTATE_AND_CROP_DEFAULTS enabled!"); |
| return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; |
| } |
| boolean ignoreResizableAndSdkCheck = false; |
| if ((taskInfo != null) && (CompatChanges.isChangeEnabled( |
| OVERRIDE_CAMERA_RESIZABLE_AND_SDK_CHECK, packageName, |
| UserHandle.getUserHandleForUid(taskInfo.userId)))) { |
| Slog.v(TAG, "OVERRIDE_CAMERA_RESIZABLE_AND_SDK_CHECK enabled!"); |
| ignoreResizableAndSdkCheck = true; |
| } |
| |
| DisplayManager displayManager = mContext.getSystemService(DisplayManager.class); |
| int displayRotation; |
| if (displayManager != null) { |
| Display display = displayManager.getDisplay(taskInfo.displayId); |
| if (display == null) { |
| Slog.e(TAG, "Invalid display id: " + taskInfo.displayId); |
| return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; |
| } |
| |
| displayRotation = display.getRotation(); |
| } else { |
| Slog.e(TAG, "Failed to query display manager!"); |
| return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; |
| } |
| |
| return getCropRotateScale(mContext, packageName, taskInfo, displayRotation, |
| lensFacing, ignoreResizableAndSdkCheck); |
| } |
| |
| @Override |
| public void pingForUserUpdate() { |
| if (Binder.getCallingUid() != Process.CAMERASERVER_UID) { |
| Slog.e(TAG, "Calling UID: " + Binder.getCallingUid() + " doesn't match expected " + |
| " camera service UID!"); |
| return; |
| } |
| notifySwitchWithRetries(RETRY_TIMES); |
| notifyDeviceStateWithRetries(RETRY_TIMES); |
| } |
| |
| @Override |
| public void notifyCameraState(CameraSessionStats cameraState) { |
| if (Binder.getCallingUid() != Process.CAMERASERVER_UID) { |
| Slog.e(TAG, "Calling UID: " + Binder.getCallingUid() + " doesn't match expected " + |
| " camera service UID!"); |
| return; |
| } |
| String state = cameraStateToString(cameraState.getNewCameraState()); |
| String facingStr = cameraFacingToString(cameraState.getFacing()); |
| if (DEBUG) { |
| Slog.v(TAG, "Camera " + cameraState.getCameraId() |
| + " facing " + facingStr + " state now " + state |
| + " for client " + cameraState.getClientName() |
| + " API Level " + cameraState.getApiLevel()); |
| } |
| |
| updateActivityCount(cameraState); |
| } |
| |
| @Override |
| public boolean isCameraDisabled(int userId) { |
| DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); |
| if (dpm == null) { |
| Slog.e(TAG, "Failed to get the device policy manager service"); |
| return false; |
| } |
| try { |
| return dpm.getCameraDisabled(null, userId); |
| } catch (Exception e) { |
| e.printStackTrace(); |
| return false; |
| } |
| } |
| }; |
| |
| private final FoldStateListener mFoldStateListener; |
| |
| public CameraServiceProxy(Context context) { |
| super(context); |
| mContext = context; |
| mHandlerThread = new ServiceThread(TAG, Process.THREAD_PRIORITY_DISPLAY, /*allowTo*/false); |
| mHandlerThread.start(); |
| mHandler = new Handler(mHandlerThread.getLooper(), this); |
| |
| mNotifyNfc = SystemProperties.getInt(NFC_NOTIFICATION_PROP, 0) > 0; |
| if (DEBUG) Slog.v(TAG, "Notify NFC behavior is " + (mNotifyNfc ? "active" : "disabled")); |
| // Don't keep any extra logging threads if not needed |
| mLogWriterService.setKeepAliveTime(1, TimeUnit.SECONDS); |
| mLogWriterService.allowCoreThreadTimeOut(true); |
| |
| mFoldStateListener = new FoldStateListener(mContext, folded -> { |
| if (folded) { |
| setDeviceStateFlags(ICameraService.DEVICE_STATE_FOLDED); |
| } else { |
| clearDeviceStateFlags(ICameraService.DEVICE_STATE_FOLDED); |
| } |
| }); |
| } |
| |
| /** |
| * Sets the device state bits set within {@code deviceStateFlags} leaving all other bits the |
| * same. |
| * <p> |
| * Calling requires permission {@link android.Manifest.permission#CAMERA_SEND_SYSTEM_EVENTS}. |
| * |
| * @param deviceStateFlags a bitmask of the device state bits that should be set. |
| * |
| * @see #clearDeviceStateFlags(int) |
| */ |
| private void setDeviceStateFlags(@DeviceStateFlags int deviceStateFlags) { |
| synchronized (mLock) { |
| mHandler.removeMessages(MSG_NOTIFY_DEVICE_STATE); |
| mDeviceState |= deviceStateFlags; |
| if (mDeviceState != mLastReportedDeviceState) { |
| notifyDeviceStateWithRetriesLocked(RETRY_TIMES); |
| } |
| } |
| } |
| |
| /** |
| * Clears the device state bits set within {@code deviceStateFlags} leaving all other bits the |
| * same. |
| * <p> |
| * Calling requires permission {@link android.Manifest.permission#CAMERA_SEND_SYSTEM_EVENTS}. |
| * |
| * @param deviceStateFlags a bitmask of the device state bits that should be cleared. |
| * |
| * @see #setDeviceStateFlags(int) |
| */ |
| private void clearDeviceStateFlags(@DeviceStateFlags int deviceStateFlags) { |
| synchronized (mLock) { |
| mHandler.removeMessages(MSG_NOTIFY_DEVICE_STATE); |
| mDeviceState &= ~deviceStateFlags; |
| if (mDeviceState != mLastReportedDeviceState) { |
| notifyDeviceStateWithRetriesLocked(RETRY_TIMES); |
| } |
| } |
| } |
| |
| @Override |
| public boolean handleMessage(Message msg) { |
| switch(msg.what) { |
| case MSG_SWITCH_USER: { |
| notifySwitchWithRetries(msg.arg1); |
| } break; |
| case MSG_NOTIFY_DEVICE_STATE: { |
| notifyDeviceStateWithRetries(msg.arg1); |
| } break; |
| default: { |
| Slog.e(TAG, "CameraServiceProxy error, invalid message: " + msg.what); |
| } break; |
| } |
| return true; |
| } |
| |
| @Override |
| public void onStart() { |
| mUserManager = UserManager.get(mContext); |
| if (mUserManager == null) { |
| // Should never see this unless someone messes up the SystemServer service boot order. |
| throw new IllegalStateException("UserManagerService must start before" + |
| " CameraServiceProxy!"); |
| } |
| |
| IntentFilter filter = new IntentFilter(); |
| filter.addAction(Intent.ACTION_USER_ADDED); |
| filter.addAction(Intent.ACTION_USER_REMOVED); |
| filter.addAction(Intent.ACTION_USER_INFO_CHANGED); |
| filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED); |
| filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED); |
| filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); |
| filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); |
| mContext.registerReceiver(mIntentReceiver, filter); |
| |
| publishBinderService(CAMERA_SERVICE_PROXY_BINDER_NAME, mCameraServiceProxy); |
| publishLocalService(CameraServiceProxy.class, this); |
| } |
| |
| @Override |
| public void onBootPhase(int phase) { |
| if (phase == PHASE_BOOT_COMPLETED) { |
| CameraStatsJobService.schedule(mContext); |
| |
| try { |
| int[] displayIds = WindowManagerGlobal.getWindowManagerService() |
| .registerDisplayWindowListener(mDisplayWindowListener); |
| for (int i = 0; i < displayIds.length; i++) { |
| mDisplayWindowListener.onDisplayAdded(displayIds[i]); |
| } |
| } catch (RemoteException e) { |
| Log.e(TAG, "Failed to register display window listener!"); |
| } |
| |
| mContext.getSystemService(DeviceStateManager.class) |
| .registerCallback(new HandlerExecutor(mHandler), mFoldStateListener); |
| } |
| } |
| |
| @Override |
| public void onUserStarting(@NonNull TargetUser user) { |
| synchronized(mLock) { |
| if (mEnabledCameraUsers == null) { |
| // Initialize cameraserver, or update cameraserver if we are recovering |
| // from a crash. |
| switchUserLocked(user.getUserIdentifier()); |
| } |
| } |
| } |
| |
| @Override |
| public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) { |
| synchronized(mLock) { |
| switchUserLocked(to.getUserIdentifier()); |
| } |
| } |
| |
| /** |
| * Handle the death of the native camera service |
| */ |
| @Override |
| public void binderDied() { |
| if (DEBUG) Slog.w(TAG, "Native camera service has died"); |
| synchronized(mLock) { |
| mCameraServiceRaw = null; |
| |
| // All cameras reset to idle on camera service death |
| boolean wasEmpty = mActiveCameraUsage.isEmpty(); |
| mActiveCameraUsage.clear(); |
| |
| if ( mNotifyNfc && !wasEmpty ) { |
| notifyNfcService(/*enablePolling*/ true); |
| } |
| } |
| } |
| |
| private class EventWriterTask implements Runnable { |
| private ArrayList<CameraUsageEvent> mEventList; |
| private static final long WRITER_SLEEP_MS = 100; |
| |
| public EventWriterTask(ArrayList<CameraUsageEvent> eventList) { |
| mEventList = eventList; |
| } |
| |
| @Override |
| public void run() { |
| if (mEventList != null) { |
| for (CameraUsageEvent event : mEventList) { |
| logCameraUsageEvent(event); |
| try { |
| Thread.sleep(WRITER_SLEEP_MS); |
| } catch (InterruptedException e) {} |
| } |
| mEventList.clear(); |
| } |
| } |
| |
| /** |
| * Write camera usage events to stats log. |
| * Package-private |
| */ |
| private void logCameraUsageEvent(CameraUsageEvent e) { |
| int facing = FrameworkStatsLog.CAMERA_ACTION_EVENT__FACING__UNKNOWN; |
| switch(e.mCameraFacing) { |
| case CameraSessionStats.CAMERA_FACING_BACK: |
| facing = FrameworkStatsLog.CAMERA_ACTION_EVENT__FACING__BACK; |
| break; |
| case CameraSessionStats.CAMERA_FACING_FRONT: |
| facing = FrameworkStatsLog.CAMERA_ACTION_EVENT__FACING__FRONT; |
| break; |
| case CameraSessionStats.CAMERA_FACING_EXTERNAL: |
| facing = FrameworkStatsLog.CAMERA_ACTION_EVENT__FACING__EXTERNAL; |
| break; |
| default: |
| Slog.w(TAG, "Unknown camera facing: " + e.mCameraFacing); |
| } |
| |
| int streamCount = 0; |
| if (e.mStreamStats != null) { |
| streamCount = e.mStreamStats.size(); |
| } |
| if (CameraServiceProxy.DEBUG) { |
| Slog.v(TAG, "CAMERA_ACTION_EVENT: action " + e.mAction |
| + " clientName " + e.mClientName |
| + ", duration " + e.getDuration() |
| + ", APILevel " + e.mAPILevel |
| + ", cameraId " + e.mCameraId |
| + ", facing " + facing |
| + ", isNdk " + e.mIsNdk |
| + ", latencyMs " + e.mLatencyMs |
| + ", operatingMode " + e.mOperatingMode |
| + ", internalReconfigure " + e.mInternalReconfigure |
| + ", requestCount " + e.mRequestCount |
| + ", resultErrorCount " + e.mResultErrorCount |
| + ", deviceError " + e.mDeviceError |
| + ", streamCount is " + streamCount |
| + ", userTag is " + e.mUserTag |
| + ", videoStabilizationMode " + e.mVideoStabilizationMode); |
| } |
| // Convert from CameraStreamStats to CameraStreamProto |
| CameraStreamProto[] streamProtos = new CameraStreamProto[MAX_STREAM_STATISTICS]; |
| for (int i = 0; i < MAX_STREAM_STATISTICS; i++) { |
| streamProtos[i] = new CameraStreamProto(); |
| if (i < streamCount) { |
| CameraStreamStats streamStats = e.mStreamStats.get(i); |
| streamProtos[i].width = streamStats.getWidth(); |
| streamProtos[i].height = streamStats.getHeight(); |
| streamProtos[i].format = streamStats.getFormat(); |
| streamProtos[i].dataSpace = streamStats.getDataSpace(); |
| streamProtos[i].usage = streamStats.getUsage(); |
| streamProtos[i].requestCount = streamStats.getRequestCount(); |
| streamProtos[i].errorCount = streamStats.getErrorCount(); |
| streamProtos[i].firstCaptureLatencyMillis = streamStats.getStartLatencyMs(); |
| streamProtos[i].maxHalBuffers = streamStats.getMaxHalBuffers(); |
| streamProtos[i].maxAppBuffers = streamStats.getMaxAppBuffers(); |
| streamProtos[i].histogramType = streamStats.getHistogramType(); |
| streamProtos[i].histogramBins = streamStats.getHistogramBins(); |
| streamProtos[i].histogramCounts = streamStats.getHistogramCounts(); |
| streamProtos[i].dynamicRangeProfile = streamStats.getDynamicRangeProfile(); |
| streamProtos[i].streamUseCase = streamStats.getStreamUseCase(); |
| |
| if (CameraServiceProxy.DEBUG) { |
| String histogramTypeName = |
| cameraHistogramTypeToString(streamProtos[i].histogramType); |
| Slog.v(TAG, "Stream " + i + ": width " + streamProtos[i].width |
| + ", height " + streamProtos[i].height |
| + ", format " + streamProtos[i].format |
| + ", maxPreviewFps " + streamStats.getMaxPreviewFps() |
| + ", dataSpace " + streamProtos[i].dataSpace |
| + ", usage " + streamProtos[i].usage |
| + ", requestCount " + streamProtos[i].requestCount |
| + ", errorCount " + streamProtos[i].errorCount |
| + ", firstCaptureLatencyMillis " |
| + streamProtos[i].firstCaptureLatencyMillis |
| + ", maxHalBuffers " + streamProtos[i].maxHalBuffers |
| + ", maxAppBuffers " + streamProtos[i].maxAppBuffers |
| + ", histogramType " + histogramTypeName |
| + ", histogramBins " |
| + Arrays.toString(streamProtos[i].histogramBins) |
| + ", histogramCounts " |
| + Arrays.toString(streamProtos[i].histogramCounts) |
| + ", dynamicRangeProfile " + streamProtos[i].dynamicRangeProfile |
| + ", streamUseCase " + streamProtos[i].streamUseCase); |
| } |
| } |
| } |
| FrameworkStatsLog.write(FrameworkStatsLog.CAMERA_ACTION_EVENT, e.getDuration(), |
| e.mAPILevel, e.mClientName, facing, e.mCameraId, e.mAction, e.mIsNdk, |
| e.mLatencyMs, e.mOperatingMode, e.mInternalReconfigure, |
| e.mRequestCount, e.mResultErrorCount, e.mDeviceError, |
| streamCount, MessageNano.toByteArray(streamProtos[0]), |
| MessageNano.toByteArray(streamProtos[1]), |
| MessageNano.toByteArray(streamProtos[2]), |
| MessageNano.toByteArray(streamProtos[3]), |
| MessageNano.toByteArray(streamProtos[4]), |
| e.mUserTag, e.mVideoStabilizationMode); |
| } |
| } |
| |
| /** |
| * Dump camera usage events to log. |
| * Package-private |
| */ |
| void dumpUsageEvents() { |
| synchronized(mLock) { |
| // Randomize order of events so that it's not meaningful |
| Collections.shuffle(mCameraUsageHistory); |
| mLogWriterService.execute(new EventWriterTask( |
| new ArrayList<CameraUsageEvent>(mCameraUsageHistory))); |
| |
| mCameraUsageHistory.clear(); |
| } |
| final long ident = Binder.clearCallingIdentity(); |
| try { |
| CameraStatsJobService.schedule(mContext); |
| } finally { |
| Binder.restoreCallingIdentity(ident); |
| } |
| } |
| |
| @Nullable |
| private ICameraService getCameraServiceRawLocked() { |
| if (mCameraServiceRaw == null) { |
| IBinder cameraServiceBinder = getBinderService(CAMERA_SERVICE_BINDER_NAME); |
| if (cameraServiceBinder == null) { |
| return null; |
| } |
| try { |
| cameraServiceBinder.linkToDeath(this, /*flags*/ 0); |
| } catch (RemoteException e) { |
| Slog.w(TAG, "Could not link to death of native camera service"); |
| return null; |
| } |
| |
| mCameraServiceRaw = ICameraService.Stub.asInterface(cameraServiceBinder); |
| } |
| return mCameraServiceRaw; |
| } |
| |
| private void switchUserLocked(int userHandle) { |
| Set<Integer> currentUserHandles = getEnabledUserHandles(userHandle); |
| mLastUser = userHandle; |
| if (mEnabledCameraUsers == null || !mEnabledCameraUsers.equals(currentUserHandles)) { |
| // Some user handles have been added or removed, update cameraserver. |
| mEnabledCameraUsers = currentUserHandles; |
| notifySwitchWithRetriesLocked(RETRY_TIMES); |
| } |
| } |
| |
| private Set<Integer> getEnabledUserHandles(int currentUserHandle) { |
| int[] userProfiles = mUserManager.getEnabledProfileIds(currentUserHandle); |
| Set<Integer> handles = new ArraySet<>(userProfiles.length); |
| |
| for (int id : userProfiles) { |
| handles.add(id); |
| } |
| |
| return handles; |
| } |
| |
| private void notifySwitchWithRetries(int retries) { |
| synchronized(mLock) { |
| notifySwitchWithRetriesLocked(retries); |
| } |
| } |
| |
| private void notifySwitchWithRetriesLocked(int retries) { |
| if (mEnabledCameraUsers == null) { |
| return; |
| } |
| if (notifyCameraserverLocked(ICameraService.EVENT_USER_SWITCHED, mEnabledCameraUsers)) { |
| retries = 0; |
| } |
| if (retries <= 0) { |
| return; |
| } |
| Slog.i(TAG, "Could not notify camera service of user switch, retrying..."); |
| mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SWITCH_USER, retries - 1, 0, null), |
| RETRY_DELAY_TIME); |
| } |
| |
| private boolean notifyCameraserverLocked(int eventType, Set<Integer> updatedUserHandles) { |
| // Forward the user switch event to the native camera service running in the cameraserver |
| // process. |
| ICameraService cameraService = getCameraServiceRawLocked(); |
| if (cameraService == null) { |
| Slog.w(TAG, "Could not notify cameraserver, camera service not available."); |
| return false; |
| } |
| |
| try { |
| mCameraServiceRaw.notifySystemEvent(eventType, toArray(updatedUserHandles)); |
| } catch (RemoteException e) { |
| Slog.w(TAG, "Could not notify cameraserver, remote exception: " + e); |
| // Not much we can do if camera service is dead. |
| return false; |
| } |
| return true; |
| } |
| |
| private void notifyDeviceStateWithRetries(int retries) { |
| synchronized (mLock) { |
| notifyDeviceStateWithRetriesLocked(retries); |
| } |
| } |
| |
| private void notifyDeviceStateWithRetriesLocked(int retries) { |
| if (notifyDeviceStateChangeLocked(mDeviceState)) { |
| return; |
| } |
| if (retries <= 0) { |
| return; |
| } |
| Slog.i(TAG, "Could not notify camera service of device state change, retrying..."); |
| mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_NOTIFY_DEVICE_STATE, retries - 1, |
| 0, null), RETRY_DELAY_TIME); |
| } |
| |
| private boolean notifyDeviceStateChangeLocked(@DeviceStateFlags int deviceState) { |
| // Forward the state to the native camera service running in the cameraserver process. |
| ICameraService cameraService = getCameraServiceRawLocked(); |
| if (cameraService == null) { |
| Slog.w(TAG, "Could not notify cameraserver, camera service not available."); |
| return false; |
| } |
| |
| try { |
| mCameraServiceRaw.notifyDeviceStateChange(deviceState); |
| } catch (RemoteException e) { |
| Slog.w(TAG, "Could not notify cameraserver, remote exception: " + e); |
| // Not much we can do if camera service is dead. |
| return false; |
| } |
| mLastReportedDeviceState = deviceState; |
| return true; |
| } |
| |
| private boolean notifyUsbDeviceHotplugLocked(@NonNull UsbDevice device, boolean attached) { |
| // Only handle external USB camera devices |
| if (device.getHasVideoCapture()) { |
| // Forward the usb hotplug event to the native camera service running in the |
| // cameraserver |
| // process. |
| ICameraService cameraService = getCameraServiceRawLocked(); |
| if (cameraService == null) { |
| Slog.w(TAG, "Could not notify cameraserver, camera service not available."); |
| return false; |
| } |
| |
| try { |
| int eventType = attached ? ICameraService.EVENT_USB_DEVICE_ATTACHED |
| : ICameraService.EVENT_USB_DEVICE_DETACHED; |
| mCameraServiceRaw.notifySystemEvent(eventType, new int[]{device.getDeviceId()}); |
| } catch (RemoteException e) { |
| Slog.w(TAG, "Could not notify cameraserver, remote exception: " + e); |
| // Not much we can do if camera service is dead. |
| return false; |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| private float getMinFps(CameraSessionStats cameraState) { |
| float maxFps = cameraState.getMaxPreviewFps(); |
| return Math.max(Math.min(maxFps, MAX_PREVIEW_FPS), MIN_PREVIEW_FPS); |
| } |
| |
| private void updateActivityCount(CameraSessionStats cameraState) { |
| String cameraId = cameraState.getCameraId(); |
| int newCameraState = cameraState.getNewCameraState(); |
| int facing = cameraState.getFacing(); |
| String clientName = cameraState.getClientName(); |
| int apiLevel = cameraState.getApiLevel(); |
| boolean isNdk = cameraState.isNdk(); |
| int sessionType = cameraState.getSessionType(); |
| int internalReconfigureCount = cameraState.getInternalReconfigureCount(); |
| int latencyMs = cameraState.getLatencyMs(); |
| long requestCount = cameraState.getRequestCount(); |
| long resultErrorCount = cameraState.getResultErrorCount(); |
| boolean deviceError = cameraState.getDeviceErrorFlag(); |
| List<CameraStreamStats> streamStats = cameraState.getStreamStats(); |
| String userTag = cameraState.getUserTag(); |
| int videoStabilizationMode = cameraState.getVideoStabilizationMode(); |
| synchronized(mLock) { |
| // Update active camera list and notify NFC if necessary |
| boolean wasEmpty = mActiveCameraUsage.isEmpty(); |
| switch (newCameraState) { |
| case CameraSessionStats.CAMERA_STATE_OPEN: |
| // Notify the audio subsystem about the facing of the most-recently opened |
| // camera This can be used to select the best audio tuning in case video |
| // recording with that camera will happen. Since only open events are used, if |
| // multiple cameras are opened at once, the one opened last will be used to |
| // select audio tuning. |
| AudioManager audioManager = getContext().getSystemService(AudioManager.class); |
| if (audioManager != null) { |
| // Map external to front for audio tuning purposes |
| String facingStr = (facing == CameraSessionStats.CAMERA_FACING_BACK) ? |
| "back" : "front"; |
| String facingParameter = "cameraFacing=" + facingStr; |
| audioManager.setParameters(facingParameter); |
| } |
| CameraUsageEvent openEvent = new CameraUsageEvent( |
| cameraId, facing, clientName, apiLevel, isNdk, |
| FrameworkStatsLog.CAMERA_ACTION_EVENT__ACTION__OPEN, |
| latencyMs, sessionType); |
| mCameraUsageHistory.add(openEvent); |
| break; |
| case CameraSessionStats.CAMERA_STATE_ACTIVE: |
| // Check current active camera IDs to see if this package is already talking to |
| // some camera |
| boolean alreadyActivePackage = false; |
| for (int i = 0; i < mActiveCameraUsage.size(); i++) { |
| if (mActiveCameraUsage.valueAt(i).mClientName.equals(clientName)) { |
| alreadyActivePackage = true; |
| break; |
| } |
| } |
| // If not already active, notify window manager about this new package using a |
| // camera |
| if (!alreadyActivePackage) { |
| WindowManagerInternal wmi = |
| LocalServices.getService(WindowManagerInternal.class); |
| float minFps = getMinFps(cameraState); |
| wmi.addRefreshRateRangeForPackage(clientName, |
| minFps, MAX_PREVIEW_FPS); |
| } |
| |
| // Update activity events |
| CameraUsageEvent newEvent = new CameraUsageEvent( |
| cameraId, facing, clientName, apiLevel, isNdk, |
| FrameworkStatsLog.CAMERA_ACTION_EVENT__ACTION__SESSION, |
| latencyMs, sessionType); |
| CameraUsageEvent oldEvent = mActiveCameraUsage.put(cameraId, newEvent); |
| if (oldEvent != null) { |
| Slog.w(TAG, "Camera " + cameraId + " was already marked as active"); |
| oldEvent.markCompleted(/*internalReconfigure*/0, /*requestCount*/0, |
| /*resultErrorCount*/0, /*deviceError*/false, streamStats, |
| /*userTag*/"", /*videoStabilizationMode*/-1); |
| mCameraUsageHistory.add(oldEvent); |
| } |
| break; |
| case CameraSessionStats.CAMERA_STATE_IDLE: |
| case CameraSessionStats.CAMERA_STATE_CLOSED: |
| CameraUsageEvent doneEvent = mActiveCameraUsage.remove(cameraId); |
| if (doneEvent != null) { |
| |
| doneEvent.markCompleted(internalReconfigureCount, requestCount, |
| resultErrorCount, deviceError, streamStats, userTag, |
| videoStabilizationMode); |
| mCameraUsageHistory.add(doneEvent); |
| |
| // Check current active camera IDs to see if this package is still |
| // talking to some camera |
| boolean stillActivePackage = false; |
| for (int i = 0; i < mActiveCameraUsage.size(); i++) { |
| if (mActiveCameraUsage.valueAt(i).mClientName.equals(clientName)) { |
| stillActivePackage = true; |
| break; |
| } |
| } |
| // If not longer active, notify window manager about this package being done |
| // with camera |
| if (!stillActivePackage) { |
| WindowManagerInternal wmi = |
| LocalServices.getService(WindowManagerInternal.class); |
| wmi.removeRefreshRateRangeForPackage(clientName); |
| } |
| } |
| |
| if (newCameraState == CameraSessionStats.CAMERA_STATE_CLOSED) { |
| CameraUsageEvent closeEvent = new CameraUsageEvent( |
| cameraId, facing, clientName, apiLevel, isNdk, |
| FrameworkStatsLog.CAMERA_ACTION_EVENT__ACTION__CLOSE, |
| latencyMs, sessionType); |
| mCameraUsageHistory.add(closeEvent); |
| } |
| |
| if (mCameraUsageHistory.size() > MAX_USAGE_HISTORY) { |
| dumpUsageEvents(); |
| } |
| |
| break; |
| } |
| boolean isEmpty = mActiveCameraUsage.isEmpty(); |
| if ( mNotifyNfc && (wasEmpty != isEmpty) ) { |
| notifyNfcService(isEmpty); |
| } |
| } |
| } |
| |
| private void notifyNfcService(boolean enablePolling) { |
| |
| IBinder nfcServiceBinder = getBinderService(NFC_SERVICE_BINDER_NAME); |
| if (nfcServiceBinder == null) { |
| Slog.w(TAG, "Could not connect to NFC service to notify it of camera state"); |
| return; |
| } |
| INfcAdapter nfcAdapterRaw = INfcAdapter.Stub.asInterface(nfcServiceBinder); |
| int flags = enablePolling ? ENABLE_POLLING_FLAGS : DISABLE_POLLING_FLAGS; |
| if (DEBUG) Slog.v(TAG, "Setting NFC reader mode to flags " + flags); |
| try { |
| nfcAdapterRaw.setReaderMode(nfcInterfaceToken, null, flags, null); |
| } catch (RemoteException e) { |
| Slog.w(TAG, "Could not notify NFC service, remote exception: " + e); |
| } |
| } |
| |
| private static int[] toArray(Collection<Integer> c) { |
| int len = c.size(); |
| int[] ret = new int[len]; |
| int idx = 0; |
| for (Integer i : c) { |
| ret[idx++] = i; |
| } |
| return ret; |
| } |
| |
| private static String cameraStateToString(int newCameraState) { |
| switch (newCameraState) { |
| case CameraSessionStats.CAMERA_STATE_OPEN: return "CAMERA_STATE_OPEN"; |
| case CameraSessionStats.CAMERA_STATE_ACTIVE: return "CAMERA_STATE_ACTIVE"; |
| case CameraSessionStats.CAMERA_STATE_IDLE: return "CAMERA_STATE_IDLE"; |
| case CameraSessionStats.CAMERA_STATE_CLOSED: return "CAMERA_STATE_CLOSED"; |
| default: break; |
| } |
| return "CAMERA_STATE_UNKNOWN"; |
| } |
| |
| private static String cameraFacingToString(int cameraFacing) { |
| switch (cameraFacing) { |
| case CameraSessionStats.CAMERA_FACING_BACK: return "CAMERA_FACING_BACK"; |
| case CameraSessionStats.CAMERA_FACING_FRONT: return "CAMERA_FACING_FRONT"; |
| case CameraSessionStats.CAMERA_FACING_EXTERNAL: return "CAMERA_FACING_EXTERNAL"; |
| default: break; |
| } |
| return "CAMERA_FACING_UNKNOWN"; |
| } |
| |
| private static String cameraHistogramTypeToString(int cameraHistogramType) { |
| switch (cameraHistogramType) { |
| case CameraStreamStats.HISTOGRAM_TYPE_CAPTURE_LATENCY: |
| return "HISTOGRAM_TYPE_CAPTURE_LATENCY"; |
| default: |
| break; |
| } |
| return "HISTOGRAM_TYPE_UNKNOWN"; |
| } |
| |
| } |