Loading services/core/java/com/android/server/wm/AppCompatCameraStateStrategy.java 0 → 100644 +80 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.wm; import android.annotation.NonNull; /** * Interface for tracking camera state, and notifying {@link AppCompatCameraStatePolicy} of changes. * * <p>{@link AppCompatCameraStateStrategy} implementations should track which apps hold the camera * access, and any ongoing camera state changes changes. 'track' methods should always be called * before appropriate 'maybeNotify' methods for the same task-cameraId pair, but the order of * open/close can vary, for example due to built-in delays from the caller. */ interface AppCompatCameraStateStrategy { /** * Allows saving cameraId, to be processed later on * {@link AppCompatCameraStateStrategy#notifyPolicyCameraOpenedIfNeeded} after a delay. * * <p>The {@link AppCompatCameraStateStrategy} should track which camera operations have been * started (delayed), as camera opened/closed operations often compete with each other, and due * to built-in delays can cause different order of these operations when they are finally * processed. Examples of quickly closing and opening the camera: activity relaunch due to * configuration change, switching front/back cameras, new app requesting camera and taking the * access rights away from the existing camera app. */ void trackOnCameraOpened(@NonNull String cameraId); /** * Processes camera opened signal, and if the change is relevant for {@link * AppCompatCameraStatePolicy} calls {@link AppCompatCameraStatePolicy#onCameraOpened}. */ void notifyPolicyCameraOpenedIfNeeded(@NonNull String cameraId, @NonNull String packageName, @NonNull AppCompatCameraStatePolicy policy); /** * Allows saving cameraId to be processed later on * {@link AppCompatCameraStateStrategy#notifyPolicyCameraClosedIfNeeded} after a delay. * * <p>The {@link AppCompatCameraStateStrategy} should track which camera operations have been * started (delayed), as camera opened/closed operations often compete with each other, and due * to built-in delays can cause different order of these operations when they are finally * processed. Examples of quickly closing and opening the camera: activity relaunch due to * configuration change, switching front/back cameras, new app requesting camera and taking the * access rights away from the existing camera app. */ void trackOnCameraClosed(@NonNull String cameraId); /** * Processes camera closed signal, and if the change is relevant for {@link * AppCompatCameraStatePolicy} calls {@link AppCompatCameraStatePolicy#onCameraClosed}. * * @return true if policies were able to handle the camera closed event, or false if it needs to * be rescheduled. */ boolean notifyPolicyCameraClosedIfNeeded(@NonNull String cameraId, @NonNull AppCompatCameraStatePolicy policy); /** Returns whether a given activity holds any camera opened. */ boolean isCameraRunningForActivity(@NonNull ActivityRecord activity); /** Returns whether a given activity holds a specific camera opened. */ // TODO(b/336474959): try to decouple `cameraId` from the listeners. boolean isCameraWithIdRunningForActivity(@NonNull ActivityRecord activity, @NonNull String cameraId); } services/core/java/com/android/server/wm/AppCompatCameraStateStrategyForPackage.java 0 → 100644 +166 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.wm; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.annotation.NonNull; import android.annotation.Nullable; import android.util.ArraySet; import android.util.Slog; import java.util.ArrayList; import java.util.Set; /** {@link AppCompatCameraStateStrategy} that tracks packageName-cameraId pairs. */ class AppCompatCameraStateStrategyForPackage implements AppCompatCameraStateStrategy { private static final String TAG = TAG_WITH_CLASS_NAME ? "AppCompatCameraStateStrategyForPackage" : TAG_WM; @NonNull private final DisplayContent mDisplayContent; // Bi-directional map between package names and active camera IDs since we need to 1) get a // camera id by a package name when resizing the window; 2) get a package name by a camera id // when camera connection is closed and we need to clean up our records. private final CameraIdPackageNameBiMapping mCameraIdPackageBiMapping = new CameraIdPackageNameBiMapping(); // TODO(b/380840084): Consider making this a set of CameraId/PackageName pairs. This is to // keep track of camera-closed signals when apps are switching camera access, so that the policy // can restore app configuration when an app closes camera (e.g. loses camera access due to // another app). private final Set<String> mScheduledToBeRemovedCameraIdSet = new ArraySet<>(); private final Set<String> mScheduledCompatModeUpdateCameraIdSet = new ArraySet<>(); AppCompatCameraStateStrategyForPackage(@NonNull DisplayContent displayContent) { mDisplayContent = displayContent; } @Override public void trackOnCameraOpened(@NonNull String cameraId) { mScheduledToBeRemovedCameraIdSet.remove(cameraId); mScheduledCompatModeUpdateCameraIdSet.add(cameraId); } @Override public void notifyPolicyCameraOpenedIfNeeded(@NonNull String cameraId, @NonNull String packageName, @NonNull AppCompatCameraStatePolicy policy) { if (!mScheduledCompatModeUpdateCameraIdSet.remove(cameraId)) { // Camera compat mode update has happened already or was cancelled // because camera was closed. return; } mCameraIdPackageBiMapping.put(packageName, cameraId); // If there are multiple activities of the same package name and none of // them are the top running activity, we do not apply treatment (rather than // guessing and applying it to the wrong activity). final ActivityRecord cameraActivity = findUniqueActivityWithPackageName(packageName); if (cameraActivity == null) { // If camera is active, activity, task and app process must exist. No need to notify // listeners or track the package otherwise. return; } policy.onCameraOpened(cameraActivity); } @Override public void trackOnCameraClosed(@NonNull String cameraId) { mScheduledToBeRemovedCameraIdSet.add(cameraId); // No need to update window size for this camera if it's already closed. mScheduledCompatModeUpdateCameraIdSet.remove(cameraId); } @Override public boolean notifyPolicyCameraClosedIfNeeded(@NonNull String cameraId, @NonNull AppCompatCameraStatePolicy policy) { if (!mScheduledToBeRemovedCameraIdSet.remove(cameraId)) { // Already reconnected to this camera, no need to clean up. return true; } final boolean canClose = policy.canCameraBeClosed(cameraId); if (canClose) { // Finish cleaning up. mCameraIdPackageBiMapping.removeCameraId(cameraId); policy.onCameraClosed(); return true; } else { mScheduledToBeRemovedCameraIdSet.add(cameraId); // Not ready to process closure yet - the camera activity might be refreshing. // Try again later. return false; } } @Override public boolean isCameraRunningForActivity(@NonNull ActivityRecord activity) { return getCameraIdForActivity(activity) != null; } @Override public boolean isCameraWithIdRunningForActivity(@NonNull ActivityRecord activity, @NonNull String cameraId) { return cameraId.equals(getCameraIdForActivity(activity)); } /** Returns the information about apps using camera, for logging purposes. */ private String getCameraIdForActivity(@NonNull ActivityRecord activity) { return mCameraIdPackageBiMapping.getCameraId(activity.packageName); } // TODO(b/335165310): verify that this works in multi instance and permission dialogs. /** * Finds a visible activity with the given package name. * * <p>If there are multiple visible activities with a given package name, and none of them are * the `topRunningActivity`, returns null. */ @Nullable private ActivityRecord findUniqueActivityWithPackageName(@NonNull String packageName) { final ActivityRecord topActivity = mDisplayContent.topRunningActivity( /* considerKeyguardState= */ true); if (topActivity != null && topActivity.packageName.equals(packageName)) { return topActivity; } final ArrayList<ActivityRecord> activitiesOfPackageWhichOpenedCamera = new ArrayList<>(); mDisplayContent.forAllActivities(activityRecord -> { if (activityRecord.isVisibleRequested() && activityRecord.packageName.equals(packageName)) { activitiesOfPackageWhichOpenedCamera.add(activityRecord); } }); if (activitiesOfPackageWhichOpenedCamera.isEmpty()) { Slog.w(TAG, "Cannot find camera activity."); return null; } if (activitiesOfPackageWhichOpenedCamera.size() == 1) { return activitiesOfPackageWhichOpenedCamera.get(0); } // Return null if we cannot determine which activity opened camera. This is preferred to // applying treatment to the wrong activity. Slog.w(TAG, "Cannot determine which activity opened camera."); return null; } @Override public String toString() { return "CameraIdPackageNameBiMapping=" + mCameraIdPackageBiMapping.toString(); } } services/core/java/com/android/server/wm/CameraIdPackageNameBiMapping.java +2 −1 Original line number Diff line number Diff line Loading @@ -63,7 +63,8 @@ final class CameraIdPackageNameBiMapping { } @NonNull String getSummaryForDisplayRotationHistoryRecord() { @Override public String toString() { return "{ mPackageToCameraIdMap=" + mPackageToCameraIdMap + " }"; } Loading services/core/java/com/android/server/wm/CameraStateMonitor.java +35 −121 Original line number Diff line number Diff line Loading @@ -23,23 +23,16 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.hardware.camera2.CameraManager; import android.os.Handler; import android.util.ArraySet; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.ProtoLog; import java.util.ArrayList; import java.util.List; import java.util.Set; /** * Class that listens to camera open/closed signals, keeps track of the current apps using camera, * and notifies listeners. */ class CameraStateMonitor { private static final String TAG = TAG_WITH_CLASS_NAME ? "CameraStateMonitor" : TAG_WM; // Delay for updating letterbox after Camera connection is closed. Needed to avoid flickering // when an app is flipping between front and rear cameras or when size compat mode is restarted. // TODO(b/330148095): Investigate flickering without using delays, remove them if possible. Loading @@ -53,6 +46,7 @@ class CameraStateMonitor { private static final int CAMERA_OPENED_LETTERBOX_UPDATE_DELAY_MS = CAMERA_CLOSED_LETTERBOX_UPDATE_DELAY_MS / 2; /** Returns the information about apps using camera, for logging purposes. */ @NonNull private final DisplayContent mDisplayContent; @NonNull Loading @@ -62,20 +56,8 @@ class CameraStateMonitor { @NonNull private final Handler mHandler; // Bi-directional map between package names and active camera IDs since we need to 1) get a // camera id by a package name when resizing the window; 2) get a package name by a camera id // when camera connection is closed and we need to clean up our records. private final CameraIdPackageNameBiMapping mCameraIdPackageBiMapping = new CameraIdPackageNameBiMapping(); // TODO(b/380840084): Consider making this a set of CameraId/PackageName pairs. This is to // keep track of camera-closed signals when apps are switching camera access, so that the policy // can restore app configuration when an app closes camera (e.g. loses camera access due to // another app). private final Set<String> mScheduledToBeRemovedCameraIdSet = new ArraySet<>(); // TODO(b/336474959): should/can this go in the compat listeners? private final Set<String> mScheduledCompatModeUpdateCameraIdSet = new ArraySet<>(); @VisibleForTesting final AppCompatCameraStateStrategy mAppCompatCameraStateStrategy; @VisibleForTesting final AppCompatCameraStatePolicy mAppCompatCameraStatePolicy; Loading Loading @@ -110,6 +92,7 @@ class CameraStateMonitor { mAppCompatCameraStatePolicy = appCompatCameraStatePolicy; mWmService = displayContent.mWmService; mCameraManager = mWmService.mContext.getSystemService(CameraManager.class); mAppCompatCameraStateStrategy = new AppCompatCameraStateStrategyForPackage(displayContent); } /** Starts listening to camera opened/closed signals. */ Loading @@ -122,7 +105,7 @@ class CameraStateMonitor { } /** Stops listening to camera opened/closed signals. */ public void stopListeningToCameraState() { void stopListeningToCameraState() { if (mCameraManager != null) { mCameraManager.unregisterAvailabilityCallback(mAvailabilityCallback); } Loading @@ -140,37 +123,23 @@ class CameraStateMonitor { private void notifyCameraOpenedWithDelay(@NonNull String cameraId, @NonNull String packageName) { // Some apps can’t handle configuration changes coming at the same time with Camera setup so // delaying orientation update to accommodate for that. // If an activity is restarting or camera is flipping, the camera connection can be // quickly closed and reopened. mScheduledToBeRemovedCameraIdSet.remove(cameraId); ProtoLog.v(WM_DEBUG_STATES, "Display id=%d is notified that Camera %s is open for package %s", mDisplayContent.mDisplayId, cameraId, packageName); // Some apps can’t handle configuration changes coming at the same time with Camera setup so // delaying orientation update to accommodate for that. mScheduledCompatModeUpdateCameraIdSet.add(cameraId); mHandler.postDelayed(() -> notifyCameraOpenedInternal(cameraId, packageName), CAMERA_OPENED_LETTERBOX_UPDATE_DELAY_MS); mAppCompatCameraStateStrategy.trackOnCameraOpened(cameraId); mHandler.postDelayed(() -> { synchronized (mWmService.mGlobalLock) { notifyCameraOpenedInternal(cameraId, packageName); }}, CAMERA_OPENED_LETTERBOX_UPDATE_DELAY_MS); } private void notifyCameraOpenedInternal(@NonNull String cameraId, @NonNull String packageName) { synchronized (mWmService.mGlobalLock) { if (!mScheduledCompatModeUpdateCameraIdSet.remove(cameraId)) { // Camera compat mode update has happened already or was cancelled // because camera was closed. return; } mCameraIdPackageBiMapping.put(packageName, cameraId); // If there are multiple activities of the same package name and none of // them are the top running activity, we do not apply treatment (rather than // guessing and applying it to the wrong activity). final ActivityRecord cameraActivity = findUniqueActivityWithPackageName(packageName); if (cameraActivity == null || cameraActivity.getTask() == null) { return; } mAppCompatCameraStatePolicy.onCameraOpened(cameraActivity); } mAppCompatCameraStateStrategy.notifyPolicyCameraOpenedIfNeeded(cameraId, packageName, mAppCompatCameraStatePolicy); } /** Loading @@ -183,99 +152,44 @@ class CameraStateMonitor { ProtoLog.v(WM_DEBUG_STATES, "Display id=%d is notified that Camera %s is closed.", mDisplayContent.mDisplayId, cameraId); mScheduledToBeRemovedCameraIdSet.add(cameraId); // No need to update window size for this camera if it's already closed. mScheduledCompatModeUpdateCameraIdSet.remove(cameraId); scheduleRemoveCameraId(cameraId); } /** Returns whether a given activity holds any camera opened. */ boolean isCameraRunningForActivity(@NonNull ActivityRecord activity) { return getCameraIdForActivity(activity) != null; return mAppCompatCameraStateStrategy.isCameraRunningForActivity(activity); } /** Returns whether a given activity holds a specific camera opened. */ // TODO(b/336474959): try to decouple `cameraId` from the listeners. boolean isCameraWithIdRunningForActivity(@NonNull ActivityRecord activity, String cameraId) { return cameraId.equals(getCameraIdForActivity(activity)); } void rescheduleRemoveCameraActivity(@NonNull String cameraId) { mScheduledToBeRemovedCameraIdSet.add(cameraId); scheduleRemoveCameraId(cameraId); } @Nullable private String getCameraIdForActivity(@NonNull ActivityRecord activity) { return mCameraIdPackageBiMapping.getCameraId(activity.packageName); boolean isCameraWithIdRunningForActivity(@NonNull ActivityRecord activity, @NonNull String cameraId) { return mAppCompatCameraStateStrategy.isCameraWithIdRunningForActivity(activity, cameraId); } // Delay is needed to avoid rotation flickering when an app is flipping between front and // rear cameras, when size compat mode is restarted or activity is being refreshed. private void scheduleRemoveCameraId(@NonNull String cameraId) { mHandler.postDelayed( () -> removeCameraId(cameraId), CAMERA_CLOSED_LETTERBOX_UPDATE_DELAY_MS); mAppCompatCameraStateStrategy.trackOnCameraClosed(cameraId); mHandler.postDelayed(() -> { synchronized (mWmService.mGlobalLock) { removeCameraId(cameraId); }}, CAMERA_CLOSED_LETTERBOX_UPDATE_DELAY_MS); } private void removeCameraId(@NonNull String cameraId) { synchronized (mWmService.mGlobalLock) { if (!mScheduledToBeRemovedCameraIdSet.remove(cameraId)) { // Already reconnected to this camera, no need to clean up. return; } final boolean canClose = mAppCompatCameraStatePolicy.canCameraBeClosed(cameraId); if (canClose) { // Finish cleaning up. mCameraIdPackageBiMapping.removeCameraId(cameraId); mAppCompatCameraStatePolicy.onCameraClosed(); } else { final boolean completed = mAppCompatCameraStateStrategy.notifyPolicyCameraClosedIfNeeded( cameraId, mAppCompatCameraStatePolicy); if (!completed) { // Not ready to process closure yet - the camera activity might be refreshing. // Try again later. rescheduleRemoveCameraActivity(cameraId); } } } // TODO(b/335165310): verify that this works in multi instance and permission dialogs. /** * Finds a visible activity with the given package name. * * <p>If there are multiple visible activities with a given package name, and none of them are * the `topRunningActivity`, returns null. */ @Nullable private ActivityRecord findUniqueActivityWithPackageName(@NonNull String packageName) { final ActivityRecord topActivity = mDisplayContent.topRunningActivity( /* considerKeyguardState= */ true); if (topActivity != null && topActivity.packageName.equals(packageName)) { return topActivity; } final List<ActivityRecord> activitiesOfPackageWhichOpenedCamera = new ArrayList<>(); mDisplayContent.forAllActivities(activityRecord -> { if (activityRecord.isVisibleRequested() && activityRecord.packageName.equals(packageName)) { activitiesOfPackageWhichOpenedCamera.add(activityRecord); } }); if (activitiesOfPackageWhichOpenedCamera.isEmpty()) { Slog.w(TAG, "Cannot find camera activity."); return null; } if (activitiesOfPackageWhichOpenedCamera.size() == 1) { return activitiesOfPackageWhichOpenedCamera.getFirst(); scheduleRemoveCameraId(cameraId); } // Return null if we cannot determine which activity opened camera. This is preferred to // applying treatment to the wrong activity. Slog.w(TAG, "Cannot determine which activity opened camera."); return null; } String getSummary() { return " CameraIdPackageNameBiMapping=" + mCameraIdPackageBiMapping .getSummaryForDisplayRotationHistoryRecord(); @NonNull @Override public String toString() { return mAppCompatCameraStateStrategy.toString(); } } services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java +1 −2 Original line number Diff line number Diff line Loading @@ -211,8 +211,7 @@ final class DisplayRotationCompatPolicy implements AppCompatCameraStatePolicy, + (topActivity == null ? "null" : topActivity.shortComponentName) + " isTreatmentEnabledForActivity=" + isTreatmentEnabledForActivity(topActivity) + "mCameraStateMonitor=" + mCameraStateMonitor.getSummary(); + "mCameraStateMonitor=" + mCameraStateMonitor; } return "DisplayRotationCompatPolicy{" + " isTreatmentEnabledForDisplay=" + isTreatmentEnabledForDisplay() Loading Loading
services/core/java/com/android/server/wm/AppCompatCameraStateStrategy.java 0 → 100644 +80 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.wm; import android.annotation.NonNull; /** * Interface for tracking camera state, and notifying {@link AppCompatCameraStatePolicy} of changes. * * <p>{@link AppCompatCameraStateStrategy} implementations should track which apps hold the camera * access, and any ongoing camera state changes changes. 'track' methods should always be called * before appropriate 'maybeNotify' methods for the same task-cameraId pair, but the order of * open/close can vary, for example due to built-in delays from the caller. */ interface AppCompatCameraStateStrategy { /** * Allows saving cameraId, to be processed later on * {@link AppCompatCameraStateStrategy#notifyPolicyCameraOpenedIfNeeded} after a delay. * * <p>The {@link AppCompatCameraStateStrategy} should track which camera operations have been * started (delayed), as camera opened/closed operations often compete with each other, and due * to built-in delays can cause different order of these operations when they are finally * processed. Examples of quickly closing and opening the camera: activity relaunch due to * configuration change, switching front/back cameras, new app requesting camera and taking the * access rights away from the existing camera app. */ void trackOnCameraOpened(@NonNull String cameraId); /** * Processes camera opened signal, and if the change is relevant for {@link * AppCompatCameraStatePolicy} calls {@link AppCompatCameraStatePolicy#onCameraOpened}. */ void notifyPolicyCameraOpenedIfNeeded(@NonNull String cameraId, @NonNull String packageName, @NonNull AppCompatCameraStatePolicy policy); /** * Allows saving cameraId to be processed later on * {@link AppCompatCameraStateStrategy#notifyPolicyCameraClosedIfNeeded} after a delay. * * <p>The {@link AppCompatCameraStateStrategy} should track which camera operations have been * started (delayed), as camera opened/closed operations often compete with each other, and due * to built-in delays can cause different order of these operations when they are finally * processed. Examples of quickly closing and opening the camera: activity relaunch due to * configuration change, switching front/back cameras, new app requesting camera and taking the * access rights away from the existing camera app. */ void trackOnCameraClosed(@NonNull String cameraId); /** * Processes camera closed signal, and if the change is relevant for {@link * AppCompatCameraStatePolicy} calls {@link AppCompatCameraStatePolicy#onCameraClosed}. * * @return true if policies were able to handle the camera closed event, or false if it needs to * be rescheduled. */ boolean notifyPolicyCameraClosedIfNeeded(@NonNull String cameraId, @NonNull AppCompatCameraStatePolicy policy); /** Returns whether a given activity holds any camera opened. */ boolean isCameraRunningForActivity(@NonNull ActivityRecord activity); /** Returns whether a given activity holds a specific camera opened. */ // TODO(b/336474959): try to decouple `cameraId` from the listeners. boolean isCameraWithIdRunningForActivity(@NonNull ActivityRecord activity, @NonNull String cameraId); }
services/core/java/com/android/server/wm/AppCompatCameraStateStrategyForPackage.java 0 → 100644 +166 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.wm; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.annotation.NonNull; import android.annotation.Nullable; import android.util.ArraySet; import android.util.Slog; import java.util.ArrayList; import java.util.Set; /** {@link AppCompatCameraStateStrategy} that tracks packageName-cameraId pairs. */ class AppCompatCameraStateStrategyForPackage implements AppCompatCameraStateStrategy { private static final String TAG = TAG_WITH_CLASS_NAME ? "AppCompatCameraStateStrategyForPackage" : TAG_WM; @NonNull private final DisplayContent mDisplayContent; // Bi-directional map between package names and active camera IDs since we need to 1) get a // camera id by a package name when resizing the window; 2) get a package name by a camera id // when camera connection is closed and we need to clean up our records. private final CameraIdPackageNameBiMapping mCameraIdPackageBiMapping = new CameraIdPackageNameBiMapping(); // TODO(b/380840084): Consider making this a set of CameraId/PackageName pairs. This is to // keep track of camera-closed signals when apps are switching camera access, so that the policy // can restore app configuration when an app closes camera (e.g. loses camera access due to // another app). private final Set<String> mScheduledToBeRemovedCameraIdSet = new ArraySet<>(); private final Set<String> mScheduledCompatModeUpdateCameraIdSet = new ArraySet<>(); AppCompatCameraStateStrategyForPackage(@NonNull DisplayContent displayContent) { mDisplayContent = displayContent; } @Override public void trackOnCameraOpened(@NonNull String cameraId) { mScheduledToBeRemovedCameraIdSet.remove(cameraId); mScheduledCompatModeUpdateCameraIdSet.add(cameraId); } @Override public void notifyPolicyCameraOpenedIfNeeded(@NonNull String cameraId, @NonNull String packageName, @NonNull AppCompatCameraStatePolicy policy) { if (!mScheduledCompatModeUpdateCameraIdSet.remove(cameraId)) { // Camera compat mode update has happened already or was cancelled // because camera was closed. return; } mCameraIdPackageBiMapping.put(packageName, cameraId); // If there are multiple activities of the same package name and none of // them are the top running activity, we do not apply treatment (rather than // guessing and applying it to the wrong activity). final ActivityRecord cameraActivity = findUniqueActivityWithPackageName(packageName); if (cameraActivity == null) { // If camera is active, activity, task and app process must exist. No need to notify // listeners or track the package otherwise. return; } policy.onCameraOpened(cameraActivity); } @Override public void trackOnCameraClosed(@NonNull String cameraId) { mScheduledToBeRemovedCameraIdSet.add(cameraId); // No need to update window size for this camera if it's already closed. mScheduledCompatModeUpdateCameraIdSet.remove(cameraId); } @Override public boolean notifyPolicyCameraClosedIfNeeded(@NonNull String cameraId, @NonNull AppCompatCameraStatePolicy policy) { if (!mScheduledToBeRemovedCameraIdSet.remove(cameraId)) { // Already reconnected to this camera, no need to clean up. return true; } final boolean canClose = policy.canCameraBeClosed(cameraId); if (canClose) { // Finish cleaning up. mCameraIdPackageBiMapping.removeCameraId(cameraId); policy.onCameraClosed(); return true; } else { mScheduledToBeRemovedCameraIdSet.add(cameraId); // Not ready to process closure yet - the camera activity might be refreshing. // Try again later. return false; } } @Override public boolean isCameraRunningForActivity(@NonNull ActivityRecord activity) { return getCameraIdForActivity(activity) != null; } @Override public boolean isCameraWithIdRunningForActivity(@NonNull ActivityRecord activity, @NonNull String cameraId) { return cameraId.equals(getCameraIdForActivity(activity)); } /** Returns the information about apps using camera, for logging purposes. */ private String getCameraIdForActivity(@NonNull ActivityRecord activity) { return mCameraIdPackageBiMapping.getCameraId(activity.packageName); } // TODO(b/335165310): verify that this works in multi instance and permission dialogs. /** * Finds a visible activity with the given package name. * * <p>If there are multiple visible activities with a given package name, and none of them are * the `topRunningActivity`, returns null. */ @Nullable private ActivityRecord findUniqueActivityWithPackageName(@NonNull String packageName) { final ActivityRecord topActivity = mDisplayContent.topRunningActivity( /* considerKeyguardState= */ true); if (topActivity != null && topActivity.packageName.equals(packageName)) { return topActivity; } final ArrayList<ActivityRecord> activitiesOfPackageWhichOpenedCamera = new ArrayList<>(); mDisplayContent.forAllActivities(activityRecord -> { if (activityRecord.isVisibleRequested() && activityRecord.packageName.equals(packageName)) { activitiesOfPackageWhichOpenedCamera.add(activityRecord); } }); if (activitiesOfPackageWhichOpenedCamera.isEmpty()) { Slog.w(TAG, "Cannot find camera activity."); return null; } if (activitiesOfPackageWhichOpenedCamera.size() == 1) { return activitiesOfPackageWhichOpenedCamera.get(0); } // Return null if we cannot determine which activity opened camera. This is preferred to // applying treatment to the wrong activity. Slog.w(TAG, "Cannot determine which activity opened camera."); return null; } @Override public String toString() { return "CameraIdPackageNameBiMapping=" + mCameraIdPackageBiMapping.toString(); } }
services/core/java/com/android/server/wm/CameraIdPackageNameBiMapping.java +2 −1 Original line number Diff line number Diff line Loading @@ -63,7 +63,8 @@ final class CameraIdPackageNameBiMapping { } @NonNull String getSummaryForDisplayRotationHistoryRecord() { @Override public String toString() { return "{ mPackageToCameraIdMap=" + mPackageToCameraIdMap + " }"; } Loading
services/core/java/com/android/server/wm/CameraStateMonitor.java +35 −121 Original line number Diff line number Diff line Loading @@ -23,23 +23,16 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.hardware.camera2.CameraManager; import android.os.Handler; import android.util.ArraySet; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.ProtoLog; import java.util.ArrayList; import java.util.List; import java.util.Set; /** * Class that listens to camera open/closed signals, keeps track of the current apps using camera, * and notifies listeners. */ class CameraStateMonitor { private static final String TAG = TAG_WITH_CLASS_NAME ? "CameraStateMonitor" : TAG_WM; // Delay for updating letterbox after Camera connection is closed. Needed to avoid flickering // when an app is flipping between front and rear cameras or when size compat mode is restarted. // TODO(b/330148095): Investigate flickering without using delays, remove them if possible. Loading @@ -53,6 +46,7 @@ class CameraStateMonitor { private static final int CAMERA_OPENED_LETTERBOX_UPDATE_DELAY_MS = CAMERA_CLOSED_LETTERBOX_UPDATE_DELAY_MS / 2; /** Returns the information about apps using camera, for logging purposes. */ @NonNull private final DisplayContent mDisplayContent; @NonNull Loading @@ -62,20 +56,8 @@ class CameraStateMonitor { @NonNull private final Handler mHandler; // Bi-directional map between package names and active camera IDs since we need to 1) get a // camera id by a package name when resizing the window; 2) get a package name by a camera id // when camera connection is closed and we need to clean up our records. private final CameraIdPackageNameBiMapping mCameraIdPackageBiMapping = new CameraIdPackageNameBiMapping(); // TODO(b/380840084): Consider making this a set of CameraId/PackageName pairs. This is to // keep track of camera-closed signals when apps are switching camera access, so that the policy // can restore app configuration when an app closes camera (e.g. loses camera access due to // another app). private final Set<String> mScheduledToBeRemovedCameraIdSet = new ArraySet<>(); // TODO(b/336474959): should/can this go in the compat listeners? private final Set<String> mScheduledCompatModeUpdateCameraIdSet = new ArraySet<>(); @VisibleForTesting final AppCompatCameraStateStrategy mAppCompatCameraStateStrategy; @VisibleForTesting final AppCompatCameraStatePolicy mAppCompatCameraStatePolicy; Loading Loading @@ -110,6 +92,7 @@ class CameraStateMonitor { mAppCompatCameraStatePolicy = appCompatCameraStatePolicy; mWmService = displayContent.mWmService; mCameraManager = mWmService.mContext.getSystemService(CameraManager.class); mAppCompatCameraStateStrategy = new AppCompatCameraStateStrategyForPackage(displayContent); } /** Starts listening to camera opened/closed signals. */ Loading @@ -122,7 +105,7 @@ class CameraStateMonitor { } /** Stops listening to camera opened/closed signals. */ public void stopListeningToCameraState() { void stopListeningToCameraState() { if (mCameraManager != null) { mCameraManager.unregisterAvailabilityCallback(mAvailabilityCallback); } Loading @@ -140,37 +123,23 @@ class CameraStateMonitor { private void notifyCameraOpenedWithDelay(@NonNull String cameraId, @NonNull String packageName) { // Some apps can’t handle configuration changes coming at the same time with Camera setup so // delaying orientation update to accommodate for that. // If an activity is restarting or camera is flipping, the camera connection can be // quickly closed and reopened. mScheduledToBeRemovedCameraIdSet.remove(cameraId); ProtoLog.v(WM_DEBUG_STATES, "Display id=%d is notified that Camera %s is open for package %s", mDisplayContent.mDisplayId, cameraId, packageName); // Some apps can’t handle configuration changes coming at the same time with Camera setup so // delaying orientation update to accommodate for that. mScheduledCompatModeUpdateCameraIdSet.add(cameraId); mHandler.postDelayed(() -> notifyCameraOpenedInternal(cameraId, packageName), CAMERA_OPENED_LETTERBOX_UPDATE_DELAY_MS); mAppCompatCameraStateStrategy.trackOnCameraOpened(cameraId); mHandler.postDelayed(() -> { synchronized (mWmService.mGlobalLock) { notifyCameraOpenedInternal(cameraId, packageName); }}, CAMERA_OPENED_LETTERBOX_UPDATE_DELAY_MS); } private void notifyCameraOpenedInternal(@NonNull String cameraId, @NonNull String packageName) { synchronized (mWmService.mGlobalLock) { if (!mScheduledCompatModeUpdateCameraIdSet.remove(cameraId)) { // Camera compat mode update has happened already or was cancelled // because camera was closed. return; } mCameraIdPackageBiMapping.put(packageName, cameraId); // If there are multiple activities of the same package name and none of // them are the top running activity, we do not apply treatment (rather than // guessing and applying it to the wrong activity). final ActivityRecord cameraActivity = findUniqueActivityWithPackageName(packageName); if (cameraActivity == null || cameraActivity.getTask() == null) { return; } mAppCompatCameraStatePolicy.onCameraOpened(cameraActivity); } mAppCompatCameraStateStrategy.notifyPolicyCameraOpenedIfNeeded(cameraId, packageName, mAppCompatCameraStatePolicy); } /** Loading @@ -183,99 +152,44 @@ class CameraStateMonitor { ProtoLog.v(WM_DEBUG_STATES, "Display id=%d is notified that Camera %s is closed.", mDisplayContent.mDisplayId, cameraId); mScheduledToBeRemovedCameraIdSet.add(cameraId); // No need to update window size for this camera if it's already closed. mScheduledCompatModeUpdateCameraIdSet.remove(cameraId); scheduleRemoveCameraId(cameraId); } /** Returns whether a given activity holds any camera opened. */ boolean isCameraRunningForActivity(@NonNull ActivityRecord activity) { return getCameraIdForActivity(activity) != null; return mAppCompatCameraStateStrategy.isCameraRunningForActivity(activity); } /** Returns whether a given activity holds a specific camera opened. */ // TODO(b/336474959): try to decouple `cameraId` from the listeners. boolean isCameraWithIdRunningForActivity(@NonNull ActivityRecord activity, String cameraId) { return cameraId.equals(getCameraIdForActivity(activity)); } void rescheduleRemoveCameraActivity(@NonNull String cameraId) { mScheduledToBeRemovedCameraIdSet.add(cameraId); scheduleRemoveCameraId(cameraId); } @Nullable private String getCameraIdForActivity(@NonNull ActivityRecord activity) { return mCameraIdPackageBiMapping.getCameraId(activity.packageName); boolean isCameraWithIdRunningForActivity(@NonNull ActivityRecord activity, @NonNull String cameraId) { return mAppCompatCameraStateStrategy.isCameraWithIdRunningForActivity(activity, cameraId); } // Delay is needed to avoid rotation flickering when an app is flipping between front and // rear cameras, when size compat mode is restarted or activity is being refreshed. private void scheduleRemoveCameraId(@NonNull String cameraId) { mHandler.postDelayed( () -> removeCameraId(cameraId), CAMERA_CLOSED_LETTERBOX_UPDATE_DELAY_MS); mAppCompatCameraStateStrategy.trackOnCameraClosed(cameraId); mHandler.postDelayed(() -> { synchronized (mWmService.mGlobalLock) { removeCameraId(cameraId); }}, CAMERA_CLOSED_LETTERBOX_UPDATE_DELAY_MS); } private void removeCameraId(@NonNull String cameraId) { synchronized (mWmService.mGlobalLock) { if (!mScheduledToBeRemovedCameraIdSet.remove(cameraId)) { // Already reconnected to this camera, no need to clean up. return; } final boolean canClose = mAppCompatCameraStatePolicy.canCameraBeClosed(cameraId); if (canClose) { // Finish cleaning up. mCameraIdPackageBiMapping.removeCameraId(cameraId); mAppCompatCameraStatePolicy.onCameraClosed(); } else { final boolean completed = mAppCompatCameraStateStrategy.notifyPolicyCameraClosedIfNeeded( cameraId, mAppCompatCameraStatePolicy); if (!completed) { // Not ready to process closure yet - the camera activity might be refreshing. // Try again later. rescheduleRemoveCameraActivity(cameraId); } } } // TODO(b/335165310): verify that this works in multi instance and permission dialogs. /** * Finds a visible activity with the given package name. * * <p>If there are multiple visible activities with a given package name, and none of them are * the `topRunningActivity`, returns null. */ @Nullable private ActivityRecord findUniqueActivityWithPackageName(@NonNull String packageName) { final ActivityRecord topActivity = mDisplayContent.topRunningActivity( /* considerKeyguardState= */ true); if (topActivity != null && topActivity.packageName.equals(packageName)) { return topActivity; } final List<ActivityRecord> activitiesOfPackageWhichOpenedCamera = new ArrayList<>(); mDisplayContent.forAllActivities(activityRecord -> { if (activityRecord.isVisibleRequested() && activityRecord.packageName.equals(packageName)) { activitiesOfPackageWhichOpenedCamera.add(activityRecord); } }); if (activitiesOfPackageWhichOpenedCamera.isEmpty()) { Slog.w(TAG, "Cannot find camera activity."); return null; } if (activitiesOfPackageWhichOpenedCamera.size() == 1) { return activitiesOfPackageWhichOpenedCamera.getFirst(); scheduleRemoveCameraId(cameraId); } // Return null if we cannot determine which activity opened camera. This is preferred to // applying treatment to the wrong activity. Slog.w(TAG, "Cannot determine which activity opened camera."); return null; } String getSummary() { return " CameraIdPackageNameBiMapping=" + mCameraIdPackageBiMapping .getSummaryForDisplayRotationHistoryRecord(); @NonNull @Override public String toString() { return mAppCompatCameraStateStrategy.toString(); } }
services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java +1 −2 Original line number Diff line number Diff line Loading @@ -211,8 +211,7 @@ final class DisplayRotationCompatPolicy implements AppCompatCameraStatePolicy, + (topActivity == null ? "null" : topActivity.shortComponentName) + " isTreatmentEnabledForActivity=" + isTreatmentEnabledForActivity(topActivity) + "mCameraStateMonitor=" + mCameraStateMonitor.getSummary(); + "mCameraStateMonitor=" + mCameraStateMonitor; } return "DisplayRotationCompatPolicy{" + " isTreatmentEnabledForDisplay=" + isTreatmentEnabledForDisplay() Loading