From 0b575a3cdfce8cd5394044c4c6a7092487ba93cb Mon Sep 17 00:00:00 2001 From: Makoto Onuki Date: Mon, 16 Apr 2018 14:33:59 -0700 Subject: Split provider / service dumpsys into platform and non-platform Also extend the timeout to 60 seconds. - Because each provider / service dump may time out, the total time should relatively be large. Bug: 78017892 Fix: 78017892 Test: Manual test with the following dumpsys commands: dumpsys activity provider all dumpsys activity provider all-platform dumpsys activity provider all-non-platform dumpsys activity provider com.android.providers.contacts/com.android.providers.contacts.VoicemailContentProvider dumpsys activity provider com.android.providers.contacts/.VoicemailContentProvider dumpsys activity provider contacts dumpsys activity provider voicemail dumpsys activity provider 4d45a78 dumpsys activity service all dumpsys activity service all-platform dumpsys activity service all-non-platform dumpsys activity service bluetooth Test: atest /android/pi-dev/frameworks/base/core/tests/coretests/src/com/android/internal/util/DumpTest.java Test: atest /android/pi-dev/frameworks/base/core/tests/coretests/src/com/android/internal/util/ParseUtilsTest.java Test: Manual test with "adb bugreport" with adding sleep(10s) to ProviderMap.dumpProvider() Change-Id: I00bce0090b8dbb947d7f8b1e5d01bb8a70d84bd8 --- core/java/android/content/ComponentName.java | 10 ++ .../com/android/internal/util/CollectionUtils.java | 12 ++ core/java/com/android/internal/util/DumpUtils.java | 102 ++++++++++++++++ .../java/com/android/internal/util/ParseUtils.java | 98 ++++++++++++++++ .../com/android/internal/util/DumpUtilsTest.java | 128 +++++++++++++++++++++ .../com/android/internal/util/ParseUtilsTest.java | 104 +++++++++++++++++ .../java/com/android/server/am/ActiveServices.java | 70 ++++------- .../android/server/am/ContentProviderRecord.java | 6 +- .../java/com/android/server/am/ProviderMap.java | 46 +++----- .../java/com/android/server/am/ServiceRecord.java | 6 +- 10 files changed, 501 insertions(+), 81 deletions(-) create mode 100644 core/java/com/android/internal/util/ParseUtils.java create mode 100644 core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java create mode 100644 core/tests/coretests/src/com/android/internal/util/ParseUtilsTest.java diff --git a/core/java/android/content/ComponentName.java b/core/java/android/content/ComponentName.java index ead6c25989ce..fc5853353ce6 100644 --- a/core/java/android/content/ComponentName.java +++ b/core/java/android/content/ComponentName.java @@ -396,4 +396,14 @@ public final class ComponentName implements Parcelable, Cloneable, Comparable void addIf(@Nullable List source, @NonNull Collection dest, + @Nullable Predicate predicate) { + for (int i = 0; i < size(source); i++) { + final T item = source.get(i); + if (predicate.test(item)) { + dest.add(item); + } + } + } + /** * Returns a list of items resulting from applying the given function to each element of the * provided list. diff --git a/core/java/com/android/internal/util/DumpUtils.java b/core/java/com/android/internal/util/DumpUtils.java index e85b782facb2..7fd83bc6c8b9 100644 --- a/core/java/com/android/internal/util/DumpUtils.java +++ b/core/java/com/android/internal/util/DumpUtils.java @@ -16,18 +16,25 @@ package com.android.internal.util; +import android.annotation.Nullable; import android.app.AppOpsManager; +import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; import android.os.Binder; import android.os.Handler; +import android.text.TextUtils; import android.util.Slog; import java.io.PrintWriter; import java.io.StringWriter; +import java.util.Objects; +import java.util.function.Predicate; /** * Helper functions for dumping the state of system services. + * Test: + atest /android/pi-dev/frameworks/base/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java */ public final class DumpUtils { private static final String TAG = "DumpUtils"; @@ -153,4 +160,99 @@ public final class DumpUtils { PrintWriter pw) { return checkDumpPermission(context, tag, pw) && checkUsageStatsPermission(context, tag, pw); } + + /** + * Return whether a package name is considered to be part of the platform. + * @hide + */ + public static boolean isPlatformPackage(@Nullable String packageName) { + return (packageName != null) + && (packageName.equals("android") + || packageName.startsWith("android.") + || packageName.startsWith("com.android.")); + } + + /** + * Return whether a package name is considered to be part of the platform. + * @hide + */ + public static boolean isPlatformPackage(@Nullable ComponentName cname) { + return (cname != null) && isPlatformPackage(cname.getPackageName()); + } + + /** + * Return whether a package name is considered to be part of the platform. + * @hide + */ + public static boolean isPlatformPackage(@Nullable ComponentName.WithComponentName wcn) { + return (wcn != null) && isPlatformPackage(wcn.getComponentName()); + } + + /** + * Return whether a package name is NOT considered to be part of the platform. + * @hide + */ + public static boolean isNonPlatformPackage(@Nullable String packageName) { + return (packageName != null) && !isPlatformPackage(packageName); + } + + /** + * Return whether a package name is NOT considered to be part of the platform. + * @hide + */ + public static boolean isNonPlatformPackage(@Nullable ComponentName cname) { + return (cname != null) && isNonPlatformPackage(cname.getPackageName()); + } + + /** + * Return whether a package name is NOT considered to be part of the platform. + * @hide + */ + public static boolean isNonPlatformPackage(@Nullable ComponentName.WithComponentName wcn) { + return (wcn != null) && !isPlatformPackage(wcn.getComponentName()); + } + + /** + * Used for dumping providers and services. Return a predicate for a given filter string. + * @hide + */ + public static Predicate filterRecord( + @Nullable String filterString) { + + if (TextUtils.isEmpty(filterString)) { + return rec -> false; + } + + // Dump all? + if ("all".equals(filterString)) { + return Objects::nonNull; + } + + // Dump all platform? + if ("all-platform".equals(filterString)) { + return DumpUtils::isPlatformPackage; + } + + // Dump all non-platform? + if ("all-non-platform".equals(filterString)) { + return DumpUtils::isNonPlatformPackage; + } + + // Is the filter a component name? If so, do an exact match. + final ComponentName filterCname = ComponentName.unflattenFromString(filterString); + if (filterCname != null) { + // Do exact component name check. + return rec -> (rec != null) && filterCname.equals(rec.getComponentName()); + } + + // Otherwise, do a partial match against the component name. + // Also if the filter is a hex-decimal string, do the object ID match too. + final int id = ParseUtils.parseIntWithBase(filterString, 16, -1); + return rec -> { + final ComponentName cn = rec.getComponentName(); + return ((id != -1) && (System.identityHashCode(rec) == id)) + || cn.flattenToString().toLowerCase().contains(filterString.toLowerCase()); + }; + } } + diff --git a/core/java/com/android/internal/util/ParseUtils.java b/core/java/com/android/internal/util/ParseUtils.java new file mode 100644 index 000000000000..a591f4aa41fc --- /dev/null +++ b/core/java/com/android/internal/util/ParseUtils.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2018 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.internal.util; + +import android.annotation.Nullable; + +/** + * Various numeric -> strings conversion. + * + * Test: + atest /android/pi-dev/frameworks/base/core/tests/coretests/src/com/android/internal/util/ParseUtilsTest.java + */ +public final class ParseUtils { + private ParseUtils() { + } + + /** Parse a value as a base-10 integer. */ + public static int parseInt(@Nullable String value, int defValue) { + return parseIntWithBase(value, 10, defValue); + } + + /** Parse a value as an integer of a given base. */ + public static int parseIntWithBase(@Nullable String value, int base, int defValue) { + if (value == null) { + return defValue; + } + try { + return Integer.parseInt(value, base); + } catch (NumberFormatException e) { + return defValue; + } + } + + /** Parse a value as a base-10 long. */ + public static long parseLong(@Nullable String value, long defValue) { + return parseLongWithBase(value, 10, defValue); + } + + /** Parse a value as a long of a given base. */ + public static long parseLongWithBase(@Nullable String value, int base, long defValue) { + if (value == null) { + return defValue; + } + try { + return Long.parseLong(value, base); + } catch (NumberFormatException e) { + return defValue; + } + } + + /** Parse a value as a float. */ + public static float parseFloat(@Nullable String value, float defValue) { + if (value == null) { + return defValue; + } + try { + return Float.parseFloat(value); + } catch (NumberFormatException e) { + return defValue; + } + } + + /** Parse a value as a double. */ + public static double parseDouble(@Nullable String value, double defValue) { + if (value == null) { + return defValue; + } + try { + return Double.parseDouble(value); + } catch (NumberFormatException e) { + return defValue; + } + } + + /** Parse a value as a boolean. */ + public static boolean parseBoolean(@Nullable String value, boolean defValue) { + if ("true".equals(value)) { + return true; + } + if ("false".equals(value)) { + return false; + } + return parseInt(value, defValue ? 1 : 0) != 0; + } +} diff --git a/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java b/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java new file mode 100644 index 000000000000..45b19bccff88 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2018 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.internal.util; + +import static com.android.internal.util.DumpUtils.filterRecord; +import static com.android.internal.util.DumpUtils.isNonPlatformPackage; +import static com.android.internal.util.DumpUtils.isPlatformPackage; + +import android.content.ComponentName; + +import junit.framework.TestCase; + +/** + * Run with: + atest /android/pi-dev/frameworks/base/core/tests/coretests/src/com/android/internal/util/DumpTest.java + */ +public class DumpUtilsTest extends TestCase { + + private static ComponentName cn(String componentName) { + if (componentName == null) { + return null; + } + return ComponentName.unflattenFromString(componentName); + } + + private static ComponentName.WithComponentName wcn(String componentName) { + if (componentName == null) { + return null; + } + return () -> cn(componentName); + } + + public void testIsPlatformPackage() { + assertTrue(isPlatformPackage("android")); + assertTrue(isPlatformPackage("android.abc")); + assertTrue(isPlatformPackage("com.android.abc")); + + assertFalse(isPlatformPackage((String) null)); + assertFalse(isPlatformPackage("com.google")); + + assertTrue(isPlatformPackage(cn("android/abc"))); + assertTrue(isPlatformPackage(cn("android.abc/abc"))); + assertTrue(isPlatformPackage(cn("com.android.def/abc"))); + + assertFalse(isPlatformPackage(cn(null))); + assertFalse(isPlatformPackage(cn("com.google.def/abc"))); + + assertTrue(isPlatformPackage(wcn("android/abc"))); + assertTrue(isPlatformPackage(wcn("android.abc/abc"))); + assertTrue(isPlatformPackage(wcn("com.android.def/abc"))); + + assertFalse(isPlatformPackage(wcn(null))); + assertFalse(isPlatformPackage(wcn("com.google.def/abc"))); + } + + public void testIsNonPlatformPackage() { + assertFalse(isNonPlatformPackage("android")); + assertFalse(isNonPlatformPackage("android.abc")); + assertFalse(isNonPlatformPackage("com.android.abc")); + + assertFalse(isNonPlatformPackage((String) null)); + assertTrue(isNonPlatformPackage("com.google")); + + assertFalse(isNonPlatformPackage(cn("android/abc"))); + assertFalse(isNonPlatformPackage(cn("android.abc/abc"))); + assertFalse(isNonPlatformPackage(cn("com.android.def/abc"))); + + assertFalse(isNonPlatformPackage(cn(null))); + assertTrue(isNonPlatformPackage(cn("com.google.def/abc"))); + + assertFalse(isNonPlatformPackage(wcn("android/abc"))); + assertFalse(isNonPlatformPackage(wcn("android.abc/abc"))); + assertFalse(isNonPlatformPackage(wcn("com.android.def/abc"))); + + assertFalse(isNonPlatformPackage(wcn(null))); + assertTrue(isNonPlatformPackage(wcn("com.google.def/abc"))); + } + + public void testFilterRecord() { + assertFalse(filterRecord(null).test(wcn("com.google.p/abc"))); + assertFalse(filterRecord(null).test(wcn("com.android.p/abc"))); + + assertTrue(filterRecord("all").test(wcn("com.google.p/abc"))); + assertTrue(filterRecord("all").test(wcn("com.android.p/abc"))); + assertFalse(filterRecord("all").test(wcn(null))); + + assertFalse(filterRecord("all-platform").test(wcn("com.google.p/abc"))); + assertTrue(filterRecord("all-platform").test(wcn("com.android.p/abc"))); + assertFalse(filterRecord("all-platform").test(wcn(null))); + + assertTrue(filterRecord("all-non-platform").test(wcn("com.google.p/abc"))); + assertFalse(filterRecord("all-non-platform").test(wcn("com.android.p/abc"))); + assertFalse(filterRecord("all-non-platform").test(wcn(null))); + + // Partial string match. + assertTrue(filterRecord("abc").test(wcn("com.google.p/.abc"))); + assertFalse(filterRecord("abc").test(wcn("com.google.p/.def"))); + assertTrue(filterRecord("com").test(wcn("com.google.p/.xyz"))); + + // Full component name match. + assertTrue(filterRecord("com.google/com.google.abc").test(wcn("com.google/.abc"))); + assertFalse(filterRecord("com.google/com.google.abc").test(wcn("com.google/.abc.def"))); + + + // Hex ID match + ComponentName.WithComponentName component = wcn("com.google/.abc"); + + assertTrue(filterRecord( + Integer.toHexString(System.identityHashCode(component))).test(component)); + // Same component name, but different ID, no match. + assertFalse(filterRecord( + Integer.toHexString(System.identityHashCode(component))).test( + wcn("com.google/.abc"))); + } +} diff --git a/core/tests/coretests/src/com/android/internal/util/ParseUtilsTest.java b/core/tests/coretests/src/com/android/internal/util/ParseUtilsTest.java new file mode 100644 index 000000000000..f00c48c96b5d --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/util/ParseUtilsTest.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2018 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.internal.util; + +import junit.framework.TestCase; + +/** + * Run with: + atest /android/pi-dev/frameworks/base/core/tests/coretests/src/com/android/internal/util/ParseUtilsTest.java + */ +public class ParseUtilsTest extends TestCase { + public void testParseInt() { + assertEquals(1, ParseUtils.parseInt(null, 1)); + assertEquals(1, ParseUtils.parseInt("", 1)); + assertEquals(1, ParseUtils.parseInt("1x", 1)); + assertEquals(2, ParseUtils.parseInt("2", 1)); + + assertEquals(2, ParseUtils.parseInt("+2", 1)); + assertEquals(-2, ParseUtils.parseInt("-2", 1)); + } + + public void testParseIntWithBase() { + assertEquals(1, ParseUtils.parseIntWithBase(null, 10, 1)); + assertEquals(1, ParseUtils.parseIntWithBase("", 10, 1)); + assertEquals(1, ParseUtils.parseIntWithBase("1x", 10, 1)); + assertEquals(2, ParseUtils.parseIntWithBase("2", 10, 1)); + assertEquals(10, ParseUtils.parseIntWithBase("10", 10, 1)); + assertEquals(3, ParseUtils.parseIntWithBase("10", 3, 1)); + + assertEquals(3, ParseUtils.parseIntWithBase("+10", 3, 1)); + assertEquals(-3, ParseUtils.parseIntWithBase("-10", 3, 1)); + } + + public void testParseLong() { + assertEquals(1L, ParseUtils.parseLong(null, 1)); + assertEquals(1L, ParseUtils.parseLong("", 1)); + assertEquals(1L, ParseUtils.parseLong("1x", 1)); + assertEquals(2L, ParseUtils.parseLong("2", 1)); + } + + public void testParseLongWithBase() { + assertEquals(1L, ParseUtils.parseLongWithBase(null, 10, 1)); + assertEquals(1L, ParseUtils.parseLongWithBase("", 10, 1)); + assertEquals(1L, ParseUtils.parseLongWithBase("1x", 10, 1)); + assertEquals(2L, ParseUtils.parseLongWithBase("2", 10, 1)); + assertEquals(10L, ParseUtils.parseLongWithBase("10", 10, 1)); + assertEquals(3L, ParseUtils.parseLongWithBase("10", 3, 1)); + + assertEquals(3L, ParseUtils.parseLongWithBase("+10", 3, 1)); + assertEquals(-3L, ParseUtils.parseLongWithBase("-10", 3, 1)); + + assertEquals(10_000_000_000L, ParseUtils.parseLongWithBase("+10000000000", 10, 1)); + assertEquals(-10_000_000_000L, ParseUtils.parseLongWithBase("-10000000000", 10, 1)); + + assertEquals(10_000_000_000L, ParseUtils.parseLongWithBase(null, 10, 10_000_000_000L)); + } + + public void testParseFloat() { + assertEquals(0.5f, ParseUtils.parseFloat(null, 0.5f)); + assertEquals(0.5f, ParseUtils.parseFloat("", 0.5f)); + assertEquals(0.5f, ParseUtils.parseFloat("1x", 0.5f)); + assertEquals(1.5f, ParseUtils.parseFloat("1.5", 0.5f)); + } + + public void testParseDouble() { + assertEquals(0.5, ParseUtils.parseDouble(null, 0.5)); + assertEquals(0.5, ParseUtils.parseDouble("", 0.5)); + assertEquals(0.5, ParseUtils.parseDouble("1x", 0.5)); + assertEquals(1.5, ParseUtils.parseDouble("1.5", 0.5)); + } + + public void testParseBoolean() { + assertEquals(false, ParseUtils.parseBoolean(null, false)); + assertEquals(true, ParseUtils.parseBoolean(null, true)); + + assertEquals(false, ParseUtils.parseBoolean("", false)); + assertEquals(true, ParseUtils.parseBoolean("", true)); + + assertEquals(true, ParseUtils.parseBoolean("true", false)); + assertEquals(true, ParseUtils.parseBoolean("true", true)); + + assertEquals(false, ParseUtils.parseBoolean("false", false)); + assertEquals(false, ParseUtils.parseBoolean("false", true)); + + assertEquals(true, ParseUtils.parseBoolean("1", false)); + assertEquals(true, ParseUtils.parseBoolean("1", true)); + + assertEquals(false, ParseUtils.parseBoolean("0", false)); + assertEquals(false, ParseUtils.parseBoolean("0", true)); + } +} diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index b1cb2fe70a6e..19cda8aa77a1 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -24,15 +24,19 @@ import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; +import java.util.function.Predicate; import android.app.ActivityThread; import android.app.AppOpsManager; import android.app.NotificationManager; import android.app.ServiceStartArgs; +import android.content.ComponentName.WithComponentName; import android.content.IIntentSender; import android.content.IntentSender; import android.content.pm.ParceledListSlice; @@ -55,6 +59,8 @@ import com.android.internal.messages.nano.SystemMessageProto; import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.BatteryStatsImpl; import com.android.internal.os.TransferPipe; +import com.android.internal.util.CollectionUtils; +import com.android.internal.util.DumpUtils; import com.android.internal.util.FastPrintWriter; import com.android.server.AppStateTracker; import com.android.server.LocalServices; @@ -4063,57 +4069,26 @@ public final class ActiveServices { * - the first arg isn't the flattened component name of an existing service: * dump all services whose component contains the first arg as a substring */ - protected boolean dumpService(FileDescriptor fd, PrintWriter pw, String name, String[] args, - int opti, boolean dumpAll) { - ArrayList services = new ArrayList(); + protected boolean dumpService(FileDescriptor fd, PrintWriter pw, final String name, + String[] args, int opti, boolean dumpAll) { + final ArrayList services = new ArrayList<>(); + + final Predicate filter = DumpUtils.filterRecord(name); synchronized (mAm) { int[] users = mAm.mUserController.getUsers(); - if ("all".equals(name)) { - for (int user : users) { - ServiceMap smap = mServiceMap.get(user); - if (smap == null) { - continue; - } - ArrayMap alls = smap.mServicesByName; - for (int i=0; i alls = smap.mServicesByName; + for (int i=0; i alls = smap.mServicesByName; - for (int i=0; i getProvidersForName(String name) { ArrayList allProviders = new ArrayList(); - ArrayList providers = new ArrayList(); + final ArrayList ret = new ArrayList<>(); + + final Predicate filter = DumpUtils.filterRecord(name); synchronized (mAm) { allProviders.addAll(mSingletonByClass.values()); @@ -333,39 +341,11 @@ public final class ProviderMap { allProviders.addAll(mProvidersByClassPerUser.valueAt(i).values()); } - if ("all".equals(name)) { - providers.addAll(allProviders); - } else { - ComponentName componentName = name != null - ? ComponentName.unflattenFromString(name) : null; - int objectId = 0; - if (componentName == null) { - // Not a '/' separated full component name; maybe an object ID? - try { - objectId = Integer.parseInt(name, 16); - name = null; - componentName = null; - } catch (RuntimeException e) { - } - } - - for (int i=0; i