Add ContentProvider.dump()
This is similar to the existing dump() facility for services.
ContentProviders can now implement dump() and that info will be shown
when running "dumpsys activity provider" and when taking a bugreport.
Change-Id: I33b3b132e3c4f920153355cc368eda2f725a715f
diff --git a/cmds/dumpstate/dumpstate.c b/cmds/dumpstate/dumpstate.c
index aae25a1..02e1f1c 100644
--- a/cmds/dumpstate/dumpstate.c
+++ b/cmds/dumpstate/dumpstate.c
@@ -253,6 +253,13 @@
run_command("APP SERVICES", 30, "dumpsys", "activity", "service", "all", NULL);
printf("========================================================\n");
+ printf("== Running Application Providers\n");
+ printf("========================================================\n");
+
+ run_command("APP SERVICES", 30, "dumpsys", "activity", "provider", "all", NULL);
+
+
+ printf("========================================================\n");
printf("== dumpstate: done\n");
printf("========================================================\n");
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 0c761fc..9e88bc6 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -813,6 +813,19 @@
}
}
+ public void dumpProvider(FileDescriptor fd, IBinder providertoken,
+ String[] args) {
+ DumpComponentInfo data = new DumpComponentInfo();
+ try {
+ data.fd = ParcelFileDescriptor.dup(fd);
+ data.token = providertoken;
+ data.args = args;
+ queueOrSendMessage(H.DUMP_PROVIDER, data);
+ } catch (IOException e) {
+ Slog.w(TAG, "dumpProvider failed", e);
+ }
+ }
+
@Override
public Debug.MemoryInfo dumpMemInfo(FileDescriptor fd, boolean checkin,
boolean all, String[] args) {
@@ -1044,6 +1057,7 @@
public void scheduleTrimMemory(int level) {
queueOrSendMessage(H.TRIM_MEMORY, null, level);
}
+
}
private class H extends Handler {
@@ -1088,6 +1102,7 @@
public static final int SET_CORE_SETTINGS = 138;
public static final int UPDATE_PACKAGE_COMPATIBILITY_INFO = 139;
public static final int TRIM_MEMORY = 140;
+ public static final int DUMP_PROVIDER = 141;
String codeToString(int code) {
if (DEBUG_MESSAGES) {
switch (code) {
@@ -1132,6 +1147,7 @@
case SET_CORE_SETTINGS: return "SET_CORE_SETTINGS";
case UPDATE_PACKAGE_COMPATIBILITY_INFO: return "UPDATE_PACKAGE_COMPATIBILITY_INFO";
case TRIM_MEMORY: return "TRIM_MEMORY";
+ case DUMP_PROVIDER: return "DUMP_PROVIDER";
}
}
return "(unknown)";
@@ -1264,6 +1280,9 @@
case DUMP_ACTIVITY:
handleDumpActivity((DumpComponentInfo)msg.obj);
break;
+ case DUMP_PROVIDER:
+ handleDumpProvider((DumpComponentInfo)msg.obj);
+ break;
case SLEEPING:
handleSleeping((IBinder)msg.obj, msg.arg1 != 0);
break;
@@ -2347,6 +2366,19 @@
}
}
+ private void handleDumpProvider(DumpComponentInfo info) {
+ ProviderClientRecord r = mLocalProviders.get(info.token);
+ if (r != null && r.mLocalProvider != null) {
+ PrintWriter pw = new PrintWriter(new FileOutputStream(info.fd.getFileDescriptor()));
+ r.mLocalProvider.dump(info.fd.getFileDescriptor(), pw, info.args);
+ pw.flush();
+ try {
+ info.fd.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
private void handleServiceArgs(ServiceArgsData data) {
Service s = mServices.get(data.token);
if (s != null) {
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index c4a4fea..e6d28ef 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -352,6 +352,21 @@
return true;
}
+ case DUMP_PROVIDER_TRANSACTION: {
+ data.enforceInterface(IApplicationThread.descriptor);
+ ParcelFileDescriptor fd = data.readFileDescriptor();
+ final IBinder service = data.readStrongBinder();
+ final String[] args = data.readStringArray();
+ if (fd != null) {
+ dumpProvider(fd.getFileDescriptor(), service, args);
+ try {
+ fd.close();
+ } catch (IOException e) {
+ }
+ }
+ return true;
+ }
+
case SCHEDULE_REGISTERED_RECEIVER_TRANSACTION: {
data.enforceInterface(IApplicationThread.descriptor);
IIntentReceiver receiver = IIntentReceiver.Stub.asInterface(
@@ -931,6 +946,17 @@
data.recycle();
}
+ public void dumpProvider(FileDescriptor fd, IBinder token, String[] args)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ data.writeInterfaceToken(IApplicationThread.descriptor);
+ data.writeFileDescriptor(fd);
+ data.writeStrongBinder(token);
+ data.writeStringArray(args);
+ mRemote.transact(DUMP_PROVIDER_TRANSACTION, data, null, IBinder.FLAG_ONEWAY);
+ data.recycle();
+ }
+
public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent,
int resultCode, String dataStr, Bundle extras, boolean ordered, boolean sticky)
throws RemoteException {
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index 1253fe7..a362778 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -102,6 +102,8 @@
void processInBackground() throws RemoteException;
void dumpService(FileDescriptor fd, IBinder servicetoken, String[] args)
throws RemoteException;
+ void dumpProvider(FileDescriptor fd, IBinder servicetoken, String[] args)
+ throws RemoteException;
void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent,
int resultCode, String data, Bundle extras, boolean ordered, boolean sticky)
throws RemoteException;
@@ -171,4 +173,5 @@
int SCHEDULE_TRIM_MEMORY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+41;
int DUMP_MEM_INFO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+42;
int DUMP_GFX_INFO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+43;
+ int DUMP_PROVIDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+44;
}
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 092a0c8..116ca48 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -32,8 +32,10 @@
import android.util.Log;
import java.io.File;
+import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.io.PrintWriter;
import java.util.ArrayList;
/**
@@ -1013,4 +1015,19 @@
Log.w(TAG, "implement ContentProvider shutdown() to make sure all database " +
"connections are gracefully shutdown");
}
+
+ /**
+ * Print the Provider's state into the given stream. This gets invoked if
+ * you run "adb shell dumpsys activity provider <provider_component_name>".
+ *
+ * @param prefix Desired prefix to prepend at each line of output.
+ * @param fd The raw file descriptor that the dump is being sent to.
+ * @param writer The PrintWriter to which you should dump your state. This will be
+ * closed for you after you return.
+ * @param args additional arguments to the dump request.
+ * @hide
+ */
+ public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ writer.println("nothing to dump");
+ }
}
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index df58e83..4067f6e 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -7945,6 +7945,7 @@
pw.println(" p[rocesses] [PACKAGE_NAME]: process state");
pw.println(" o[om]: out of memory management");
pw.println(" prov[iders] [COMP_SPEC ...]: content provider state");
+ pw.println(" provider [COMP_SPEC]: provider client-side state");
pw.println(" s[ervices] [COMP_SPEC ...]: service state");
pw.println(" service [COMP_SPEC]: service client-side state");
pw.println(" package [PACKAGE_NAME]: all state related to given package");
@@ -8027,6 +8028,23 @@
dumpOomLocked(fd, pw, args, opti, true);
}
return;
+ } else if ("provider".equals(cmd)) {
+ String[] newArgs;
+ String name;
+ if (opti >= args.length) {
+ name = null;
+ newArgs = EMPTY_STRING_ARRAY;
+ } else {
+ name = args[opti];
+ opti++;
+ newArgs = new String[args.length - opti];
+ if (args.length > 2) System.arraycopy(args, opti, newArgs, 0, args.length - opti);
+ }
+ if (!dumpProvider(fd, pw, name, newArgs, 0, dumpAll)) {
+ pw.println("No providers match: " + name);
+ pw.println("Use -h for help.");
+ }
+ return;
} else if ("providers".equals(cmd) || "prov".equals(cmd)) {
synchronized (this) {
dumpProvidersLocked(fd, pw, args, opti, true, null);
@@ -8611,6 +8629,110 @@
}
}
+ /**
+ * There are three ways to call this:
+ * - no provider specified: dump all the providers
+ * - a flattened component name that matched an existing provider was specified as the
+ * first arg: dump that one provider
+ * - the first arg isn't the flattened component name of an existing provider:
+ * dump all providers whose component contains the first arg as a substring
+ */
+ protected boolean dumpProvider(FileDescriptor fd, PrintWriter pw, String name, String[] args,
+ int opti, boolean dumpAll) {
+ ArrayList<ContentProviderRecord> providers = new ArrayList<ContentProviderRecord>();
+
+ if ("all".equals(name)) {
+ synchronized (this) {
+ for (ContentProviderRecord r1 : mProvidersByClass.values()) {
+ providers.add(r1);
+ }
+ }
+ } 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) {
+ }
+ }
+
+ synchronized (this) {
+ for (ContentProviderRecord r1 : mProvidersByClass.values()) {
+ if (componentName != null) {
+ if (r1.name.equals(componentName)) {
+ providers.add(r1);
+ }
+ } else if (name != null) {
+ if (r1.name.flattenToString().contains(name)) {
+ providers.add(r1);
+ }
+ } else if (System.identityHashCode(r1) == objectId) {
+ providers.add(r1);
+ }
+ }
+ }
+ }
+
+ if (providers.size() <= 0) {
+ return false;
+ }
+
+ boolean needSep = false;
+ for (int i=0; i<providers.size(); i++) {
+ if (needSep) {
+ pw.println();
+ }
+ needSep = true;
+ dumpProvider("", fd, pw, providers.get(i), args, dumpAll);
+ }
+ return true;
+ }
+
+ /**
+ * Invokes IApplicationThread.dumpProvider() on the thread of the specified provider if
+ * there is a thread associated with the provider.
+ */
+ private void dumpProvider(String prefix, FileDescriptor fd, PrintWriter pw,
+ final ContentProviderRecord r, String[] args, boolean dumpAll) {
+ String innerPrefix = prefix + " ";
+ synchronized (this) {
+ pw.print(prefix); pw.print("PROVIDER ");
+ pw.print(r);
+ pw.print(" pid=");
+ if (r.proc != null) pw.println(r.proc.pid);
+ else pw.println("(not running)");
+ if (dumpAll) {
+ r.dump(pw, innerPrefix);
+ }
+ }
+ if (r.proc != null && r.proc.thread != null) {
+ pw.println(" Client:");
+ pw.flush();
+ try {
+ TransferPipe tp = new TransferPipe();
+ try {
+ r.proc.thread.dumpProvider(
+ tp.getWriteFd().getFileDescriptor(), r.provider.asBinder(), args);
+ tp.setBufferPrefix(" ");
+ // Short timeout, since blocking here can
+ // deadlock with the application.
+ tp.go(fd, 2000);
+ } finally {
+ tp.kill();
+ }
+ } catch (IOException ex) {
+ pw.println(" Failure while dumping the provider: " + ex);
+ } catch (RemoteException ex) {
+ pw.println(" Got a RemoteException while dumping the service");
+ }
+ }
+ }
+
static class ItemMatcher {
ArrayList<ComponentName> components;
ArrayList<String> strings;
@@ -8950,6 +9072,9 @@
return needSep;
}
+ /**
+ * Prints a list of ServiceRecords (dumpsys activity services)
+ */
boolean dumpServicesLocked(FileDescriptor fd, PrintWriter pw, String[] args,
int opti, boolean dumpAll, boolean dumpClient, String dumpPackage) {
boolean needSep = false;