1 /*
2  * Copyright (C) 2016 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 
17 package com.android.server.wm;
18 
19 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_UNKNOWN_APP_VISIBILITY;
20 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
21 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
22 
23 import android.annotation.NonNull;
24 import android.util.ArrayMap;
25 import android.util.Slog;
26 
27 import com.android.server.wm.WindowManagerService.H;
28 
29 import java.io.PrintWriter;
30 
31 /**
32  * Manages the set of {@link AppWindowToken}s for which we don't know yet whether it's visible or
33  * not. This happens when starting an activity while the lockscreen is showing. In that case, the
34  * keyguard flags an app might set influence it's visibility, so we wait until this is resolved to
35  * start the transition to avoid flickers.
36  */
37 class UnknownAppVisibilityController {
38 
39     private static final String TAG = TAG_WITH_CLASS_NAME ? "UnknownAppVisibility" : TAG_WM;
40 
41     /**
42      * We are currently waiting until the app is done resuming.
43      */
44     private static final int UNKNOWN_STATE_WAITING_RESUME = 1;
45 
46     /**
47      * The activity has finished resuming, and we are waiting on the next relayout.
48      */
49     private static final int UNKNOWN_STATE_WAITING_RELAYOUT = 2;
50 
51     /**
52      * The client called {@link Session#relayout} with the appropriate Keyguard flags and we are
53      * waiting until activity manager has updated the visibilities of all the apps.
54      */
55     private static final int UNKNOWN_STATE_WAITING_VISIBILITY_UPDATE = 3;
56 
57     // Set of apps for which we don't know yet whether it's visible or not, depending on what kind
58     // of lockscreen flags the app might set during its first relayout.
59     private final ArrayMap<AppWindowToken, Integer> mUnknownApps = new ArrayMap<>();
60 
61     private final WindowManagerService mService;
62 
63     private final DisplayContent mDisplayContent;
64 
UnknownAppVisibilityController(WindowManagerService service, DisplayContent displayContent)65     UnknownAppVisibilityController(WindowManagerService service, DisplayContent displayContent) {
66         mService = service;
67         mDisplayContent = displayContent;
68     }
69 
allResolved()70     boolean allResolved() {
71         return mUnknownApps.isEmpty();
72     }
73 
clear()74     void clear() {
75         mUnknownApps.clear();
76     }
77 
getDebugMessage()78     String getDebugMessage() {
79         final StringBuilder builder = new StringBuilder();
80         for (int i = mUnknownApps.size() - 1; i >= 0; i--) {
81             builder.append("app=").append(mUnknownApps.keyAt(i))
82                     .append(" state=").append(mUnknownApps.valueAt(i));
83             if (i != 0) {
84                 builder.append(' ');
85             }
86         }
87         return builder.toString();
88     }
89 
appRemovedOrHidden(@onNull AppWindowToken appWindow)90     void appRemovedOrHidden(@NonNull AppWindowToken appWindow) {
91         if (DEBUG_UNKNOWN_APP_VISIBILITY) {
92             Slog.d(TAG, "App removed or hidden appWindow=" + appWindow);
93         }
94         mUnknownApps.remove(appWindow);
95     }
96 
97     /**
98      * Notifies that {@param appWindow} has been launched behind Keyguard, and we need to wait until
99      * it is resumed and relaid out to resolve the visibility.
100      */
notifyLaunched(@onNull AppWindowToken appWindow)101     void notifyLaunched(@NonNull AppWindowToken appWindow) {
102         if (DEBUG_UNKNOWN_APP_VISIBILITY) {
103             Slog.d(TAG, "App launched appWindow=" + appWindow);
104         }
105         mUnknownApps.put(appWindow, UNKNOWN_STATE_WAITING_RESUME);
106     }
107 
108     /**
109      * Notifies that {@param appWindow} has finished resuming.
110      */
notifyAppResumedFinished(@onNull AppWindowToken appWindow)111     void notifyAppResumedFinished(@NonNull AppWindowToken appWindow) {
112         if (mUnknownApps.containsKey(appWindow)
113                 && mUnknownApps.get(appWindow) == UNKNOWN_STATE_WAITING_RESUME) {
114             if (DEBUG_UNKNOWN_APP_VISIBILITY) {
115                 Slog.d(TAG, "App resume finished appWindow=" + appWindow);
116             }
117             mUnknownApps.put(appWindow, UNKNOWN_STATE_WAITING_RELAYOUT);
118         }
119     }
120 
121     /**
122      * Notifies that {@param appWindow} has relaid out.
123      */
notifyRelayouted(@onNull AppWindowToken appWindow)124     void notifyRelayouted(@NonNull AppWindowToken appWindow) {
125         if (!mUnknownApps.containsKey(appWindow)) {
126             return;
127         }
128         if (DEBUG_UNKNOWN_APP_VISIBILITY) {
129             Slog.d(TAG, "App relayouted appWindow=" + appWindow);
130         }
131         int state = mUnknownApps.get(appWindow);
132         if (state == UNKNOWN_STATE_WAITING_RELAYOUT) {
133             mUnknownApps.put(appWindow, UNKNOWN_STATE_WAITING_VISIBILITY_UPDATE);
134             mService.notifyKeyguardFlagsChanged(this::notifyVisibilitiesUpdated,
135                     appWindow.getDisplayContent().getDisplayId());
136         }
137     }
138 
notifyVisibilitiesUpdated()139     private void notifyVisibilitiesUpdated() {
140         if (DEBUG_UNKNOWN_APP_VISIBILITY) {
141             Slog.d(TAG, "Visibility updated DONE");
142         }
143         boolean changed = false;
144         for (int i = mUnknownApps.size() - 1; i >= 0; i--) {
145             if (mUnknownApps.valueAt(i) == UNKNOWN_STATE_WAITING_VISIBILITY_UPDATE) {
146                 mUnknownApps.removeAt(i);
147                 changed = true;
148             }
149         }
150         if (changed) {
151             mService.mWindowPlacerLocked.performSurfacePlacement();
152         }
153     }
154 
dump(PrintWriter pw, String prefix)155     void dump(PrintWriter pw, String prefix) {
156         if (mUnknownApps.isEmpty()) {
157             return;
158         }
159         pw.println(prefix + "Unknown visibilities:");
160         for (int i = mUnknownApps.size() - 1; i >= 0; i--) {
161             pw.println(prefix + "  app=" + mUnknownApps.keyAt(i)
162                     + " state=" + mUnknownApps.valueAt(i));
163         }
164     }
165 }
166