1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.server.wm;
17 
18 import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
19 import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
20 
21 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
22 
23 import android.annotation.NonNull;
24 import android.app.servertransaction.RefreshCallbackItem;
25 import android.app.servertransaction.ResumeActivityItem;
26 import android.content.res.Configuration;
27 import android.os.Handler;
28 import android.os.RemoteException;
29 
30 import com.android.internal.protolog.common.ProtoLog;
31 import com.android.internal.util.ArrayUtils;
32 
33 import java.util.ArrayList;
34 
35 /**
36  * Class that refreshes the activity (through stop/pause -> resume) based on configuration change.
37  *
38  * <p>This class queries all of its {@link Evaluator}s and restarts the activity if any of them
39  * return {@code true} in {@link Evaluator#shouldRefreshActivity}. {@link ActivityRefresher} cycles
40  * through either stop or pause and then resume, based on the global config and per-app override.
41  */
42 class ActivityRefresher {
43     // Delay for ensuring that onActivityRefreshed is always called after an activity refresh. The
44     // client process may not always report the event back to the server, such as process is
45     // crashed or got killed.
46     private static final long REFRESH_CALLBACK_TIMEOUT_MS = 2000L;
47 
48     @NonNull private final WindowManagerService mWmService;
49     @NonNull private final Handler mHandler;
50     @NonNull private final ArrayList<Evaluator> mEvaluators = new ArrayList<>();
51 
ActivityRefresher(@onNull WindowManagerService wmService, @NonNull Handler handler)52     ActivityRefresher(@NonNull WindowManagerService wmService, @NonNull Handler handler) {
53         mWmService = wmService;
54         mHandler = handler;
55     }
56 
addEvaluator(@onNull Evaluator evaluator)57     void addEvaluator(@NonNull Evaluator evaluator) {
58         mEvaluators.add(evaluator);
59     }
60 
removeEvaluator(@onNull Evaluator evaluator)61     void removeEvaluator(@NonNull Evaluator evaluator) {
62         mEvaluators.remove(evaluator);
63     }
64 
65     /**
66      * "Refreshes" activity by going through "stopped -> resumed" or "paused -> resumed" cycle.
67      * This allows to clear cached values in apps (e.g. display or camera rotation) that influence
68      * camera preview and can lead to sideways or stretching issues persisting even after force
69      * rotation.
70      */
onActivityConfigurationChanging(@onNull ActivityRecord activity, @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig)71     void onActivityConfigurationChanging(@NonNull ActivityRecord activity,
72             @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) {
73         if (!shouldRefreshActivity(activity, newConfig, lastReportedConfig)) {
74             return;
75         }
76 
77         final boolean cycleThroughStop =
78                 mWmService.mLetterboxConfiguration
79                         .isCameraCompatRefreshCycleThroughStopEnabled()
80                         && !activity.mLetterboxUiController
81                         .shouldRefreshActivityViaPauseForCameraCompat();
82 
83         activity.mLetterboxUiController.setIsRefreshRequested(true);
84         ProtoLog.v(WM_DEBUG_STATES,
85                 "Refreshing activity for freeform camera compatibility treatment, "
86                         + "activityRecord=%s", activity);
87         final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(
88                 activity.token, cycleThroughStop ? ON_STOP : ON_PAUSE);
89         final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(
90                 activity.token, /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
91         try {
92             activity.mAtmService.getLifecycleManager().scheduleTransactionAndLifecycleItems(
93                     activity.app.getThread(), refreshCallbackItem, resumeActivityItem);
94             mHandler.postDelayed(() -> {
95                 synchronized (mWmService.mGlobalLock) {
96                     onActivityRefreshed(activity);
97                 }
98             }, REFRESH_CALLBACK_TIMEOUT_MS);
99         } catch (RemoteException e) {
100             activity.mLetterboxUiController.setIsRefreshRequested(false);
101         }
102     }
103 
isActivityRefreshing(@onNull ActivityRecord activity)104     boolean isActivityRefreshing(@NonNull ActivityRecord activity) {
105         return activity.mLetterboxUiController.isRefreshRequested();
106     }
107 
onActivityRefreshed(@onNull ActivityRecord activity)108     void onActivityRefreshed(@NonNull ActivityRecord activity) {
109         // TODO(b/333060789): can we tell that refresh did not happen by observing the activity
110         //  state?
111         activity.mLetterboxUiController.setIsRefreshRequested(false);
112     }
113 
shouldRefreshActivity(@onNull ActivityRecord activity, @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig)114     private boolean shouldRefreshActivity(@NonNull ActivityRecord activity,
115             @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) {
116         return mWmService.mLetterboxConfiguration.isCameraCompatRefreshEnabled()
117                 && activity.mLetterboxUiController.shouldRefreshActivityForCameraCompat()
118                 && ArrayUtils.find(mEvaluators.toArray(), evaluator ->
119                 ((Evaluator) evaluator)
120                         .shouldRefreshActivity(activity, newConfig, lastReportedConfig)) != null;
121     }
122 
123     /**
124      * Interface for classes that would like to refresh the recently updated activity, based on the
125      * configuration change.
126      */
127     interface Evaluator {
shouldRefreshActivity(@onNull ActivityRecord activity, @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig)128         boolean shouldRefreshActivity(@NonNull ActivityRecord activity,
129                 @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig);
130     }
131 }
132