• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 android.graphics.Bitmap.CompressFormat.JPEG;
20 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
21 
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.NonNull;
26 import android.graphics.Bitmap;
27 import android.graphics.PixelFormat;
28 import android.hardware.HardwareBuffer;
29 import android.media.Image;
30 import android.media.ImageReader;
31 import android.os.Process;
32 import android.os.SystemClock;
33 import android.os.Trace;
34 import android.util.AtomicFile;
35 import android.util.Slog;
36 import android.window.TaskSnapshot;
37 
38 import com.android.internal.annotations.GuardedBy;
39 import com.android.internal.annotations.VisibleForTesting;
40 import com.android.internal.policy.TransitionAnimation;
41 import com.android.server.LocalServices;
42 import com.android.server.pm.UserManagerInternal;
43 import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider;
44 import com.android.server.wm.nano.WindowManagerProtos.TaskSnapshotProto;
45 import com.android.window.flags.Flags;
46 
47 import java.io.File;
48 import java.io.FileOutputStream;
49 import java.io.IOException;
50 import java.io.PrintWriter;
51 import java.util.ArrayDeque;
52 
53 /**
54  * Singleton worker thread to queue up persist or delete tasks of {@link TaskSnapshot}s to disk.
55  */
56 class SnapshotPersistQueue {
57     private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotPersister" : TAG_WM;
58     private static final long DELAY_MS = 100;
59     static final int MAX_STORE_QUEUE_DEPTH = 2;
60     private static final int COMPRESS_QUALITY = 95;
61 
62     @GuardedBy("mLock")
63     private final ArrayDeque<WriteQueueItem> mWriteQueue = new ArrayDeque<>();
64     @GuardedBy("mLock")
65     private final ArrayDeque<StoreWriteQueueItem> mStoreQueueItems = new ArrayDeque<>();
66     @GuardedBy("mLock")
67     private boolean mQueueIdling;
68     @GuardedBy("mLock")
69     private boolean mPaused;
70     private boolean mStarted;
71     private final Object mLock = new Object();
72     private final UserManagerInternal mUserManagerInternal;
73     private boolean mShutdown;
74 
SnapshotPersistQueue()75     SnapshotPersistQueue() {
76         mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
77     }
78 
getLock()79     Object getLock() {
80         return mLock;
81     }
82 
systemReady()83     void systemReady() {
84         start();
85     }
86 
87     /**
88      * Starts persisting.
89      */
start()90     void start() {
91         if (!mStarted) {
92             mStarted = true;
93             mPersister.start();
94         }
95     }
96 
97     /**
98      * Temporarily pauses/unpauses persisting of task snapshots.
99      *
100      * @param paused Whether task snapshot persisting should be paused.
101      */
setPaused(boolean paused)102     void setPaused(boolean paused) {
103         synchronized (mLock) {
104             mPaused = paused;
105             if (!paused) {
106                 mLock.notifyAll();
107             }
108         }
109     }
110 
111     /**
112      * Prepare to enqueue all visible task snapshots because of shutdown.
113      */
prepareShutdown()114     void prepareShutdown() {
115         synchronized (mLock) {
116             mShutdown = true;
117         }
118     }
119 
isQueueEmpty()120     private boolean isQueueEmpty() {
121         synchronized (mLock) {
122             return mWriteQueue.isEmpty() || mQueueIdling || mPaused;
123         }
124     }
125 
waitFlush(long timeout)126     void waitFlush(long timeout) {
127         if (timeout <= 0) {
128             return;
129         }
130         final long endTime = System.currentTimeMillis() + timeout;
131         while (true) {
132             if (!isQueueEmpty()) {
133                 long timeRemaining = endTime - System.currentTimeMillis();
134                 if (timeRemaining > 0) {
135                     synchronized (mLock) {
136                         try {
137                             mLock.wait(timeRemaining);
138                         } catch (InterruptedException e) {
139                         }
140                     }
141                 } else {
142                     Slog.w(TAG, "Snapshot Persist Queue flush timed out");
143                     break;
144                 }
145             } else {
146                 break;
147             }
148         }
149     }
150 
151     @VisibleForTesting
waitForQueueEmpty()152     void waitForQueueEmpty() {
153         while (true) {
154             synchronized (mLock) {
155                 if (mWriteQueue.isEmpty() && mQueueIdling) {
156                     return;
157                 }
158             }
159             SystemClock.sleep(DELAY_MS);
160         }
161     }
162 
peekWriteQueueSize()163     int peekWriteQueueSize() {
164         synchronized (mLock) {
165             return mStoreQueueItems.size();
166         }
167     }
168 
peekQueueSize()169     int peekQueueSize() {
170         synchronized (mLock) {
171             return mWriteQueue.size();
172         }
173     }
174 
addToQueueInternal(WriteQueueItem item, boolean insertToFront)175     private void addToQueueInternal(WriteQueueItem item, boolean insertToFront) {
176         mWriteQueue.removeFirstOccurrence(item);
177         if (insertToFront) {
178             mWriteQueue.addFirst(item);
179         } else {
180             mWriteQueue.addLast(item);
181         }
182         item.onQueuedLocked();
183         if (!mShutdown) {
184             ensureStoreQueueDepthLocked();
185         }
186         if (!mPaused) {
187             mLock.notifyAll();
188         }
189     }
190 
191     @GuardedBy("mLock")
sendToQueueLocked(WriteQueueItem item)192     void sendToQueueLocked(WriteQueueItem item) {
193         addToQueueInternal(item, false /* insertToFront */);
194     }
195 
196     @GuardedBy("mLock")
insertQueueAtFirstLocked(WriteQueueItem item)197     void insertQueueAtFirstLocked(WriteQueueItem item) {
198         addToQueueInternal(item, true /* insertToFront */);
199     }
200 
201     @GuardedBy("mLock")
ensureStoreQueueDepthLocked()202     private void ensureStoreQueueDepthLocked() {
203         while (mStoreQueueItems.size() > MAX_STORE_QUEUE_DEPTH) {
204             final StoreWriteQueueItem item = mStoreQueueItems.poll();
205             mWriteQueue.remove(item);
206             Slog.i(TAG, "Queue is too deep! Purged item with index=" + item.mId);
207         }
208     }
209 
deleteSnapshot(int index, int userId, PersistInfoProvider provider)210     void deleteSnapshot(int index, int userId, PersistInfoProvider provider) {
211         final File protoFile = provider.getProtoFile(index, userId);
212         final File bitmapLowResFile = provider.getLowResolutionBitmapFile(index, userId);
213         if (protoFile.exists()) {
214             protoFile.delete();
215         }
216         if (bitmapLowResFile.exists()) {
217             bitmapLowResFile.delete();
218         }
219         final File bitmapFile = provider.getHighResolutionBitmapFile(index, userId);
220         if (bitmapFile.exists()) {
221             bitmapFile.delete();
222         }
223     }
224 
225     private final Thread mPersister = new Thread("TaskSnapshotPersister") {
226         public void run() {
227             android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
228             while (true) {
229                 WriteQueueItem next;
230                 boolean isReadyToWrite = false;
231                 synchronized (mLock) {
232                     if (mPaused) {
233                         next = null;
234                     } else {
235                         next = mWriteQueue.poll();
236                         if (next != null) {
237                             if (next.isReady(mUserManagerInternal)) {
238                                 isReadyToWrite = true;
239                                 next.onDequeuedLocked();
240                             } else if (!mShutdown) {
241                                 mWriteQueue.addLast(next);
242                             } else {
243                                 // User manager is locked and device is shutting down, skip writing
244                                 // this item.
245                                 next.onDequeuedLocked();
246                                 next = null;
247                             }
248                         }
249                     }
250                 }
251                 if (next != null) {
252                     if (isReadyToWrite) {
253                         next.write();
254                     }
255                     if (!mShutdown) {
256                         SystemClock.sleep(DELAY_MS);
257                     }
258                 }
259                 synchronized (mLock) {
260                     final boolean writeQueueEmpty = mWriteQueue.isEmpty();
261                     if (!writeQueueEmpty && !mPaused) {
262                         continue;
263                     }
264                     if (mShutdown && writeQueueEmpty) {
265                         mLock.notifyAll();
266                     }
267                     try {
268                         mQueueIdling = writeQueueEmpty;
269                         mLock.wait();
270                         mQueueIdling = false;
271                     } catch (InterruptedException e) {
272                     }
273                 }
274             }
275         }
276     };
277 
278     abstract static class WriteQueueItem {
279         protected final PersistInfoProvider mPersistInfoProvider;
280         protected final int mUserId;
WriteQueueItem(@onNull PersistInfoProvider persistInfoProvider, int userId)281         WriteQueueItem(@NonNull PersistInfoProvider persistInfoProvider, int userId) {
282             mPersistInfoProvider = persistInfoProvider;
283             mUserId = userId;
284         }
285         /**
286          * @return {@code true} if item is ready to have {@link WriteQueueItem#write} called
287          */
isReady(UserManagerInternal userManager)288         boolean isReady(UserManagerInternal userManager) {
289             return userManager.isUserUnlocked(mUserId);
290         }
291 
write()292         abstract void write();
293 
294         /**
295          * Called when this queue item has been put into the queue.
296          */
onQueuedLocked()297         void onQueuedLocked() {
298         }
299 
300         /**
301          * Called when this queue item has been taken out of the queue.
302          */
onDequeuedLocked()303         void onDequeuedLocked() {
304         }
305     }
306 
createStoreWriteQueueItem(int id, int userId, TaskSnapshot snapshot, PersistInfoProvider provider)307     StoreWriteQueueItem createStoreWriteQueueItem(int id, int userId, TaskSnapshot snapshot,
308             PersistInfoProvider provider) {
309         return new StoreWriteQueueItem(id, userId, snapshot, provider);
310     }
311 
312     class StoreWriteQueueItem extends WriteQueueItem {
313         private final int mId;
314         private final TaskSnapshot mSnapshot;
315 
StoreWriteQueueItem(int id, int userId, TaskSnapshot snapshot, PersistInfoProvider provider)316         StoreWriteQueueItem(int id, int userId, TaskSnapshot snapshot,
317                 PersistInfoProvider provider) {
318             super(provider, userId);
319             mId = id;
320             snapshot.addReference(TaskSnapshot.REFERENCE_PERSIST);
321             mSnapshot = snapshot;
322         }
323 
324         @GuardedBy("mLock")
325         @Override
onQueuedLocked()326         void onQueuedLocked() {
327             // Remove duplicate request.
328             mStoreQueueItems.removeIf(item -> {
329                 if (item.equals(this) && item.mSnapshot != mSnapshot) {
330                     item.mSnapshot.removeReference(TaskSnapshot.REFERENCE_PERSIST);
331                     return true;
332                 }
333                 return false;
334             });
335             mStoreQueueItems.offer(this);
336         }
337 
338         @GuardedBy("mLock")
339         @Override
onDequeuedLocked()340         void onDequeuedLocked() {
341             mStoreQueueItems.remove(this);
342         }
343 
344         @Override
write()345         void write() {
346             if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
347                 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "StoreWriteQueueItem#" + mId);
348             }
349             if (!mPersistInfoProvider.createDirectory(mUserId)) {
350                 Slog.e(TAG, "Unable to create snapshot directory for user dir="
351                         + mPersistInfoProvider.getDirectory(mUserId));
352             }
353             boolean failed = false;
354             if (!writeProto()) {
355                 failed = true;
356             }
357             if (!writeBuffer()) {
358                 failed = true;
359             }
360             if (failed) {
361                 deleteSnapshot(mId, mUserId, mPersistInfoProvider);
362             }
363             mSnapshot.removeReference(TaskSnapshot.REFERENCE_PERSIST);
364             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
365         }
366 
writeProto()367         boolean writeProto() {
368             final TaskSnapshotProto proto = new TaskSnapshotProto();
369             proto.orientation = mSnapshot.getOrientation();
370             proto.rotation = mSnapshot.getRotation();
371             proto.taskWidth = mSnapshot.getTaskSize().x;
372             proto.taskHeight = mSnapshot.getTaskSize().y;
373             proto.insetLeft = mSnapshot.getContentInsets().left;
374             proto.insetTop = mSnapshot.getContentInsets().top;
375             proto.insetRight = mSnapshot.getContentInsets().right;
376             proto.insetBottom = mSnapshot.getContentInsets().bottom;
377             proto.letterboxInsetLeft = mSnapshot.getLetterboxInsets().left;
378             proto.letterboxInsetTop = mSnapshot.getLetterboxInsets().top;
379             proto.letterboxInsetRight = mSnapshot.getLetterboxInsets().right;
380             proto.letterboxInsetBottom = mSnapshot.getLetterboxInsets().bottom;
381             proto.isRealSnapshot = mSnapshot.isRealSnapshot();
382             proto.windowingMode = mSnapshot.getWindowingMode();
383             proto.appearance = mSnapshot.getAppearance();
384             proto.isTranslucent = mSnapshot.isTranslucent();
385             proto.topActivityComponent = mSnapshot.getTopActivityComponent().flattenToString();
386             proto.uiMode = mSnapshot.getUiMode();
387             proto.id = mSnapshot.getId();
388             final byte[] bytes = TaskSnapshotProto.toByteArray(proto);
389             final File file = mPersistInfoProvider.getProtoFile(mId, mUserId);
390             final AtomicFile atomicFile = new AtomicFile(file);
391             FileOutputStream fos = null;
392             try {
393                 fos = atomicFile.startWrite();
394                 fos.write(bytes);
395                 atomicFile.finishWrite(fos);
396             } catch (IOException e) {
397                 atomicFile.failWrite(fos);
398                 Slog.e(TAG, "Unable to open " + file + " for persisting. " + e);
399                 return false;
400             }
401             return true;
402         }
403 
writeBuffer()404         boolean writeBuffer() {
405             if (AbsAppSnapshotController.isInvalidHardwareBuffer(mSnapshot.getHardwareBuffer())) {
406                 Slog.e(TAG, "Invalid task snapshot hw buffer, taskId=" + mId);
407                 return false;
408             }
409 
410             final HardwareBuffer hwBuffer = mSnapshot.getHardwareBuffer();
411             final int width = hwBuffer.getWidth();
412             final int height = hwBuffer.getHeight();
413             final int pixelFormat = hwBuffer.getFormat();
414             final Bitmap swBitmap = !Flags.reduceTaskSnapshotMemoryUsage()
415                     || (pixelFormat != PixelFormat.RGB_565 && pixelFormat != PixelFormat.RGBA_8888)
416                     || !mSnapshot.isRealSnapshot()
417                     || TransitionAnimation.hasProtectedContent(hwBuffer)
418                     ? copyToSwBitmapReadBack()
419                     : copyToSwBitmapDirect(width, height, pixelFormat);
420             if (swBitmap == null) {
421                 return false;
422             }
423             final File file = mPersistInfoProvider.getHighResolutionBitmapFile(mId, mUserId);
424             try (FileOutputStream fos = new FileOutputStream(file)) {
425                 swBitmap.compress(JPEG, COMPRESS_QUALITY, fos);
426             } catch (IOException e) {
427                 Slog.e(TAG, "Unable to open " + file + " for persisting.", e);
428                 return false;
429             }
430 
431             if (!mPersistInfoProvider.enableLowResSnapshots()) {
432                 swBitmap.recycle();
433                 return true;
434             }
435 
436             final Bitmap lowResBitmap = Bitmap.createScaledBitmap(swBitmap,
437                     (int) (width * mPersistInfoProvider.lowResScaleFactor()),
438                     (int) (height * mPersistInfoProvider.lowResScaleFactor()),
439                     true /* filter */);
440             swBitmap.recycle();
441 
442             final File lowResFile = mPersistInfoProvider.getLowResolutionBitmapFile(mId, mUserId);
443             try (FileOutputStream lowResFos = new FileOutputStream(lowResFile)) {
444                 lowResBitmap.compress(JPEG, COMPRESS_QUALITY, lowResFos);
445             } catch (IOException e) {
446                 Slog.e(TAG, "Unable to open " + lowResFile + " for persisting.", e);
447                 return false;
448             }
449             lowResBitmap.recycle();
450 
451             return true;
452         }
453 
copyToSwBitmapReadBack()454         private Bitmap copyToSwBitmapReadBack() {
455             final Bitmap bitmap = Bitmap.wrapHardwareBuffer(
456                     mSnapshot.getHardwareBuffer(), mSnapshot.getColorSpace());
457             if (bitmap == null) {
458                 Slog.e(TAG, "Invalid task snapshot hw bitmap");
459                 return null;
460             }
461 
462             final Bitmap swBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false /* isMutable */);
463             if (swBitmap == null) {
464                 Slog.e(TAG, "Bitmap conversion from (config=" + bitmap.getConfig()
465                         + ", isMutable=" + bitmap.isMutable()
466                         + ") to (config=ARGB_8888, isMutable=false) failed.");
467                 return null;
468             }
469             bitmap.recycle();
470             return swBitmap;
471         }
472 
473         /**
474          * Use ImageReader to create the software bitmap, so SkImage won't create an extra texture.
475          */
copyToSwBitmapDirect(int width, int height, int pixelFormat)476         private Bitmap copyToSwBitmapDirect(int width, int height, int pixelFormat) {
477             try (ImageReader ir = ImageReader.newInstance(width, height,
478                     pixelFormat, 1 /* maxImages */)) {
479                 ir.getSurface().attachAndQueueBufferWithColorSpace(mSnapshot.getHardwareBuffer(),
480                         mSnapshot.getColorSpace());
481                 try (Image image = ir.acquireLatestImage()) {
482                     if (image == null || image.getPlaneCount() < 1) {
483                         Slog.e(TAG, "Image reader cannot acquire image");
484                         return null;
485                     }
486 
487                     final Image.Plane[] planes = image.getPlanes();
488                     if (planes.length != 1) {
489                         Slog.e(TAG, "Image reader cannot get plane");
490                         return null;
491                     }
492                     final Image.Plane plane = planes[0];
493                     final int rowPadding = plane.getRowStride() - plane.getPixelStride()
494                             * image.getWidth();
495                     final Bitmap swBitmap = Bitmap.createBitmap(
496                             image.getWidth() + rowPadding / plane.getPixelStride() /* width */,
497                             image.getHeight() /* height */,
498                             pixelFormat == PixelFormat.RGB_565
499                                     ? Bitmap.Config.RGB_565 : Bitmap.Config.ARGB_8888);
500                     swBitmap.copyPixelsFromBuffer(plane.getBuffer());
501                     return swBitmap;
502                 }
503             }
504         }
505 
506         @Override
equals(Object o)507         public boolean equals(Object o) {
508             if (o == null || getClass() != o.getClass()) return false;
509             final StoreWriteQueueItem other = (StoreWriteQueueItem) o;
510             return mId == other.mId && mUserId == other.mUserId
511                     && mPersistInfoProvider == other.mPersistInfoProvider;
512         }
513 
514         @Override
toString()515         public String toString() {
516             return "StoreWriteQueueItem{ID=" + mId + ", UserId=" + mUserId + "}";
517         }
518     }
519 
createDeleteWriteQueueItem(int id, int userId, PersistInfoProvider provider)520     DeleteWriteQueueItem createDeleteWriteQueueItem(int id, int userId,
521             PersistInfoProvider provider) {
522         return new DeleteWriteQueueItem(id, userId, provider);
523     }
524 
525     private class DeleteWriteQueueItem extends WriteQueueItem {
526         private final int mId;
527 
DeleteWriteQueueItem(int id, int userId, PersistInfoProvider provider)528         DeleteWriteQueueItem(int id, int userId, PersistInfoProvider provider) {
529             super(provider, userId);
530             mId = id;
531         }
532 
533         @Override
write()534         void write() {
535             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "DeleteWriteQueueItem");
536             deleteSnapshot(mId, mUserId, mPersistInfoProvider);
537             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
538         }
539 
540         @Override
toString()541         public String toString() {
542             return "DeleteWriteQueueItem{ID=" + mId + ", UserId=" + mUserId + "}";
543         }
544     }
545 
dump(PrintWriter pw, String prefix)546     void dump(PrintWriter pw, String prefix) {
547         final WriteQueueItem[] items;
548         synchronized (mLock) {
549             items = mWriteQueue.toArray(new WriteQueueItem[0]);
550         }
551         if (items.length == 0) {
552             return;
553         }
554         pw.println(prefix + "PersistQueue contains:");
555         for (int i = items.length - 1; i >= 0; --i) {
556             pw.println(prefix + "  " + items[i] + "");
557         }
558     }
559 }
560