blob: aec60deba9335985b9f2742ca5c034931dc46753 [file] [log] [blame]
/*
* 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";
}
}