summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Dianne Hackborn <hackbod@google.com> 2013-03-11 17:48:43 -0700
committer Dianne Hackborn <hackbod@google.com> 2013-03-12 12:51:38 -0700
commitc895be7bc68b6f5b37fbb9881f464dd5ea0eb017 (patch)
treefe7ba171c41d3df63b595adaf46a67ace2a7d7d3
parent9725d80adc7426ea20f3a193fc81dc1e8b8c4b31 (diff)
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
-rw-r--r--core/java/android/content/pm/PackageManager.java11
-rw-r--r--core/java/android/content/pm/PackageParser.java25
-rw-r--r--core/res/res/values/attrs_manifest.xml15
-rw-r--r--services/java/com/android/server/pm/PackageManagerService.java473
-rw-r--r--services/java/com/android/server/pm/Settings.java297
-rw-r--r--tests/SharedLibrary/client/Android.mk12
-rw-r--r--tests/SharedLibrary/client/AndroidManifest.xml29
-rw-r--r--tests/SharedLibrary/client/res/values/strings.xml19
-rw-r--r--tests/SharedLibrary/client/src/com/google/android/test/lib_client/ActivityMain.java35
-rw-r--r--tests/SharedLibrary/lib/Android.mk10
-rw-r--r--tests/SharedLibrary/lib/AndroidManifest.xml29
-rw-r--r--tests/SharedLibrary/lib/res/values/strings.xml22
-rw-r--r--tests/SharedLibrary/lib/src/com/google/android/test/shared_library/ActivityMain.java32
-rw-r--r--tests/SharedLibrary/lib/src/com/google/android/test/shared_library/SharedLibraryMain.java87
14 files changed, 885 insertions, 211 deletions
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index c50724505927..0d463ee6d3ec 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -691,6 +691,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
* succeeded in deleting the package.
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index e1887bc64452..5eac90373d17 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<String>();
+ }
+ 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<Boolean> requestedPermissionsRequired = new ArrayList<Boolean>();
public ArrayList<String> protectedBroadcasts;
-
+
+ public ArrayList<String> libraryNames = null;
public ArrayList<String> usesLibraries = null;
public ArrayList<String> 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 d899e9dc61b6..f1d8c03c5e58 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1065,6 +1065,21 @@
<attr name="maxSdkVersion" format="integer" />
</declare-styleable>
+ <!-- The <code>library</code> tag declares that this apk is providing itself
+ as a shared library for other applications to use. It can only be used
+ with apks that are built in to the system image. Other apks can link to
+ it with the {@link #AndroidManifestUsesLibrary uses-library} tag.
+
+ <p>This appears as a child tag of the
+ {@link #AndroidManifestApplication application} tag. -->
+ <declare-styleable name="AndroidManifestLibrary" parent="AndroidManifest">
+ <!-- Required public name of the library, which other components and
+ packages will use when referring to this library. This is a string using
+ Java-style scoping to ensure it is unique. The name should typically
+ be the same as the apk's package name. -->
+ <attr name="name" />
+ </declare-styleable>
+
<!-- The <code>uses-libraries</code> specifies a shared library that this
package requires to be linked against. Specifying this flag tells the
system to include this library's code in your class loader.
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index 51f001f655a7..2d12a774bdaf 100644
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -170,7 +170,7 @@ adb shell am instrument -w -e class com.android.unit_tests.PackageManagerTests c
public class PackageManagerService extends IPackageManager.Stub {
static final String TAG = "PackageManager";
static final boolean DEBUG_SETTINGS = false;
- static final boolean DEBUG_PREFERRED = true;
+ static final boolean DEBUG_PREFERRED = false;
static final boolean DEBUG_UPGRADE = false;
private static final boolean DEBUG_INSTALL = false;
private static final boolean DEBUG_REMOVE = false;
@@ -339,9 +339,20 @@ public class PackageManagerService extends IPackageManager.Stub {
final SparseArray<HashSet<String>> mSystemPermissions =
new SparseArray<HashSet<String>>();
+ static final class SharedLibraryEntry {
+ final String path;
+ final String apk;
+
+ SharedLibraryEntry(String _path, String _apk) {
+ path = _path;
+ apk = _apk;
+ }
+ }
+
// These are the built-in shared libraries that were read from the
// etc/permissions.xml file.
- final HashMap<String, String> mSharedLibraries = new HashMap<String, String>();
+ final HashMap<String, SharedLibraryEntry> mSharedLibraries
+ = new HashMap<String, SharedLibraryEntry>();
// Temporary for building the final shared libraries for an .apk.
String[] mTmpSharedLibraries = null;
@@ -390,8 +401,7 @@ public class PackageManagerService extends IPackageManager.Stub {
final SparseArray<PackageVerificationState> mPendingVerification
= new SparseArray<PackageVerificationState>();
- final ArrayList<PackageParser.Package> mDeferredDexOpt =
- new ArrayList<PackageParser.Package>();
+ HashSet<PackageParser.Package> mDeferredDexOpt = null;
/** Token for keys in mPendingVerification. */
private int mPendingVerificationToken = 0;
@@ -514,10 +524,9 @@ public class PackageManagerService extends IPackageManager.Stub {
void doHandleMessage(Message msg) {
switch (msg.what) {
case INIT_COPY: {
- if (DEBUG_INSTALL) Slog.i(TAG, "init_copy");
HandlerParams params = (HandlerParams) msg.obj;
int idx = mPendingInstalls.size();
- if (DEBUG_INSTALL) Slog.i(TAG, "idx=" + idx);
+ if (DEBUG_INSTALL) Slog.i(TAG, "init_copy idx=" + idx + ": " + params);
// If a bind was already initiated we dont really
// need to do anything. The pending install
// will be processed later on.
@@ -1071,9 +1080,12 @@ public class PackageManagerService extends IPackageManager.Stub {
* Also ensure all external libraries have had dexopt run on them.
*/
if (mSharedLibraries.size() > 0) {
- Iterator<String> libs = mSharedLibraries.values().iterator();
+ Iterator<SharedLibraryEntry> libs = mSharedLibraries.values().iterator();
while (libs.hasNext()) {
- String lib = libs.next();
+ String lib = libs.next().path;
+ if (lib == null) {
+ continue;
+ }
try {
if (dalvik.system.DexFile.isDexOptNeeded(lib)) {
libFiles.add(lib);
@@ -1277,6 +1289,10 @@ public class PackageManagerService extends IPackageManager.Stub {
mDrmAppInstallObserver = null;
}
+ // Now that we know all of the shared libraries, update all clients to have
+ // the correct library paths.
+ updateAllSharedLibrariesLPw();
+
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SCAN_END,
SystemClock.uptimeMillis());
Slog.i(TAG, "Time to scan packages: "
@@ -1517,7 +1533,7 @@ public class PackageManagerService extends IPackageManager.Stub {
+ parser.getPositionDescription());
} else {
//Log.i(TAG, "Got library " + lname + " in " + lfile);
- mSharedLibraries.put(lname, lfile);
+ mSharedLibraries.put(lname, new SharedLibraryEntry(lfile, null));
}
XmlUtils.skipCurrentTag(parser);
continue;
@@ -3249,6 +3265,7 @@ public class PackageManagerService extends IPackageManager.Stub {
int parseFlags, int scanMode, long currentTime, UserHandle user) {
mLastScanError = PackageManager.INSTALL_SUCCEEDED;
String scanPath = scanFile.getPath();
+ if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanPath);
parseFlags |= mDefParseFlags;
PackageParser pp = new PackageParser(scanPath);
pp.setSeparateProcesses(mSeparateProcesses);
@@ -3278,6 +3295,7 @@ public class PackageManagerService extends IPackageManager.Stub {
// package. Must look for it either under the original or real
// package name depending on our state.
updatedPkg = mSettings.getDisabledSystemPkgLPr(ps != null ? ps.name : pkg.packageName);
+ if (DEBUG_INSTALL && updatedPkg != null) Slog.d(TAG, "updatedPkg = " + updatedPkg);
}
// First check if this is a system package that may involve an update
if (updatedPkg != null && (parseFlags&PackageParser.PARSE_IS_SYSTEM) != 0) {
@@ -3285,6 +3303,7 @@ public class PackageManagerService extends IPackageManager.Stub {
// The path has changed from what was last scanned... check the
// version of the new path against what we have stored to determine
// what to do.
+ if (DEBUG_INSTALL) Slog.d(TAG, "Path changing from " + ps.codePath);
if (pkg.mVersionCode < ps.versionCode) {
// The system package has been updated and the code path does not match
// Ignore entry. Skip it.
@@ -3298,6 +3317,7 @@ public class PackageManagerService extends IPackageManager.Stub {
updatedPkg.codePath = scanFile;
updatedPkg.codePathString = scanFile.toString();
}
+ updatedPkg.pkg = pkg;
mLastScanError = PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
return null;
} else {
@@ -3353,6 +3373,7 @@ public class PackageManagerService extends IPackageManager.Stub {
*/
if (compareSignatures(ps.signatures.mSignatures, pkg.mSignatures)
!= PackageManager.SIGNATURE_MATCH) {
+ if (DEBUG_INSTALL) Slog.d(TAG, "Signature mismatch!");
deletePackageLI(pkg.packageName, null, true, 0, null, false);
ps = null;
} else {
@@ -3484,28 +3505,28 @@ public class PackageManagerService extends IPackageManager.Stub {
}
public void performBootDexOpt() {
- ArrayList<PackageParser.Package> pkgs = null;
+ HashSet<PackageParser.Package> pkgs = null;
synchronized (mPackages) {
- if (mDeferredDexOpt.size() > 0) {
- pkgs = new ArrayList<PackageParser.Package>(mDeferredDexOpt);
- mDeferredDexOpt.clear();
- }
+ pkgs = mDeferredDexOpt;
+ mDeferredDexOpt = null;
}
if (pkgs != null) {
- for (int i=0; i<pkgs.size(); i++) {
+ int i = 0;
+ for (PackageParser.Package pkg : pkgs) {
if (!isFirstBoot()) {
+ i++;
try {
ActivityManagerNative.getDefault().showBootMessage(
mContext.getResources().getString(
com.android.internal.R.string.android_upgrading_apk,
- i+1, pkgs.size()), true);
+ i, pkgs.size()), true);
} catch (RemoteException e) {
}
}
- PackageParser.Package p = pkgs.get(i);
+ PackageParser.Package p = pkg;
synchronized (mInstallLock) {
if (!p.mDidDexOpt) {
- performDexOptLI(p, false, false);
+ performDexOptLI(p, false, false, true);
}
}
}
@@ -3527,7 +3548,27 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
synchronized (mInstallLock) {
- return performDexOptLI(p, false, false) == DEX_OPT_PERFORMED;
+ return performDexOptLI(p, false, false, true) == DEX_OPT_PERFORMED;
+ }
+ }
+
+ private void performDexOptLibsLI(ArrayList<String> libs, boolean forceDex, boolean defer,
+ HashSet<String> done) {
+ for (int i=0; i<libs.size(); i++) {
+ PackageParser.Package libPkg;
+ String libName;
+ synchronized (mPackages) {
+ libName = libs.get(i);
+ SharedLibraryEntry lib = mSharedLibraries.get(libName);
+ if (lib != null && lib.apk != null) {
+ libPkg = mPackages.get(lib.apk);
+ } else {
+ libPkg = null;
+ }
+ }
+ if (libPkg != null && !done.contains(libName)) {
+ performDexOptLI(libPkg, forceDex, defer, done);
+ }
}
}
@@ -3536,14 +3577,27 @@ public class PackageManagerService extends IPackageManager.Stub {
static final int DEX_OPT_DEFERRED = 2;
static final int DEX_OPT_FAILED = -1;
- private int performDexOptLI(PackageParser.Package pkg, boolean forceDex, boolean defer) {
+ private int performDexOptLI(PackageParser.Package pkg, boolean forceDex, boolean defer,
+ HashSet<String> done) {
boolean performed = false;
+ if (done != null) {
+ done.add(pkg.packageName);
+ if (pkg.usesLibraries != null) {
+ performDexOptLibsLI(pkg.usesLibraries, forceDex, defer, done);
+ }
+ if (pkg.usesOptionalLibraries != null) {
+ performDexOptLibsLI(pkg.usesOptionalLibraries, forceDex, defer, done);
+ }
+ }
if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0) {
String path = pkg.mScanPath;
int ret = 0;
try {
if (forceDex || dalvik.system.DexFile.isDexOptNeeded(path)) {
if (!forceDex && defer) {
+ if (mDeferredDexOpt == null) {
+ mDeferredDexOpt = new HashSet<PackageParser.Package>();
+ }
mDeferredDexOpt.add(pkg);
return DEX_OPT_DEFERRED;
} else {
@@ -3576,6 +3630,19 @@ public class PackageManagerService extends IPackageManager.Stub {
return performed ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED;
}
+ private int performDexOptLI(PackageParser.Package pkg, boolean forceDex, boolean defer,
+ boolean inclDependencies) {
+ HashSet<String> done;
+ boolean performed = false;
+ if (inclDependencies && (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null)) {
+ done = new HashSet<String>();
+ done.add(pkg.packageName);
+ } else {
+ done = null;
+ }
+ return performDexOptLI(pkg, forceDex, defer, done);
+ }
+
private boolean verifyPackageUpdateLPr(PackageSetting oldPkg, PackageParser.Package newPkg) {
if ((oldPkg.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) {
Slog.w(TAG, "Unable to update from " + oldPkg.name
@@ -3646,6 +3713,113 @@ public class PackageManagerService extends IPackageManager.Stub {
return res;
}
+ private int addSharedLibraryLPw(final SharedLibraryEntry file, int num,
+ PackageParser.Package changingLib) {
+ if (file.path != null) {
+ mTmpSharedLibraries[num] = file.path;
+ return num+1;
+ }
+ PackageParser.Package p = mPackages.get(file.apk);
+ if (changingLib != null && changingLib.packageName.equals(file.apk)) {
+ // If we are doing this while in the middle of updating a library apk,
+ // then we need to make sure to use that new apk for determining the
+ // dependencies here. (We haven't yet finished committing the new apk
+ // to the package manager state.)
+ if (p == null || p.packageName.equals(changingLib.packageName)) {
+ p = changingLib;
+ }
+ }
+ if (p != null) {
+ String path = p.mPath;
+ for (int i=0; i<num; i++) {
+ if (mTmpSharedLibraries[i].equals(path)) {
+ return num;
+ }
+ }
+ mTmpSharedLibraries[num] = p.mPath;
+ return num+1;
+ }
+ return num;
+ }
+
+ private boolean updateSharedLibrariesLPw(PackageParser.Package pkg,
+ PackageParser.Package changingLib) {
+ if (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null) {
+ if (mTmpSharedLibraries == null ||
+ mTmpSharedLibraries.length < mSharedLibraries.size()) {
+ mTmpSharedLibraries = new String[mSharedLibraries.size()];
+ }
+ int num = 0;
+ int N = pkg.usesLibraries != null ? pkg.usesLibraries.size() : 0;
+ for (int i=0; i<N; i++) {
+ final SharedLibraryEntry file = mSharedLibraries.get(pkg.usesLibraries.get(i));
+ if (file == null) {
+ Slog.e(TAG, "Package " + pkg.packageName
+ + " requires unavailable shared library "
+ + pkg.usesLibraries.get(i) + "; failing!");
+ mLastScanError = PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
+ return false;
+ }
+ num = addSharedLibraryLPw(file, num, changingLib);
+ }
+ N = pkg.usesOptionalLibraries != null ? pkg.usesOptionalLibraries.size() : 0;
+ for (int i=0; i<N; i++) {
+ final SharedLibraryEntry file = mSharedLibraries.get(pkg.usesOptionalLibraries.get(i));
+ if (file == null) {
+ Slog.w(TAG, "Package " + pkg.packageName
+ + " desires unavailable shared library "
+ + pkg.usesOptionalLibraries.get(i) + "; ignoring!");
+ } else {
+ num = addSharedLibraryLPw(file, num, changingLib);
+ }
+ }
+ if (num > 0) {
+ pkg.usesLibraryFiles = new String[num];
+ System.arraycopy(mTmpSharedLibraries, 0,
+ pkg.usesLibraryFiles, 0, num);
+ } else {
+ pkg.usesLibraryFiles = null;
+ }
+ }
+ return true;
+ }
+
+ private static boolean hasString(List<String> list, List<String> which) {
+ if (list == null) {
+ return false;
+ }
+ for (int i=list.size()-1; i>=0; i--) {
+ for (int j=which.size()-1; j>=0; j--) {
+ if (which.get(j).equals(list.get(i))) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private void updateAllSharedLibrariesLPw() {
+ for (PackageParser.Package pkg : mPackages.values()) {
+ updateSharedLibrariesLPw(pkg, null);
+ }
+ }
+
+ private ArrayList<PackageParser.Package> updateAllSharedLibrariesLPw(
+ PackageParser.Package changingPkg) {
+ ArrayList<PackageParser.Package> res = null;
+ for (PackageParser.Package pkg : mPackages.values()) {
+ if (hasString(pkg.usesLibraries, changingPkg.libraryNames)
+ || hasString(pkg.usesOptionalLibraries, changingPkg.libraryNames)) {
+ if (res == null) {
+ res = new ArrayList<PackageParser.Package>();
+ }
+ res.add(pkg);
+ updateSharedLibrariesLPw(pkg, changingPkg);
+ }
+ }
+ return res;
+ }
+
private PackageParser.Package scanPackageLI(PackageParser.Package pkg,
int parseFlags, int scanMode, long currentTime, UserHandle user) {
File scanFile = new File(pkg.mScanPath);
@@ -3725,42 +3899,14 @@ public class PackageManagerService extends IPackageManager.Stub {
// writer
synchronized (mPackages) {
- // Check all shared libraries and map to their actual file path.
- if (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null) {
- if (mTmpSharedLibraries == null ||
- mTmpSharedLibraries.length < mSharedLibraries.size()) {
- mTmpSharedLibraries = new String[mSharedLibraries.size()];
- }
- int num = 0;
- int N = pkg.usesLibraries != null ? pkg.usesLibraries.size() : 0;
- for (int i=0; i<N; i++) {
- final String file = mSharedLibraries.get(pkg.usesLibraries.get(i));
- if (file == null) {
- Slog.e(TAG, "Package " + pkg.packageName
- + " requires unavailable shared library "
- + pkg.usesLibraries.get(i) + "; failing!");
- mLastScanError = PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
- return null;
- }
- mTmpSharedLibraries[num] = file;
- num++;
- }
- N = pkg.usesOptionalLibraries != null ? pkg.usesOptionalLibraries.size() : 0;
- for (int i=0; i<N; i++) {
- final String file = mSharedLibraries.get(pkg.usesOptionalLibraries.get(i));
- if (file == null) {
- Slog.w(TAG, "Package " + pkg.packageName
- + " desires unavailable shared library "
- + pkg.usesOptionalLibraries.get(i) + "; ignoring!");
- } else {
- mTmpSharedLibraries[num] = file;
- num++;
- }
- }
- if (num > 0) {
- pkg.usesLibraryFiles = new String[num];
- System.arraycopy(mTmpSharedLibraries, 0,
- pkg.usesLibraryFiles, 0, num);
+ if ((parseFlags&PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
+ // Check all shared libraries and map to their actual file path.
+ // We only do this here for apps not on a system dir, because those
+ // are the only ones that can fail an install due to this. We
+ // will take care of the system apps by updating all of their
+ // library paths after the scan is done.
+ if (!updateSharedLibrariesLPw(pkg, null)) {
+ return null;
}
}
@@ -4166,7 +4312,7 @@ public class PackageManagerService extends IPackageManager.Stub {
pkg.mScanPath = path;
if ((scanMode&SCAN_NO_DEX) == 0) {
- if (performDexOptLI(pkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0)
+ if (performDexOptLI(pkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false)
== DEX_OPT_FAILED) {
mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT;
return null;
@@ -4178,6 +4324,80 @@ public class PackageManagerService extends IPackageManager.Stub {
pkg.applicationInfo.flags |= ApplicationInfo.FLAG_FACTORY_TEST;
}
+ ArrayList<PackageParser.Package> clientLibPkgs = null;
+
+ // writer
+ synchronized (mPackages) {
+ if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0) {
+ // Only system apps can add new shared libraries.
+ if (pkg.libraryNames != null) {
+ for (int i=0; i<pkg.libraryNames.size(); i++) {
+ String name = pkg.libraryNames.get(i);
+ boolean allowed = false;
+ if (isUpdatedSystemApp(pkg)) {
+ // New library entries can only be added through the
+ // system image. This is important to get rid of a lot
+ // of nasty edge cases: for example if we allowed a non-
+ // system update of the app to add a library, then uninstalling
+ // the update would make the library go away, and assumptions
+ // we made such as through app install filtering would now
+ // have allowed apps on the device which aren't compatible
+ // with it. Better to just have the restriction here, be
+ // conservative, and create many fewer cases that can negatively
+ // impact the user experience.
+ final PackageSetting sysPs = mSettings
+ .getDisabledSystemPkgLPr(pkg.packageName);
+ if (sysPs.pkg != null && sysPs.pkg.libraryNames != null) {
+ for (int j=0; j<sysPs.pkg.libraryNames.size(); j++) {
+ if (name.equals(sysPs.pkg.libraryNames.get(j))) {
+ allowed = true;
+ allowed = true;
+ break;
+ }
+ }
+ }
+ } else {
+ allowed = true;
+ }
+ if (allowed) {
+ if (!mSharedLibraries.containsKey(name)) {
+ mSharedLibraries.put(name, new SharedLibraryEntry(null,
+ pkg.packageName));
+ } else if (!name.equals(pkg.packageName)) {
+ Slog.w(TAG, "Package " + pkg.packageName + " library "
+ + name + " already exists; skipping");
+ }
+ } else {
+ Slog.w(TAG, "Package " + pkg.packageName + " declares lib "
+ + name + " that is not declared on system image; skipping");
+ }
+ }
+ if ((scanMode&SCAN_BOOTING) == 0) {
+ // If we are not booting, we need to update any applications
+ // that are clients of our shared library. If we are booting,
+ // this will all be done once the scan is complete.
+ clientLibPkgs = updateAllSharedLibrariesLPw(pkg);
+ }
+ }
+ }
+ }
+
+ // We also need to dexopt any apps that are dependent on this library. Note that
+ // if these fail, we should abort the install since installing the library will
+ // result in some apps being broken.
+ if (clientLibPkgs != null) {
+ if ((scanMode&SCAN_NO_DEX) == 0) {
+ for (int i=0; i<clientLibPkgs.size(); i++) {
+ PackageParser.Package clientPkg = clientLibPkgs.get(i);
+ if (performDexOptLI(clientPkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false)
+ == DEX_OPT_FAILED) {
+ mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT;
+ return null;
+ }
+ }
+ }
+ }
+
// Request the ActivityManager to kill the process(only for existing packages)
// so that we do not end up in a confused state while the user is still using the older
// version of the application while the new one gets installed.
@@ -4186,6 +4406,15 @@ public class PackageManagerService extends IPackageManager.Stub {
pkg.applicationInfo.uid);
}
+ // Also need to kill any apps that are dependent on the library.
+ if (clientLibPkgs != null) {
+ for (int i=0; i<clientLibPkgs.size(); i++) {
+ PackageParser.Package clientPkg = clientLibPkgs.get(i);
+ killApplication(clientPkg.applicationInfo.packageName,
+ clientPkg.applicationInfo.uid);
+ }
+ }
+
// writer
synchronized (mPackages) {
// We don't expect installation to fail beyond this point,
@@ -4591,7 +4820,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
}
- if (chatty) {
+ if (DEBUG_REMOVE && chatty) {
if (r == null) {
r = new StringBuilder(256);
} else {
@@ -4627,7 +4856,7 @@ public class PackageManagerService extends IPackageManager.Stub {
for (i=0; i<N; i++) {
PackageParser.Activity a = pkg.receivers.get(i);
mReceivers.removeActivity(a, "receiver");
- if (chatty) {
+ if (DEBUG_REMOVE && chatty) {
if (r == null) {
r = new StringBuilder(256);
} else {
@@ -4645,7 +4874,7 @@ public class PackageManagerService extends IPackageManager.Stub {
for (i=0; i<N; i++) {
PackageParser.Activity a = pkg.activities.get(i);
mActivities.removeActivity(a, "activity");
- if (chatty) {
+ if (DEBUG_REMOVE && chatty) {
if (r == null) {
r = new StringBuilder(256);
} else {
@@ -4668,7 +4897,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
if (bp != null && bp.perm == p) {
bp.perm = null;
- if (chatty) {
+ if (DEBUG_REMOVE && chatty) {
if (r == null) {
r = new StringBuilder(256);
} else {
@@ -4687,7 +4916,7 @@ public class PackageManagerService extends IPackageManager.Stub {
for (i=0; i<N; i++) {
PackageParser.Instrumentation a = pkg.instrumentation.get(i);
mInstrumentation.remove(a.getComponentName());
- if (chatty) {
+ if (DEBUG_REMOVE && chatty) {
if (r == null) {
r = new StringBuilder(256);
} else {
@@ -4699,6 +4928,31 @@ public class PackageManagerService extends IPackageManager.Stub {
if (r != null) {
if (DEBUG_REMOVE) Log.d(TAG, " Instrumentation: " + r);
}
+
+ r = null;
+ if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0) {
+ // Only system apps can hold shared libraries.
+ if (pkg.libraryNames != null) {
+ for (i=0; i<pkg.libraryNames.size(); i++) {
+ String name = pkg.libraryNames.get(i);
+ SharedLibraryEntry cur = mSharedLibraries.get(name);
+ if (cur != null && cur.apk != null && cur.apk.equals(pkg.packageName)) {
+ mSharedLibraries.remove(name);
+ if (DEBUG_REMOVE && chatty) {
+ if (r == null) {
+ r = new StringBuilder(256);
+ } else {
+ r.append(' ');
+ }
+ r.append(name);
+ }
+ }
+ }
+ }
+ }
+ if (r != null) {
+ if (DEBUG_REMOVE) Log.d(TAG, " Libraries: " + r);
+ }
}
private static final boolean isPackageFilename(String name) {
@@ -4860,7 +5114,23 @@ public class PackageManagerService extends IPackageManager.Stub {
if (origGp.grantedPermissions.contains(perm)) {
allowed = true;
} else {
+ // The system apk may have been updated with an older
+ // version of the one on the data partition, but which
+ // granted a new system permission that it didn't have
+ // before. In this case we do want to allow the app to
+ // now get the new permission, because it is allowed by
+ // the system image.
allowed = false;
+ if (sysPs.pkg != null) {
+ for (int j=0;
+ j<sysPs.pkg.requestedPermissions.size(); j++) {
+ if (perm.equals(
+ sysPs.pkg.requestedPermissions.get(j))) {
+ allowed = true;
+ break;
+ }
+ }
+ }
}
} else {
allowed = true;
@@ -5557,6 +5827,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
if ((event&REMOVE_EVENTS) != 0) {
if (ps != null) {
+ if (DEBUG_REMOVE) Slog.d(TAG, "Package disappeared: " + ps);
removePackageLI(ps, true);
removedPackage = ps.name;
removedAppId = ps.appId;
@@ -5565,6 +5836,7 @@ public class PackageManagerService extends IPackageManager.Stub {
if ((event&ADD_EVENTS) != 0) {
if (p == null) {
+ if (DEBUG_INSTALL) Slog.d(TAG, "New file appeared: " + fullPath);
p = scanPackageLI(fullPath,
(mIsRom ? PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR: 0) |
@@ -6126,7 +6398,7 @@ public class PackageManagerService extends IPackageManager.Stub {
final boolean startCopy() {
boolean res;
try {
- if (DEBUG_INSTALL) Slog.i(TAG, "startCopy");
+ if (DEBUG_INSTALL) Slog.i(TAG, "startCopy " + mUser + ": " + this);
if (++mRetries > MAX_RETRIES) {
Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
@@ -6170,6 +6442,13 @@ public class PackageManagerService extends IPackageManager.Stub {
}
@Override
+ public String toString() {
+ return "MeasureParams{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + mStats.packageName + "}";
+ }
+
+ @Override
void handleStartCopy() throws RemoteException {
synchronized (mInstallLock) {
mSuccess = getPackageSizeInfoLI(mStats.packageName, mStats.userHandle, mStats);
@@ -6258,6 +6537,13 @@ public class PackageManagerService extends IPackageManager.Stub {
this.encryptionParams = encryptionParams;
}
+ @Override
+ public String toString() {
+ return "InstallParams{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + mPackageURI + "}";
+ }
+
public ManifestDigest getManifestDigest() {
if (verificationParams == null) {
return null;
@@ -6671,6 +6957,13 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
+ @Override
+ public String toString() {
+ return "MoveParams{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + packageName + "}";
+ }
+
public void handleStartCopy() throws RemoteException {
mRet = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
// Check for storage space on target medium
@@ -7582,6 +7875,7 @@ public class PackageManagerService extends IPackageManager.Stub {
// Remember this for later, in case we need to rollback this install
String pkgName = pkg.packageName;
+ if (DEBUG_INSTALL) Slog.d(TAG, "installNewPackageLI: " + pkg);
boolean dataDirExists = getDataPathForPackage(pkg.packageName, 0).exists();
synchronized(mPackages) {
if (mSettings.mRenamedPackages.containsKey(pkgName)) {
@@ -7638,6 +7932,7 @@ public class PackageManagerService extends IPackageManager.Stub {
// First find the old package info and check signatures
synchronized(mPackages) {
oldPackage = mPackages.get(pkgName);
+ if (DEBUG_INSTALL) Slog.d(TAG, "replacePackageLI: new=" + pkg + ", old=" + oldPackage);
if (compareSignatures(oldPackage.mSignatures, pkg.mSignatures)
!= PackageManager.SIGNATURE_MATCH) {
Slog.w(TAG, "New package has a different signature: " + pkgName);
@@ -7663,6 +7958,8 @@ public class PackageManagerService extends IPackageManager.Stub {
boolean deletedPkg = true;
boolean updatedSettings = false;
+ if (DEBUG_INSTALL) Slog.d(TAG, "replaceNonSystemPackageLI: new=" + pkg + ", old="
+ + deletedPackage);
long origUpdateTime;
if (pkg.mExtras != null) {
origUpdateTime = ((PackageSetting)pkg.mExtras).lastUpdateTime;
@@ -7700,6 +7997,7 @@ public class PackageManagerService extends IPackageManager.Stub {
// scanPackageLocked, unless those directories existed before we even tried to
// install.
if(updatedSettings) {
+ if (DEBUG_INSTALL) Slog.d(TAG, "Install failed, rolling pack: " + pkgName);
deletePackageLI(
pkgName, null, true,
PackageManager.DELETE_KEEP_DATA,
@@ -7708,6 +8006,7 @@ public class PackageManagerService extends IPackageManager.Stub {
// Since we failed to install the new package we need to restore the old
// package that we deleted.
if(deletedPkg) {
+ if (DEBUG_INSTALL) Slog.d(TAG, "Install failed, reinstalling: " + deletedPackage);
File restoreFile = new File(deletedPackage.mPath);
// Parse old package
boolean oldOnSd = isExternal(deletedPackage);
@@ -7737,6 +8036,8 @@ public class PackageManagerService extends IPackageManager.Stub {
private void replaceSystemPackageLI(PackageParser.Package deletedPackage,
PackageParser.Package pkg, int parseFlags, int scanMode, UserHandle user,
String installerPackageName, PackageInstalledInfo res) {
+ if (DEBUG_INSTALL) Slog.d(TAG, "replaceSystemPackageLI: new=" + pkg
+ + ", old=" + deletedPackage);
PackageParser.Package newPackage = null;
boolean updatedSettings = false;
parseFlags |= PackageManager.INSTALL_REPLACE_EXISTING |
@@ -7859,7 +8160,7 @@ public class PackageManagerService extends IPackageManager.Stub {
return;
}
- Log.d(TAG, "New package installed in " + newPackage.mPath);
+ if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + newPackage.mPath);
synchronized (mPackages) {
updatePermissionsLPw(newPackage.packageName, newPackage,
@@ -7889,6 +8190,7 @@ public class PackageManagerService extends IPackageManager.Stub {
// Result object to be returned
res.returnCode = PackageManager.INSTALL_SUCCEEDED;
+ if (DEBUG_INSTALL) Slog.d(TAG, "installPackageLI: path=" + tmpPackageFile);
// Retrieve PackageSettings and parse package
int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
| (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0)
@@ -7950,14 +8252,18 @@ public class PackageManagerService extends IPackageManager.Stub {
pkg.setPackageName(oldName);
pkgName = pkg.packageName;
replace = true;
+ if (DEBUG_INSTALL) Slog.d(TAG, "Replacing existing renamed package: oldName="
+ + oldName + " pkgName=" + pkgName);
} else if (mPackages.containsKey(pkgName)) {
// This package, under its official name, already exists
// on the device; we should replace it.
replace = true;
+ if (DEBUG_INSTALL) Slog.d(TAG, "Replace existing pacakge: " + pkgName);
}
}
PackageSetting ps = mSettings.mPackages.get(pkgName);
if (ps != null) {
+ if (DEBUG_INSTALL) Slog.d(TAG, "Existing package: " + ps);
oldCodePath = mSettings.mPackages.get(pkgName).codePathString;
if (ps.pkg != null && ps.pkg.applicationInfo != null) {
systemApp = (ps.pkg.applicationInfo.flags &
@@ -8104,6 +8410,7 @@ public class PackageManagerService extends IPackageManager.Stub {
return;
}
+ if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageAsUser: pkg=" + packageName + " user=" + userId);
// Queue up an async operation since the package deletion may take a little while.
mHandler.post(new Runnable() {
public void run() {
@@ -8151,6 +8458,7 @@ public class PackageManagerService extends IPackageManager.Stub {
boolean removedForAllUsers = false;
boolean systemUpdate = false;
synchronized (mInstallLock) {
+ if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageX: pkg=" + packageName + " user=" + userId);
res = deletePackageLI(packageName,
(flags & PackageManager.DELETE_ALL_USERS) != 0
? UserHandle.ALL : new UserHandle(userId),
@@ -8159,6 +8467,8 @@ public class PackageManagerService extends IPackageManager.Stub {
if (res && !systemUpdate && mPackages.get(packageName) == null) {
removedForAllUsers = true;
}
+ if (DEBUG_REMOVE) Slog.d(TAG, "delete res: systemUpdate=" + systemUpdate
+ + " removedForAllUsers=" + removedForAllUsers);
}
if (res) {
@@ -8234,6 +8544,7 @@ public class PackageManagerService extends IPackageManager.Stub {
private void removePackageDataLI(PackageSetting ps, PackageRemovedInfo outInfo,
int flags, boolean writeSettings) {
String packageName = ps.name;
+ if (DEBUG_REMOVE) Slog.d(TAG, "removePackageDataLI: " + ps);
removePackageLI(ps, (flags&REMOVE_CHATTY) != 0);
// Retrieve object to delete permissions for shared user later on
final PackageSetting deletedPs;
@@ -8289,11 +8600,13 @@ public class PackageManagerService extends IPackageManager.Stub {
synchronized (mPackages) {
disabledPs = mSettings.getDisabledSystemPkgLPr(newPs.name);
}
+ if (DEBUG_REMOVE) Slog.d(TAG, "deleteSystemPackageLI: newPs=" + newPs
+ + " disabledPs=" + disabledPs);
if (disabledPs == null) {
Slog.w(TAG, "Attempt to delete unknown system package "+ newPs.name);
return false;
- } else {
- Log.i(TAG, "Deleting system pkg from data partition");
+ } else if (DEBUG_REMOVE) {
+ Slog.d(TAG, "Deleting system pkg from data partition");
}
// Delete the updated package
outInfo.isRemovedPackageSystemUpdate = true;
@@ -8317,6 +8630,7 @@ public class PackageManagerService extends IPackageManager.Stub {
NativeLibraryHelper.removeNativeBinariesLI(newPs.nativeLibraryPathString);
}
// Install the system package
+ if (DEBUG_REMOVE) Slog.d(TAG, "Re-installing system package: " + disabledPs);
PackageParser.Package newPkg = scanPackageLI(disabledPs.codePath,
PackageParser.PARSE_MUST_BE_APK | PackageParser.PARSE_IS_SYSTEM,
SCAN_MONITOR | SCAN_NO_PATHS, 0, null);
@@ -8366,6 +8680,7 @@ public class PackageManagerService extends IPackageManager.Stub {
Slog.w(TAG, "Attempt to delete null packageName.");
return false;
}
+ if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageLI: " + packageName + " user " + user);
PackageSetting ps;
boolean dataOnly = false;
int removeUser = -1;
@@ -8376,11 +8691,14 @@ public class PackageManagerService extends IPackageManager.Stub {
Slog.w(TAG, "Package named '" + packageName + "' doesn't exist.");
return false;
}
- if (user != null
+ if ((!isSystemApp(ps) || (flags&PackageManager.DELETE_SYSTEM_APP) != 0) && user != null
&& user.getIdentifier() != UserHandle.USER_ALL) {
// The caller is asking that the package only be deleted for a single
// user. To do this, we just mark its uninstalled state and delete
- // its data.
+ // its data. If this is a system app, we only allow this to happen if
+ // they have set the special DELETE_SYSTEM_APP which requests different
+ // semantics than normal for uninstalling system apps.
+ if (DEBUG_REMOVE) Slog.d(TAG, "Only deleting for single user");
ps.setUserState(user.getIdentifier(),
COMPONENT_ENABLED_STATE_DEFAULT,
false, //installed
@@ -8392,12 +8710,14 @@ public class PackageManagerService extends IPackageManager.Stub {
// Other user still have this package installed, so all
// we need to do is clear this user's data and save that
// it is uninstalled.
+ if (DEBUG_REMOVE) Slog.d(TAG, "Still installed by other users");
removeUser = user.getIdentifier();
appId = ps.appId;
mSettings.writePackageRestrictionsLPr(removeUser);
} else {
// We need to set it back to 'installed' so the uninstall
// broadcasts will be sent correctly.
+ if (DEBUG_REMOVE) Slog.d(TAG, "Not installed by other users, full delete");
ps.setInstalled(true, user.getIdentifier());
}
} else {
@@ -8405,6 +8725,7 @@ public class PackageManagerService extends IPackageManager.Stub {
// other users still have this package installed, so all
// we need to do is clear this user's data and save that
// it is uninstalled.
+ if (DEBUG_REMOVE) Slog.d(TAG, "Deleting system app");
removeUser = user.getIdentifier();
appId = ps.appId;
mSettings.writePackageRestrictionsLPr(removeUser);
@@ -8415,6 +8736,7 @@ public class PackageManagerService extends IPackageManager.Stub {
if (removeUser >= 0) {
// From above, we determined that we are deleting this only
// for a single user. Continue the work here.
+ if (DEBUG_REMOVE) Slog.d(TAG, "Updating install state for user: " + removeUser);
if (outInfo != null) {
outInfo.removedPackage = packageName;
outInfo.removedAppId = appId;
@@ -8427,17 +8749,18 @@ public class PackageManagerService extends IPackageManager.Stub {
if (dataOnly) {
// Delete application data first
+ if (DEBUG_REMOVE) Slog.d(TAG, "Removing package data only");
removePackageDataLI(ps, outInfo, flags, writeSettings);
return true;
}
boolean ret = false;
if (isSystemApp(ps)) {
- Log.i(TAG, "Removing system package:" + ps.name);
+ if (DEBUG_REMOVE) Slog.d(TAG, "Removing system package:" + ps.name);
// When an updated system application is deleted we delete the existing resources as well and
// fall back to existing code in system partition
ret = deleteSystemPackageLI(ps, flags, outInfo, writeSettings);
} else {
- Log.i(TAG, "Removing non-system package:" + ps.name);
+ if (DEBUG_REMOVE) Slog.d(TAG, "Removing non-system package:" + ps.name);
// Kill application pre-emptively especially for apps on sd.
killApplication(packageName, ps.appId);
ret = deleteInstalledPackageLI(ps, deleteCodeAndResources, flags, outInfo,
@@ -9408,7 +9731,15 @@ public class PackageManagerService extends IPackageManager.Stub {
pw.print(" ");
pw.print(name);
pw.print(" -> ");
- pw.println(mSharedLibraries.get(name));
+ SharedLibraryEntry ent = mSharedLibraries.get(name);
+ if (ent.path != null) {
+ pw.print("(jar) ");
+ pw.print(ent.path);
+ } else {
+ pw.print("(apk) ");
+ pw.print(ent.apk);
+ }
+ pw.println();
}
}
diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java
index 13f514b26073..e6450787a9b6 100644
--- a/services/java/com/android/server/pm/Settings.java
+++ b/services/java/com/android/server/pm/Settings.java
@@ -32,7 +32,6 @@ import android.util.LogPrinter;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.JournaledFile;
import com.android.internal.util.XmlUtils;
-import com.android.server.IntentResolver;
import com.android.server.pm.PackageManagerService.DumpState;
import org.xmlpull.v1.XmlPullParser;
@@ -2657,6 +2656,162 @@ final class Settings {
ApplicationInfo.FLAG_CANT_SAVE_STATE, "CANT_SAVE_STATE",
};
+ void dumpPackageLPr(PrintWriter pw, String prefix, PackageSetting ps, SimpleDateFormat sdf,
+ Date date, List<UserInfo> users) {
+ pw.print(prefix); pw.print("Package [");
+ pw.print(ps.realName != null ? ps.realName : ps.name);
+ pw.print("] (");
+ pw.print(Integer.toHexString(System.identityHashCode(ps)));
+ pw.println("):");
+
+ if (ps.realName != null) {
+ pw.print(prefix); pw.print(" compat name=");
+ pw.println(ps.name);
+ }
+
+ pw.print(prefix); pw.print(" userId="); pw.print(ps.appId);
+ pw.print(" gids="); pw.println(PackageManagerService.arrayToString(ps.gids));
+ if (ps.sharedUser != null) {
+ pw.print(prefix); pw.print(" sharedUser="); pw.println(ps.sharedUser);
+ }
+ pw.print(prefix); pw.print(" pkg="); pw.println(ps.pkg);
+ pw.print(prefix); pw.print(" codePath="); pw.println(ps.codePathString);
+ pw.print(prefix); pw.print(" resourcePath="); pw.println(ps.resourcePathString);
+ pw.print(prefix); pw.print(" nativeLibraryPath="); pw.println(ps.nativeLibraryPathString);
+ pw.print(prefix); pw.print(" versionCode="); pw.print(ps.versionCode);
+ if (ps.pkg != null) {
+ pw.print(" targetSdk="); pw.print(ps.pkg.applicationInfo.targetSdkVersion);
+ }
+ pw.println();
+ if (ps.pkg != null) {
+ pw.print(prefix); pw.print(" versionName="); pw.println(ps.pkg.mVersionName);
+ pw.print(prefix); pw.print(" applicationInfo=");
+ pw.println(ps.pkg.applicationInfo.toString());
+ pw.print(prefix); pw.print(" flags="); printFlags(pw, ps.pkg.applicationInfo.flags,
+ FLAG_DUMP_SPEC); pw.println();
+ pw.print(prefix); pw.print(" dataDir="); pw.println(ps.pkg.applicationInfo.dataDir);
+ if (ps.pkg.mOperationPending) {
+ pw.print(prefix); pw.println(" mOperationPending=true");
+ }
+ pw.print(prefix); pw.print(" supportsScreens=[");
+ boolean first = true;
+ if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS) != 0) {
+ if (!first)
+ pw.print(", ");
+ first = false;
+ pw.print("small");
+ }
+ if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS) != 0) {
+ if (!first)
+ pw.print(", ");
+ first = false;
+ pw.print("medium");
+ }
+ if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) {
+ if (!first)
+ pw.print(", ");
+ first = false;
+ pw.print("large");
+ }
+ if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) {
+ if (!first)
+ pw.print(", ");
+ first = false;
+ pw.print("xlarge");
+ }
+ if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) {
+ if (!first)
+ pw.print(", ");
+ first = false;
+ pw.print("resizeable");
+ }
+ if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) {
+ if (!first)
+ pw.print(", ");
+ first = false;
+ pw.print("anyDensity");
+ }
+ pw.println("]");
+ if (ps.pkg.libraryNames != null && ps.pkg.libraryNames.size() > 0) {
+ pw.print(prefix); pw.println(" libraries:");
+ for (int i=0; i<ps.pkg.libraryNames.size(); i++) {
+ pw.print(prefix); pw.print(" "); pw.println(ps.pkg.libraryNames.get(i));
+ }
+ }
+ if (ps.pkg.usesLibraries != null && ps.pkg.usesLibraries.size() > 0) {
+ pw.print(prefix); pw.println(" usesLibraries:");
+ for (int i=0; i<ps.pkg.usesLibraries.size(); i++) {
+ pw.print(prefix); pw.print(" "); pw.println(ps.pkg.usesLibraries.get(i));
+ }
+ }
+ if (ps.pkg.usesOptionalLibraries != null
+ && ps.pkg.usesOptionalLibraries.size() > 0) {
+ pw.print(prefix); pw.println(" usesOptionalLibraries:");
+ for (int i=0; i<ps.pkg.usesOptionalLibraries.size(); i++) {
+ pw.print(prefix); pw.print(" ");
+ pw.println(ps.pkg.usesOptionalLibraries.get(i));
+ }
+ }
+ if (ps.pkg.usesLibraryFiles != null
+ && ps.pkg.usesLibraryFiles.length > 0) {
+ pw.print(prefix); pw.println(" usesLibraryFiles:");
+ for (int i=0; i<ps.pkg.usesLibraryFiles.length; i++) {
+ pw.print(prefix); pw.print(" "); pw.println(ps.pkg.usesLibraryFiles[i]);
+ }
+ }
+ }
+ pw.print(prefix); pw.print(" timeStamp=");
+ date.setTime(ps.timeStamp);
+ pw.println(sdf.format(date));
+ pw.print(prefix); pw.print(" firstInstallTime=");
+ date.setTime(ps.firstInstallTime);
+ pw.println(sdf.format(date));
+ pw.print(prefix); pw.print(" lastUpdateTime=");
+ date.setTime(ps.lastUpdateTime);
+ pw.println(sdf.format(date));
+ if (ps.installerPackageName != null) {
+ pw.print(prefix); pw.print(" installerPackageName=");
+ pw.println(ps.installerPackageName);
+ }
+ pw.print(prefix); pw.print(" signatures="); pw.println(ps.signatures);
+ pw.print(prefix); pw.print(" permissionsFixed="); pw.print(ps.permissionsFixed);
+ pw.print(" haveGids="); pw.print(ps.haveGids);
+ pw.print(" installStatus="); pw.println(ps.installStatus);
+ pw.print(prefix); pw.print(" pkgFlags="); printFlags(pw, ps.pkgFlags, FLAG_DUMP_SPEC);
+ pw.println();
+ for (UserInfo user : users) {
+ pw.print(prefix); pw.print(" User "); pw.print(user.id); pw.print(": ");
+ pw.print(" installed=");
+ pw.print(ps.getInstalled(user.id));
+ pw.print(" stopped=");
+ pw.print(ps.getStopped(user.id));
+ pw.print(" notLaunched=");
+ pw.print(ps.getNotLaunched(user.id));
+ pw.print(" enabled=");
+ pw.println(ps.getEnabled(user.id));
+ HashSet<String> cmp = ps.getDisabledComponents(user.id);
+ if (cmp != null && cmp.size() > 0) {
+ pw.print(prefix); pw.println(" disabledComponents:");
+ for (String s : cmp) {
+ pw.print(prefix); pw.print(" "); pw.println(s);
+ }
+ }
+ cmp = ps.getEnabledComponents(user.id);
+ if (cmp != null && cmp.size() > 0) {
+ pw.print(prefix); pw.println(" enabledComponents:");
+ for (String s : cmp) {
+ pw.print(prefix); pw.print(" "); pw.println(s);
+ }
+ }
+ }
+ if (ps.grantedPermissions.size() > 0) {
+ pw.print(prefix); pw.println(" grantedPermissions:");
+ for (String s : ps.grantedPermissions) {
+ pw.print(prefix); pw.print(" "); pw.println(s);
+ }
+ }
+ }
+
void dumpPackagesLPr(PrintWriter pw, String packageName, DumpState dumpState) {
final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
final Date date = new Date();
@@ -2678,123 +2833,7 @@ final class Settings {
pw.println("Packages:");
printedSomething = true;
}
- pw.print(" Package [");
- pw.print(ps.realName != null ? ps.realName : ps.name);
- pw.print("] (");
- pw.print(Integer.toHexString(System.identityHashCode(ps)));
- pw.println("):");
-
- if (ps.realName != null) {
- pw.print(" compat name=");
- pw.println(ps.name);
- }
-
- pw.print(" userId="); pw.print(ps.appId);
- pw.print(" gids="); pw.println(PackageManagerService.arrayToString(ps.gids));
- pw.print(" sharedUser="); pw.println(ps.sharedUser);
- pw.print(" pkg="); pw.println(ps.pkg);
- pw.print(" codePath="); pw.println(ps.codePathString);
- pw.print(" resourcePath="); pw.println(ps.resourcePathString);
- pw.print(" nativeLibraryPath="); pw.println(ps.nativeLibraryPathString);
- pw.print(" versionCode="); pw.println(ps.versionCode);
- if (ps.pkg != null) {
- pw.print(" applicationInfo="); pw.println(ps.pkg.applicationInfo.toString());
- pw.print(" flags="); printFlags(pw, ps.pkg.applicationInfo.flags, FLAG_DUMP_SPEC); pw.println();
- pw.print(" versionName="); pw.println(ps.pkg.mVersionName);
- pw.print(" dataDir="); pw.println(ps.pkg.applicationInfo.dataDir);
- pw.print(" targetSdk="); pw.println(ps.pkg.applicationInfo.targetSdkVersion);
- if (ps.pkg.mOperationPending) {
- pw.println(" mOperationPending=true");
- }
- pw.print(" supportsScreens=[");
- boolean first = true;
- if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS) != 0) {
- if (!first)
- pw.print(", ");
- first = false;
- pw.print("small");
- }
- if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS) != 0) {
- if (!first)
- pw.print(", ");
- first = false;
- pw.print("medium");
- }
- if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) {
- if (!first)
- pw.print(", ");
- first = false;
- pw.print("large");
- }
- if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) {
- if (!first)
- pw.print(", ");
- first = false;
- pw.print("xlarge");
- }
- if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) {
- if (!first)
- pw.print(", ");
- first = false;
- pw.print("resizeable");
- }
- if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) {
- if (!first)
- pw.print(", ");
- first = false;
- pw.print("anyDensity");
- }
- pw.println("]");
- }
- pw.print(" timeStamp=");
- date.setTime(ps.timeStamp);
- pw.println(sdf.format(date));
- pw.print(" firstInstallTime=");
- date.setTime(ps.firstInstallTime);
- pw.println(sdf.format(date));
- pw.print(" lastUpdateTime=");
- date.setTime(ps.lastUpdateTime);
- pw.println(sdf.format(date));
- if (ps.installerPackageName != null) {
- pw.print(" installerPackageName="); pw.println(ps.installerPackageName);
- }
- pw.print(" signatures="); pw.println(ps.signatures);
- pw.print(" permissionsFixed="); pw.print(ps.permissionsFixed);
- pw.print(" haveGids="); pw.print(ps.haveGids);
- pw.print(" installStatus="); pw.println(ps.installStatus);
- pw.print(" pkgFlags="); printFlags(pw, ps.pkgFlags, FLAG_DUMP_SPEC);
- pw.println();
- for (UserInfo user : users) {
- pw.print(" User "); pw.print(user.id); pw.print(": ");
- pw.print(" installed=");
- pw.print(ps.getInstalled(user.id));
- pw.print(" stopped=");
- pw.print(ps.getStopped(user.id));
- pw.print(" notLaunched=");
- pw.print(ps.getNotLaunched(user.id));
- pw.print(" enabled=");
- pw.println(ps.getEnabled(user.id));
- HashSet<String> cmp = ps.getDisabledComponents(user.id);
- if (cmp != null && cmp.size() > 0) {
- pw.println(" disabledComponents:");
- for (String s : cmp) {
- pw.print(" "); pw.println(s);
- }
- }
- cmp = ps.getEnabledComponents(user.id);
- if (cmp != null && cmp.size() > 0) {
- pw.println(" enabledComponents:");
- for (String s : cmp) {
- pw.print(" "); pw.println(s);
- }
- }
- }
- if (ps.grantedPermissions.size() > 0) {
- pw.println(" grantedPermissions:");
- for (String s : ps.grantedPermissions) {
- pw.print(" "); pw.println(s);
- }
- }
+ dumpPackageLPr(pw, " ", ps, sdf, date, users);
}
printedSomething = false;
@@ -2830,27 +2869,7 @@ final class Settings {
pw.println("Hidden system packages:");
printedSomething = true;
}
- pw.print(" Package [");
- pw.print(ps.realName != null ? ps.realName : ps.name);
- pw.print("] (");
- pw.print(Integer.toHexString(System.identityHashCode(ps)));
- pw.println("):");
- if (ps.realName != null) {
- pw.print(" compat name=");
- pw.println(ps.name);
- }
- if (ps.pkg != null && ps.pkg.applicationInfo != null) {
- pw.print(" applicationInfo=");
- pw.println(ps.pkg.applicationInfo.toString());
- }
- pw.print(" userId=");
- pw.println(ps.appId);
- pw.print(" sharedUser=");
- pw.println(ps.sharedUser);
- pw.print(" codePath=");
- pw.println(ps.codePathString);
- pw.print(" resourcePath=");
- pw.println(ps.resourcePathString);
+ dumpPackageLPr(pw, " ", ps, sdf, date, users);
}
}
}
diff --git a/tests/SharedLibrary/client/Android.mk b/tests/SharedLibrary/client/Android.mk
new file mode 100644
index 000000000000..60ef92af55e5
--- /dev/null
+++ b/tests/SharedLibrary/client/Android.mk
@@ -0,0 +1,12 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_APK_LIBRARIES := SharedLibrary
+
+LOCAL_PACKAGE_NAME := SharedLibraryClient
+
+LOCAL_MODULE_TAGS := tests
+
+include $(BUILD_PACKAGE)
diff --git a/tests/SharedLibrary/client/AndroidManifest.xml b/tests/SharedLibrary/client/AndroidManifest.xml
new file mode 100644
index 000000000000..c6a43c0fcd93
--- /dev/null
+++ b/tests/SharedLibrary/client/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.google.android.test.lib_client">
+ <application android:label="@string/app_title">
+ <uses-library android:name="android.test.runner" />
+ <uses-library android:name="com.google.android.test.shared_library" />
+ <activity android:name="ActivityMain">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/SharedLibrary/client/res/values/strings.xml b/tests/SharedLibrary/client/res/values/strings.xml
new file mode 100644
index 000000000000..3757a2554f8b
--- /dev/null
+++ b/tests/SharedLibrary/client/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources>
+ <string name="app_title">SharedLibrary client</string>
+</resources>
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 000000000000..d6121a503780
--- /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 000000000000..c19e23adcbee
--- /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 000000000000..31fac20e4163
--- /dev/null
+++ b/tests/SharedLibrary/lib/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.google.android.test.shared_library"
+ android:versionCode="2">
+ <application android:label="SharedLibrary">
+ <library android:name="com.google.android.test.shared_library" />
+ <activity android:name="ActivityMain">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/SharedLibrary/lib/res/values/strings.xml b/tests/SharedLibrary/lib/res/values/strings.xml
new file mode 100644
index 000000000000..bbfb0b4d81d5
--- /dev/null
+++ b/tests/SharedLibrary/lib/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="upgrade_title">Upgrade required</string>
+ <string name="upgrade_body"><xliff:g id="app">%1$s</xliff:g> requires a newer version
+ of <xliff:g id="lib">%2$s</xliff:g> to run.</string>
+ <string name="upgrade_button">Upgrade</string>
+</resources>
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 000000000000..895acedb1e7f
--- /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 000000000000..c1cd9254a30d
--- /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();
+ }
+}