diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java index 00b39f11a4c64..cd24576359328 100644 --- a/services/core/java/com/android/server/camera/CameraServiceProxy.java +++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java @@ -15,17 +15,33 @@ */ package com.android.server.camera; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; +import static android.os.Build.VERSION_CODES.M; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.ActivityTaskManager; +import android.app.TaskStackListener; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +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.CameraMetadata; +import android.hardware.display.DisplayManager; import android.media.AudioManager; import android.metrics.LogMaker; import android.nfc.INfcAdapter; @@ -41,7 +57,10 @@ 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.Surface; import com.android.internal.annotations.GuardedBy; import com.android.framework.protobuf.nano.MessageNano; @@ -61,6 +80,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -203,6 +223,63 @@ public class CameraServiceProxy extends SystemService } } + private final TaskStateHandler mTaskStackListener = new TaskStateHandler(); + + private final class TaskInfo { + private int frontTaskId; + private boolean isResizeable; + private boolean isFixedOrientationLandscape; + private boolean isFixedOrientationPortrait; + private int displayId; + } + + private final class TaskStateHandler extends TaskStackListener { + private final Object mMapLock = new Object(); + + // maps the current top level task id to its corresponding package name + @GuardedBy("mMapLock") + private final ArrayMap mTaskInfoMap = new ArrayMap<>(); + + @Override + public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) + throws RemoteException { + synchronized (mMapLock) { + TaskInfo info = new TaskInfo(); + info.frontTaskId = taskInfo.taskId; + info.isResizeable = taskInfo.isResizeable; + info.displayId = taskInfo.displayId; + info.isFixedOrientationLandscape = ActivityInfo.isFixedOrientationLandscape( + taskInfo.topActivityInfo.screenOrientation); + info.isFixedOrientationPortrait = ActivityInfo.isFixedOrientationPortrait( + taskInfo.topActivityInfo.screenOrientation); + mTaskInfoMap.put(taskInfo.topActivityInfo.packageName, info); + } + } + + @Override + public void onTaskRemoved(int taskId) throws RemoteException { + synchronized (mMapLock) { + for (Map.Entry entry : mTaskInfoMap.entrySet()){ + if (entry.getValue().frontTaskId == taskId) { + mTaskInfoMap.remove(entry.getKey()); + break; + } + } + } + } + + public @Nullable TaskInfo getFrontTaskInfo(String packageName) { + synchronized (mMapLock) { + if (mTaskInfoMap.containsKey(packageName)) { + return mTaskInfoMap.get(packageName); + } + } + + Log.e(TAG, "Top task with package name: " + packageName + " not found!"); + return null; + } + }; + private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -229,6 +306,102 @@ public class CameraServiceProxy extends SystemService }; private final ICameraServiceProxy.Stub mCameraServiceProxy = new ICameraServiceProxy.Stub() { + private 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; + } + + /** + * Gets whether crop-rotate-scale is needed. + */ + private boolean getNeedCropRotateScale(Context ctx, String packageName, + @Nullable TaskInfo taskInfo, int sensorOrientation, int lensFacing) { + if (taskInfo == null) { + return false; + } + + // 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 false; + } + + // Only enable the crop-rotate-scale workaround if the app targets M or below and is not + // resizeable. + if ((ctx != null) && !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 false; + } + + DisplayManager displayManager = ctx.getSystemService(DisplayManager.class); + Display display = displayManager.getDisplay(taskInfo.displayId); + int rotation = display.getRotation(); + int rotationDegree = 0; + switch (rotation) { + 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; + } + + // Here we only need to know whether the camera is landscape or portrait. Therefore we + // don't need to consider whether it is a front or back camera. The formula works for + // both. + boolean landscapeCamera = ((rotationDegree + sensorOrientation) % 180 == 0); + Slog.v(TAG, + "Display.getRotation()=" + rotationDegree + + " CameraCharacteristics.SENSOR_ORIENTATION=" + sensorOrientation + + " isFixedOrientationPortrait=" + taskInfo.isFixedOrientationPortrait + + " isFixedOrientationLandscape=" + + taskInfo.isFixedOrientationLandscape); + // We need to do crop-rotate-scale when camera is landscape and activity is portrait or + // vice versa. + if ((taskInfo.isFixedOrientationPortrait && landscapeCamera) + || (taskInfo.isFixedOrientationLandscape && !landscapeCamera)) { + return true; + } else { + return false; + } + } + + @Override + public boolean isRotateAndCropOverrideNeeded(String packageName, int sensorOrientation, + int lensFacing) { + if (Binder.getCallingUid() != Process.CAMERASERVER_UID) { + Slog.e(TAG, "Calling UID: " + Binder.getCallingUid() + " doesn't match expected " + + " camera service UID!"); + return false; + } + + // A few remaining todos: + // 1) Do the same check when working in WM compatible mode. The sequence needs + // to be adjusted and use orientation events as triggers for all active camera + // clients. + // 2) 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. + return getNeedCropRotateScale(mContext, packageName, + mTaskStackListener.getFrontTaskInfo(packageName), sensorOrientation, + lensFacing); + } + @Override public void pingForUserUpdate() { if (Binder.getCallingUid() != Process.CAMERASERVER_UID) { @@ -350,6 +523,12 @@ public class CameraServiceProxy extends SystemService publishBinderService(CAMERA_SERVICE_PROXY_BINDER_NAME, mCameraServiceProxy); publishLocalService(CameraServiceProxy.class, this); + try { + ActivityTaskManager.getService().registerTaskStackListener(mTaskStackListener); + } catch (RemoteException e) { + Log.e(TAG, "Failed to register task stack listener!"); + } + CameraStatsJobService.schedule(mContext); }