From 353e64eef51db68d0402a372c2adb5ecf32926c3 Mon Sep 17 00:00:00 2001 From: Jeremy Meyer Date: Thu, 16 Dec 2021 19:07:41 +0000 Subject: [PATCH] Add dumping of Resource paths and resource history Sample of results: history 0 class=class android.content.res.Resources resourcesImpl class=class android.content.res.ResourcesImpl assets class=class android.content.res.AssetManager apkAssets= 0 class=class android.content.res.ApkAssets debugName= and /system/framework/framework-res.apk assetPath=/system/framework/framework-res.apk 1 class=class android.content.res.ApkAssets debugName=/product/overlay/GoogleConfigOverlay.apk assetPath=/product/overlay/GoogleConfigOverlay.apk Fixes: 206615535 Test: Called from custom app to confirm format and info Change-Id: I19a9fc60b61fff86e5b41a2d789d6dde8acf51a3 --- core/java/android/app/ActivityThread.java | 41 +++++++ core/java/android/app/IApplicationThread.aidl | 1 + core/java/android/content/Context.java | 9 ++ core/java/android/content/res/ApkAssets.java | 7 ++ .../android/content/res/AssetManager.java | 10 ++ .../content/res/IResourcesManager.aidl | 30 ++++++ core/java/android/content/res/Resources.java | 36 +++++++ .../android/content/res/ResourcesImpl.java | 7 ++ .../TransactionParcelTests.java | 4 + .../server/am/ActivityManagerService.java | 57 ++++++++++ .../resources/ResourcesManagerService.java | 102 ++++++++++++++++++ .../ResourcesManagerShellCommand.java | 94 ++++++++++++++++ .../java/com/android/server/SystemServer.java | 8 ++ 13 files changed, 406 insertions(+) create mode 100644 core/java/android/content/res/IResourcesManager.aidl create mode 100644 services/core/java/com/android/server/resources/ResourcesManagerService.java create mode 100644 services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 3b2176ee10a25..0d3967fa3fcc8 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -1004,6 +1004,11 @@ public final class ActivityThread extends ClientTransactionHandler RemoteCallback finishCallback; } + static final class DumpResourcesData { + public ParcelFileDescriptor fd; + public RemoteCallback finishCallback; + } + static final class UpdateCompatibilityData { String pkg; CompatibilityInfo info; @@ -1315,6 +1320,20 @@ public final class ActivityThread extends ClientTransactionHandler sendMessage(H.SCHEDULE_CRASH, args, typeId); } + @Override + public void dumpResources(ParcelFileDescriptor fd, RemoteCallback callback) { + DumpResourcesData data = new DumpResourcesData(); + try { + data.fd = fd.dup(); + data.finishCallback = callback; + sendMessage(H.DUMP_RESOURCES, data, 0, 0, false /*async*/); + } catch (IOException e) { + Slog.w(TAG, "dumpResources failed", e); + } finally { + IoUtils.closeQuietly(fd); + } + } + public void dumpActivity(ParcelFileDescriptor pfd, IBinder activitytoken, String prefix, String[] args) { DumpComponentInfo data = new DumpComponentInfo(); @@ -2038,6 +2057,7 @@ public final class ActivityThread extends ClientTransactionHandler public static final int UPDATE_UI_TRANSLATION_STATE = 163; public static final int SET_CONTENT_CAPTURE_OPTIONS_CALLBACK = 164; public static final int DUMP_GFXINFO = 165; + public static final int DUMP_RESOURCES = 166; public static final int INSTRUMENT_WITHOUT_RESTART = 170; public static final int FINISH_INSTRUMENTATION_WITHOUT_RESTART = 171; @@ -2091,6 +2111,7 @@ public final class ActivityThread extends ClientTransactionHandler case INSTRUMENT_WITHOUT_RESTART: return "INSTRUMENT_WITHOUT_RESTART"; case FINISH_INSTRUMENTATION_WITHOUT_RESTART: return "FINISH_INSTRUMENTATION_WITHOUT_RESTART"; + case DUMP_RESOURCES: return "DUMP_RESOURCES"; } } return Integer.toString(code); @@ -2206,6 +2227,9 @@ public final class ActivityThread extends ClientTransactionHandler case DUMP_HEAP: handleDumpHeap((DumpHeapData) msg.obj); break; + case DUMP_RESOURCES: + handleDumpResources((DumpResourcesData) msg.obj); + break; case DUMP_ACTIVITY: handleDumpActivity((DumpComponentInfo)msg.obj); break; @@ -4584,6 +4608,23 @@ public final class ActivityThread extends ClientTransactionHandler } } + private void handleDumpResources(DumpResourcesData info) { + final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); + try { + PrintWriter pw = new FastPrintWriter(new FileOutputStream( + info.fd.getFileDescriptor())); + + Resources.dumpHistory(pw, ""); + pw.flush(); + if (info.finishCallback != null) { + info.finishCallback.sendResult(null); + } + } finally { + IoUtils.closeQuietly(info.fd); + StrictMode.setThreadPolicy(oldPolicy); + } + } + private void handleDumpActivity(DumpComponentInfo info) { final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); try { diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl index 1714229486e4a..77657d58cc4c1 100644 --- a/core/java/android/app/IApplicationThread.aidl +++ b/core/java/android/app/IApplicationThread.aidl @@ -113,6 +113,7 @@ oneway interface IApplicationThread { in ParcelFileDescriptor fd, in RemoteCallback finishCallback); void dumpActivity(in ParcelFileDescriptor fd, IBinder servicetoken, in String prefix, in String[] args); + void dumpResources(in ParcelFileDescriptor fd, in RemoteCallback finishCallback); void clearDnsCache(); void updateHttpProxy(); void setCoreSettings(in Bundle coreSettings); diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 4b4e00855ac1b..e0aa656c43b66 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -5637,6 +5637,15 @@ public abstract class Context { */ public static final String OVERLAY_SERVICE = "overlay"; + /** + * Use with {@link #getSystemService(String)} to manage resources. + * + * @see #getSystemService(String) + * @see com.android.server.resources.ResourcesManagerService + * @hide + */ + public static final String RESOURCES_SERVICE = "resources"; + /** * Use with {@link #getSystemService(String)} to retrieve a * {android.os.IIdmap2} for managing idmap files (used by overlay diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java index 6fd2d05ad1357..7a5ac8ede4a42 100644 --- a/core/java/android/content/res/ApkAssets.java +++ b/core/java/android/content/res/ApkAssets.java @@ -28,6 +28,7 @@ import com.android.internal.annotations.GuardedBy; import java.io.FileDescriptor; import java.io.IOException; +import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Objects; @@ -438,6 +439,12 @@ public final class ApkAssets { } } + void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "class=" + getClass()); + pw.println(prefix + "debugName=" + getDebugName()); + pw.println(prefix + "assetPath=" + getAssetPath()); + } + private static native long nativeLoad(@FormatType int format, @NonNull String path, @PropertyFlags int flags, @Nullable AssetsProvider asset) throws IOException; private static native long nativeLoadEmpty(@PropertyFlags int flags, diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index bfd9fd0a4ef9c..a05f5c927b297 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -43,6 +43,7 @@ import java.io.FileDescriptor; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.io.PrintWriter; import java.lang.ref.Reference; import java.util.ArrayList; import java.util.Arrays; @@ -1531,6 +1532,15 @@ public final class AssetManager implements AutoCloseable { } } + synchronized void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "class=" + getClass()); + pw.println(prefix + "apkAssets="); + for (int i = 0; i < mApkAssets.length; i++) { + pw.println(prefix + i); + mApkAssets[i].dump(pw, prefix + " "); + } + } + // AssetManager setup native methods. private static native long nativeCreate(); private static native void nativeDestroy(long ptr); diff --git a/core/java/android/content/res/IResourcesManager.aidl b/core/java/android/content/res/IResourcesManager.aidl new file mode 100644 index 0000000000000..d1373788f1c58 --- /dev/null +++ b/core/java/android/content/res/IResourcesManager.aidl @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 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.content.res; + +import android.os.RemoteCallback; + +/** + * Api for getting information about resources. + * + * {@hide} + */ +interface IResourcesManager { + boolean dumpResources(in String process, + in ParcelFileDescriptor fd, + in RemoteCallback finishCallback); +} \ No newline at end of file diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 5fd0d841f0e38..ebef0535f0779 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -53,6 +53,7 @@ import android.graphics.drawable.Drawable.ConstantState; import android.graphics.drawable.DrawableInflater; import android.os.Build; import android.os.Bundle; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.AttributeSet; import android.util.DisplayMetrics; @@ -78,11 +79,15 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.io.InputStream; +import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; /** * Class for accessing an application's resources. This sits on top of the @@ -172,6 +177,11 @@ public class Resources { private int mBaseApkAssetsSize; + /** @hide */ + private static Set sResourcesHistory = Collections.synchronizedSet( + Collections.newSetFromMap( + new WeakHashMap<>())); + /** * Returns the most appropriate default theme for the specified target SDK version. *
    @@ -318,6 +328,7 @@ public class Resources { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public Resources(@Nullable ClassLoader classLoader) { mClassLoader = classLoader == null ? ClassLoader.getSystemClassLoader() : classLoader; + sResourcesHistory.add(this); } /** @@ -2649,4 +2660,29 @@ public class Resources { } } } + + /** @hide */ + public void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "class=" + getClass()); + pw.println(prefix + "resourcesImpl"); + mResourcesImpl.dump(pw, prefix + " "); + } + + /** @hide */ + public static void dumpHistory(PrintWriter pw, String prefix) { + pw.println(prefix + "history"); + // Putting into a map keyed on the apk assets to deduplicate resources that are different + // objects but ultimately represent the same assets + Map, Resources> history = new ArrayMap<>(); + for (Resources r : sResourcesHistory) { + history.put(Arrays.asList(r.mResourcesImpl.mAssets.getApkAssets()), r); + } + int i = 0; + for (Resources r : history.values()) { + if (r != null) { + pw.println(prefix + i++); + r.dump(pw, prefix + " "); + } + } + } } diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java index 4d850b0ccfd5e..ff072916292be 100644 --- a/core/java/android/content/res/ResourcesImpl.java +++ b/core/java/android/content/res/ResourcesImpl.java @@ -61,6 +61,7 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.io.InputStream; +import java.io.PrintWriter; import java.util.Arrays; import java.util.Locale; @@ -1271,6 +1272,12 @@ public class ResourcesImpl { NativeAllocationRegistry.createMalloced(ResourcesImpl.class.getClassLoader(), AssetManager.getThemeFreeFunction()); + void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "class=" + getClass()); + pw.println(prefix + "assets"); + mAssets.dump(pw, prefix + " "); + } + public class ThemeImpl { /** * Unique key for the series of styles applied to this theme. diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java index b2c427408d01f..5c9044c56f950 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java @@ -645,6 +645,10 @@ public class TransactionParcelTests { ParcelFileDescriptor fd, RemoteCallback finishCallback) { } + @Override + public void dumpResources(ParcelFileDescriptor fd, RemoteCallback finishCallback) { + } + @Override public final void runIsolatedEntryPoint(String entryPoint, String[] entryPointArgs) { } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 902659c278188..7e90383abe64c 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -256,6 +256,7 @@ import android.os.BinderProxy; import android.os.BugreportParams; import android.os.Build; import android.os.Bundle; +import android.os.ConditionVariable; import android.os.Debug; import android.os.DropBoxManager; import android.os.FactoryTest; @@ -15478,6 +15479,62 @@ public class ActivityManagerService extends IActivityManager.Stub } } + /** + * Dump the resources structure for the given process + * + * @param process The process to dump resource info for + * @param fd The FileDescriptor to dump it into + * @throws RemoteException + */ + public boolean dumpResources(String process, ParcelFileDescriptor fd, RemoteCallback callback) + throws RemoteException { + synchronized (this) { + ProcessRecord proc = findProcessLOSP(process, UserHandle.USER_CURRENT, "dumpResources"); + IApplicationThread thread; + if (proc == null || (thread = proc.getThread()) == null) { + throw new IllegalArgumentException("Unknown process: " + process); + } + thread.dumpResources(fd, callback); + return true; + } + } + + /** + * Dump the resources structure for all processes + * + * @param fd The FileDescriptor to dump it into + * @throws RemoteException + */ + public void dumpAllResources(ParcelFileDescriptor fd, PrintWriter pw) throws RemoteException { + synchronized (mProcLock) { + mProcessList.forEachLruProcessesLOSP(true, app -> { + ConditionVariable lock = new ConditionVariable(); + RemoteCallback + finishCallback = new RemoteCallback(result -> lock.open(), null); + + pw.println(String.format("------ DUMP RESOURCES %s (%s) ------", + app.processName, + app.info.packageName)); + pw.flush(); + try { + app.getThread().dumpResources(fd.dup(), finishCallback); + lock.block(2000); + } catch (Exception e) { + pw.println(String.format( + "------ EXCEPTION DUMPING RESOURCES for %s (%s): %s ------", + app.processName, + app.info.packageName, + e.getMessage())); + pw.flush(); + } + pw.println(String.format("------ END DUMP RESOURCES %s (%s) ------", + app.processName, + app.info.packageName)); + pw.flush(); + }); + } + } + @Override public void setDumpHeapDebugLimit(String processName, int uid, long maxMemSize, String reportPackage) { diff --git a/services/core/java/com/android/server/resources/ResourcesManagerService.java b/services/core/java/com/android/server/resources/ResourcesManagerService.java new file mode 100644 index 0000000000000..cc275466a5ff7 --- /dev/null +++ b/services/core/java/com/android/server/resources/ResourcesManagerService.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2022 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.resources; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.IResourcesManager; +import android.os.Binder; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.os.Process; +import android.os.RemoteCallback; +import android.os.RemoteException; + +import com.android.server.SystemService; +import com.android.server.am.ActivityManagerService; + +import java.io.FileDescriptor; +import java.io.PrintWriter; + +/** + * A service for managing information about ResourcesManagers + */ +public class ResourcesManagerService extends SystemService { + private ActivityManagerService mActivityManagerService; + + /** + * Initializes the system service. + *

    + * Subclasses must define a single argument constructor that accepts the context + * and passes it to super. + *

    + * + * @param context The system server context. + */ + public ResourcesManagerService(@NonNull Context context) { + super(context); + publishBinderService(Context.RESOURCES_SERVICE, mService); + } + + @Override + public void onStart() { + // Intentionally left empty. + } + + private final IBinder mService = new IResourcesManager.Stub() { + @Override + public boolean dumpResources(String process, ParcelFileDescriptor fd, + RemoteCallback callback) throws RemoteException { + final int callingUid = Binder.getCallingUid(); + if (callingUid != Process.ROOT_UID && callingUid != Process.SHELL_UID) { + callback.sendResult(null); + throw new SecurityException("dump should only be called by shell"); + } + return mActivityManagerService.dumpResources(process, fd, callback); + } + + @Override + protected void dump(@NonNull FileDescriptor fd, + @NonNull PrintWriter pw, @Nullable String[] args) { + try { + mActivityManagerService.dumpAllResources(ParcelFileDescriptor.dup(fd), pw); + } catch (Exception e) { + pw.println("Exception while trying to dump all resources: " + e.getMessage()); + e.printStackTrace(pw); + } + } + + @Override + public int handleShellCommand(@NonNull ParcelFileDescriptor in, + @NonNull ParcelFileDescriptor out, + @NonNull ParcelFileDescriptor err, + @NonNull String[] args) { + return (new ResourcesManagerShellCommand(this)).exec( + this, + in.getFileDescriptor(), + out.getFileDescriptor(), + err.getFileDescriptor(), + args); + } + }; + + public void setActivityManagerService( + ActivityManagerService activityManagerService) { + mActivityManagerService = activityManagerService; + } +} diff --git a/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java b/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java new file mode 100644 index 0000000000000..7d8336a0d3e95 --- /dev/null +++ b/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2022 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.resources; + +import android.content.res.IResourcesManager; +import android.os.ConditionVariable; +import android.os.ParcelFileDescriptor; +import android.os.RemoteCallback; +import android.os.RemoteException; +import android.os.ShellCommand; +import android.util.Slog; + +import java.io.IOException; +import java.io.PrintWriter; + +/** + * Shell command handler for resources related commands + */ +public class ResourcesManagerShellCommand extends ShellCommand { + private static final String TAG = "ResourcesManagerShellCommand"; + + private final IResourcesManager mInterface; + + public ResourcesManagerShellCommand(IResourcesManager anInterface) { + mInterface = anInterface; + } + + @Override + public int onCommand(String cmd) { + if (cmd == null) { + return handleDefaultCommands(cmd); + } + final PrintWriter err = getErrPrintWriter(); + try { + switch (cmd) { + case "dump": + return dumpResources(); + default: + return handleDefaultCommands(cmd); + } + } catch (IllegalArgumentException e) { + err.println("Error: " + e.getMessage()); + } catch (RemoteException e) { + err.println("Remote exception: " + e); + } + return -1; + } + + private int dumpResources() throws RemoteException { + String processId = getNextArgRequired(); + try { + ConditionVariable lock = new ConditionVariable(); + RemoteCallback + finishCallback = new RemoteCallback(result -> lock.open(), null); + + if (!mInterface.dumpResources(processId, + ParcelFileDescriptor.dup(getOutFileDescriptor()), finishCallback)) { + getErrPrintWriter().println("RESOURCES DUMP FAILED on process " + processId); + return -1; + } + lock.block(5000); + return 0; + } catch (IOException e) { + Slog.e(TAG, "Exception while dumping resources", e); + getErrPrintWriter().println("Exception while dumping resources: " + e.getMessage()); + } + return -1; + } + + @Override + public void onHelp() { + final PrintWriter out = getOutPrintWriter(); + out.println("Resources manager commands:"); + out.println(" help"); + out.println(" Print this help text."); + out.println(" dump "); + out.println(" Dump the Resources objects in use as well as the history of Resources"); + + } +} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 74e04ed6e58b3..00792f212b313 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -176,6 +176,7 @@ import com.android.server.power.hint.HintManagerService; import com.android.server.powerstats.PowerStatsService; import com.android.server.profcollect.ProfcollectForwardingService; import com.android.server.recoverysystem.RecoverySystemService; +import com.android.server.resources.ResourcesManagerService; import com.android.server.restrictions.RestrictionsManagerService; import com.android.server.role.RoleServicePlatformHelper; import com.android.server.rotationresolver.RotationResolverManagerService; @@ -1289,6 +1290,13 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(new OverlayManagerService(mSystemContext)); t.traceEnd(); + // Manages Resources packages + t.traceBegin("StartResourcesManagerService"); + ResourcesManagerService resourcesService = new ResourcesManagerService(mSystemContext); + resourcesService.setActivityManagerService(mActivityManagerService); + mSystemServiceManager.startService(resourcesService); + t.traceEnd(); + t.traceBegin("StartSensorPrivacyService"); mSystemServiceManager.startService(new SensorPrivacyService(mSystemContext)); t.traceEnd();