Merge "Persist task snapshots to disk"
This commit is contained in:
30
proto/src/task_snapshot.proto
Normal file
30
proto/src/task_snapshot.proto
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package com.android.server.wm;
|
||||
|
||||
option java_package = "com.android.server.wm";
|
||||
option java_outer_classname = "WindowManagerProtos";
|
||||
|
||||
message TaskSnapshotProto {
|
||||
int32 orientation = 1;
|
||||
int32 inset_left = 2;
|
||||
int32 inset_top = 3;
|
||||
int32 inset_right = 4;
|
||||
int32 inset_bottom = 5;
|
||||
}
|
||||
@@ -651,6 +651,8 @@ public class TaskPersister {
|
||||
"omitting from persistentTaskIds task=" + task);
|
||||
}
|
||||
}
|
||||
mService.mWindowManager.removeObsoleteTaskFiles(persistentTaskIds,
|
||||
mRecentTasks.usersWithRecentsLoadedLocked());
|
||||
}
|
||||
removeObsoleteFiles(persistentTaskIds);
|
||||
}
|
||||
|
||||
@@ -788,6 +788,9 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta
|
||||
inRecents = false;
|
||||
mService.notifyTaskPersisterLocked(this, false);
|
||||
}
|
||||
|
||||
// TODO: Use window container controller once tasks are better synced between AM and WM
|
||||
mService.mWindowManager.notifyTaskRemovedFromRecents(taskId, userId);
|
||||
}
|
||||
|
||||
void setTaskToAffiliateWith(TaskRecord taskToAffiliateWith) {
|
||||
|
||||
@@ -22,11 +22,13 @@ import android.annotation.Nullable;
|
||||
import android.app.ActivityManager.StackId;
|
||||
import android.app.ActivityManager.TaskSnapshot;
|
||||
import android.graphics.GraphicBuffer;
|
||||
import android.os.Environment;
|
||||
import android.util.ArraySet;
|
||||
import android.view.WindowManagerPolicy.StartingSurface;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
/**
|
||||
@@ -45,14 +47,21 @@ import java.io.PrintWriter;
|
||||
class TaskSnapshotController {
|
||||
|
||||
private final WindowManagerService mService;
|
||||
private final TaskSnapshotCache mCache = new TaskSnapshotCache();
|
||||
|
||||
private final TaskSnapshotCache mCache = new TaskSnapshotCache();
|
||||
private final TaskSnapshotPersister mPersister = new TaskSnapshotPersister(
|
||||
Environment::getDataSystemCeDirectory);
|
||||
private final TaskSnapshotLoader mLoader = new TaskSnapshotLoader(mPersister);
|
||||
private final ArraySet<Task> mTmpTasks = new ArraySet<>();
|
||||
|
||||
TaskSnapshotController(WindowManagerService service) {
|
||||
mService = service;
|
||||
}
|
||||
|
||||
void systemReady() {
|
||||
mPersister.start();
|
||||
}
|
||||
|
||||
void onTransitionStarting() {
|
||||
if (!ENABLE_TASK_SNAPSHOTS) {
|
||||
return;
|
||||
@@ -69,6 +78,7 @@ class TaskSnapshotController {
|
||||
final TaskSnapshot snapshot = snapshotTask(task);
|
||||
if (snapshot != null) {
|
||||
mCache.putSnapshot(task, snapshot);
|
||||
mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
|
||||
if (task.getController() != null) {
|
||||
task.getController().reportSnapshotChanged(snapshot);
|
||||
}
|
||||
@@ -141,6 +151,17 @@ class TaskSnapshotController {
|
||||
mCache.cleanCache(wtoken);
|
||||
}
|
||||
|
||||
void notifyTaskRemovedFromRecents(int taskId, int userId) {
|
||||
mPersister.onTaskRemovedFromRecents(taskId, userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link TaskSnapshotPersister#removeObsoleteFiles}
|
||||
*/
|
||||
void removeObsoleteTaskFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) {
|
||||
mPersister.removeObsoleteFiles(persistentTaskIds, runningUserIds);
|
||||
}
|
||||
|
||||
void dump(PrintWriter pw, String prefix) {
|
||||
mCache.dump(pw, prefix);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License
|
||||
*/
|
||||
|
||||
package com.android.server.wm;
|
||||
|
||||
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
|
||||
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
|
||||
|
||||
import android.app.ActivityManager.TaskSnapshot;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.Config;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.BitmapFactory.Options;
|
||||
import android.graphics.GraphicBuffer;
|
||||
import android.graphics.Rect;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.server.wm.nano.WindowManagerProtos.TaskSnapshotProto;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
|
||||
/**
|
||||
* Loads a persisted {@link TaskSnapshot} from disk.
|
||||
* <p>
|
||||
* Do not hold the window manager lock when accessing this class.
|
||||
* <p>
|
||||
* Test class: {@link TaskSnapshotPersisterLoaderTest}
|
||||
*/
|
||||
class TaskSnapshotLoader {
|
||||
|
||||
private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotLoader" : TAG_WM;
|
||||
|
||||
private final TaskSnapshotPersister mPersister;
|
||||
|
||||
TaskSnapshotLoader(TaskSnapshotPersister persister) {
|
||||
mPersister = persister;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a task from the disk.
|
||||
* <p>
|
||||
* Do not hold the window manager lock when calling this method, as we directly read data from
|
||||
* disk here, which might be slow.
|
||||
*
|
||||
* @param taskId The id of the task to load.
|
||||
* @param userId The id of the user the task belonged to.
|
||||
* @return The loaded {@link TaskSnapshot} or {@code null} if it couldn't be loaded.
|
||||
*/
|
||||
TaskSnapshot loadTask(int taskId, int userId) {
|
||||
final File protoFile = mPersister.getProtoFile(taskId, userId);
|
||||
final File bitmapFile = mPersister.getBitmapFile(taskId, userId);
|
||||
if (!protoFile.exists() || !bitmapFile.exists()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
final byte[] bytes = Files.readAllBytes(protoFile.toPath());
|
||||
final TaskSnapshotProto proto = TaskSnapshotProto.parseFrom(bytes);
|
||||
final Options options = new Options();
|
||||
options.inPreferredConfig = Config.HARDWARE;
|
||||
final Bitmap bitmap = BitmapFactory.decodeFile(bitmapFile.getPath(), options);
|
||||
if (bitmap == null) {
|
||||
Slog.w(TAG, "Failed to load bitmap: " + bitmapFile.getPath());
|
||||
return null;
|
||||
}
|
||||
final GraphicBuffer buffer = bitmap.createGraphicBufferHandle();
|
||||
if (buffer == null) {
|
||||
Slog.w(TAG, "Failed to retrieve gralloc buffer for bitmap: "
|
||||
+ bitmapFile.getPath());
|
||||
return null;
|
||||
}
|
||||
return new TaskSnapshot(buffer, proto.orientation,
|
||||
new Rect(proto.insetLeft, proto.insetTop, proto.insetRight, proto.insetBottom));
|
||||
} catch (IOException e) {
|
||||
Slog.w(TAG, "Unable to load task snapshot data for taskId=" + taskId);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,335 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License
|
||||
*/
|
||||
|
||||
package com.android.server.wm;
|
||||
|
||||
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
|
||||
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
|
||||
|
||||
import android.annotation.TestApi;
|
||||
import android.app.ActivityManager.TaskSnapshot;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.CompressFormat;
|
||||
import android.os.Process;
|
||||
import android.os.SystemClock;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.os.AtomicFile;
|
||||
import com.android.server.wm.nano.WindowManagerProtos.TaskSnapshotProto;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayDeque;
|
||||
|
||||
/**
|
||||
* Persists {@link TaskSnapshot}s to disk.
|
||||
* <p>
|
||||
* Test class: {@link TaskSnapshotPersisterLoaderTest}
|
||||
*/
|
||||
class TaskSnapshotPersister {
|
||||
|
||||
private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotPersister" : TAG_WM;
|
||||
private static final String SNAPSHOTS_DIRNAME = "snapshots";
|
||||
private static final long DELAY_MS = 100;
|
||||
private static final String PROTO_EXTENSION = ".proto";
|
||||
private static final String BITMAP_EXTENSION = ".png";
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private final ArrayDeque<WriteQueueItem> mWriteQueue = new ArrayDeque<>();
|
||||
@GuardedBy("mLock")
|
||||
private boolean mQueueIdling;
|
||||
private boolean mStarted;
|
||||
private final Object mLock = new Object();
|
||||
private final DirectoryResolver mDirectoryResolver;
|
||||
|
||||
/**
|
||||
* The list of ids of the tasks that have been persisted since {@link #removeObsoleteFiles} was
|
||||
* called.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
private final ArraySet<Integer> mPersistedTaskIdsSinceLastRemoveObsolete = new ArraySet<>();
|
||||
|
||||
TaskSnapshotPersister(DirectoryResolver resolver) {
|
||||
mDirectoryResolver = resolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts persisting.
|
||||
*/
|
||||
void start() {
|
||||
if (!mStarted) {
|
||||
mStarted = true;
|
||||
mPersister.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists a snapshot of a task to disk.
|
||||
*
|
||||
* @param taskId The id of the task that needs to be persisted.
|
||||
* @param userId The id of the user this tasks belongs to.
|
||||
* @param snapshot The snapshot to persist.
|
||||
*/
|
||||
void persistSnapshot(int taskId, int userId, TaskSnapshot snapshot) {
|
||||
synchronized (mLock) {
|
||||
mPersistedTaskIdsSinceLastRemoveObsolete.add(taskId);
|
||||
sendToQueueLocked(new StoreWriteQueueItem(taskId, userId, snapshot));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callend when a task has been removed.
|
||||
*
|
||||
* @param taskId The id of task that has been removed.
|
||||
* @param userId The id of the user the task belonged to.
|
||||
*/
|
||||
void onTaskRemovedFromRecents(int taskId, int userId) {
|
||||
synchronized (mLock) {
|
||||
mPersistedTaskIdsSinceLastRemoveObsolete.remove(taskId);
|
||||
sendToQueueLocked(new DeleteWriteQueueItem(taskId, userId));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* In case a write/delete operation was lost because the system crashed, this makes sure to
|
||||
* clean up the directory to remove obsolete files.
|
||||
*
|
||||
* @param persistentTaskIds A set of task ids that exist in our in-memory model.
|
||||
* @param runningUserIds The ids of the list of users that have tasks loaded in our in-memory
|
||||
* model.
|
||||
*/
|
||||
void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) {
|
||||
synchronized (mLock) {
|
||||
mPersistedTaskIdsSinceLastRemoveObsolete.clear();
|
||||
sendToQueueLocked(new RemoveObsoleteFilesQueueItem(persistentTaskIds, runningUserIds));
|
||||
}
|
||||
}
|
||||
|
||||
@TestApi
|
||||
void waitForQueueEmpty() {
|
||||
while (true) {
|
||||
synchronized (mLock) {
|
||||
if (mWriteQueue.isEmpty() && mQueueIdling) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
SystemClock.sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void sendToQueueLocked(WriteQueueItem item) {
|
||||
mWriteQueue.offer(item);
|
||||
mLock.notifyAll();
|
||||
}
|
||||
|
||||
private File getDirectory(int userId) {
|
||||
return new File(mDirectoryResolver.getSystemDirectoryForUser(userId), SNAPSHOTS_DIRNAME);
|
||||
}
|
||||
|
||||
File getProtoFile(int taskId, int userId) {
|
||||
return new File(getDirectory(userId), taskId + PROTO_EXTENSION);
|
||||
}
|
||||
|
||||
File getBitmapFile(int taskId, int userId) {
|
||||
return new File(getDirectory(userId), taskId + BITMAP_EXTENSION);
|
||||
}
|
||||
|
||||
private boolean createDirectory(int userId) {
|
||||
final File dir = getDirectory(userId);
|
||||
return dir.exists() || dir.mkdirs();
|
||||
}
|
||||
|
||||
private void deleteSnapshot(int taskId, int userId) {
|
||||
final File protoFile = getProtoFile(taskId, userId);
|
||||
final File bitmapFile = getBitmapFile(taskId, userId);
|
||||
protoFile.delete();
|
||||
bitmapFile.delete();
|
||||
}
|
||||
|
||||
interface DirectoryResolver {
|
||||
File getSystemDirectoryForUser(int userId);
|
||||
}
|
||||
|
||||
private Thread mPersister = new Thread("TaskSnapshotPersister") {
|
||||
public void run() {
|
||||
android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
|
||||
while (true) {
|
||||
WriteQueueItem next;
|
||||
synchronized (mLock) {
|
||||
next = mWriteQueue.poll();
|
||||
}
|
||||
if (next != null) {
|
||||
next.write();
|
||||
SystemClock.sleep(DELAY_MS);
|
||||
}
|
||||
synchronized (mLock) {
|
||||
if (!mWriteQueue.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
mQueueIdling = true;
|
||||
mLock.wait();
|
||||
mQueueIdling = false;
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private abstract class WriteQueueItem {
|
||||
abstract void write();
|
||||
}
|
||||
|
||||
private class StoreWriteQueueItem extends WriteQueueItem {
|
||||
private final int mTaskId;
|
||||
private final int mUserId;
|
||||
private final TaskSnapshot mSnapshot;
|
||||
|
||||
StoreWriteQueueItem(int taskId, int userId, TaskSnapshot snapshot) {
|
||||
mTaskId = taskId;
|
||||
mUserId = userId;
|
||||
mSnapshot = snapshot;
|
||||
}
|
||||
|
||||
@Override
|
||||
void write() {
|
||||
if (!createDirectory(mUserId)) {
|
||||
Slog.e(TAG, "Unable to create snapshot directory for user dir="
|
||||
+ getDirectory(mUserId));
|
||||
}
|
||||
boolean failed = false;
|
||||
if (!writeProto()) {
|
||||
failed = true;
|
||||
}
|
||||
if (!writeBuffer()) {
|
||||
writeBuffer();
|
||||
failed = true;
|
||||
}
|
||||
if (failed) {
|
||||
deleteSnapshot(mTaskId, mUserId);
|
||||
}
|
||||
}
|
||||
|
||||
boolean writeProto() {
|
||||
final TaskSnapshotProto proto = new TaskSnapshotProto();
|
||||
proto.orientation = mSnapshot.getOrientation();
|
||||
proto.insetLeft = mSnapshot.getContentInsets().left;
|
||||
proto.insetTop = mSnapshot.getContentInsets().top;
|
||||
proto.insetRight = mSnapshot.getContentInsets().right;
|
||||
proto.insetBottom = mSnapshot.getContentInsets().bottom;
|
||||
final byte[] bytes = TaskSnapshotProto.toByteArray(proto);
|
||||
final File file = getProtoFile(mTaskId, mUserId);
|
||||
final AtomicFile atomicFile = new AtomicFile(file);
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = atomicFile.startWrite();
|
||||
fos.write(bytes);
|
||||
atomicFile.finishWrite(fos);
|
||||
} catch (IOException e) {
|
||||
atomicFile.failWrite(fos);
|
||||
Slog.e(TAG, "Unable to open " + file + " for persisting. " + e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean writeBuffer() {
|
||||
final File file = getBitmapFile(mTaskId, mUserId);
|
||||
final Bitmap bitmap = Bitmap.createHardwareBitmap(mSnapshot.getSnapshot());
|
||||
try {
|
||||
FileOutputStream fos = new FileOutputStream(file);
|
||||
bitmap.compress(CompressFormat.PNG, 0 /* quality */, fos);
|
||||
fos.close();
|
||||
} catch (IOException e) {
|
||||
Slog.e(TAG, "Unable to open " + file + " for persisting. " + e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private class DeleteWriteQueueItem extends WriteQueueItem {
|
||||
private final int mTaskId;
|
||||
private final int mUserId;
|
||||
|
||||
DeleteWriteQueueItem(int taskId, int userId) {
|
||||
mTaskId = taskId;
|
||||
mUserId = userId;
|
||||
}
|
||||
|
||||
@Override
|
||||
void write() {
|
||||
deleteSnapshot(mTaskId, mUserId);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
class RemoveObsoleteFilesQueueItem extends WriteQueueItem {
|
||||
private final ArraySet<Integer> mPersistentTaskIds;
|
||||
private final int[] mRunningUserIds;
|
||||
|
||||
@VisibleForTesting
|
||||
RemoveObsoleteFilesQueueItem(ArraySet<Integer> persistentTaskIds,
|
||||
int[] runningUserIds) {
|
||||
mPersistentTaskIds = persistentTaskIds;
|
||||
mRunningUserIds = runningUserIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
void write() {
|
||||
final ArraySet<Integer> newPersistedTaskIds;
|
||||
synchronized (mLock) {
|
||||
newPersistedTaskIds = new ArraySet<>(mPersistedTaskIdsSinceLastRemoveObsolete);
|
||||
}
|
||||
for (int userId : mRunningUserIds) {
|
||||
final File dir = getDirectory(userId);
|
||||
final String[] files = dir.list();
|
||||
if (files == null) {
|
||||
continue;
|
||||
}
|
||||
for (String file : files) {
|
||||
final int taskId = getTaskId(file);
|
||||
if (!mPersistentTaskIds.contains(taskId)
|
||||
&& !newPersistedTaskIds.contains(taskId)) {
|
||||
new File(dir, file).delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
int getTaskId(String fileName) {
|
||||
if (!fileName.endsWith(PROTO_EXTENSION) && !fileName.endsWith(BITMAP_EXTENSION)) {
|
||||
return -1;
|
||||
}
|
||||
final int end = fileName.lastIndexOf('.');
|
||||
if (end == -1) {
|
||||
return -1;
|
||||
}
|
||||
try {
|
||||
return Integer.parseInt(fileName.substring(0, end));
|
||||
} catch (NumberFormatException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3901,6 +3901,20 @@ public class WindowManagerService extends IWindowManager.Stub
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* In case a task write/delete operation was lost because the system crashed, this makes sure to
|
||||
* clean up the directory to remove obsolete files.
|
||||
*
|
||||
* @param persistentTaskIds A set of task ids that exist in our in-memory model.
|
||||
* @param runningUserIds The ids of the list of users that have tasks loaded in our in-memory
|
||||
* model.
|
||||
*/
|
||||
public void removeObsoleteTaskFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) {
|
||||
synchronized (mWindowMap) {
|
||||
mTaskSnapshotController.removeObsoleteTaskFiles(persistentTaskIds, runningUserIds);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a snapshot of the screen. In landscape mode this grabs the whole screen.
|
||||
* In portrait mode, it grabs the full screenshot.
|
||||
@@ -5346,6 +5360,7 @@ public class WindowManagerService extends IWindowManager.Stub
|
||||
|
||||
public void systemReady() {
|
||||
mPolicy.systemReady();
|
||||
mTaskSnapshotController.systemReady();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
@@ -6985,6 +7000,18 @@ public class WindowManagerService extends IWindowManager.Stub
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a task has been removed from the recent tasks list.
|
||||
* <p>
|
||||
* Note: This doesn't go through {@link TaskWindowContainerController} yet as the window
|
||||
* container may not exist when this happens.
|
||||
*/
|
||||
public void notifyTaskRemovedFromRecents(int taskId, int userId) {
|
||||
synchronized (mWindowMap) {
|
||||
mTaskSnapshotController.notifyTaskRemovedFromRecents(taskId, userId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDockedDividerInsetsLw() {
|
||||
return getDefaultDisplayContentLocked().getDockedDividerController().getContentInsets();
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License
|
||||
*/
|
||||
|
||||
package com.android.server.wm;
|
||||
|
||||
import static android.graphics.GraphicBuffer.USAGE_HW_TEXTURE;
|
||||
import static android.graphics.GraphicBuffer.USAGE_SW_READ_RARELY;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import android.app.ActivityManager.TaskSnapshot;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.GraphicBuffer;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.os.SystemClock;
|
||||
import android.os.UserManager;
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.filters.MediumTest;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.util.ArraySet;
|
||||
|
||||
import com.android.internal.util.Predicate;
|
||||
import com.android.server.wm.TaskSnapshotPersister.RemoveObsoleteFilesQueueItem;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Test class for {@link TaskSnapshotPersister} and {@link TaskSnapshotLoader}
|
||||
*
|
||||
* runtest frameworks-services -c com.android.server.wm.TaskSnapshotPersisterLoaderTest
|
||||
*/
|
||||
@MediumTest
|
||||
@Presubmit
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class TaskSnapshotPersisterLoaderTest {
|
||||
|
||||
private static final String TEST_USER_NAME = "TaskSnapshotPersisterTest User";
|
||||
private static final Rect TEST_INSETS = new Rect(10, 20, 30, 40);
|
||||
|
||||
private TaskSnapshotPersister mPersister;
|
||||
private TaskSnapshotLoader mLoader;
|
||||
private static int sTestUserId;
|
||||
private static File sFilesDir;
|
||||
private static UserManager sUserManager;
|
||||
|
||||
@BeforeClass
|
||||
public static void setUpUser() {
|
||||
sUserManager = UserManager.get(InstrumentationRegistry.getContext());
|
||||
sTestUserId = createUser(TEST_USER_NAME, 0);
|
||||
sFilesDir = InstrumentationRegistry.getContext().getFilesDir();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDownUser() {
|
||||
removeUser(sTestUserId);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mPersister = new TaskSnapshotPersister(
|
||||
userId -> sFilesDir);
|
||||
mLoader = new TaskSnapshotLoader(mPersister);
|
||||
mPersister.start();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
cleanDirectory();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPersistAndLoadSnapshot() {
|
||||
mPersister.persistSnapshot(1 , sTestUserId, createSnapshot());
|
||||
mPersister.waitForQueueEmpty();
|
||||
final File[] files = new File[] { new File(sFilesDir.getPath() + "/snapshots/1.proto"),
|
||||
new File(sFilesDir.getPath() + "/snapshots/1.png") };
|
||||
assertTrueForFiles(files, File::exists, " must exist");
|
||||
final TaskSnapshot snapshot = mLoader.loadTask(1, sTestUserId);
|
||||
assertNotNull(snapshot);
|
||||
assertEquals(TEST_INSETS, snapshot.getContentInsets());
|
||||
assertNotNull(snapshot.getSnapshot());
|
||||
assertEquals(Configuration.ORIENTATION_PORTRAIT, snapshot.getOrientation());
|
||||
}
|
||||
|
||||
private void assertTrueForFiles(File[] files, Predicate<File> predicate, String message) {
|
||||
for (File file : files) {
|
||||
assertTrue(file.getName() + message, predicate.apply(file));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTaskRemovedFromRecents() {
|
||||
mPersister.persistSnapshot(1, sTestUserId, createSnapshot());
|
||||
mPersister.onTaskRemovedFromRecents(1, sTestUserId);
|
||||
mPersister.waitForQueueEmpty();
|
||||
assertFalse(new File(sFilesDir.getPath() + "/snapshots/1.proto").exists());
|
||||
assertFalse(new File(sFilesDir.getPath() + "/snapshots/1.png").exists());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that persisting a couple of snapshots is being throttled.
|
||||
*/
|
||||
@Test
|
||||
public void testThrottling() {
|
||||
long ms = SystemClock.elapsedRealtime();
|
||||
mPersister.persistSnapshot(1, sTestUserId, createSnapshot());
|
||||
mPersister.persistSnapshot(2, sTestUserId, createSnapshot());
|
||||
mPersister.persistSnapshot(3, sTestUserId, createSnapshot());
|
||||
mPersister.persistSnapshot(4, sTestUserId, createSnapshot());
|
||||
mPersister.persistSnapshot(5, sTestUserId, createSnapshot());
|
||||
mPersister.persistSnapshot(6, sTestUserId, createSnapshot());
|
||||
mPersister.waitForQueueEmpty();
|
||||
assertTrue(SystemClock.elapsedRealtime() - ms > 500);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTaskId() {
|
||||
RemoveObsoleteFilesQueueItem removeObsoleteFilesQueueItem =
|
||||
mPersister.new RemoveObsoleteFilesQueueItem(new ArraySet<>(), new int[] {});
|
||||
assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("blablablulp"));
|
||||
assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("nothing.err"));
|
||||
assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("/invalid/"));
|
||||
assertEquals(12, removeObsoleteFilesQueueItem.getTaskId("12.png"));
|
||||
assertEquals(12, removeObsoleteFilesQueueItem.getTaskId("12.proto"));
|
||||
assertEquals(1, removeObsoleteFilesQueueItem.getTaskId("1.png"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveObsoleteFiles() {
|
||||
mPersister.persistSnapshot(1, sTestUserId, createSnapshot());
|
||||
mPersister.persistSnapshot(2, sTestUserId, createSnapshot());
|
||||
final ArraySet<Integer> taskIds = new ArraySet<>();
|
||||
taskIds.add(1);
|
||||
mPersister.removeObsoleteFiles(taskIds, new int[] { sTestUserId });
|
||||
mPersister.waitForQueueEmpty();
|
||||
final File[] existsFiles = new File[] {
|
||||
new File(sFilesDir.getPath() + "/snapshots/1.proto"),
|
||||
new File(sFilesDir.getPath() + "/snapshots/1.png") };
|
||||
final File[] nonExistsFiles = new File[] {
|
||||
new File(sFilesDir.getPath() + "/snapshots/2.proto"),
|
||||
new File(sFilesDir.getPath() + "/snapshots/2.png") };
|
||||
assertTrueForFiles(existsFiles, File::exists, " must exist");
|
||||
assertTrueForFiles(nonExistsFiles, file -> !file.exists(), " must not exist");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveObsoleteFiles_addedOneInTheMeantime() {
|
||||
mPersister.persistSnapshot(1, sTestUserId, createSnapshot());
|
||||
final ArraySet<Integer> taskIds = new ArraySet<>();
|
||||
taskIds.add(1);
|
||||
mPersister.removeObsoleteFiles(taskIds, new int[] { sTestUserId });
|
||||
mPersister.persistSnapshot(2, sTestUserId, createSnapshot());
|
||||
mPersister.waitForQueueEmpty();
|
||||
final File[] existsFiles = new File[] {
|
||||
new File(sFilesDir.getPath() + "/snapshots/1.proto"),
|
||||
new File(sFilesDir.getPath() + "/snapshots/1.png"),
|
||||
new File(sFilesDir.getPath() + "/snapshots/2.proto"),
|
||||
new File(sFilesDir.getPath() + "/snapshots/2.png") };
|
||||
assertTrueForFiles(existsFiles, File::exists, " must exist");
|
||||
}
|
||||
|
||||
private TaskSnapshot createSnapshot() {
|
||||
GraphicBuffer buffer = GraphicBuffer.create(100, 100, PixelFormat.RGBA_8888,
|
||||
USAGE_HW_TEXTURE | USAGE_SW_READ_RARELY | USAGE_SW_READ_RARELY);
|
||||
Canvas c = buffer.lockCanvas();
|
||||
c.drawColor(Color.RED);
|
||||
buffer.unlockCanvasAndPost(c);
|
||||
return new TaskSnapshot(buffer, Configuration.ORIENTATION_PORTRAIT, TEST_INSETS);
|
||||
}
|
||||
|
||||
private static int createUser(String name, int flags) {
|
||||
UserInfo user = sUserManager.createUser(name, flags);
|
||||
if (user == null) {
|
||||
Assert.fail("Error while creating the test user: " + TEST_USER_NAME);
|
||||
}
|
||||
return user.id;
|
||||
}
|
||||
|
||||
private static void removeUser(int userId) {
|
||||
if (!sUserManager.removeUser(userId)) {
|
||||
Assert.fail("Error while removing the test user: " + TEST_USER_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanDirectory() {
|
||||
for (File file : new File(sFilesDir, "snapshots").listFiles()) {
|
||||
if (!file.isDirectory()) {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user