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
This commit is contained in:
Stefan Andonian
2023-03-22 23:33:03 +00:00
parent a87fa51a6a
commit 3c492d8644
4 changed files with 145 additions and 0 deletions

View File

@@ -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<String, LauncherActivityInfoInternal> 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);
}

View File

@@ -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}.
*

View File

@@ -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);
}

View File

@@ -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<PosixFilePermission> 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<IDumpCallback> 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,