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