summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSvetoslav Ganov <svetoslavganov@google.com>2012-02-03 19:19:09 -0800
committerSvetoslav Ganov <svetoslavganov@google.com>2012-02-15 14:55:47 -0800
commit25872aa3ef189ae5506a923398af11ce5eb1a9b9 (patch)
treeece1cffcc342e5e7133ca42d33ce97bbf403f779
parent91ec0b722f659bb5e4bcc64339f2fbbe30a31287 (diff)
downloadframeworks_base-25872aa3ef189ae5506a923398af11ce5eb1a9b9.zip
frameworks_base-25872aa3ef189ae5506a923398af11ce5eb1a9b9.tar.gz
frameworks_base-25872aa3ef189ae5506a923398af11ce5eb1a9b9.tar.bz2
Adding shell commands for modifying content.
1. Added methods to the ActivityManagerService remote interface that allow accessing content providers outside of an application. These methods are guarded by an internal signature protected permission which is given to the shell user. This enables a shell program to access content providers. 2. Implemented a shell command that takes as input as standart fagls with values and manipulates content via the content provider mechanism. Change-Id: I2943f8b59fbab33eb623458fa01ea61a077b9845
-rw-r--r--cmds/content/Android.mk31
-rw-r--r--cmds/content/MODULE_LICENSE_APACHE20
-rw-r--r--cmds/content/NOTICE190
-rwxr-xr-xcmds/content/content5
-rw-r--r--cmds/content/src/com/android/commands/content/Content.java442
-rw-r--r--core/java/android/app/ActivityManagerNative.java59
-rw-r--r--core/java/android/app/IActivityManager.java7
-rw-r--r--core/res/AndroidManifest.xml11
-rwxr-xr-xcore/res/res/values/strings.xml8
-rw-r--r--data/etc/platform.xml1
-rw-r--r--services/java/com/android/server/am/ActivityManagerService.java73
-rw-r--r--services/java/com/android/server/am/ContentProviderRecord.java106
12 files changed, 899 insertions, 34 deletions
diff --git a/cmds/content/Android.mk b/cmds/content/Android.mk
new file mode 100644
index 0000000..33fd664
--- /dev/null
+++ b/cmds/content/Android.mk
@@ -0,0 +1,31 @@
+# Copyright 2012 The Android Open Source Project
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_MODULE := content
+include $(BUILD_JAVA_LIBRARY)
+
+include $(CLEAR_VARS)
+ALL_PREBUILT += $(TARGET_OUT)/bin/content
+$(TARGET_OUT)/bin/content : $(LOCAL_PATH)/content | $(ACP)
+ $(transform-prebuilt-to-target)
+
+NOTICE_FILE := NOTICE
+files_noticed := bin/content
+
+# Generate rules for a single file. The argument is the file path relative to
+# the installation root
+define make-notice-file
+
+$(TARGET_OUT_NOTICE_FILES)/src/$(1).txt: $(LOCAL_PATH)/$(NOTICE_FILE)
+ @echo Notice file: $$< -- $$@
+ @mkdir -p $$(dir $$@)
+ @cat $$< >> $$@
+
+$(TARGET_OUT_NOTICE_FILES)/hash-timestamp: $(TARGET_OUT_NOTICE_FILES)/src/$(1).txt
+
+endef
+
+$(foreach file,$(files_noticed),$(eval $(call make-notice-file,$(file))))
diff --git a/cmds/content/MODULE_LICENSE_APACHE2 b/cmds/content/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/cmds/content/MODULE_LICENSE_APACHE2
diff --git a/cmds/content/NOTICE b/cmds/content/NOTICE
new file mode 100644
index 0000000..33ff961
--- /dev/null
+++ b/cmds/content/NOTICE
@@ -0,0 +1,190 @@
+
+ Copyright (c) 2005-2012, 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.
+
+ 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.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/cmds/content/content b/cmds/content/content
new file mode 100755
index 0000000..a8e056d
--- /dev/null
+++ b/cmds/content/content
@@ -0,0 +1,5 @@
+# Script to start "content" on the device, which has a very rudimentary shell.
+base=/system
+export CLASSPATH=$base/framework/content.jar
+exec app_process $base/bin com.android.commands.content.Content "$@"
+
diff --git a/cmds/content/src/com/android/commands/content/Content.java b/cmds/content/src/com/android/commands/content/Content.java
new file mode 100644
index 0000000..1dcba70
--- /dev/null
+++ b/cmds/content/src/com/android/commands/content/Content.java
@@ -0,0 +1,442 @@
+/*
+** Copyright 2012, 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.commands.content;
+
+import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
+import android.app.IActivityManager.ContentProviderHolder;
+import android.content.ContentValues;
+import android.content.IContentProvider;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.IBinder;
+import android.text.TextUtils;
+
+/**
+ * This class is a command line utility for manipulating content. A client
+ * can insert, update, and remove records in a content provider. For example,
+ * some settings may be configured before running the CTS tests, etc.
+ * <p>
+ * Examples:
+ * <ul>
+ * <li>
+ * # Add "new_setting" secure setting with value "new_value".</br>
+ * adb shell content insert --uri content://settings/secure --bind name:s:new_setting
+ * --bind value:s:new_value
+ * </li>
+ * <li>
+ * # Change "new_setting" secure setting to "newer_value" (You have to escape single quotes in
+ * the where clause).</br>
+ * adb shell content update --uri content://settings/secure --bind value:s:newer_value
+ * --where "name=\'new_setting\'"
+ * </li>
+ * <li>
+ * # Remove "new_setting" secure setting.</br>
+ * adb shell content delete --uri content://settings/secure --where "name=\'new_setting\'"
+ * </li>
+ * <li>
+ * # Query \"name\" and \"value\" columns from secure settings where \"name\" is equal to"
+ * \"new_setting\" and sort the result by name in ascending order.\n"
+ * adb shell content query --uri content://settings/secure --projection name:value
+ * --where "name=\'new_setting\'" --sort \"name ASC\"
+ * </li>
+ * </ul>
+ * </p>
+ */
+public class Content {
+
+ private static final String USAGE =
+ "usage: adb shell content [subcommand] [options]\n"
+ + "\n"
+ + "usage: adb shell content insert --uri <URI> --bind <BINDING> [--bind <BINDING>...]\n"
+ + " <URI> a content provider URI.\n"
+ + " <BINDING> binds a typed value to a column and is formatted:\n"
+ + " <COLUMN_NAME>:<TYPE>:<COLUMN_VALUE> where:\n"
+ + " <TYPE> specifies data type such as:\n"
+ + " b - boolean, s - string, i - integer, l - long, f - float, d - double\n"
+ + " Example:\n"
+ + " # Add \"new_setting\" secure setting with value \"new_value\".\n"
+ + " adb shell content insert --uri content://settings/secure --bind name:s:new_setting"
+ + " --bind value:s:new_value\n"
+ + "\n"
+ + "usage: adb shell content update --uri <URI> [--where <WHERE>]\n"
+ + " <WHERE> is a SQL style where clause in quotes (You have to escape single quotes"
+ + " - see example below).\n"
+ + " Example:\n"
+ + " # Change \"new_setting\" secure setting to \"newer_value\".\n"
+ + " adb shell content update --uri content://settings/secure --bind"
+ + " value:s:newer_value --where \"name=\'new_setting\'\"\n"
+ + "\n"
+ + "usage: adb shell content delete --uri <URI> --bind <BINDING>"
+ + " [--bind <BINDING>...] [--where <WHERE>]\n"
+ + " Example:\n"
+ + " # Remove \"new_setting\" secure setting.\n"
+ + " adb shell content delete --uri content://settings/secure "
+ + "--where \"name=\'new_setting\'\"\n"
+ + "\n"
+ + "usage: adb shell content query --uri <URI> [--projection <PROJECTION>]"
+ + " [--where <WHERE>] [--sort <SORT_ORDER>]\n"
+ + " <PROJECTION> is a list of colon separated column names and is formatted:\n"
+ + " <COLUMN_NAME>[:<COLUMN_NAME>...]\n"
+ + " <SORT_OREDER> is the order in which rows in the result should be sorted.\n"
+ + " Example:\n"
+ + " # Select \"name\" and \"value\" columns from secure settings where \"name\" is "
+ + "equal to \"new_setting\" and sort the result by name in ascending order.\n"
+ + " adb shell content query --uri content://settings/secure --projection name:value"
+ + " --where \"name=\'new_setting\'\" --sort \"name ASC\"\n"
+ + "\n";
+
+ private static class Parser {
+ private static final String ARGUMENT_INSERT = "insert";
+ private static final String ARGUMENT_DELETE = "delete";
+ private static final String ARGUMENT_UPDATE = "update";
+ private static final String ARGUMENT_QUERY = "query";
+ private static final String ARGUMENT_WHERE = "--where";
+ private static final String ARGUMENT_BIND = "--bind";
+ private static final String ARGUMENT_URI = "--uri";
+ private static final String ARGUMENT_PROJECTION = "--projection";
+ private static final String ARGUMENT_SORT = "--sort";
+ private static final String TYPE_BOOLEAN = "b";
+ private static final String TYPE_STRING = "s";
+ private static final String TYPE_INTEGER = "i";
+ private static final String TYPE_LONG = "l";
+ private static final String TYPE_FLOAT = "f";
+ private static final String TYPE_DOUBLE = "d";
+ private static final String COLON = ":";
+ private static final String ARGUMENT_PREFIX = "--";
+
+ private final Tokenizer mTokenizer;
+
+ public Parser(String[] args) {
+ mTokenizer = new Tokenizer(args);
+ }
+
+ public Command parseCommand() {
+ try {
+ String operation = mTokenizer.nextArg();
+ if (ARGUMENT_INSERT.equals(operation)) {
+ return parseInsertCommand();
+ } else if (ARGUMENT_DELETE.equals(operation)) {
+ return parseDeleteCommand();
+ } else if (ARGUMENT_UPDATE.equals(operation)) {
+ return parseUpdateCommand();
+ } else if (ARGUMENT_QUERY.equals(operation)) {
+ return parseQueryCommand();
+ } else {
+ throw new IllegalArgumentException("Unsupported operation: " + operation);
+ }
+ } catch (IllegalArgumentException iae) {
+ System.out.println(USAGE);
+ System.out.println("[ERROR] " + iae.getMessage());
+ return null;
+ }
+ }
+
+ private InsertCommand parseInsertCommand() {
+ Uri uri = null;
+ ContentValues values = new ContentValues();
+ for (String argument; (argument = mTokenizer.nextArg()) != null;) {
+ if (ARGUMENT_URI.equals(argument)) {
+ uri = Uri.parse(argumentValueRequired(argument));
+ } else if (ARGUMENT_BIND.equals(argument)) {
+ parseBindValue(values);
+ } else {
+ throw new IllegalArgumentException("Unsupported argument: " + argument);
+ }
+ }
+ if (uri == null) {
+ throw new IllegalArgumentException("Content provider URI not specified."
+ + " Did you specify --uri argument?");
+ }
+ if (values.size() == 0) {
+ throw new IllegalArgumentException("Bindings not specified."
+ + " Did you specify --bind argument(s)?");
+ }
+ return new InsertCommand(uri, values);
+ }
+
+ private DeleteCommand parseDeleteCommand() {
+ Uri uri = null;
+ String where = null;
+ for (String argument; (argument = mTokenizer.nextArg())!= null;) {
+ if (ARGUMENT_URI.equals(argument)) {
+ uri = Uri.parse(argumentValueRequired(argument));
+ } else if (ARGUMENT_WHERE.equals(argument)) {
+ where = argumentValueRequired(argument);
+ } else {
+ throw new IllegalArgumentException("Unsupported argument: " + argument);
+ }
+ }
+ if (uri == null) {
+ throw new IllegalArgumentException("Content provider URI not specified."
+ + " Did you specify --uri argument?");
+ }
+ return new DeleteCommand(uri, where);
+ }
+
+ private UpdateCommand parseUpdateCommand() {
+ Uri uri = null;
+ String where = null;
+ ContentValues values = new ContentValues();
+ for (String argument; (argument = mTokenizer.nextArg())!= null;) {
+ if (ARGUMENT_URI.equals(argument)) {
+ uri = Uri.parse(argumentValueRequired(argument));
+ } else if (ARGUMENT_WHERE.equals(argument)) {
+ where = argumentValueRequired(argument);
+ } else if (ARGUMENT_BIND.equals(argument)) {
+ parseBindValue(values);
+ } else {
+ throw new IllegalArgumentException("Unsupported argument: " + argument);
+ }
+ }
+ if (uri == null) {
+ throw new IllegalArgumentException("Content provider URI not specified."
+ + " Did you specify --uri argument?");
+ }
+ if (values.size() == 0) {
+ throw new IllegalArgumentException("Bindings not specified."
+ + " Did you specify --bind argument(s)?");
+ }
+ return new UpdateCommand(uri, values, where);
+ }
+
+ public QueryCommand parseQueryCommand() {
+ Uri uri = null;
+ String[] projection = null;
+ String sort = null;
+ String where = null;
+ for (String argument; (argument = mTokenizer.nextArg())!= null;) {
+ if (ARGUMENT_URI.equals(argument)) {
+ uri = Uri.parse(argumentValueRequired(argument));
+ } else if (ARGUMENT_WHERE.equals(argument)) {
+ where = argumentValueRequired(argument);
+ } else if (ARGUMENT_SORT.equals(argument)) {
+ sort = argumentValueRequired(argument);
+ } else if (ARGUMENT_PROJECTION.equals(argument)) {
+ projection = argumentValueRequired(argument).split("[\\s]*:[\\s]*");
+ } else {
+ throw new IllegalArgumentException("Unsupported argument: " + argument);
+ }
+ }
+ if (uri == null) {
+ throw new IllegalArgumentException("Content provider URI not specified."
+ + " Did you specify --uri argument?");
+ }
+ return new QueryCommand(uri, projection, where, sort);
+ }
+
+ private void parseBindValue(ContentValues values) {
+ String argument = mTokenizer.nextArg();
+ if (TextUtils.isEmpty(argument)) {
+ throw new IllegalArgumentException("Binding not well formed: " + argument);
+ }
+ String[] binding = argument.split(COLON);
+ if (binding.length != 3) {
+ throw new IllegalArgumentException("Binding not well formed: " + argument);
+ }
+ String column = binding[0];
+ String type = binding[1];
+ String value = binding[2];
+ if (TYPE_STRING.equals(type)) {
+ values.put(column, value);
+ } else if (TYPE_BOOLEAN.equalsIgnoreCase(type)) {
+ values.put(column, Boolean.parseBoolean(value));
+ } else if (TYPE_INTEGER.equalsIgnoreCase(type) || TYPE_LONG.equalsIgnoreCase(type)) {
+ values.put(column, Long.parseLong(value));
+ } else if (TYPE_FLOAT.equalsIgnoreCase(type) || TYPE_DOUBLE.equalsIgnoreCase(type)) {
+ values.put(column, Double.parseDouble(value));
+ } else {
+ throw new IllegalArgumentException("Unsupported type: " + type);
+ }
+ }
+
+ private String argumentValueRequired(String argument) {
+ String value = mTokenizer.nextArg();
+ if (TextUtils.isEmpty(value) || value.startsWith(ARGUMENT_PREFIX)) {
+ throw new IllegalArgumentException("No value for argument: " + argument);
+ }
+ return value;
+ }
+ }
+
+ private static class Tokenizer {
+ private final String[] mArgs;
+ private int mNextArg;
+
+ public Tokenizer(String[] args) {
+ mArgs = args;
+ }
+
+ private String nextArg() {
+ if (mNextArg < mArgs.length) {
+ return mArgs[mNextArg++];
+ } else {
+ return null;
+ }
+ }
+ }
+
+ private static abstract class Command {
+ final Uri mUri;
+
+ public Command(Uri uri) {
+ mUri = uri;
+ }
+
+ public final void execute() {
+ String providerName = mUri.getAuthority();
+ try {
+ IActivityManager activityManager = ActivityManagerNative.getDefault();
+ IContentProvider provider = null;
+ IBinder token = new Binder();
+ try {
+ ContentProviderHolder holder = activityManager.getContentProviderExternal(
+ providerName, token);
+ if (holder == null) {
+ throw new IllegalStateException("Could not find provider: " + providerName);
+ }
+ provider = holder.provider;
+ onExecute(provider);
+ } finally {
+ if (provider != null) {
+ activityManager.removeContentProviderExternal(providerName, token);
+ }
+ }
+ } catch (Exception e) {
+ System.err.println("Error while accessing provider:" + providerName);
+ e.printStackTrace();
+ }
+ }
+
+ protected abstract void onExecute(IContentProvider provider) throws Exception;
+ }
+
+ private static class InsertCommand extends Command {
+ final ContentValues mContentValues;
+
+ public InsertCommand(Uri uri, ContentValues contentValues) {
+ super(uri);
+ mContentValues = contentValues;
+ }
+
+ @Override
+ public void onExecute(IContentProvider provider) throws Exception {
+ provider.insert(mUri, mContentValues);
+ }
+ }
+
+ private static class DeleteCommand extends Command {
+ final String mWhere;
+
+ public DeleteCommand(Uri uri, String where) {
+ super(uri);
+ mWhere = where;
+ }
+
+ @Override
+ public void onExecute(IContentProvider provider) throws Exception {
+ provider.delete(mUri, mWhere, null);
+ }
+ }
+
+ private static class QueryCommand extends DeleteCommand {
+ final String[] mProjection;
+ final String mSortOrder;
+
+ public QueryCommand(Uri uri, String[] projection, String where, String sortOrder) {
+ super(uri, where);
+ mProjection = projection;
+ mSortOrder = sortOrder;
+ }
+
+ @Override
+ public void onExecute(IContentProvider provider) throws Exception {
+ Cursor cursor = provider.query(mUri, mProjection, mWhere, null, mSortOrder, null);
+ if (cursor == null) {
+ System.out.println("No result found.");
+ return;
+ }
+ try {
+ if (cursor.moveToFirst()) {
+ int rowIndex = 0;
+ StringBuilder builder = new StringBuilder();
+ do {
+ builder.setLength(0);
+ builder.append("Row: ").append(rowIndex).append(" ");
+ rowIndex++;
+ final int columnCount = cursor.getColumnCount();
+ for (int i = 0; i < columnCount; i++) {
+ if (i > 0) {
+ builder.append(", ");
+ }
+ String columnName = cursor.getColumnName(i);
+ String columnValue = null;
+ final int columnIndex = cursor.getColumnIndex(columnName);
+ final int type = cursor.getType(columnIndex);
+ switch (type) {
+ case Cursor.FIELD_TYPE_FLOAT:
+ columnValue = String.valueOf(cursor.getFloat(columnIndex));
+ break;
+ case Cursor.FIELD_TYPE_INTEGER:
+ columnValue = String.valueOf(cursor.getInt(columnIndex));
+ break;
+ case Cursor.FIELD_TYPE_STRING:
+ columnValue = cursor.getString(columnIndex);
+ break;
+ case Cursor.FIELD_TYPE_BLOB:
+ columnValue = "BLOB";
+ break;
+ case Cursor.FIELD_TYPE_NULL:
+ columnValue = "NULL";
+ break;
+ }
+ builder.append(columnName).append("=").append(columnValue);
+ }
+ System.out.println(builder);
+ } while (cursor.moveToNext());
+ } else {
+ System.out.println("No reuslt found.");
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+ }
+
+ private static class UpdateCommand extends InsertCommand {
+ final String mWhere;
+
+ public UpdateCommand(Uri uri, ContentValues contentValues, String where) {
+ super(uri, contentValues);
+ mWhere = where;
+ }
+
+ @Override
+ public void onExecute(IContentProvider provider) throws Exception {
+ provider.update(mUri, mContentValues, mWhere, null);
+ }
+ }
+
+ public static void main(String[] args) {
+ Parser parser = new Parser(args);
+ Command command = parser.parseCommand();
+ if (command != null) {
+ command.execute();
+ }
+ }
+}
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 5a36466..24079a5 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -581,6 +581,21 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case GET_CONTENT_PROVIDER_EXTERNAL_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ String name = data.readString();
+ IBinder token = data.readStrongBinder();
+ ContentProviderHolder cph = getContentProviderExternal(name, token);
+ reply.writeNoException();
+ if (cph != null) {
+ reply.writeInt(1);
+ cph.writeToParcel(reply, 0);
+ } else {
+ reply.writeInt(0);
+ }
+ return true;
+ }
+
case PUBLISH_CONTENT_PROVIDERS_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
@@ -601,7 +616,16 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
reply.writeNoException();
return true;
}
-
+
+ case REMOVE_CONTENT_PROVIDER_EXTERNAL_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ String name = data.readString();
+ IBinder token = data.readStrongBinder();
+ removeContentProviderExternal(name, token);
+ reply.writeNoException();
+ return true;
+ }
+
case GET_RUNNING_SERVICE_CONTROL_PANEL_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
ComponentName comp = ComponentName.CREATOR.createFromParcel(data);
@@ -2178,6 +2202,25 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
return cph;
}
+ public ContentProviderHolder getContentProviderExternal(String name, IBinder token)
+ throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeString(name);
+ data.writeStrongBinder(token);
+ mRemote.transact(GET_CONTENT_PROVIDER_EXTERNAL_TRANSACTION, data, reply, 0);
+ reply.readException();
+ int res = reply.readInt();
+ ContentProviderHolder cph = null;
+ if (res != 0) {
+ cph = ContentProviderHolder.CREATOR.createFromParcel(reply);
+ }
+ data.recycle();
+ reply.recycle();
+ return cph;
+ }
public void publishContentProviders(IApplicationThread caller,
List<ContentProviderHolder> providers) throws RemoteException
{
@@ -2204,7 +2247,19 @@ class ActivityManagerProxy implements IActivityManager
data.recycle();
reply.recycle();
}
-
+
+ public void removeContentProviderExternal(String name, IBinder token) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeString(name);
+ data.writeStrongBinder(token);
+ mRemote.transact(REMOVE_CONTENT_PROVIDER_EXTERNAL_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
public PendingIntent getRunningServiceControlPanel(ComponentName service)
throws RemoteException
{
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 7deb615..53a71db 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -150,8 +150,11 @@ public interface IActivityManager extends IInterface {
Bitmap thumbnail, CharSequence description) throws RemoteException;
public ContentProviderHolder getContentProvider(IApplicationThread caller,
String name) throws RemoteException;
+ public ContentProviderHolder getContentProviderExternal(String name, IBinder token)
+ throws RemoteException;
public void removeContentProvider(IApplicationThread caller,
String name) throws RemoteException;
+ public void removeContentProviderExternal(String name, IBinder token) throws RemoteException;
public void publishContentProviders(IApplicationThread caller,
List<ContentProviderHolder> providers) throws RemoteException;
public PendingIntent getRunningServiceControlPanel(ComponentName service)
@@ -415,7 +418,7 @@ public interface IActivityManager extends IInterface {
source.readStrongBinder());
noReleaseNeeded = source.readInt() != 0;
}
- };
+ }
/** Information returned after waiting for an activity start. */
public static class WaitResult implements Parcelable {
@@ -601,4 +604,6 @@ public interface IActivityManager extends IInterface {
int SHOW_BOOT_MESSAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+137;
int DISMISS_KEYGUARD_ON_NEXT_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+138;
int KILL_ALL_BACKGROUND_PROCESSES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+139;
+ int GET_CONTENT_PROVIDER_EXTERNAL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+140;
+ int REMOVE_CONTENT_PROVIDER_EXTERNAL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+141;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 68c919e..a2b1117 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1521,6 +1521,17 @@
android:description="@string/permdesc_serialPort"
android:protectionLevel="normal" />
+ <!-- Allows the holder to access content providers from outside an ApplicationThread.
+ This permission is enforced by the ActivityManagerService on the corresponding APIs,
+ in particular ActivityManagerService#getContentProviderExternal(String) and
+ ActivityManagerService#removeContentProviderExternal(String).
+ @hide
+ -->
+ <permission android:name="android.permission.ACCESS_CONTENT_PROVIDERS_EXTERNALLY"
+ android:label="@string/permlab_accessContentProvidersExternally"
+ android:description="@string/permdesc_accessContentProvidersExternally"
+ android:protectionLevel="signature" />
+
<!-- The system process is explicitly the only one allowed to launch the
confirmation UI for full backup/restore -->
<uses-permission android:name="android.permission.CONFIRM_FULL_BACKUP"/>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index c95dddd..061460a 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2248,6 +2248,14 @@
<!-- Description of an application permission which allows the application access serial ports via the SerialManager. [CHAR LIMIT=NONE] -->
<string name="permdesc_serialPort">Allows the holder to access serial ports using the SerialManager API.</string>
+ <!-- Title of an application permission which allows the holder to access content
+ providers from outside an ApplicationThread. [CHAR LIMIT=40] -->
+ <string name="permlab_accessContentProvidersExternally">access content providers externally</string>
+ <!-- Description of an application permission which allows the holder to access
+ content providers from outside an ApplicationThread. [CHAR LIMIT=NONE] -->
+ <string name="permdesc_accessContentProvidersExternally">Allows the holder to access content
+ providers from the shell. Should never be needed for normal apps.</string>
+
<!-- If the user enters a password in a form on a website, a dialog will come up asking if they want to save the password. Text in the save password dialog, asking if the browser should remember a password. -->
<string name="save_password_message">Do you want the browser to remember this password?</string>
<!-- If the user enters a password in a form on a website, a dialog will come up asking if they want to save the password. Button in the save password dialog, saying not to remember this password. -->
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 6cd07a3..8be1db2 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -158,6 +158,7 @@
<assign-permission name="android.permission.BACKUP" uid="shell" />
<assign-permission name="android.permission.FORCE_STOP_PACKAGES" uid="shell" />
<assign-permission name="android.permission.STOP_APP_SWITCHES" uid="shell" />
+ <assign-permission name="android.permission.ACCESS_CONTENT_PROVIDERS_EXTERNALLY" uid="shell" />
<assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="media" />
<assign-permission name="android.permission.ACCESS_DRM" uid="media" />
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 9d5caae..7852123 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -5754,7 +5754,7 @@ public final class ActivityManagerService extends ActivityManagerNative
ComponentName comp = new ComponentName(cpi.packageName, cpi.name);
ContentProviderRecord cpr = mProviderMap.getProviderByClass(comp, userId);
if (cpr == null) {
- cpr = new ContentProviderRecord(cpi, app.info, comp);
+ cpr = new ContentProviderRecord(this, cpi, app.info, comp);
mProviderMap.putProviderByClass(comp, cpr);
}
if (DEBUG_MU)
@@ -5826,7 +5826,8 @@ public final class ActivityManagerService extends ActivityManagerNative
return msg;
}
- boolean incProviderCount(ProcessRecord r, ContentProviderRecord cpr) {
+ boolean incProviderCount(ProcessRecord r, final ContentProviderRecord cpr,
+ IBinder externalProcessToken) {
if (r != null) {
Integer cnt = r.conProviders.get(cpr);
if (DEBUG_PROVIDER) Slog.v(TAG,
@@ -5842,12 +5843,13 @@ public final class ActivityManagerService extends ActivityManagerNative
r.conProviders.put(cpr, new Integer(cnt.intValue()+1));
}
} else {
- cpr.externals++;
+ cpr.addExternalProcessHandleLocked(externalProcessToken);
}
return false;
}
- boolean decProviderCount(ProcessRecord r, ContentProviderRecord cpr) {
+ boolean decProviderCount(ProcessRecord r, final ContentProviderRecord cpr,
+ IBinder externalProcessToken) {
if (r != null) {
Integer cnt = r.conProviders.get(cpr);
if (DEBUG_PROVIDER) Slog.v(TAG,
@@ -5863,13 +5865,13 @@ public final class ActivityManagerService extends ActivityManagerNative
r.conProviders.put(cpr, new Integer(cnt.intValue()-1));
}
} else {
- cpr.externals++;
+ cpr.removeExternalProcessHandleLocked(externalProcessToken);
}
return false;
}
private final ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
- String name) {
+ String name, IBinder token) {
ContentProviderRecord cpr;
ProviderInfo cpi = null;
@@ -5913,7 +5915,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// In this case the provider instance already exists, so we can
// return it right away.
- final boolean countChanged = incProviderCount(r, cpr);
+ final boolean countChanged = incProviderCount(r, cpr, token);
if (countChanged) {
if (cpr.proc != null && r.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) {
// If this is a perceptible app accessing the provider,
@@ -5947,7 +5949,7 @@ public final class ActivityManagerService extends ActivityManagerNative
Slog.i(TAG,
"Existing provider " + cpr.name.flattenToShortString()
+ " is crashing; detaching " + r);
- boolean lastRef = decProviderCount(r, cpr);
+ boolean lastRef = decProviderCount(r, cpr, token);
appDiedLocked(cpr.proc, cpr.proc.pid, cpr.proc.thread);
if (!lastRef) {
// This wasn't the last ref our process had on
@@ -6005,7 +6007,7 @@ public final class ActivityManagerService extends ActivityManagerNative
return null;
}
ai = getAppInfoForUser(ai, Binder.getOrigCallingUser());
- cpr = new ContentProviderRecord(cpi, ai, comp);
+ cpr = new ContentProviderRecord(this, cpi, ai, comp);
} catch (RemoteException ex) {
// pm is in same process, this will never happen.
}
@@ -6075,8 +6077,9 @@ public final class ActivityManagerService extends ActivityManagerNative
if (firstClass) {
mProviderMap.putProviderByClass(comp, cpr);
}
+
mProviderMap.putProviderByName(name, cpr);
- incProviderCount(r, cpr);
+ incProviderCount(r, cpr, token);
}
}
@@ -6116,12 +6119,17 @@ public final class ActivityManagerService extends ActivityManagerNative
throw new SecurityException(msg);
}
- ContentProviderHolder contentProvider = getContentProviderImpl(caller, name);
- return contentProvider;
+ return getContentProviderImpl(caller, name, null);
+ }
+
+ public ContentProviderHolder getContentProviderExternal(String name, IBinder token) {
+ enforceCallingPermission(android.Manifest.permission.ACCESS_CONTENT_PROVIDERS_EXTERNALLY,
+ "Do not have permission in call getContentProviderExternal()");
+ return getContentProviderExternalUnchecked(name, token);
}
- private ContentProviderHolder getContentProviderExternal(String name) {
- return getContentProviderImpl(null, name);
+ private ContentProviderHolder getContentProviderExternalUnchecked(String name,IBinder token) {
+ return getContentProviderImpl(null, name, token);
}
/**
@@ -6157,14 +6165,20 @@ public final class ActivityManagerService extends ActivityManagerNative
+ cpr.info.name + " in process " + r.processName);
return;
} else {
- if (decProviderCount(r, localCpr)) {
+ if (decProviderCount(r, localCpr, null)) {
updateOomAdjLocked();
}
}
}
}
- private void removeContentProviderExternal(String name) {
+ public void removeContentProviderExternal(String name, IBinder token) {
+ enforceCallingPermission(android.Manifest.permission.ACCESS_CONTENT_PROVIDERS_EXTERNALLY,
+ "Do not have permission in call removeContentProviderExternal()");
+ removeContentProviderExternalUnchecked(name, token);
+ }
+
+ private void removeContentProviderExternalUnchecked(String name, IBinder token) {
synchronized (this) {
ContentProviderRecord cpr = mProviderMap.getProviderByName(name,
Binder.getOrigCallingUser());
@@ -6178,11 +6192,18 @@ public final class ActivityManagerService extends ActivityManagerNative
ComponentName comp = new ComponentName(cpr.info.packageName, cpr.info.name);
ContentProviderRecord localCpr = mProviderMap.getProviderByClass(comp,
Binder.getOrigCallingUser());
- localCpr.externals--;
- if (localCpr.externals < 0) {
- Slog.e(TAG, "Externals < 0 for content provider " + localCpr);
+ if (localCpr.hasExternalProcessHandles()) {
+ if (localCpr.removeExternalProcessHandleLocked(token)) {
+ updateOomAdjLocked();
+ } else {
+ Slog.e(TAG, "Attmpt to remove content provider " + localCpr
+ + " with no external reference for token: "
+ + token + ".");
+ }
+ } else {
+ Slog.e(TAG, "Attmpt to remove content provider: " + localCpr
+ + " with no external references.");
}
- updateOomAdjLocked();
}
}
@@ -6286,7 +6307,7 @@ public final class ActivityManagerService extends ActivityManagerNative
ContentProviderHolder holder = null;
try {
- holder = getContentProviderExternal(name);
+ holder = getContentProviderExternalUnchecked(name, null);
if (holder != null) {
return holder.provider.getType(uri);
}
@@ -6295,7 +6316,7 @@ public final class ActivityManagerService extends ActivityManagerNative
return null;
} finally {
if (holder != null) {
- removeContentProviderExternal(name);
+ removeContentProviderExternalUnchecked(name, null);
}
Binder.restoreCallingIdentity(ident);
}
@@ -6400,7 +6421,7 @@ public final class ActivityManagerService extends ActivityManagerNative
public ParcelFileDescriptor openContentUri(Uri uri) throws RemoteException {
enforceNotIsolatedCaller("openContentUri");
String name = uri.getAuthority();
- ContentProviderHolder cph = getContentProviderExternal(name);
+ ContentProviderHolder cph = getContentProviderExternalUnchecked(name, null);
ParcelFileDescriptor pfd = null;
if (cph != null) {
// We record the binder invoker's uid in thread-local storage before
@@ -6422,7 +6443,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
// We've got the fd now, so we're done with the provider.
- removeContentProviderExternal(name);
+ removeContentProviderExternalUnchecked(name, null);
} else {
Slog.d(TAG, "Failed to get provider for authority '" + name + "'");
}
@@ -10253,7 +10274,7 @@ public final class ActivityManagerService extends ActivityManagerNative
for (int i=0; i<NL; i++) {
ContentProviderRecord cpr = (ContentProviderRecord)
mLaunchingProviders.get(i);
- if (cpr.clients.size() <= 0 && cpr.externals <= 0) {
+ if (cpr.clients.size() <= 0 && !cpr.hasExternalProcessHandles()) {
synchronized (cpr) {
cpr.launchingApp = null;
cpr.notifyAll();
@@ -13455,7 +13476,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// If the provider has external (non-framework) process
// dependencies, ensure that its adjustment is at least
// FOREGROUND_APP_ADJ.
- if (cpr.externals != 0) {
+ if (cpr.hasExternalProcessHandles()) {
if (adj > ProcessList.FOREGROUND_APP_ADJ) {
adj = ProcessList.FOREGROUND_APP_ADJ;
schedGroup = Process.THREAD_GROUP_DEFAULT;
diff --git a/services/java/com/android/server/am/ContentProviderRecord.java b/services/java/com/android/server/am/ContentProviderRecord.java
index 3835553..f338cfc 100644
--- a/services/java/com/android/server/am/ContentProviderRecord.java
+++ b/services/java/com/android/server/am/ContentProviderRecord.java
@@ -20,24 +20,35 @@ import android.app.IActivityManager.ContentProviderHolder;
import android.content.ComponentName;
import android.content.pm.ApplicationInfo;
import android.content.pm.ProviderInfo;
+import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
import android.os.Process;
+import android.os.RemoteException;
+import android.util.Slog;
import java.io.PrintWriter;
+import java.util.HashMap;
import java.util.HashSet;
class ContentProviderRecord extends ContentProviderHolder {
// All attached clients
final HashSet<ProcessRecord> clients = new HashSet<ProcessRecord>();
+ // Handles for non-framework processes supported by this provider
+ HashMap<IBinder, ExternalProcessHandle> externalProcessTokenToHandle;
+ // Count for external process for which we have no handles.
+ int externalProcessNoHandleCount;
+ final ActivityManagerService service;
final int uid;
final ApplicationInfo appInfo;
final ComponentName name;
- int externals; // number of non-framework processes supported by this provider
ProcessRecord proc; // if non-null, hosting process.
ProcessRecord launchingApp; // if non-null, waiting for this app to be launched.
String stringName;
-
- public ContentProviderRecord(ProviderInfo _info, ApplicationInfo ai, ComponentName _name) {
+
+ public ContentProviderRecord(ActivityManagerService _service, ProviderInfo _info,
+ ApplicationInfo ai, ComponentName _name) {
super(_info);
+ service = _service;
uid = ai.uid;
appInfo = ai;
name = _name;
@@ -50,6 +61,7 @@ class ContentProviderRecord extends ContentProviderHolder {
appInfo = cpr.appInfo;
name = cpr.name;
noReleaseNeeded = cpr.noReleaseNeeded;
+ service = cpr.service;
}
public boolean canRunHere(ProcessRecord app) {
@@ -57,6 +69,57 @@ class ContentProviderRecord extends ContentProviderHolder {
&& (uid == Process.SYSTEM_UID || uid == app.info.uid);
}
+ public void addExternalProcessHandleLocked(IBinder token) {
+ if (token == null) {
+ externalProcessNoHandleCount++;
+ } else {
+ if (externalProcessTokenToHandle == null) {
+ externalProcessTokenToHandle = new HashMap<IBinder, ExternalProcessHandle>();
+ }
+ ExternalProcessHandle handle = externalProcessTokenToHandle.get(token);
+ if (handle == null) {
+ handle = new ExternalProcessHandle(token);
+ externalProcessTokenToHandle.put(token, handle);
+ }
+ handle.mAcquisitionCount++;
+ }
+ }
+
+ public boolean removeExternalProcessHandleLocked(IBinder token) {
+ if (hasExternalProcessHandles()) {
+ boolean hasHandle = false;
+ if (externalProcessTokenToHandle != null) {
+ ExternalProcessHandle handle = externalProcessTokenToHandle.get(token);
+ if (handle != null) {
+ hasHandle = true;
+ handle.mAcquisitionCount--;
+ if (handle.mAcquisitionCount == 0) {
+ removeExternalProcessHandleInternalLocked(token);
+ return true;
+ }
+ }
+ }
+ if (!hasHandle) {
+ externalProcessNoHandleCount--;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void removeExternalProcessHandleInternalLocked(IBinder token) {
+ ExternalProcessHandle handle = externalProcessTokenToHandle.get(token);
+ handle.unlinkFromOwnDeathLocked();
+ externalProcessTokenToHandle.remove(token);
+ if (externalProcessTokenToHandle.size() == 0) {
+ externalProcessTokenToHandle = null;
+ }
+ }
+
+ public boolean hasExternalProcessHandles() {
+ return (externalProcessTokenToHandle != null || externalProcessNoHandleCount > 0);
+ }
+
void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("package=");
pw.print(info.applicationInfo.packageName);
@@ -73,8 +136,9 @@ class ContentProviderRecord extends ContentProviderHolder {
pw.print("multiprocess="); pw.print(info.multiprocess);
pw.print(" initOrder="); pw.println(info.initOrder);
}
- if (externals != 0) {
- pw.print(prefix); pw.print("externals="); pw.println(externals);
+ if (hasExternalProcessHandles()) {
+ pw.print(prefix); pw.print("externals=");
+ pw.println(externalProcessTokenToHandle.size());
}
if (clients.size() > 0) {
pw.print(prefix); pw.println("Clients:");
@@ -84,6 +148,7 @@ class ContentProviderRecord extends ContentProviderHolder {
}
}
+ @Override
public String toString() {
if (stringName != null) {
return stringName;
@@ -96,4 +161,35 @@ class ContentProviderRecord extends ContentProviderHolder {
sb.append('}');
return stringName = sb.toString();
}
+
+ // This class represents a handle from an external process to a provider.
+ private class ExternalProcessHandle implements DeathRecipient {
+ private static final String LOG_TAG = "ExternalProcessHanldle";
+
+ private final IBinder mToken;
+ private int mAcquisitionCount;
+
+ public ExternalProcessHandle(IBinder token) {
+ mToken = token;
+ try {
+ token.linkToDeath(this, 0);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Couldn't register for death for token: " + mToken, re);
+ }
+ }
+
+ public void unlinkFromOwnDeathLocked() {
+ mToken.unlinkToDeath(this, 0);
+ }
+
+ @Override
+ public void binderDied() {
+ synchronized (service) {
+ if (hasExternalProcessHandles() &&
+ externalProcessTokenToHandle.get(mToken) != null) {
+ removeExternalProcessHandleInternalLocked(mToken);
+ }
+ }
+ }
+ }
}