Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e73b0cf1 authored by Mina Granic's avatar Mina Granic
Browse files

Send CameraAppInfo instead of just package in AppCompatCameraStateStrategy.

This change allows refactoring of the way cameraId and app that opened it are tracked (see child CL).

Flag: com.android.window.flags.enable_camera_compat_track_task_and_app_bugfix
Test: atest WmTests:CameraStateMonitorTests
Test: atest WmTests:AppCompatCameraStateStrategyForPackageTests
Bug: 380840084
Change-Id: Ibff654aa87bee78302136ad8914e2176b8e5f0b9
parent c81d26cd
Loading
Loading
Loading
Loading
+13 −3
Original line number Original line Diff line number Diff line
@@ -16,6 +16,7 @@
package com.android.server.wm;
package com.android.server.wm;


import android.annotation.NonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;


/**
/**
 * Interface that camera compat policies to implement to be notified of camera open/close signals.
 * Interface that camera compat policies to implement to be notified of camera open/close signals.
@@ -23,8 +24,10 @@ import android.annotation.NonNull;
interface AppCompatCameraStatePolicy {
interface AppCompatCameraStatePolicy {
    /**
    /**
     * Notifies the compat listener that a task has opened camera.
     * Notifies the compat listener that a task has opened camera.
     *
     * @param appProcess The process in the {@link Task} which requested camera to be opened.
     */
     */
    void onCameraOpened(@NonNull ActivityRecord cameraActivity);
    void onCameraOpened(@NonNull WindowProcessController appProcess, @NonNull Task task);


    /**
    /**
     * Checks whether a listener is ready to do a cleanup when camera is closed.
     * Checks whether a listener is ready to do a cleanup when camera is closed.
@@ -35,10 +38,17 @@ interface AppCompatCameraStatePolicy {
    //  change based on the cameraId - CameraStateMonitor should keep track of this.
    //  change based on the cameraId - CameraStateMonitor should keep track of this.
    //  This method actually checks "did an activity only temporarily close the camera", because a
    //  This method actually checks "did an activity only temporarily close the camera", because a
    //  refresh for compatibility is triggered.
    //  refresh for compatibility is triggered.
    boolean canCameraBeClosed(@NonNull String cameraId);
    boolean canCameraBeClosed(@NonNull String cameraId, @NonNull Task task);


    /**
    /**
     * Notifies the compat listener that camera is closed.
     * Notifies the compat listener that camera is closed.
     *
     * <p>Due to delays in notifying that camera is closed (currently 2 seconds), and the cause of
     * camera close could the that the task is closed, the app process and/or task might be null. As
     * parts of camera compat are done on activity level, application level, or even camera compat
     * policy level, the policies are notified even if the task or app are not active anymore.
     *
     * @param appProcess The process in the {@link Task} which requested camera to be opened.
     */
     */
    void onCameraClosed();
    void onCameraClosed(@Nullable WindowProcessController appProcess, @Nullable Task task);
}
}
+8 −7
Original line number Original line Diff line number Diff line
@@ -16,7 +16,8 @@


package com.android.server.wm;
package com.android.server.wm;


import androidx.annotation.NonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;


import java.util.ArrayList;
import java.util.ArrayList;


@@ -37,9 +38,9 @@ class AppCompatCameraStateSource implements AppCompatCameraStatePolicy {
    }
    }


    @Override
    @Override
    public void onCameraOpened(@NonNull ActivityRecord cameraActivity) {
    public void onCameraOpened(@NonNull WindowProcessController appProcess, @NonNull Task task) {
        for (int i = 0; i < mCameraStatePolicies.size(); i++) {
        for (int i = 0; i < mCameraStatePolicies.size(); i++) {
            mCameraStatePolicies.get(i).onCameraOpened(cameraActivity);
            mCameraStatePolicies.get(i).onCameraOpened(appProcess, task);
        }
        }
    }
    }


