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:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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}.
|
||||
*
|
||||
|
||||
24
core/java/android/window/IDumpCallback.aidl
Normal file
24
core/java/android/window/IDumpCallback.aidl
Normal 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);
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user