Home | History | Annotate | Download | only in wm
      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.TaskSnapshotPersister.DISABLE_FULL_SIZED_BITMAPS;
     20 import static com.android.server.wm.TaskSnapshotPersister.REDUCED_SCALE;
     21 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
     22 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
     23 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
     24 
     25 import android.annotation.Nullable;
     26 import android.app.ActivityManager;
     27 import android.app.ActivityManager.TaskSnapshot;
     28 import android.content.pm.PackageManager;
     29 import android.graphics.Bitmap;
     30 import android.graphics.GraphicBuffer;
     31 import android.graphics.PixelFormat;
     32 import android.graphics.Rect;
     33 import android.os.Environment;
     34 import android.os.Handler;
     35 import android.util.ArraySet;
     36 import android.util.Slog;
     37 import android.view.DisplayListCanvas;
     38 import android.view.RenderNode;
     39 import android.view.SurfaceControl;
     40 import android.view.ThreadedRenderer;
     41 import android.view.View;
     42 import android.view.WindowManager.LayoutParams;
     43 
     44 import com.android.internal.annotations.VisibleForTesting;
     45 import com.android.internal.graphics.ColorUtils;
     46 import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
     47 import com.android.server.policy.WindowManagerPolicy.StartingSurface;
     48 import com.android.server.wm.TaskSnapshotSurface.SystemBarBackgroundPainter;
     49 import com.android.server.wm.utils.InsetUtils;
     50 
     51 import com.google.android.collect.Sets;
     52 
     53 import java.io.PrintWriter;
     54 
     55 /**
     56  * When an app token becomes invisible, we take a snapshot (bitmap) of the corresponding task and
     57  * put it into our cache. Internally we use gralloc buffers to be able to draw them wherever we
     58  * like without any copying.
     59  * <p>
     60  * System applications may retrieve a snapshot to represent the current state of a task, and draw
     61  * them in their own process.
     62  * <p>
     63  * When we task becomes visible again, we show a starting window with the snapshot as the content to
     64  * make app transitions more responsive.
     65  * <p>
     66  * To access this class, acquire the global window manager lock.
     67  */
     68 class TaskSnapshotController {
     69     private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotController" : TAG_WM;
     70 
     71     /**
     72      * Return value for {@link #getSnapshotMode}: We are allowed to take a real screenshot to be
     73      * used as the snapshot.
     74      */
     75     @VisibleForTesting
     76     static final int SNAPSHOT_MODE_REAL = 0;
     77 
     78     /**
     79      * Return value for {@link #getSnapshotMode}: We are not allowed to take a real screenshot but
     80      * we should try to use the app theme to create a dummy representation of the app.
     81      */
     82     @VisibleForTesting
     83     static final int SNAPSHOT_MODE_APP_THEME = 1;
     84 
     85     /**
     86      * Return value for {@link #getSnapshotMode}: We aren't allowed to take any snapshot.
     87      */
     88     @VisibleForTesting
     89     static final int SNAPSHOT_MODE_NONE = 2;
     90 
     91     private final WindowManagerService mService;
     92 
     93     private final TaskSnapshotCache mCache;
     94     private final TaskSnapshotPersister mPersister = new TaskSnapshotPersister(
     95             Environment::getDataSystemCeDirectory);
     96     private final TaskSnapshotLoader mLoader = new TaskSnapshotLoader(mPersister);
     97     private final ArraySet<Task> mSkipClosingAppSnapshotTasks = new ArraySet<>();
     98     private final ArraySet<Task> mTmpTasks = new ArraySet<>();
     99     private final Handler mHandler = new Handler();
    100 
    101     private final Rect mTmpRect = new Rect();
    102 
    103     /**
    104      * Flag indicating whether we are running on an Android TV device.
    105      */
    106     private final boolean mIsRunningOnTv;
    107 
    108     /**
    109      * Flag indicating whether we are running on an IoT device.
    110      */
    111     private final boolean mIsRunningOnIoT;
    112 
    113     /**
    114      * Flag indicating whether we are running on an Android Wear device.
    115      */
    116     private final boolean mIsRunningOnWear;
    117 
    118     TaskSnapshotController(WindowManagerService service) {
    119         mService = service;
    120         mCache = new TaskSnapshotCache(mService, mLoader);
    121         mIsRunningOnTv = mService.mContext.getPackageManager().hasSystemFeature(
    122                 PackageManager.FEATURE_LEANBACK);
    123         mIsRunningOnIoT = mService.mContext.getPackageManager().hasSystemFeature(
    124                 PackageManager.FEATURE_EMBEDDED);
    125         mIsRunningOnWear = mService.mContext.getPackageManager().hasSystemFeature(
    126             PackageManager.FEATURE_WATCH);
    127     }
    128 
    129     void systemReady() {
    130         mPersister.start();
    131     }
    132 
    133     void onTransitionStarting() {
    134         handleClosingApps(mService.mClosingApps);
    135     }
    136 
    137     /**
    138      * Called when the visibility of an app changes outside of the regular app transition flow.
    139      */
    140     void notifyAppVisibilityChanged(AppWindowToken appWindowToken, boolean visible) {
    141         if (!visible) {
    142             handleClosingApps(Sets.newArraySet(appWindowToken));
    143         }
    144     }
    145 
    146     private void handleClosingApps(ArraySet<AppWindowToken> closingApps) {
    147         if (shouldDisableSnapshots()) {
    148             return;
    149         }
    150 
    151         // We need to take a snapshot of the task if and only if all activities of the task are
    152         // either closing or hidden.
    153         getClosingTasks(closingApps, mTmpTasks);
    154         snapshotTasks(mTmpTasks);
    155         mSkipClosingAppSnapshotTasks.clear();
    156     }
    157 
    158     /**
    159      * Adds the given {@param tasks} to the list of tasks which should not have their snapshots
    160      * taken upon the next processing of the set of closing apps. The caller is responsible for
    161      * calling {@link #snapshotTasks} to ensure that the task has an up-to-date snapshot.
    162      */
    163     @VisibleForTesting
    164     void addSkipClosingAppSnapshotTasks(ArraySet<Task> tasks) {
    165         mSkipClosingAppSnapshotTasks.addAll(tasks);
    166     }
    167 
    168     void snapshotTasks(ArraySet<Task> tasks) {
    169         for (int i = tasks.size() - 1; i >= 0; i--) {
    170             final Task task = tasks.valueAt(i);
    171             final int mode = getSnapshotMode(task);
    172             final TaskSnapshot snapshot;
    173             switch (mode) {
    174                 case SNAPSHOT_MODE_NONE:
    175                     continue;
    176                 case SNAPSHOT_MODE_APP_THEME:
    177                     snapshot = drawAppThemeSnapshot(task);
    178                     break;
    179                 case SNAPSHOT_MODE_REAL:
    180                     snapshot = snapshotTask(task);
    181                     break;
    182                 default:
    183                     snapshot = null;
    184                     break;
    185             }
    186             if (snapshot != null) {
    187                 final GraphicBuffer buffer = snapshot.getSnapshot();
    188                 if (buffer.getWidth() == 0 || buffer.getHeight() == 0) {
    189                     buffer.destroy();
    190                     Slog.e(TAG, "Invalid task snapshot dimensions " + buffer.getWidth() + "x"
    191                             + buffer.getHeight());
    192                 } else {
    193                     mCache.putSnapshot(task, snapshot);
    194                     mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
    195                     if (task.getController() != null) {
    196                         task.getController().reportSnapshotChanged(snapshot);
    197                     }
    198                 }
    199             }
    200         }
    201     }
    202 
    203     /**
    204      * Retrieves a snapshot. If {@param restoreFromDisk} equals {@code true}, DO HOLD THE WINDOW
    205      * MANAGER LOCK WHEN CALLING THIS METHOD!
    206      */
    207     @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
    208             boolean reducedResolution) {
    209         return mCache.getSnapshot(taskId, userId, restoreFromDisk, reducedResolution
    210                 || DISABLE_FULL_SIZED_BITMAPS);
    211     }
    212 
    213     /**
    214      * Creates a starting surface for {@param token} with {@param snapshot}. DO NOT HOLD THE WINDOW
    215      * MANAGER LOCK WHEN CALLING THIS METHOD!
    216      */
    217     StartingSurface createStartingSurface(AppWindowToken token,
    218             TaskSnapshot snapshot) {
    219         return TaskSnapshotSurface.create(mService, token, snapshot);
    220     }
    221 
    222     private TaskSnapshot snapshotTask(Task task) {
    223         final AppWindowToken top = task.getTopChild();
    224         if (top == null) {
    225             return null;
    226         }
    227         final WindowState mainWindow = top.findMainWindow();
    228         if (mainWindow == null) {
    229             return null;
    230         }
    231         if (!mService.mPolicy.isScreenOn()) {
    232             if (DEBUG_SCREENSHOT) {
    233                 Slog.i(TAG_WM, "Attempted to take screenshot while display was off.");
    234             }
    235             return null;
    236         }
    237         if (task.getSurfaceControl() == null) {
    238             return null;
    239         }
    240 
    241         if (top.hasCommittedReparentToAnimationLeash()) {
    242             if (DEBUG_SCREENSHOT) {
    243                 Slog.w(TAG_WM, "Failed to take screenshot. App is animating " + top);
    244             }
    245             return null;
    246         }
    247 
    248         final boolean hasVisibleChild = top.forAllWindows(
    249                 // Ensure at least one window for the top app is visible before attempting to take
    250                 // a screenshot. Visible here means that the WSA surface is shown and has an alpha
    251                 // greater than 0.
    252                 ws -> (ws.mAppToken == null || ws.mAppToken.isSurfaceShowing())
    253                         && ws.mWinAnimator != null && ws.mWinAnimator.getShown()
    254                         && ws.mWinAnimator.mLastAlpha > 0f, true);
    255 
    256         if (!hasVisibleChild) {
    257             if (DEBUG_SCREENSHOT) {
    258                 Slog.w(TAG_WM, "Failed to take screenshot. No visible windows for " + task);
    259             }
    260             return null;
    261         }
    262 
    263         final boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic();
    264         final float scaleFraction = isLowRamDevice ? REDUCED_SCALE : 1f;
    265         task.getBounds(mTmpRect);
    266         mTmpRect.offsetTo(0, 0);
    267 
    268         final GraphicBuffer buffer = SurfaceControl.captureLayers(
    269                 task.getSurfaceControl().getHandle(), mTmpRect, scaleFraction);
    270         final boolean isWindowTranslucent = mainWindow.getAttrs().format != PixelFormat.OPAQUE;
    271         if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) {
    272             if (DEBUG_SCREENSHOT) {
    273                 Slog.w(TAG_WM, "Failed to take screenshot for " + task);
    274             }
    275             return null;
    276         }
    277         return new TaskSnapshot(buffer, top.getConfiguration().orientation,
    278                 getInsets(mainWindow), isLowRamDevice /* reduced */, scaleFraction /* scale */,
    279                 true /* isRealSnapshot */, task.getWindowingMode(), getSystemUiVisibility(task),
    280                 !top.fillsParent() || isWindowTranslucent);
    281     }
    282 
    283     private boolean shouldDisableSnapshots() {
    284         return mIsRunningOnWear || mIsRunningOnTv || mIsRunningOnIoT;
    285     }
    286 
    287     private Rect getInsets(WindowState state) {
    288         // XXX(b/72757033): These are insets relative to the window frame, but we're really
    289         // interested in the insets relative to the task bounds.
    290         final Rect insets = minRect(state.mContentInsets, state.mStableInsets);
    291         InsetUtils.addInsets(insets, state.mAppToken.getLetterboxInsets());
    292         return insets;
    293     }
    294 
    295     private Rect minRect(Rect rect1, Rect rect2) {
    296         return new Rect(Math.min(rect1.left, rect2.left),
    297                 Math.min(rect1.top, rect2.top),
    298                 Math.min(rect1.right, rect2.right),
    299                 Math.min(rect1.bottom, rect2.bottom));
    300     }
    301 
    302     /**
    303      * Retrieves all closing tasks based on the list of closing apps during an app transition.
    304      */
    305     @VisibleForTesting
    306     void getClosingTasks(ArraySet<AppWindowToken> closingApps, ArraySet<Task> outClosingTasks) {
    307         outClosingTasks.clear();
    308         for (int i = closingApps.size() - 1; i >= 0; i--) {
    309             final AppWindowToken atoken = closingApps.valueAt(i);
    310             final Task task = atoken.getTask();
    311 
    312             // If the task of the app is not visible anymore, it means no other app in that task
    313             // is opening. Thus, the task is closing.
    314             if (task != null && !task.isVisible() && !mSkipClosingAppSnapshotTasks.contains(task)) {
    315                 outClosingTasks.add(task);
    316             }
    317         }
    318     }
    319 
    320     @VisibleForTesting
    321     int getSnapshotMode(Task task) {
    322         final AppWindowToken topChild = task.getTopChild();
    323         if (!task.isActivityTypeStandardOrUndefined() && !task.isActivityTypeAssistant()) {
    324             return SNAPSHOT_MODE_NONE;
    325         } else if (topChild != null && topChild.shouldUseAppThemeSnapshot()) {
    326             return SNAPSHOT_MODE_APP_THEME;
    327         } else {
    328             return SNAPSHOT_MODE_REAL;
    329         }
    330     }
    331 
    332     /**
    333      * If we are not allowed to take a real screenshot, this attempts to represent the app as best
    334      * as possible by using the theme's window background.
    335      */
    336     private TaskSnapshot drawAppThemeSnapshot(Task task) {
    337         final AppWindowToken topChild = task.getTopChild();
    338         if (topChild == null) {
    339             return null;
    340         }
    341         final WindowState mainWindow = topChild.findMainWindow();
    342         if (mainWindow == null) {
    343             return null;
    344         }
    345         final int color = ColorUtils.setAlphaComponent(
    346                 task.getTaskDescription().getBackgroundColor(), 255);
    347         final int statusBarColor = task.getTaskDescription().getStatusBarColor();
    348         final int navigationBarColor = task.getTaskDescription().getNavigationBarColor();
    349         final LayoutParams attrs = mainWindow.getAttrs();
    350         final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags,
    351                 attrs.privateFlags, attrs.systemUiVisibility, statusBarColor, navigationBarColor);
    352         final int width = mainWindow.getFrameLw().width();
    353         final int height = mainWindow.getFrameLw().height();
    354 
    355         final RenderNode node = RenderNode.create("TaskSnapshotController", null);
    356         node.setLeftTopRightBottom(0, 0, width, height);
    357         node.setClipToBounds(false);
    358         final DisplayListCanvas c = node.start(width, height);
    359         c.drawColor(color);
    360         decorPainter.setInsets(mainWindow.mContentInsets, mainWindow.mStableInsets);
    361         decorPainter.drawDecors(c, null /* statusBarExcludeFrame */);
    362         node.end(c);
    363         final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width, height);
    364         if (hwBitmap == null) {
    365             return null;
    366         }
    367         // Note, the app theme snapshot is never translucent because we enforce a non-translucent
    368         // color above
    369         return new TaskSnapshot(hwBitmap.createGraphicBufferHandle(),
    370                 topChild.getConfiguration().orientation, mainWindow.mStableInsets,
    371                 ActivityManager.isLowRamDeviceStatic() /* reduced */, 1.0f /* scale */,
    372                 false /* isRealSnapshot */, task.getWindowingMode(), getSystemUiVisibility(task),
    373                 false);
    374     }
    375 
    376     /**
    377      * Called when an {@link AppWindowToken} has been removed.
    378      */
    379     void onAppRemoved(AppWindowToken wtoken) {
    380         mCache.onAppRemoved(wtoken);
    381     }
    382 
    383     /**
    384      * Called when the process of an {@link AppWindowToken} has died.
    385      */
    386     void onAppDied(AppWindowToken wtoken) {
    387         mCache.onAppDied(wtoken);
    388     }
    389 
    390     void notifyTaskRemovedFromRecents(int taskId, int userId) {
    391         mCache.onTaskRemoved(taskId);
    392         mPersister.onTaskRemovedFromRecents(taskId, userId);
    393     }
    394 
    395     /**
    396      * See {@link TaskSnapshotPersister#removeObsoleteFiles}
    397      */
    398     void removeObsoleteTaskFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) {
    399         mPersister.removeObsoleteFiles(persistentTaskIds, runningUserIds);
    400     }
    401 
    402     /**
    403      * Temporarily pauses/unpauses persisting of task snapshots.
    404      *
    405      * @param paused Whether task snapshot persisting should be paused.
    406      */
    407     void setPersisterPaused(boolean paused) {
    408         mPersister.setPaused(paused);
    409     }
    410 
    411     /**
    412      * Called when screen is being turned off.
    413      */
    414     void screenTurningOff(ScreenOffListener listener) {
    415         if (shouldDisableSnapshots()) {
    416             listener.onScreenOff();
    417             return;
    418         }
    419 
    420         // We can't take a snapshot when screen is off, so take a snapshot now!
    421         mHandler.post(() -> {
    422             try {
    423                 synchronized (mService.mWindowMap) {
    424                     mTmpTasks.clear();
    425                     mService.mRoot.forAllTasks(task -> {
    426                         if (task.isVisible()) {
    427                             mTmpTasks.add(task);
    428                         }
    429                     });
    430                     snapshotTasks(mTmpTasks);
    431                 }
    432             } finally {
    433                 listener.onScreenOff();
    434             }
    435         });
    436     }
    437 
    438     /**
    439      * @return The SystemUI visibility flags for the top fullscreen window in the given
    440      *         {@param task}.
    441      */
    442     private int getSystemUiVisibility(Task task) {
    443         final AppWindowToken topFullscreenToken = task.getTopFullscreenAppToken();
    444         final WindowState topFullscreenWindow = topFullscreenToken != null
    445                 ? topFullscreenToken.getTopFullscreenWindow()
    446                 : null;
    447         if (topFullscreenWindow != null) {
    448             return topFullscreenWindow.getSystemUiVisibility();
    449         }
    450         return 0;
    451     }
    452 
    453     void dump(PrintWriter pw, String prefix) {
    454         mCache.dump(pw, prefix);
    455     }
    456 }
    457