From 3c492d8644ab4b4896244084abfe11ce6e47c6a6 Mon Sep 17 00:00:00 2001 From: Stefan Andonian Date: Wed, 22 Mar 2023 23:33:03 +0000 Subject: [PATCH] Write ViewCapture data to WmTrace when WindowManager dumps. This allows apps like the Launcher to write ViewCapture data to the wmtrace directory so it can be shown in their UI. Normally, Launcher doesn't have the correct file permissions. Also, Launcher's dump method is called after the wmtrace dir is written to the bug report, so we need to dump sooner via a callback method invoked inside WindowManager. Bug: 224595733 Test: Latency tested this change and verified that a bugreport generated the file properly, moved it to the wmtrace directory, and was picked up properly by the go/web-hv tool. Change-Id: I9dc8e61070d2470354b79c1758103f9b2b00ac36 --- .../android/content/pm/ILauncherApps.aidl | 7 ++ .../java/android/content/pm/LauncherApps.java | 28 ++++++ core/java/android/window/IDumpCallback.aidl | 24 ++++++ .../server/pm/LauncherAppsService.java | 86 +++++++++++++++++++ 4 files changed, 145 insertions(+) create mode 100644 core/java/android/window/IDumpCallback.aidl diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl index 96a42e24bc1a5..563ed7dd6e7a4 100644 --- a/core/java/android/content/pm/ILauncherApps.aidl +++ b/core/java/android/content/pm/ILauncherApps.aidl @@ -38,6 +38,7 @@ import android.graphics.Rect; import android.os.Bundle; import android.os.UserHandle; import android.os.ParcelFileDescriptor; +import android.window.IDumpCallback; import com.android.internal.infra.AndroidFuture; @@ -116,4 +117,10 @@ interface ILauncherApps { String getShortcutIconUri(String callingPackage, String packageName, String shortcutId, int userId); Map getActivityOverrides(String callingPackage, int userId); + + /** Register a callback to be called right before the wmtrace data is moved to the bugreport. */ + void registerDumpCallback(IDumpCallback cb); + + /** Unregister a callback, so that it won't be called when LauncherApps dumps. */ + void unRegisterDumpCallback(IDumpCallback cb); } diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index 8989006a7e831..27270d9f378f8 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -17,6 +17,7 @@ package android.content.pm; import static android.Manifest.permission; +import static android.Manifest.permission.READ_FRAME_BUFFER; import android.annotation.CallbackExecutor; import android.annotation.IntDef; @@ -68,6 +69,7 @@ import android.util.ArrayMap; import android.util.DisplayMetrics; import android.util.Log; import android.util.Pair; +import android.window.IDumpCallback; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.infra.AndroidFuture; @@ -1171,6 +1173,32 @@ public class LauncherApps { return shortcuts; } + /** + * Register a callback to be called right before the wmtrace data is moved to the bugreport. + * @hide + */ + @RequiresPermission(READ_FRAME_BUFFER) + public void registerDumpCallback(IDumpCallback cb) { + try { + mService.registerDumpCallback(cb); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + + /** + * Unregister a callback, so that it won't be called when LauncherApps dumps. + * @hide + */ + @RequiresPermission(READ_FRAME_BUFFER) + public void unRegisterDumpCallback(IDumpCallback cb) { + try { + mService.unRegisterDumpCallback(cb); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + /** * Returns {@link ShortcutInfo}s that match {@code query}. * diff --git a/core/java/android/window/IDumpCallback.aidl b/core/java/android/window/IDumpCallback.aidl new file mode 100644 index 0000000000000..4c825d43add1c --- /dev/null +++ b/core/java/android/window/IDumpCallback.aidl @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2023 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 android.window; + +/** + * Callback for processes which need to feed data to another process when it dumps. + * @hide + */ + interface IDumpCallback { + oneway void onDump(in ParcelFileDescriptor outFd); + } \ No newline at end of file diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 84bee50b77b0b..402fb30437b00 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -16,6 +16,7 @@ package com.android.server.pm; +import static android.Manifest.permission.READ_FRAME_BUFFER; import static android.app.ActivityOptions.KEY_SPLASH_SCREEN_THEME; import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED; import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; @@ -25,6 +26,8 @@ import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION; +import static android.content.PermissionChecker.PERMISSION_GRANTED; +import static android.content.PermissionChecker.checkCallingOrSelfPermissionForPreflight; import static android.content.pm.LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS; import static android.content.pm.LauncherApps.FLAG_CACHE_NOTIFICATION_SHORTCUTS; import static android.content.pm.LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS; @@ -32,6 +35,7 @@ import static android.content.pm.LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS; import android.annotation.AppIdInt; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; @@ -90,6 +94,7 @@ import android.util.ArrayMap; import android.util.Log; import android.util.Pair; import android.util.Slog; +import android.window.IDumpCallback; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -104,6 +109,15 @@ import com.android.server.SystemService; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.wm.ActivityTaskManagerInternal; +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.PosixFilePermission; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -111,6 +125,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.concurrent.ExecutionException; /** @@ -118,6 +133,15 @@ import java.util.concurrent.ExecutionException; * managed profiles. */ public class LauncherAppsService extends SystemService { + private static final String WM_TRACE_DIR = "/data/misc/wmtrace/"; + private static final String VC_FILE_SUFFIX = ".vc"; + + private static final Set WM_TRACE_FILE_PERMISSIONS = Set.of( + PosixFilePermission.OWNER_WRITE, + PosixFilePermission.GROUP_READ, + PosixFilePermission.OTHERS_READ, + PosixFilePermission.OWNER_READ + ); private final LauncherAppsImpl mLauncherAppsImpl; @@ -191,6 +215,8 @@ public class LauncherAppsService extends SystemService { final LauncherAppsServiceInternal mInternal; + private RemoteCallbackList mDumpCallbacks = new RemoteCallbackList<>(); + public LauncherAppsImpl(Context context) { mContext = context; mIPM = AppGlobals.getPackageManager(); @@ -1431,6 +1457,66 @@ public class LauncherAppsService extends SystemService { getActivityOptionsForLauncher(opts), user.getIdentifier()); } + + /** + * Using a pipe, outputs view capture data to the wmtrace dir + */ + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + super.dump(fd, pw, args); + + // Before the wmtrace directory is picked up by dumpstate service, some processes need + // to write their data to that location. They can do that via these dumpCallbacks. + int i = mDumpCallbacks.beginBroadcast(); + while (i > 0) { + i--; + dumpDataToWmTrace((String) mDumpCallbacks.getBroadcastCookie(i) + "_" + i, + mDumpCallbacks.getBroadcastItem(i)); + } + mDumpCallbacks.finishBroadcast(); + } + + private void dumpDataToWmTrace(String name, IDumpCallback cb) { + ParcelFileDescriptor[] pipe; + try { + pipe = ParcelFileDescriptor.createPipe(); + cb.onDump(pipe[1]); + } catch (IOException | RemoteException e) { + Log.d(TAG, "failed to pipe view capture data", e); + return; + } + + Path path = Paths.get(WM_TRACE_DIR + Paths.get(name + VC_FILE_SUFFIX).getFileName()); + try (InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(pipe[0])) { + Files.copy(is, path, StandardCopyOption.REPLACE_EXISTING); + Files.setPosixFilePermissions(path, WM_TRACE_FILE_PERMISSIONS); + } catch (IOException e) { + Log.d(TAG, "failed to write data to file in wmtrace dir", e); + } + } + + @RequiresPermission(READ_FRAME_BUFFER) + @Override + public void registerDumpCallback(IDumpCallback cb) { + int status = checkCallingOrSelfPermissionForPreflight(mContext, READ_FRAME_BUFFER); + if (PERMISSION_GRANTED == status) { + String name = mContext.getPackageManager().getNameForUid(Binder.getCallingUid()); + mDumpCallbacks.register(cb, name); + } else { + Log.w(TAG, "caller lacks permissions to registerDumpCallback"); + } + } + + @RequiresPermission(READ_FRAME_BUFFER) + @Override + public void unRegisterDumpCallback(IDumpCallback cb) { + int status = checkCallingOrSelfPermissionForPreflight(mContext, READ_FRAME_BUFFER); + if (PERMISSION_GRANTED == status) { + mDumpCallbacks.unregister(cb); + } else { + Log.w(TAG, "caller lacks permissions to unRegisterDumpCallback"); + } + } + /** Checks if user is a profile of or same as listeningUser. * and the user is enabled. */ private boolean isEnabledProfileOf(UserHandle listeningUser, UserHandle user,