From c895be7bc68b6f5b37fbb9881f464dd5ea0eb017 Mon Sep 17 00:00:00 2001 From: Dianne Hackborn Date: Mon, 11 Mar 2013 17:48:43 -0700 Subject: [PATCH] Implement limited shared libraries in apks. You can now declare shared libraries in apks that are on the system image. This is like the existing mechanism of using raw jar files as shared libraries, but since they are contained in an apk the library can actually be updated from the Play Store. And this even (mostly) works. There are some deliberate limitations on this feature. A new shared library *must* be declared by an apk on the system image. Installing an update to a system image apk does not allow you to add new shared libraries; they must be defined by everything on the base system image. This allows us to get rid of a lot of ugly edge cases (shared libraries that were there disappearing after an update is uninstalled for example) and give some brakes on apps that happen to be pre-installed on devices from being able to throw in new shared libraries after the fact. In working on this, I ran into a recently introduced bug where uninstalling updated to system apps would fail. This was done to allow for the new restricted users that don't have all system apps, but conflicts with the existing semantics for uninstalling system apps. To fix this I added a new uninstall flag that lets you switch on the new mode if desired. Also to implement the desired logic for limitations on declaring new shared libraries in app updates, I needed to slightly tweak the initial boot to keep the Package object for hidden system packages associated with their PackageSetting, so we can look at it to determine which shared libraries are allowed. I think this is probably more right than it was before -- we already need to parse the package anyway, so we have it, and when you install an update to a system app we are in this same state until you reboot anyway. And having this fixed also allowed me to fix another bug where we wouldn't grant a new permission to an updated app if its system image version is updated to request the permission but its version is still older than whatever is currently installed as an update. So that's good. Also add new sample code showing the implementation of an apk shared library and a client app using it. Change-Id: I8ccca8f3c3bffd036c5968e22bd7f8a73e69be22 --- .../android/content/pm/PackageManager.java | 11 + .../android/content/pm/PackageParser.java | 25 +- core/res/res/values/attrs_manifest.xml | 15 + .../server/pm/PackageManagerService.java | 473 +++++++++++++++--- .../java/com/android/server/pm/Settings.java | 297 ++++++----- tests/SharedLibrary/client/Android.mk | 12 + .../SharedLibrary/client/AndroidManifest.xml | 29 ++ .../client/res/values/strings.xml | 19 + .../android/test/lib_client/ActivityMain.java | 35 ++ tests/SharedLibrary/lib/Android.mk | 10 + tests/SharedLibrary/lib/AndroidManifest.xml | 29 ++ .../SharedLibrary/lib/res/values/strings.xml | 22 + .../test/shared_library/ActivityMain.java | 32 ++ .../shared_library/SharedLibraryMain.java | 87 ++++ 14 files changed, 885 insertions(+), 211 deletions(-) create mode 100644 tests/SharedLibrary/client/Android.mk create mode 100644 tests/SharedLibrary/client/AndroidManifest.xml create mode 100644 tests/SharedLibrary/client/res/values/strings.xml create mode 100644 tests/SharedLibrary/client/src/com/google/android/test/lib_client/ActivityMain.java create mode 100644 tests/SharedLibrary/lib/Android.mk create mode 100644 tests/SharedLibrary/lib/AndroidManifest.xml create mode 100644 tests/SharedLibrary/lib/res/values/strings.xml create mode 100644 tests/SharedLibrary/lib/src/com/google/android/test/shared_library/ActivityMain.java create mode 100644 tests/SharedLibrary/lib/src/com/google/android/test/shared_library/SharedLibraryMain.java diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index c507245059272..0d463ee6d3ec4 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -690,6 +690,17 @@ public abstract class PackageManager { */ public static final int DELETE_ALL_USERS = 0x00000002; + /** + * Flag parameter for {@link #deletePackage} to indicate that, if you are calling + * uninstall on a system that has been updated, then don't do the normal process + * of uninstalling the update and rolling back to the older system version (which + * needs to happen for all users); instead, just mark the app as uninstalled for + * the current user. + * + * @hide + */ + public static final int DELETE_SYSTEM_APP = 0x00000004; + /** * Return code for when package deletion succeeds. This is passed to the * {@link IPackageDeleteObserver} by {@link #deletePackage()} if the system diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index e1887bc644525..5eac90373d173 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -1941,6 +1941,28 @@ public class PackageParser { return false; } + } else if (tagName.equals("library")) { + sa = res.obtainAttributes(attrs, + com.android.internal.R.styleable.AndroidManifestLibrary); + + // Note: don't allow this value to be a reference to a resource + // that may change. + String lname = sa.getNonResourceString( + com.android.internal.R.styleable.AndroidManifestLibrary_name); + + sa.recycle(); + + if (lname != null) { + if (owner.libraryNames == null) { + owner.libraryNames = new ArrayList(); + } + if (!owner.libraryNames.contains(lname)) { + owner.libraryNames.add(lname.intern()); + } + } + + XmlUtils.skipCurrentTag(parser); + } else if (tagName.equals("uses-library")) { sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.AndroidManifestUsesLibrary); @@ -3182,7 +3204,8 @@ public class PackageParser { public final ArrayList requestedPermissionsRequired = new ArrayList(); public ArrayList protectedBroadcasts; - + + public ArrayList libraryNames = null; public ArrayList usesLibraries = null; public ArrayList usesOptionalLibraries = null; public String[] usesLibraryFiles = null; diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index d899e9dc61b6d..f1d8c03c5e58d 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -1065,6 +1065,21 @@ + + + + + + + + + + + + + + + + + + + diff --git a/tests/SharedLibrary/client/res/values/strings.xml b/tests/SharedLibrary/client/res/values/strings.xml new file mode 100644 index 0000000000000..3757a2554f8b2 --- /dev/null +++ b/tests/SharedLibrary/client/res/values/strings.xml @@ -0,0 +1,19 @@ + + + + + SharedLibrary client + diff --git a/tests/SharedLibrary/client/src/com/google/android/test/lib_client/ActivityMain.java b/tests/SharedLibrary/client/src/com/google/android/test/lib_client/ActivityMain.java new file mode 100644 index 0000000000000..d6121a503780b --- /dev/null +++ b/tests/SharedLibrary/client/src/com/google/android/test/lib_client/ActivityMain.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013 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.google.android.test.lib_client; + +import android.app.Activity; +import android.os.Bundle; +import android.widget.TextView; +import com.google.android.test.shared_library.SharedLibraryMain; + +public class ActivityMain extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + TextView content = new TextView(this); + content.setText("Library version: " + SharedLibraryMain.getVersion(this) + "!"); + + SharedLibraryMain.ensureVersion(this, SharedLibraryMain.VERSION_BASE); + setContentView(content); + } +} diff --git a/tests/SharedLibrary/lib/Android.mk b/tests/SharedLibrary/lib/Android.mk new file mode 100644 index 0000000000000..c19e23adcbeef --- /dev/null +++ b/tests/SharedLibrary/lib/Android.mk @@ -0,0 +1,10 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_PACKAGE_NAME := SharedLibrary + +LOCAL_MODULE_TAGS := tests + +include $(BUILD_PACKAGE) diff --git a/tests/SharedLibrary/lib/AndroidManifest.xml b/tests/SharedLibrary/lib/AndroidManifest.xml new file mode 100644 index 0000000000000..31fac20e41636 --- /dev/null +++ b/tests/SharedLibrary/lib/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + diff --git a/tests/SharedLibrary/lib/res/values/strings.xml b/tests/SharedLibrary/lib/res/values/strings.xml new file mode 100644 index 0000000000000..bbfb0b4d81d5f --- /dev/null +++ b/tests/SharedLibrary/lib/res/values/strings.xml @@ -0,0 +1,22 @@ + + + + + Upgrade required + %1$s requires a newer version + of %2$s to run. + Upgrade + diff --git a/tests/SharedLibrary/lib/src/com/google/android/test/shared_library/ActivityMain.java b/tests/SharedLibrary/lib/src/com/google/android/test/shared_library/ActivityMain.java new file mode 100644 index 0000000000000..895acedb1e7f3 --- /dev/null +++ b/tests/SharedLibrary/lib/src/com/google/android/test/shared_library/ActivityMain.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2013 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.google.android.test.shared_library; + +import android.app.Activity; +import android.os.Bundle; +import android.widget.TextView; + +public class ActivityMain extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + TextView content = new TextView(this); + content.setText("Dummy main entry for this apk; not really needed..."); + setContentView(content); + } +} diff --git a/tests/SharedLibrary/lib/src/com/google/android/test/shared_library/SharedLibraryMain.java b/tests/SharedLibrary/lib/src/com/google/android/test/shared_library/SharedLibraryMain.java new file mode 100644 index 0000000000000..c1cd9254a30d2 --- /dev/null +++ b/tests/SharedLibrary/lib/src/com/google/android/test/shared_library/SharedLibraryMain.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2013 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.google.android.test.shared_library; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; + +public class SharedLibraryMain { + private static String LIBRARY_PACKAGE = "com.google.android.test.shared_library"; + + /** + * Base version of the library. + */ + public static int VERSION_BASE = 1; + + /** + * The second version of the library. + */ + public static int VERSION_SECOND = 2; + + public static int getVersion(Context context) { + PackageInfo pi = null; + try { + pi = context.getPackageManager().getPackageInfo(LIBRARY_PACKAGE, 0); + return pi.versionCode; + } catch (PackageManager.NameNotFoundException e) { + throw new IllegalStateException("Can't find my package!", e); + } + } + + public static void ensureVersion(Activity activity, int minVersion) { + if (getVersion(activity) >= minVersion) { + return; + } + + // The current version of the library does not meet the required version. Show + // a dialog to inform the user and have them update to the current version. + // Note that updating the library will be necessity mean killing the current + // application (so it can be re-started with the new version, so there is no + // reason to return a result here. + final Context context; + try { + context = activity.createPackageContext(LIBRARY_PACKAGE, 0); + } catch (PackageManager.NameNotFoundException e) { + throw new IllegalStateException("Can't find my package!", e); + } + + // Display the dialog. Note that we don't need to deal with activity lifecycle + // stuff because if the activity gets recreated, it will first call through to + // ensureVersion(), causing us to either re-display the dialog if needed or let + // it now proceed. + final Resources res = context.getResources(); + AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder.setTitle(res.getText(R.string.upgrade_title)); + builder.setMessage(res.getString(R.string.upgrade_body, + activity.getApplicationInfo().loadLabel(activity.getPackageManager()), + context.getApplicationInfo().loadLabel(context.getPackageManager()))); + builder.setPositiveButton(res.getText(R.string.upgrade_button), + new Dialog.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // Launch play store. + } + }); + builder.show(); + } +}