@@ -47,9 +48,9 @@ class AppCompatCameraStateSource implements AppCompatCameraStatePolicy {
     * @return {@code false} if any listener has reported that they cannot process camera close now.
     * @return {@code false} if any listener has reported that they cannot process camera close now.
     */
     */
    @Override
    @Override
    public boolean canCameraBeClosed(@NonNull String cameraId) {
    public boolean canCameraBeClosed(@NonNull String cameraId, @NonNull Task task) {
        for (int i = 0; i < mCameraStatePolicies.size(); i++) {
        for (int i = 0; i < mCameraStatePolicies.size(); i++) {
            if (!mCameraStatePolicies.get(i).canCameraBeClosed(cameraId)) {
            if (!mCameraStatePolicies.get(i).canCameraBeClosed(cameraId, task)) {
                return false;
                return false;
            }
            }
        }
        }
@@ -57,9 +58,9 @@ class AppCompatCameraStateSource implements AppCompatCameraStatePolicy {
    }
    }


    @Override
    @Override
    public void onCameraClosed() {
    public void onCameraClosed(@Nullable WindowProcessController appProcess, @Nullable Task task) {
        for (int i = 0; i < mCameraStatePolicies.size(); i++) {
        for (int i = 0; i < mCameraStatePolicies.size(); i++) {
            mCameraStatePolicies.get(i).onCameraClosed();
            mCameraStatePolicies.get(i).onCameraClosed(appProcess, task);
        }
        }
    }
    }
}
}
+13 −7
Original line number Original line Diff line number Diff line
@@ -22,12 +22,12 @@ import android.annotation.NonNull;
 *
 *
 * <p>{@link AppCompatCameraStateStrategy} implementations should track which apps hold the camera
 * <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
 * 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
 * before appropriate 'notifyPolicy' 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.
 * open/close can vary, for example due to built-in delays from the caller.
 */
 */
interface AppCompatCameraStateStrategy {
interface AppCompatCameraStateStrategy {
    /**
    /**
     * Allows saving cameraId, to be processed later on
     * Allows saving information: task, process, cameraId, to be processed later on
     * {@link AppCompatCameraStateStrategy#notifyPolicyCameraOpenedIfNeeded} after a delay.
     * {@link AppCompatCameraStateStrategy#notifyPolicyCameraOpenedIfNeeded} after a delay.
     *
     *
     * <p>The {@link AppCompatCameraStateStrategy} should track which camera operations have been
     * <p>The {@link AppCompatCameraStateStrategy} should track which camera operations have been
@@ -36,18 +36,21 @@ interface AppCompatCameraStateStrategy {
     * processed. Examples of quickly closing and opening the camera: activity relaunch due to
     * 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
     * configuration change, switching front/back cameras, new app requesting camera and taking the
     * access rights away from the existing camera app.
     * access rights away from the existing camera app.
     *
     * @return CameraAppInfo of the app which opened the camera with given cameraId.
     */
     */
    void trackOnCameraOpened(@NonNull String cameraId);
    @NonNull
    CameraAppInfo trackOnCameraOpened(@NonNull String cameraId, @NonNull String packageName);


    /**
    /**
     * Processes camera opened signal, and if the change is relevant for {@link
     * Processes camera opened signal, and if the change is relevant for {@link
     * AppCompatCameraStatePolicy} calls {@link AppCompatCameraStatePolicy#onCameraOpened}.
     * AppCompatCameraStatePolicy} calls {@link AppCompatCameraStatePolicy#onCameraOpened}.
     */
     */
    void notifyPolicyCameraOpenedIfNeeded(@NonNull String cameraId, @NonNull String packageName,
    void notifyPolicyCameraOpenedIfNeeded(@NonNull CameraAppInfo cameraAppInfo,
            @NonNull AppCompatCameraStatePolicy policy);
            @NonNull AppCompatCameraStatePolicy policy);


    /**
    /**
     * Allows saving cameraId to be processed later on
     * Allows saving information: task, process, cameraId, to be processed later on
     * {@link AppCompatCameraStateStrategy#notifyPolicyCameraClosedIfNeeded} after a delay.
     * {@link AppCompatCameraStateStrategy#notifyPolicyCameraClosedIfNeeded} after a delay.
     *
     *
     * <p>The {@link AppCompatCameraStateStrategy} should track which camera operations have been
     * <p>The {@link AppCompatCameraStateStrategy} should track which camera operations have been
@@ -56,8 +59,11 @@ interface AppCompatCameraStateStrategy {
     * processed. Examples of quickly closing and opening the camera: activity relaunch due to
     * 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
     * configuration change, switching front/back cameras, new app requesting camera and taking the
     * access rights away from the existing camera app.
     * access rights away from the existing camera app.
     *
     * @return CameraAppInfo of the app which closed the camera with given cameraId.
     */
     */
    void trackOnCameraClosed(@NonNull String cameraId);
    @NonNull
    CameraAppInfo trackOnCameraClosed(@NonNull String cameraId);




