blob: 35004c2956810fc51c76148740e01bfce403b536 [file] [log] [blame]
Wale Ogunwale26c0dfe2016-12-14 14:42:30 -08001/*
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
17package com.android.server.wm;
18
19import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
20import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
21import static com.android.server.wm.AppTransition.TRANSIT_UNSET;
22import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
23import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
24import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
25import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
26import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW;
27import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TOKEN_MOVEMENT;
28import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
29import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
30import static com.android.server.wm.WindowManagerService.H.ADD_STARTING;
31
32import android.graphics.Bitmap;
33import android.os.Trace;
34import com.android.server.AttributeCache;
35
36import android.content.res.CompatibilityInfo;
37import android.content.res.Configuration;
38import android.os.Binder;
39import android.os.Debug;
40import android.os.IBinder;
41import android.os.Message;
42import android.util.Slog;
43import android.view.IApplicationToken;
44
45/**
46 * Controller for the app window token container. This is created by activity manager to link
47 * activity records to the app window token container they use in window manager.
48 *
49 * Test class: {@link AppWindowContainerControllerTests}
50 */
51public class AppWindowContainerController
52 extends WindowContainerController<AppWindowToken, AppWindowContainerListener> {
53
54 private final IApplicationToken mToken;
55
56 private final Runnable mOnWindowsDrawn = () -> {
57 if (mListener == null) {
58 return;
59 }
60 if (DEBUG_VISIBILITY) Slog.v(TAG_WM, "Reporting drawn in "
61 + AppWindowContainerController.this.mToken);
62 mListener.onWindowsDrawn();
63 };
64
65 private final Runnable mOnWindowsVisible = () -> {
66 if (mListener == null) {
67 return;
68 }
69 if (DEBUG_VISIBILITY) Slog.v(TAG_WM, "Reporting visible in "
70 + AppWindowContainerController.this.mToken);
71 mListener.onWindowsVisible();
72 };
73
74 private final Runnable mOnWindowsGone = () -> {
75 if (mListener == null) {
76 return;
77 }
78 if (DEBUG_VISIBILITY) Slog.v(TAG_WM, "Reporting gone in "
79 + AppWindowContainerController.this.mToken);
80 mListener.onWindowsGone();
81 };
82
83 public AppWindowContainerController(IApplicationToken token,
84 AppWindowContainerListener listener, int taskId, int index, int requestedOrientation,
85 boolean fullscreen, boolean showForAllUsers, int configChanges,
86 boolean voiceInteraction, boolean launchTaskBehind, boolean alwaysFocusable,
87 int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos) {
88 this(token, listener, taskId, index, requestedOrientation, fullscreen, showForAllUsers,
89 configChanges, voiceInteraction, launchTaskBehind, alwaysFocusable,
90 targetSdkVersion, rotationAnimationHint, inputDispatchingTimeoutNanos,
91 WindowManagerService.getInstance());
92 }
93
94 public AppWindowContainerController(IApplicationToken token,
95 AppWindowContainerListener listener, int taskId, int index, int requestedOrientation,
96 boolean fullscreen, boolean showForAllUsers, int configChanges,
97 boolean voiceInteraction, boolean launchTaskBehind, boolean alwaysFocusable,
98 int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos,
99 WindowManagerService service) {
100 super(listener, service);
101 mToken = token;
102 synchronized(mWindowMap) {
103 AppWindowToken atoken = mRoot.getAppWindowToken(mToken.asBinder());
104 if (atoken != null) {
105 // TODO: Should this throw an exception instead?
106 Slog.w(TAG_WM, "Attempted to add existing app token: " + mToken);
107 return;
108 }
109
110 // TODO: Have the controller for the task passed in when task are changed to use
111 // controller.
112 final Task task = mService.mTaskIdToTask.get(taskId);
113 if (task == null) {
114 throw new IllegalArgumentException("addAppToken: invalid taskId=" + taskId);
115 }
116
117 atoken = new AppWindowToken(mService, token, voiceInteraction, task.getDisplayContent(),
118 inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdkVersion,
119 requestedOrientation, rotationAnimationHint, configChanges, launchTaskBehind,
120 alwaysFocusable, this);
121 if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "addAppToken: " + atoken
122 + " task=" + taskId + " at " + index);
123 task.addChild(atoken, index);
124 }
125 }
126
127 public void removeContainer(int displayId) {
128 final long origId = Binder.clearCallingIdentity();
129 try {
130 synchronized(mWindowMap) {
131 final DisplayContent dc = mRoot.getDisplayContent(displayId);
132 if (dc == null) {
133 Slog.w(TAG_WM, "removeAppToken: Attempted to remove binder token: "
134 + mToken + " from non-existing displayId=" + displayId);
135 return;
136 }
137 dc.removeAppToken(mToken.asBinder());
138 super.removeContainer();
139 }
140 } finally {
141 Binder.restoreCallingIdentity(origId);
142 }
143 }
144
145 // TODO: Move to task window controller when that is created and rename to positionChildAt()
146 public void positionAt(int taskId, int index) {
147 synchronized(mService.mWindowMap) {
148 if (mContainer == null) {
149 Slog.w(TAG_WM,
150 "Attempted to position of non-existing app token: " + mToken);
151 return;
152 }
153
154 // TODO: Should get the window container from this owner when the task owner stuff is
155 // hooked-up.
156 final Task task = mService.mTaskIdToTask.get(taskId);
157 if (task == null) {
158 throw new IllegalArgumentException("positionChildAt: invalid taskId=" + taskId);
159 }
160 task.addChild(mContainer, index);
161 }
162
163 }
164
165 public Configuration setOrientation(int requestedOrientation, int displayId,
166 Configuration displayConfig, boolean freezeScreenIfNeeded) {
167 synchronized(mWindowMap) {
168 if (mContainer == null) {
169 Slog.w(TAG_WM,
170 "Attempted to set orientation of non-existing app token: " + mToken);
171 return null;
172 }
173
174 mContainer.setOrientation(requestedOrientation);
175
176 final IBinder binder = freezeScreenIfNeeded ? mToken.asBinder() : null;
177 return mService.updateOrientationFromAppTokens(displayConfig, binder, displayId);
178
179 }
180 }
181
182 public int getOrientation() {
183 synchronized(mWindowMap) {
184 if (mContainer == null) {
185 return SCREEN_ORIENTATION_UNSPECIFIED;
186 }
187
188 return mContainer.getOrientationIgnoreVisibility();
189 }
190 }
191
192 public void setVisibility(boolean visible) {
193 synchronized(mWindowMap) {
194 if (mContainer == null) {
195 Slog.w(TAG_WM, "Attempted to set visibility of non-existing app token: "
196 + mToken);
197 return;
198 }
199
200 final AppWindowToken wtoken = mContainer;
201
202 if (DEBUG_APP_TRANSITIONS || DEBUG_ORIENTATION) Slog.v(TAG_WM, "setAppVisibility("
203 + mToken + ", visible=" + visible + "): " + mService.mAppTransition
204 + " hidden=" + wtoken.hidden + " hiddenRequested="
205 + wtoken.hiddenRequested + " Callers=" + Debug.getCallers(6));
206
207 mService.mOpeningApps.remove(wtoken);
208 mService.mClosingApps.remove(wtoken);
209 wtoken.waitingToShow = false;
210 wtoken.hiddenRequested = !visible;
211
212 if (!visible) {
213 // If the app is dead while it was visible, we kept its dead window on screen.
214 // Now that the app is going invisible, we can remove it. It will be restarted
215 // if made visible again.
216 wtoken.removeDeadWindows();
217 wtoken.setVisibleBeforeClientHidden();
218 } else {
219 if (!mService.mAppTransition.isTransitionSet()
220 && mService.mAppTransition.isReady()) {
221 // Add the app mOpeningApps if transition is unset but ready. This means
222 // we're doing a screen freeze, and the unfreeze will wait for all opening
223 // apps to be ready.
224 mService.mOpeningApps.add(wtoken);
225 }
226 wtoken.startingMoved = false;
227 // If the token is currently hidden (should be the common case), or has been
228 // stopped, then we need to set up to wait for its windows to be ready.
229 if (wtoken.hidden || wtoken.mAppStopped) {
230 wtoken.clearAllDrawn();
231
232 // If the app was already visible, don't reset the waitingToShow state.
233 if (wtoken.hidden) {
234 wtoken.waitingToShow = true;
235 }
236
237 if (wtoken.clientHidden) {
238 // In the case where we are making an app visible
239 // but holding off for a transition, we still need
240 // to tell the client to make its windows visible so
241 // they get drawn. Otherwise, we will wait on
242 // performing the transition until all windows have
243 // been drawn, they never will be, and we are sad.
244 wtoken.clientHidden = false;
245 wtoken.sendAppVisibilityToClients();
246 }
247 }
248 wtoken.requestUpdateWallpaperIfNeeded();
249
250 if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "No longer Stopped: " + wtoken);
251 wtoken.mAppStopped = false;
252 }
253
254 // If we are preparing an app transition, then delay changing
255 // the visibility of this token until we execute that transition.
256 if (mService.okToDisplay() && mService.mAppTransition.isTransitionSet()) {
257 // A dummy animation is a placeholder animation which informs others that an
258 // animation is going on (in this case an application transition). If the animation
259 // was transferred from another application/animator, no dummy animator should be
260 // created since an animation is already in progress.
261 if (wtoken.mAppAnimator.usingTransferredAnimation
262 && wtoken.mAppAnimator.animation == null) {
263 Slog.wtf(TAG_WM, "Will NOT set dummy animation on: " + wtoken
264 + ", using null transferred animation!");
265 }
266 if (!wtoken.mAppAnimator.usingTransferredAnimation &&
267 (!wtoken.startingDisplayed || mService.mSkipAppTransitionAnimation)) {
268 if (DEBUG_APP_TRANSITIONS) Slog.v(
269 TAG_WM, "Setting dummy animation on: " + wtoken);
270 wtoken.mAppAnimator.setDummyAnimation();
271 }
272 wtoken.inPendingTransaction = true;
273 if (visible) {
274 mService.mOpeningApps.add(wtoken);
275 wtoken.mEnteringAnimation = true;
276 } else {
277 mService.mClosingApps.add(wtoken);
278 wtoken.mEnteringAnimation = false;
279 }
280 if (mService.mAppTransition.getAppTransition()
281 == AppTransition.TRANSIT_TASK_OPEN_BEHIND) {
282 // We're launchingBehind, add the launching activity to mOpeningApps.
283 final WindowState win =
284 mService.getDefaultDisplayContentLocked().findFocusedWindow();
285 if (win != null) {
286 final AppWindowToken focusedToken = win.mAppToken;
287 if (focusedToken != null) {
288 if (DEBUG_APP_TRANSITIONS) Slog.d(TAG_WM, "TRANSIT_TASK_OPEN_BEHIND, "
289 + " adding " + focusedToken + " to mOpeningApps");
290 // Force animation to be loaded.
291 focusedToken.hidden = true;
292 mService.mOpeningApps.add(focusedToken);
293 }
294 }
295 }
296 return;
297 }
298
299 wtoken.setVisibility(null, visible, TRANSIT_UNSET, true, wtoken.mVoiceInteraction);
300 wtoken.updateReportedVisibilityLocked();
301 }
302 }
303
304 /**
305 * Notifies that we launched an app that might be visible or not visible depending on what kind
306 * of Keyguard flags it's going to set on its windows.
307 */
308 public void notifyUnknownVisibilityLaunched() {
309 synchronized(mWindowMap) {
310 if (mContainer != null) {
311 mService.mUnknownAppVisibilityController.notifyLaunched(mContainer);
312 }
313 }
314 }
315
316 public boolean addStartingWindow(String pkg, int theme, CompatibilityInfo compatInfo,
317 CharSequence nonLocalizedLabel, int labelRes, int icon, int logo, int windowFlags,
318 IBinder transferFrom, boolean createIfNeeded) {
319 synchronized(mWindowMap) {
320 if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "setAppStartingWindow: token=" + mToken
321 + " pkg=" + pkg + " transferFrom=" + transferFrom);
322
323 if (mContainer == null) {
324 Slog.w(TAG_WM, "Attempted to set icon of non-existing app token: " + mToken);
325 return false;
326 }
327
328 // If the display is frozen, we won't do anything until the actual window is
329 // displayed so there is no reason to put in the starting window.
330 if (!mService.okToDisplay()) {
331 return false;
332 }
333
334 if (mContainer.startingData != null) {
335 return false;
336 }
337
338 // If this is a translucent window, then don't show a starting window -- the current
339 // effect (a full-screen opaque starting window that fades away to the real contents
340 // when it is ready) does not work for this.
341 if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Checking theme of starting window: 0x"
342 + Integer.toHexString(theme));
343 if (theme != 0) {
344 AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme,
345 com.android.internal.R.styleable.Window, mService.mCurrentUserId);
346 if (ent == null) {
347 // Whoops! App doesn't exist. Um. Okay. We'll just pretend like we didn't
348 // see that.
349 return false;
350 }
351 final boolean windowIsTranslucent = ent.array.getBoolean(
352 com.android.internal.R.styleable.Window_windowIsTranslucent, false);
353 final boolean windowIsFloating = ent.array.getBoolean(
354 com.android.internal.R.styleable.Window_windowIsFloating, false);
355 final boolean windowShowWallpaper = ent.array.getBoolean(
356 com.android.internal.R.styleable.Window_windowShowWallpaper, false);
357 final boolean windowDisableStarting = ent.array.getBoolean(
358 com.android.internal.R.styleable.Window_windowDisablePreview, false);
359 if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Translucent=" + windowIsTranslucent
360 + " Floating=" + windowIsFloating
361 + " ShowWallpaper=" + windowShowWallpaper);
362 if (windowIsTranslucent) {
363 return false;
364 }
365 if (windowIsFloating || windowDisableStarting) {
366 return false;
367 }
368 if (windowShowWallpaper) {
369 if (mContainer.getDisplayContent().mWallpaperController.getWallpaperTarget()
370 == null) {
371 // If this theme is requesting a wallpaper, and the wallpaper
372 // is not currently visible, then this effectively serves as
373 // an opaque window and our starting window transition animation
374 // can still work. We just need to make sure the starting window
375 // is also showing the wallpaper.
376 windowFlags |= FLAG_SHOW_WALLPAPER;
377 } else {
378 return false;
379 }
380 }
381 }
382
383 if (mContainer.transferStartingWindow(transferFrom)) {
384 return true;
385 }
386
387 // There is no existing starting window, and the caller doesn't
388 // want us to create one, so that's it!
389 if (!createIfNeeded) {
390 return false;
391 }
392
393 if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Creating StartingData");
394 mContainer.startingData = new StartingData(pkg, theme, compatInfo, nonLocalizedLabel,
395 labelRes, icon, logo, windowFlags);
396 final Message m = mService.mH.obtainMessage(ADD_STARTING, mContainer);
397 // Note: we really want to do sendMessageAtFrontOfQueue() because we
398 // want to process the message ASAP, before any other queued
399 // messages.
400 if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Enqueueing ADD_STARTING");
401 mService.mH.sendMessageAtFrontOfQueue(m);
402 }
403 return true;
404 }
405
406 public void removeStartingWindow() {
407 synchronized (mWindowMap) {
408 mService.scheduleRemoveStartingWindowLocked(mContainer);
409 }
410 }
411
412 public void pauseKeyDispatching() {
413 synchronized (mWindowMap) {
414 if (mContainer != null) {
415 mService.mInputMonitor.pauseDispatchingLw(mContainer);
416 }
417 }
418 }
419
420 public void resumeKeyDispatching() {
421 synchronized (mWindowMap) {
422 if (mContainer != null) {
423 mService.mInputMonitor.resumeDispatchingLw(mContainer);
424 }
425 }
426 }
427
428 public void notifyAppResumed(boolean wasStopped, boolean allowSavedSurface) {
429 synchronized(mWindowMap) {
430 if (mContainer == null) {
431 Slog.w(TAG_WM, "Attempted to notify resumed of non-existing app token: " + mToken);
432 return;
433 }
434 mContainer.notifyAppResumed(wasStopped, allowSavedSurface);
435 }
436 }
437
438 public void notifyAppStopped() {
439 synchronized(mWindowMap) {
440 if (mContainer == null) {
441 Slog.w(TAG_WM, "Attempted to notify stopped of non-existing app token: "
442 + mToken);
443 return;
444 }
445 mContainer.notifyAppStopped();
446 }
447 }
448
449 public void startFreezingScreen(int configChanges) {
450 synchronized(mWindowMap) {
451 if (configChanges == 0 && mService.okToDisplay()) {
452 if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Skipping set freeze of " + mToken);
453 return;
454 }
455
456 if (mContainer == null) {
457 Slog.w(TAG_WM,
458 "Attempted to freeze screen with non-existing app token: " + mContainer);
459 return;
460 }
461 final long origId = Binder.clearCallingIdentity();
462 mContainer.startFreezingScreen();
463 Binder.restoreCallingIdentity(origId);
464 }
465 }
466
467 public void stopFreezingScreen(boolean force) {
468 synchronized(mWindowMap) {
469 if (mContainer == null) {
470 return;
471 }
472 final long origId = Binder.clearCallingIdentity();
473 if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Clear freezing of " + mToken + ": hidden="
474 + mContainer.hidden + " freezing=" + mContainer.mAppAnimator.freezingScreen);
475 mContainer.stopFreezingScreen(true, force);
476 Binder.restoreCallingIdentity(origId);
477 }
478 }
479
480 /**
481 * Takes a snapshot of the screen. In landscape mode this grabs the whole screen.
482 * In portrait mode, it grabs the full screenshot.
483 *
484 * @param displayId the Display to take a screenshot of.
485 * @param width the width of the target bitmap
486 * @param height the height of the target bitmap
487 * @param frameScale the scale to apply to the frame, only used when width = -1 and height = -1
488 */
489 public Bitmap screenshotApplications(int displayId, int width, int height, float frameScale) {
490 try {
491 Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "screenshotApplications");
492 final DisplayContent dc;
493 synchronized(mWindowMap) {
494 dc = mRoot.getDisplayContentOrCreate(displayId);
495 if (dc == null) {
496 if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Screenshot of " + mToken
497 + ": returning null. No Display for displayId=" + displayId);
498 return null;
499 }
500 }
501 return dc.screenshotApplications(mToken.asBinder(), width, height,
502 false /* includeFullDisplay */, frameScale, Bitmap.Config.RGB_565,
503 false /* wallpaperOnly */);
504 } finally {
505 Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
506 }
507 }
508
509
510 void reportWindowsDrawn() {
511 mService.mH.post(mOnWindowsDrawn);
512 }
513
514 void reportWindowsVisible() {
515 mService.mH.post(mOnWindowsVisible);
516 }
517
518 void reportWindowsGone() {
519 mService.mH.post(mOnWindowsGone);
520 }
521
522 /** Calls directly into activity manager so window manager lock shouldn't held. */
523 boolean keyDispatchingTimedOut(String reason) {
524 return mListener != null && mListener.keyDispatchingTimedOut(reason);
525 }
526}