    /**
    /**
@@ -67,7 +73,7 @@ interface AppCompatCameraStateStrategy {
     * @return true if policies were able to handle the camera closed event, or false if it needs to
     * @return true if policies were able to handle the camera closed event, or false if it needs to
     * be rescheduled.
     * be rescheduled.
     */
     */
    boolean notifyPolicyCameraClosedIfNeeded(@NonNull String cameraId,
    boolean notifyPolicyCameraClosedIfNeeded(@NonNull CameraAppInfo cameraAppInfo,
            @NonNull AppCompatCameraStatePolicy policy);
            @NonNull AppCompatCameraStatePolicy policy);


    /** Returns whether a given activity holds any camera opened. */
    /** Returns whether a given activity holds any camera opened. */
+52 −15
Original line number Original line Diff line number Diff line
@@ -15,6 +15,9 @@
 */
 */
package com.android.server.wm;
package com.android.server.wm;


import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.os.Process.INVALID_PID;

import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;


@@ -51,54 +54,74 @@ class AppCompatCameraStateStrategyForPackage implements AppCompatCameraStateStra
    }
    }


    @Override
    @Override
    public void trackOnCameraOpened(@NonNull String cameraId) {
    @NonNull
    public CameraAppInfo trackOnCameraOpened(@NonNull String cameraId,
            @NonNull String packageName) {
        mScheduledToBeRemovedCameraIdSet.remove(cameraId);
        mScheduledToBeRemovedCameraIdSet.remove(cameraId);
        mScheduledCompatModeUpdateCameraIdSet.add(cameraId);
        mScheduledCompatModeUpdateCameraIdSet.add(cameraId);
        return createCameraAppInfo(cameraId, packageName);
    }
    }


    @Override
    @Override
    public void notifyPolicyCameraOpenedIfNeeded(@NonNull String cameraId,
    public void notifyPolicyCameraOpenedIfNeeded(@NonNull CameraAppInfo cameraAppInfo,
            @NonNull String packageName, @NonNull AppCompatCameraStatePolicy policy) {
            @NonNull AppCompatCameraStatePolicy policy) {
        if (!mScheduledCompatModeUpdateCameraIdSet.remove(cameraId)) {
        if (!mScheduledCompatModeUpdateCameraIdSet.remove(cameraAppInfo.mCameraId)) {
            // Camera compat mode update has happened already or was cancelled
            // Camera compat mode update has happened already or was cancelled
            // because camera was closed.
            // because camera was closed.
            return;
            return;
        }
        }
        mCameraIdPackageBiMapping.put(packageName, cameraId);
        // If there are multiple activities of the same package name and none of
        // 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
        // them are the top running activity, we do not apply treatment (rather than
        // guessing and applying it to the wrong activity).
        // guessing and applying it to the wrong activity).
        final ActivityRecord cameraActivity = findUniqueActivityWithPackageName(packageName);
        final ActivityRecord cameraActivity = cameraAppInfo.mPackageName == null ? null
        if (cameraActivity == null) {
                : findUniqueActivityWithPackageName(cameraAppInfo.mPackageName);
        final Task task = cameraActivity == null ? null : cameraActivity.getTask();
        final WindowProcessController app = cameraActivity == null ? null : cameraActivity.app;
        if (cameraActivity == null || task == null || app == null) {
            // If camera is active, activity, task and app process must exist. No need to notify
            // If camera is active, activity, task and app process must exist. No need to notify
            // listeners or track the package otherwise.
            // listeners or track the package otherwise.
            return;
            return;
        }
        }
        policy.onCameraOpened(cameraActivity);
        mCameraIdPackageBiMapping.put(cameraAppInfo.mPackageName, cameraAppInfo.mCameraId);
        policy.onCameraOpened(app, task);
    }
    }


    /**
     * @return CameraAppInfo of the app which opened camera with given cameraId.
     */
    @Override
    @Override
    public void trackOnCameraClosed(@NonNull String cameraId) {
    @NonNull
    public CameraAppInfo trackOnCameraClosed(@NonNull String cameraId) {
        mScheduledToBeRemovedCameraIdSet.add(cameraId);
        mScheduledToBeRemovedCameraIdSet.add(cameraId);
        // No need to update window size for this camera if it's already closed.
        // No need to update window size for this camera if it's already closed.
        mScheduledCompatModeUpdateCameraIdSet.remove(cameraId);
        mScheduledCompatModeUpdateCameraIdSet.remove(cameraId);
        final String packageName =
                mCameraIdPackageBiMapping.getPackageNameForCameraId(cameraId);
        return createCameraAppInfo(cameraId, packageName);
    }
    }


    @Override
    @Override
    public boolean notifyPolicyCameraClosedIfNeeded(@NonNull String cameraId,
    public boolean notifyPolicyCameraClosedIfNeeded(@NonNull CameraAppInfo cameraAppInfo,
            @NonNull AppCompatCameraStatePolicy policy) {
            @NonNull AppCompatCameraStatePolicy policy) {
        if (!mScheduledToBeRemovedCameraIdSet.remove(cameraId)) {
        if (!mScheduledToBeRemovedCameraIdSet.remove(cameraAppInfo.mCameraId)) {
            // Already reconnected to this camera, no need to clean up.
            // Already reconnected to this camera, no need to clean up.
            return true;
            return true;
        }
        }
        final boolean canClose = policy.canCameraBeClosed(cameraId);
        final String packageName =
                mCameraIdPackageBiMapping.getPackageNameForCameraId(cameraAppInfo.mCameraId);
        final ActivityRecord activity = packageName == null ? null
                : findUniqueActivityWithPackageName(packageName);
        final Task task = activity == null ? null : activity.getTask();
        final WindowProcessController app = activity == null ? null : activity.app;
        final boolean canClose = task == null || policy.canCameraBeClosed(
                cameraAppInfo.mCameraId, task);
        if (canClose) {
        if (canClose) {
            // Finish cleaning up.
            // Finish cleaning up.
            mCameraIdPackageBiMapping.removeCameraId(cameraId);
            mCameraIdPackageBiMapping.removeCameraId(cameraAppInfo.mCameraId);
            policy.onCameraClosed();
            policy.onCameraClosed(app, task);
            return true;
            return true;
        } else {
        } else {
            mScheduledToBeRemovedCameraIdSet.add(cameraId);
            mScheduledToBeRemovedCameraIdSet.add(cameraAppInfo.mCameraId);
            // Not ready to process closure yet - the camera activity might be refreshing.
            // Not ready to process closure yet - the camera activity might be refreshing.
            // Try again later.
            // Try again later.
            return false;
            return false;
@@ -163,4 +186,18 @@ class AppCompatCameraStateStrategyForPackage implements AppCompatCameraStateStra
    public String toString() {
    public String toString() {
        return "CameraIdPackageNameBiMapping=" + mCameraIdPackageBiMapping.toString();
        return "CameraIdPackageNameBiMapping=" + mCameraIdPackageBiMapping.toString();
    }
    }

    @NonNull
    private CameraAppInfo createCameraAppInfo(@NonNull String cameraId,
            @Nullable String packageName) {
        final ActivityRecord cameraActivity = packageName == null ? null
                : findUniqueActivityWithPackageName(packageName);
        final Task cameraTask = cameraActivity == null ? null : cameraActivity.getTask();
        final WindowProcessController cameraApp = cameraActivity == null ? null
                : cameraActivity.app;
        return new CameraAppInfo(cameraId,
                cameraApp == null ? INVALID_PID : cameraApp.getPid(),
                cameraTask == null ? INVALID_TASK_ID : cameraTask.mTaskId,
                packageName);
    }
}
}
+70 −0
Original line number Original line 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;
import android.annotation.Nullable;

import java.util.Objects;

/** Data class to track apps and tasks which opened camera, for camera compat mode. */
final class CameraAppInfo {

    // ID of the camera the app is using.
    @NonNull
    final String mCameraId;

    // Process ID for the app which opened the camera.
    final int mPid;

    // Task ID for the app which opened the camera.
    final int mTaskId;

    // TODO(b/380840084): remove after refactoring flag is launched.
    @Nullable
    final String mPackageName;

    CameraAppInfo(@NonNull String cameraId, int pid, int taskId, @Nullable String packageName) {
        mCameraId = cameraId;
        mPid = pid;
        mTaskId = taskId;
        mPackageName = packageName;
    }

    @Override
    public int hashCode() {
        return Objects.hash(mCameraId, mPid, mTaskId, mPackageName);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof CameraAppInfo other)) {
            return false;
        }
        return mPid == other.mPid && mTaskId == other.mTaskId
                && Objects.equals(mCameraId, other.mCameraId)
                && Objects.equals(mPackageName, other.mPackageName);
    }

    @Override
    public String toString() {
        return "{mCameraId=" + mCameraId + ", mPid=" + mPid + ", mTaskId=" + mTaskId
                + ", mPackageName=" + mPackageName + "}";
    }
}
Loading