summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk2
-rw-r--r--core/java/android/app/ApplicationPackageManager.java17
-rw-r--r--core/java/android/app/PackageInstallObserver.java40
-rw-r--r--core/java/android/app/PackageUninstallObserver.java37
-rw-r--r--core/java/android/content/pm/ContainerEncryptionParams.java2
-rw-r--r--core/java/android/content/pm/IPackageInstallObserver2.aidl33
-rw-r--r--core/java/android/content/pm/IPackageInstaller.aidl33
-rw-r--r--core/java/android/content/pm/IPackageInstallerSession.aidl30
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl3
-rw-r--r--core/java/android/content/pm/PackageInstaller.java159
-rw-r--r--core/java/android/content/pm/PackageInstallerParams.aidl19
-rw-r--r--core/java/android/content/pm/PackageInstallerParams.java141
-rw-r--r--core/java/android/content/pm/PackageManager.java3
-rw-r--r--core/java/android/content/pm/PackageParser.java186
-rw-r--r--core/java/android/content/pm/Signature.java4
-rw-r--r--core/java/android/content/pm/VerificationParams.java2
-rw-r--r--core/java/android/os/FileUtils.java29
-rw-r--r--core/java/android/os/RemoteException.java5
-rw-r--r--core/java/com/android/internal/util/ArrayUtils.java7
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java210
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java539
-rwxr-xr-xservices/core/java/com/android/server/pm/PackageManagerService.java54
-rw-r--r--test-runner/src/android/test/mock/MockPackageManager.java6
23 files changed, 1444 insertions, 117 deletions
diff --git a/Android.mk b/Android.mk
index 5970901..497ac06 100644
--- a/Android.mk
+++ b/Android.mk
@@ -126,6 +126,8 @@ LOCAL_SRC_FILES += \
core/java/android/content/pm/IPackageDeleteObserver.aidl \
core/java/android/content/pm/IPackageInstallObserver.aidl \
core/java/android/content/pm/IPackageInstallObserver2.aidl \
+ core/java/android/content/pm/IPackageInstaller.aidl \
+ core/java/android/content/pm/IPackageInstallerSession.aidl \
core/java/android/content/pm/IPackageManager.aidl \
core/java/android/content/pm/IPackageMoveObserver.aidl \
core/java/android/content/pm/IPackageStatsObserver.aidl \
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index efd3d86..2f35160 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -35,6 +35,7 @@ import android.content.pm.IPackageMoveObserver;
import android.content.pm.IPackageStatsObserver;
import android.content.pm.InstrumentationInfo;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.PermissionGroupInfo;
@@ -1127,7 +1128,7 @@ final class ApplicationPackageManager extends PackageManager {
public void installPackage(Uri packageURI, PackageInstallObserver observer,
int flags, String installerPackageName) {
try {
- mPM.installPackageEtc(packageURI, null, observer.mObserver,
+ mPM.installPackageEtc(packageURI, null, observer.getBinder(),
flags, installerPackageName);
} catch (RemoteException e) {
// Should never happen!
@@ -1140,7 +1141,7 @@ final class ApplicationPackageManager extends PackageManager {
Uri verificationURI, ManifestDigest manifestDigest,
ContainerEncryptionParams encryptionParams) {
try {
- mPM.installPackageWithVerificationEtc(packageURI, null, observer.mObserver, flags,
+ mPM.installPackageWithVerificationEtc(packageURI, null, observer.getBinder(), flags,
installerPackageName, verificationURI, manifestDigest, encryptionParams);
} catch (RemoteException e) {
// Should never happen!
@@ -1153,7 +1154,7 @@ final class ApplicationPackageManager extends PackageManager {
VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) {
try {
mPM.installPackageWithVerificationAndEncryptionEtc(packageURI, null,
- observer.mObserver, flags, installerPackageName, verificationParams,
+ observer.getBinder(), flags, installerPackageName, verificationParams,
encryptionParams);
} catch (RemoteException e) {
// Should never happen!
@@ -1440,6 +1441,16 @@ final class ApplicationPackageManager extends PackageManager {
return null;
}
+ @Override
+ public PackageInstaller getPackageInstaller() {
+ try {
+ return new PackageInstaller(this, mPM.getPackageInstaller(), mContext.getUserId(),
+ mContext.getPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
/**
* @hide
*/
diff --git a/core/java/android/app/PackageInstallObserver.java b/core/java/android/app/PackageInstallObserver.java
index dacffb4..941efbd 100644
--- a/core/java/android/app/PackageInstallObserver.java
+++ b/core/java/android/app/PackageInstallObserver.java
@@ -18,32 +18,36 @@ package android.app;
import android.content.pm.IPackageInstallObserver2;
import android.os.Bundle;
-import android.os.RemoteException;
-/**
- * @hide
- *
- * New-style observer for package installers to use.
- */
+/** {@hide} */
public class PackageInstallObserver {
- IPackageInstallObserver2.Stub mObserver = new IPackageInstallObserver2.Stub() {
+ private final IPackageInstallObserver2.Stub mBinder = new IPackageInstallObserver2.Stub() {
@Override
- public void packageInstalled(String pkgName, Bundle extras, int result)
- throws RemoteException {
- PackageInstallObserver.this.packageInstalled(pkgName, extras, result);
+ public void packageInstalled(String basePackageName, Bundle extras, int returnCode) {
+ PackageInstallObserver.this.packageInstalled(basePackageName, extras, returnCode);
}
};
+ /** {@hide} */
+ public IPackageInstallObserver2.Stub getBinder() {
+ return mBinder;
+ }
+
/**
- * This method will be called to report the result of the package installation attempt.
+ * This method will be called to report the result of the package
+ * installation attempt.
*
- * @param pkgName Name of the package whose installation was attempted
- * @param extras If non-null, this Bundle contains extras providing additional information
- * about an install failure. See {@link android.content.pm.PackageManager} for
- * documentation about which extras apply to various failures; in particular the
- * strings named EXTRA_FAILURE_*.
- * @param result The numeric success or failure code indicating the basic outcome
+ * @param basePackageName Name of the package whose installation was
+ * attempted
+ * @param extras If non-null, this Bundle contains extras providing
+ * additional information about an install failure. See
+ * {@link android.content.pm.PackageManager} for documentation
+ * about which extras apply to various failures; in particular
+ * the strings named EXTRA_FAILURE_*.
+ * @param returnCode The numeric success or failure code indicating the
+ * basic outcome
+ * @hide
*/
- public void packageInstalled(String pkgName, Bundle extras, int result) {
+ public void packageInstalled(String basePackageName, Bundle extras, int returnCode) {
}
}
diff --git a/core/java/android/app/PackageUninstallObserver.java b/core/java/android/app/PackageUninstallObserver.java
new file mode 100644
index 0000000..0a960a7
--- /dev/null
+++ b/core/java/android/app/PackageUninstallObserver.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2014 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 android.app;
+
+import android.content.pm.IPackageDeleteObserver;
+
+/** {@hide} */
+public class PackageUninstallObserver {
+ private final IPackageDeleteObserver.Stub mBinder = new IPackageDeleteObserver.Stub() {
+ @Override
+ public void packageDeleted(String basePackageName, int returnCode) {
+ PackageUninstallObserver.this.onUninstallFinished(basePackageName, returnCode);
+ }
+ };
+
+ /** {@hide} */
+ public IPackageDeleteObserver.Stub getBinder() {
+ return mBinder;
+ }
+
+ public void onUninstallFinished(String basePackageName, int returnCode) {
+ }
+}
diff --git a/core/java/android/content/pm/ContainerEncryptionParams.java b/core/java/android/content/pm/ContainerEncryptionParams.java
index 18dcb56..dd1332b 100644
--- a/core/java/android/content/pm/ContainerEncryptionParams.java
+++ b/core/java/android/content/pm/ContainerEncryptionParams.java
@@ -32,9 +32,11 @@ import javax.crypto.spec.IvParameterSpec;
/**
* Represents encryption parameters used to read a container.
*
+ * @deprecated encrypted containers are legacy.
* @hide
*/
@PrivateApi
+@Deprecated
public class ContainerEncryptionParams implements Parcelable {
protected static final String TAG = "ContainerEncryptionParams";
diff --git a/core/java/android/content/pm/IPackageInstallObserver2.aidl b/core/java/android/content/pm/IPackageInstallObserver2.aidl
index 2602ab5..7205ce7 100644
--- a/core/java/android/content/pm/IPackageInstallObserver2.aidl
+++ b/core/java/android/content/pm/IPackageInstallObserver2.aidl
@@ -1,22 +1,22 @@
/*
-**
-** Copyright 2014, 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.
-*/
+ * Copyright (C) 2014 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 android.content.pm;
+import android.content.IntentSender;
import android.os.Bundle;
/**
@@ -40,6 +40,5 @@ oneway interface IPackageInstallObserver2 {
* </tr>
* </table>
*/
- void packageInstalled(in String packageName, in Bundle extras, int returnCode);
+ void packageInstalled(String basePackageName, in Bundle extras, int returnCode);
}
-
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
new file mode 100644
index 0000000..68c019b
--- /dev/null
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2014 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 android.content.pm;
+
+import android.content.pm.IPackageDeleteObserver;
+import android.content.pm.IPackageInstallerSession;
+import android.content.pm.PackageInstallerParams;
+import android.os.ParcelFileDescriptor;
+
+/** {@hide} */
+interface IPackageInstaller {
+ int createSession(int userId, String installerPackageName, in PackageInstallerParams params);
+ IPackageInstallerSession openSession(int sessionId);
+
+ int[] getSessions(int userId, String installerPackageName);
+
+ void uninstall(int userId, String basePackageName, in IPackageDeleteObserver observer);
+ void uninstallSplit(int userId, String basePackageName, String splitName, in IPackageDeleteObserver observer);
+}
diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl
new file mode 100644
index 0000000..f881acd
--- /dev/null
+++ b/core/java/android/content/pm/IPackageInstallerSession.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2014 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 android.content.pm;
+
+import android.content.pm.IPackageInstallObserver2;
+import android.os.ParcelFileDescriptor;
+
+/** {@hide} */
+interface IPackageInstallerSession {
+ void updateProgress(int progress);
+
+ ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes);
+
+ void install(in IPackageInstallObserver2 observer);
+ void destroy();
+}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 03eb50f..6cb781f 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -26,6 +26,7 @@ import android.content.pm.ContainerEncryptionParams;
import android.content.pm.FeatureInfo;
import android.content.pm.IPackageInstallObserver;
import android.content.pm.IPackageInstallObserver2;
+import android.content.pm.IPackageInstaller;
import android.content.pm.IPackageDeleteObserver;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageMoveObserver;
@@ -450,4 +451,6 @@ interface IPackageManager {
boolean setApplicationBlockedSettingAsUser(String packageName, boolean blocked, int userId);
boolean getApplicationBlockedSettingAsUser(String packageName, int userId);
+
+ IPackageInstaller getPackageInstaller();
}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
new file mode 100644
index 0000000..d7bd473
--- /dev/null
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2014 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 android.content.pm;
+
+import android.app.PackageInstallObserver;
+import android.app.PackageUninstallObserver;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+
+/** {@hide} */
+public class PackageInstaller {
+ private final PackageManager mPm;
+ private final IPackageInstaller mInstaller;
+ private final int mUserId;
+ private final String mInstallerPackageName;
+
+ /** {@hide} */
+ public PackageInstaller(PackageManager pm, IPackageInstaller installer, int userId,
+ String installerPackageName) {
+ mPm = pm;
+ mInstaller = installer;
+ mUserId = userId;
+ mInstallerPackageName = installerPackageName;
+ }
+
+ public boolean isPackageAvailable(String basePackageName) {
+ try {
+ final ApplicationInfo info = mPm.getApplicationInfo(basePackageName,
+ PackageManager.GET_UNINSTALLED_PACKAGES);
+ return ((info.flags & ApplicationInfo.FLAG_INSTALLED) != 0);
+ } catch (NameNotFoundException e) {
+ return false;
+ }
+ }
+
+ public void installAvailablePackage(String basePackageName, PackageInstallObserver observer) {
+ int returnCode;
+ try {
+ returnCode = mPm.installExistingPackage(basePackageName);
+ } catch (NameNotFoundException e) {
+ returnCode = PackageManager.INSTALL_FAILED_PACKAGE_CHANGED;
+ }
+ observer.packageInstalled(basePackageName, null, returnCode);
+ }
+
+ public int createSession(PackageInstallerParams params) {
+ try {
+ return mInstaller.createSession(mUserId, mInstallerPackageName, params);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ public Session openSession(int sessionId) {
+ try {
+ return new Session(mInstaller.openSession(sessionId));
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ public int[] getSessions() {
+ try {
+ return mInstaller.getSessions(mUserId, mInstallerPackageName);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ public void uninstall(String basePackageName, PackageUninstallObserver observer) {
+ try {
+ mInstaller.uninstall(mUserId, basePackageName, observer.getBinder());
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ public void uninstall(String basePackageName, String splitName,
+ PackageUninstallObserver observer) {
+ try {
+ mInstaller.uninstallSplit(mUserId, basePackageName, splitName, observer.getBinder());
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * An installation that is being actively staged. For an install to succeed,
+ * all existing and new packages must have identical package names, version
+ * codes, and signing certificates.
+ * <p>
+ * A session may contain any number of split packages. If the application
+ * does not yet exist, this session must include a base package.
+ * <p>
+ * If a package included in this session is already defined by the existing
+ * installation (for example, the same split name), the package in this
+ * session will replace the existing package.
+ */
+ public class Session {
+ private IPackageInstallerSession mSession;
+
+ /** {@hide} */
+ public Session(IPackageInstallerSession session) {
+ mSession = session;
+ }
+
+ public void updateProgress(int progress) {
+ try {
+ mSession.updateProgress(progress);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ public ParcelFileDescriptor openWrite(String overlayName, long offsetBytes,
+ long lengthBytes) {
+ try {
+ return mSession.openWrite(overlayName, offsetBytes, lengthBytes);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ public void install(PackageInstallObserver observer) {
+ try {
+ mSession.install(observer.getBinder());
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ public void close() {
+ // No resources to release at the moment
+ }
+
+ public void destroy() {
+ try {
+ mSession.destroy();
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+ }
+}
diff --git a/core/java/android/content/pm/PackageInstallerParams.aidl b/core/java/android/content/pm/PackageInstallerParams.aidl
new file mode 100644
index 0000000..b3dde21
--- /dev/null
+++ b/core/java/android/content/pm/PackageInstallerParams.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2014 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 android.content.pm;
+
+parcelable PackageInstallerParams;
diff --git a/core/java/android/content/pm/PackageInstallerParams.java b/core/java/android/content/pm/PackageInstallerParams.java
new file mode 100644
index 0000000..67cf276
--- /dev/null
+++ b/core/java/android/content/pm/PackageInstallerParams.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2014 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 android.content.pm;
+
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Parameters that define an installation session.
+ *
+ * {@hide}
+ */
+public class PackageInstallerParams implements Parcelable {
+
+ // TODO: extend to support remaining VerificationParams
+
+ /** {@hide} */
+ public boolean fullInstall;
+ /** {@hide} */
+ public int installFlags;
+ /** {@hide} */
+ public int installLocation = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY;
+ /** {@hide} */
+ public Signature[] signatures;
+ /** {@hide} */
+ public long deltaSize = -1;
+ /** {@hide} */
+ public Bitmap icon;
+ /** {@hide} */
+ public String title;
+ /** {@hide} */
+ public Uri originatingUri;
+ /** {@hide} */
+ public Uri referrerUri;
+
+ public PackageInstallerParams() {
+ }
+
+ /** {@hide} */
+ public PackageInstallerParams(Parcel source) {
+ this.fullInstall = source.readInt() != 0;
+ this.installFlags = source.readInt();
+ this.installLocation = source.readInt();
+ this.signatures = (Signature[]) source.readParcelableArray(null);
+ deltaSize = source.readLong();
+ if (source.readInt() != 0) {
+ icon = Bitmap.CREATOR.createFromParcel(source);
+ }
+ title = source.readString();
+ originatingUri = Uri.CREATOR.createFromParcel(source);
+ referrerUri = Uri.CREATOR.createFromParcel(source);
+ }
+
+ public void setFullInstall(boolean fullInstall) {
+ this.fullInstall = fullInstall;
+ }
+
+ public void setInstallFlags(int installFlags) {
+ this.installFlags = installFlags;
+ }
+
+ public void setInstallLocation(int installLocation) {
+ this.installLocation = installLocation;
+ }
+
+ public void setSignatures(Signature[] signatures) {
+ this.signatures = signatures;
+ }
+
+ public void setDeltaSize(long deltaSize) {
+ this.deltaSize = deltaSize;
+ }
+
+ public void setIcon(Bitmap icon) {
+ this.icon = icon;
+ }
+
+ public void setTitle(CharSequence title) {
+ this.title = (title != null) ? title.toString() : null;
+ }
+
+ public void setOriginatingUri(Uri originatingUri) {
+ this.originatingUri = originatingUri;
+ }
+
+ public void setReferrerUri(Uri referrerUri) {
+ this.referrerUri = referrerUri;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(fullInstall ? 1 : 0);
+ dest.writeInt(installFlags);
+ dest.writeInt(installLocation);
+ dest.writeParcelableArray(signatures, flags);
+ dest.writeLong(deltaSize);
+ if (icon != null) {
+ dest.writeInt(1);
+ icon.writeToParcel(dest, flags);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeString(title);
+ dest.writeParcelable(originatingUri, flags);
+ dest.writeParcelable(referrerUri, flags);
+ }
+
+ public static final Parcelable.Creator<PackageInstallerParams>
+ CREATOR = new Parcelable.Creator<PackageInstallerParams>() {
+ @Override
+ public PackageInstallerParams createFromParcel(Parcel p) {
+ return new PackageInstallerParams(p);
+ }
+
+ @Override
+ public PackageInstallerParams[] newArray(int size) {
+ return new PackageInstallerParams[size];
+ }
+ };
+}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 1bc1449..2aa34d8 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3551,6 +3551,9 @@ public abstract class PackageManager {
*/
public abstract VerifierDeviceIdentity getVerifierDeviceIdentity();
+ /** {@hide} */
+ public abstract PackageInstaller getPackageInstaller();
+
/**
* Returns the data directory for a particular user and package, given the uid of the package.
* @param uid uid of the package, including the userId and appId
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index ff96c51..1c838c3 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -16,6 +16,9 @@
package android.content.pm;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
@@ -32,6 +35,7 @@ import android.util.AttributeSet;
import android.util.Base64;
import android.util.DisplayMetrics;
import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
import android.util.TypedValue;
@@ -41,6 +45,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
+import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
@@ -51,7 +56,6 @@ import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -61,6 +65,7 @@ import java.util.Set;
import java.util.jar.StrictJarFile;
import java.util.zip.ZipEntry;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
@@ -207,16 +212,20 @@ public class PackageParser {
*/
public static class PackageLite {
public final String packageName;
+ public final String splitName;
public final int versionCode;
public final int installLocation;
public final VerifierInfo[] verifiers;
+ public final Signature[] signatures;
- public PackageLite(String packageName, int versionCode,
- int installLocation, List<VerifierInfo> verifiers) {
+ public PackageLite(String packageName, String splitName, int versionCode,
+ int installLocation, List<VerifierInfo> verifiers, Signature[] signatures) {
this.packageName = packageName;
+ this.splitName = splitName;
this.versionCode = versionCode;
this.installLocation = installLocation;
this.verifiers = verifiers.toArray(new VerifierInfo[verifiers.size()]);
+ this.signatures = signatures;
}
}
@@ -459,7 +468,7 @@ public class PackageParser {
return pi;
}
- private Certificate[][] loadCertificates(StrictJarFile jarFile, ZipEntry je,
+ private static Certificate[][] loadCertificates(StrictJarFile jarFile, ZipEntry je,
byte[] readBuffer) {
try {
// We must read the stream for the JarEntry to retrieve
@@ -486,6 +495,7 @@ public class PackageParser {
public final static int PARSE_ON_SDCARD = 1<<5;
public final static int PARSE_IS_SYSTEM_DIR = 1<<6;
public final static int PARSE_IS_PRIVILEGED = 1<<7;
+ public final static int PARSE_GET_SIGNATURES = 1<<8;
public int getParseError() {
return mParseError;
@@ -722,12 +732,8 @@ public class PackageParser {
mReadBuffer = readBufferRef;
}
- if (certs != null && certs.length > 0) {
- final int N = certs.length;
- pkg.mSignatures = new Signature[certs.length];
- for (int i=0; i<N; i++) {
- pkg.mSignatures[i] = new Signature(certs[i]);
- }
+ if (!ArrayUtils.isEmpty(certs)) {
+ pkg.mSignatures = convertToSignatures(certs);
} else {
Slog.e(TAG, "Package " + pkg.packageName
+ " has no certificates; ignoring!");
@@ -762,6 +768,39 @@ public class PackageParser {
return true;
}
+ /**
+ * Only collect certificates on the manifest; does not validate signatures
+ * across remainder of package.
+ */
+ private static Signature[] collectCertificates(String packageFilePath) {
+ try {
+ final StrictJarFile jarFile = new StrictJarFile(packageFilePath);
+ try {
+ final ZipEntry jarEntry = jarFile.findEntry(ANDROID_MANIFEST_FILENAME);
+ if (jarEntry != null) {
+ final Certificate[][] certs = loadCertificates(jarFile, jarEntry, null);
+ return convertToSignatures(certs);
+ }
+ } finally {
+ jarFile.close();
+ }
+ } catch (GeneralSecurityException e) {
+ Slog.w(TAG, "Failed to collect certs from " + packageFilePath + ": " + e);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to collect certs from " + packageFilePath + ": " + e);
+ }
+ return null;
+ }
+
+ private static Signature[] convertToSignatures(Certificate[][] certs)
+ throws CertificateEncodingException {
+ final Signature[] res = new Signature[certs.length];
+ for (int i = 0; i < certs.length; i++) {
+ res[i] = new Signature(certs[i]);
+ }
+ return res;
+ }
+
/*
* Utility method that retrieves just the package name and install
* location from the apk location at the given file path.
@@ -794,11 +833,22 @@ public class PackageParser {
return null;
}
+ // Only collect certificates on the manifest; does not validate
+ // signatures across remainder of package.
+ final Signature[] signatures;
+ if ((flags & PARSE_GET_SIGNATURES) != 0) {
+ signatures = collectCertificates(packageFilePath);
+ } else {
+ signatures = null;
+ }
+
final AttributeSet attrs = parser;
final String errors[] = new String[1];
PackageLite packageLite = null;
try {
- packageLite = parsePackageLite(res, parser, attrs, flags, errors);
+ packageLite = parsePackageLite(res, parser, attrs, flags, signatures, errors);
+ } catch (PackageParserException e) {
+ Slog.w(TAG, packageFilePath, e);
} catch (IOException e) {
Slog.w(TAG, packageFilePath, e);
} catch (XmlPullParserException e) {
@@ -840,72 +890,51 @@ public class PackageParser {
? null : "must have at least one '.' separator";
}
- private static String parsePackageName(XmlPullParser parser,
- AttributeSet attrs, int flags, String[] outError)
- throws IOException, XmlPullParserException {
+ private static Pair<String, String> parsePackageSplitNames(XmlPullParser parser,
+ AttributeSet attrs, int flags) throws IOException, XmlPullParserException,
+ PackageParserException {
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG
&& type != XmlPullParser.END_DOCUMENT) {
- ;
}
if (type != XmlPullParser.START_TAG) {
- outError[0] = "No start tag found";
- return null;
+ throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+ "No start tag found");
}
- if (DEBUG_PARSER)
- Slog.v(TAG, "Root element name: '" + parser.getName() + "'");
if (!parser.getName().equals("manifest")) {
- outError[0] = "No <manifest> tag";
- return null;
+ throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+ "No <manifest> tag");
}
- String pkgName = attrs.getAttributeValue(null, "package");
- if (pkgName == null || pkgName.length() == 0) {
- outError[0] = "<manifest> does not specify package";
- return null;
+
+ final String packageName = attrs.getAttributeValue(null, "package");
+ if (!"android".equals(packageName)) {
+ final String error = validateName(packageName, true);
+ if (error != null) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME,
+ "Invalid manifest package: " + error);
+ }
}
- String nameError = validateName(pkgName, true);
- if (nameError != null && !"android".equals(pkgName)) {
- outError[0] = "<manifest> specifies bad package name \""
- + pkgName + "\": " + nameError;
- return null;
+
+ final String splitName = attrs.getAttributeValue(null, "split");
+ if (splitName != null) {
+ final String error = validateName(splitName, true);
+ if (error != null) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME,
+ "Invalid manifest split: " + error);
+ }
}
- return pkgName.intern();
+ return Pair.create(packageName.intern(),
+ (splitName != null) ? splitName.intern() : splitName);
}
private static PackageLite parsePackageLite(Resources res, XmlPullParser parser,
- AttributeSet attrs, int flags, String[] outError) throws IOException,
- XmlPullParserException {
-
- int type;
- while ((type = parser.next()) != XmlPullParser.START_TAG
- && type != XmlPullParser.END_DOCUMENT) {
- ;
- }
+ AttributeSet attrs, int flags, Signature[] signatures, String[] outError)
+ throws IOException, XmlPullParserException, PackageParserException {
+ final Pair<String, String> packageSplit = parsePackageSplitNames(parser, attrs, flags);
- if (type != XmlPullParser.START_TAG) {
- outError[0] = "No start tag found";
- return null;
- }
- if (DEBUG_PARSER)
- Slog.v(TAG, "Root element name: '" + parser.getName() + "'");
- if (!parser.getName().equals("manifest")) {
- outError[0] = "No <manifest> tag";
- return null;
- }
- String pkgName = attrs.getAttributeValue(null, "package");
- if (pkgName == null || pkgName.length() == 0) {
- outError[0] = "<manifest> does not specify package";
- return null;
- }
- String nameError = validateName(pkgName, true);
- if (nameError != null && !"android".equals(pkgName)) {
- outError[0] = "<manifest> specifies bad package name \""
- + pkgName + "\": " + nameError;
- return null;
- }
int installLocation = PARSE_DEFAULT_INSTALL_LOCATION;
int versionCode = 0;
int numFound = 0;
@@ -925,6 +954,7 @@ public class PackageParser {
}
// Only search the tree when the tag is directly below <manifest>
+ int type;
final int searchDepth = parser.getDepth() + 1;
final List<VerifierInfo> verifiers = new ArrayList<VerifierInfo>();
@@ -942,7 +972,8 @@ public class PackageParser {
}
}
- return new PackageLite(pkgName.intern(), versionCode, installLocation, verifiers);
+ return new PackageLite(packageSplit.first, packageSplit.second, versionCode,
+ installLocation, verifiers, signatures);
}
/**
@@ -966,12 +997,18 @@ public class PackageParser {
mParseActivityArgs = null;
mParseServiceArgs = null;
mParseProviderArgs = null;
-
- String pkgName = parsePackageName(parser, attrs, flags, outError);
- if (pkgName == null) {
+
+ final String pkgName;
+ final String splitName;
+ try {
+ Pair<String, String> packageSplit = parsePackageSplitNames(parser, attrs, flags);
+ pkgName = packageSplit.first;
+ splitName = packageSplit.second;
+ } catch (PackageParserException e) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
return null;
}
+
int type;
if (mOnlyCoreApps) {
@@ -982,9 +1019,9 @@ public class PackageParser {
}
}
- final Package pkg = new Package(pkgName);
+ final Package pkg = new Package(pkgName, splitName);
boolean foundApp = false;
-
+
TypedArray sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AndroidManifest);
pkg.mVersionCode = pkg.applicationInfo.versionCode = sa.getInteger(
@@ -3544,6 +3581,7 @@ public class PackageParser {
public final static class Package {
public String packageName;
+ public String splitName;
// For now we only support one application per package.
public final ApplicationInfo applicationInfo = new ApplicationInfo();
@@ -3660,9 +3698,10 @@ public class PackageParser {
public Set<PublicKey> mSigningKeys;
public Map<String, Set<PublicKey>> mKeySetMapping;
- public Package(String _name) {
- packageName = _name;
- applicationInfo.packageName = _name;
+ public Package(String packageName, String splitName) {
+ this.packageName = packageName;
+ this.splitName = splitName;
+ applicationInfo.packageName = packageName;
applicationInfo.uid = -1;
}
@@ -4267,4 +4306,13 @@ public class PackageParser {
public static void setCompatibilityModeEnabled(boolean compatibilityModeEnabled) {
sCompatibilityModeEnabled = compatibilityModeEnabled;
}
+
+ public static class PackageParserException extends Exception {
+ public final int error;
+
+ public PackageParserException(int error, String detailMessage) {
+ super(detailMessage);
+ this.error = error;
+ }
+ }
}
diff --git a/core/java/android/content/pm/Signature.java b/core/java/android/content/pm/Signature.java
index f4e7dc3..96aa083 100644
--- a/core/java/android/content/pm/Signature.java
+++ b/core/java/android/content/pm/Signature.java
@@ -31,8 +31,10 @@ import java.security.cert.CertificateFactory;
import java.util.Arrays;
/**
- * Opaque, immutable representation of a signature associated with an
+ * Opaque, immutable representation of a signing certificate associated with an
* application package.
+ * <p>
+ * This class name is slightly misleading, since it's not actually a signature.
*/
public class Signature implements Parcelable {
private final byte[] mSignature;
diff --git a/core/java/android/content/pm/VerificationParams.java b/core/java/android/content/pm/VerificationParams.java
index 22e1a85..bf1f77f 100644
--- a/core/java/android/content/pm/VerificationParams.java
+++ b/core/java/android/content/pm/VerificationParams.java
@@ -24,8 +24,10 @@ import android.os.Parcelable;
/**
* Represents verification parameters used to verify packages to be installed.
*
+ * @deprecated callers should migrate to {@link PackageInstallerParams}.
* @hide
*/
+@Deprecated
public class VerificationParams implements Parcelable {
/** A constant used to indicate that a uid value is not present. */
public static final int NO_UID = -1;
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index d71c3e6..1dba77d 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -17,6 +17,7 @@
package android.os;
import android.system.ErrnoException;
+import android.text.TextUtils;
import android.system.Os;
import android.system.OsConstants;
import android.util.Log;
@@ -382,4 +383,32 @@ public class FileUtils {
}
return filePath.startsWith(dirPath);
}
+
+ public static void deleteContents(File dir) {
+ File[] files = dir.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ if (file.isDirectory()) {
+ deleteContents(file);
+ }
+ file.delete();
+ }
+ }
+ }
+
+ /**
+ * Assert that given filename is valid on ext4.
+ */
+ public static boolean isValidExtFilename(String name) {
+ if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) {
+ return false;
+ }
+ for (int i = 0; i < name.length(); i++) {
+ final char c = name.charAt(i);
+ if (c == '\0' || c == '/') {
+ return false;
+ }
+ }
+ return true;
+ }
}
diff --git a/core/java/android/os/RemoteException.java b/core/java/android/os/RemoteException.java
index e30d24f..98d7523 100644
--- a/core/java/android/os/RemoteException.java
+++ b/core/java/android/os/RemoteException.java
@@ -28,4 +28,9 @@ public class RemoteException extends AndroidException {
public RemoteException(String message) {
super(message);
}
+
+ /** {@hide} */
+ public RuntimeException rethrowAsRuntimeException() {
+ throw new RuntimeException(this);
+ }
}
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index d177410..a56fa36 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -117,6 +117,13 @@ public class ArrayUtils
}
/**
+ * Checks if given array is null or has zero elements.
+ */
+ public static <T> boolean isEmpty(T[] array) {
+ return array == null || array.length == 0;
+ }
+
+ /**
* Checks that value is present as at least one of the elements of the array.
* @param array the array to check in
* @param value the value to check for
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
new file mode 100644
index 0000000..5fdfce4
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2014 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.server.pm;
+
+import static android.content.pm.PackageManager.INSTALL_ALL_USERS;
+import static android.content.pm.PackageManager.INSTALL_FROM_ADB;
+import static android.content.pm.PackageManager.INSTALL_REPLACE_EXISTING;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.IPackageDeleteObserver;
+import android.content.pm.IPackageInstaller;
+import android.content.pm.IPackageInstallerSession;
+import android.content.pm.PackageInstallerParams;
+import android.os.Binder;
+import android.os.FileUtils;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
+import com.android.server.IoThread;
+import com.google.android.collect.Sets;
+
+import java.io.File;
+
+public class PackageInstallerService extends IPackageInstaller.Stub {
+ private static final String TAG = "PackageInstaller";
+
+ // TODO: destroy sessions with old timestamps
+ // TODO: remove outstanding sessions when installer package goes away
+
+ private final Context mContext;
+ private final PackageManagerService mPm;
+ private final AppOpsManager mAppOps;
+
+ private final File mStagingDir;
+
+ private final HandlerThread mInstallThread = new HandlerThread(TAG);
+ private final Callback mCallback = new Callback();
+
+ @GuardedBy("mSessions")
+ private int mNextSessionId;
+ @GuardedBy("mSessions")
+ private final SparseArray<PackageInstallerSession> mSessions = new SparseArray<>();
+
+ public PackageInstallerService(Context context, PackageManagerService pm, File stagingDir) {
+ mContext = context;
+ mPm = pm;
+ mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
+
+ mStagingDir = stagingDir;
+ mStagingDir.mkdirs();
+
+ synchronized (mSessions) {
+ readSessionsLocked();
+
+ // Clean up orphaned staging directories
+ final ArraySet<String> dirs = Sets.newArraySet(mStagingDir.list());
+ for (int i = 0; i < mSessions.size(); i++) {
+ dirs.remove(Integer.toString(mSessions.keyAt(i)));
+ }
+ for (String dirName : dirs) {
+ Slog.w(TAG, "Deleting orphan session " + dirName);
+ final File dir = new File(mStagingDir, dirName);
+ FileUtils.deleteContents(dir);
+ dir.delete();
+ }
+ }
+ }
+
+ private void readSessionsLocked() {
+ // TODO: implement persisting
+ mSessions.clear();
+ mNextSessionId = 1;
+ }
+
+ private void writeSessionsLocked() {
+ // TODO: implement persisting
+ }
+
+ private void writeSessionsAsync() {
+ IoThread.getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mSessions) {
+ writeSessionsLocked();
+ }
+ }
+ });
+ }
+
+ @Override
+ public int createSession(int userId, String installerPackageName,
+ PackageInstallerParams params) {
+ final int callingUid = Binder.getCallingUid();
+ mPm.enforceCrossUserPermission(callingUid, userId, false, TAG);
+ mAppOps.checkPackage(callingUid, installerPackageName);
+
+ if (mPm.isUserRestricted(UserHandle.getUserId(callingUid),
+ UserManager.DISALLOW_INSTALL_APPS)) {
+ throw new SecurityException("User restriction prevents installing");
+ }
+
+ if ((callingUid == Process.SHELL_UID) || (callingUid == 0)) {
+ params.installFlags |= INSTALL_FROM_ADB;
+ } else {
+ params.installFlags &= ~INSTALL_FROM_ADB;
+ params.installFlags &= ~INSTALL_ALL_USERS;
+ params.installFlags |= INSTALL_REPLACE_EXISTING;
+ }
+
+ synchronized (mSessions) {
+ final int sessionId = allocateSessionIdLocked();
+ final long createdMillis = System.currentTimeMillis();
+ final File sessionDir = new File(mStagingDir, Integer.toString(sessionId));
+ sessionDir.mkdirs();
+
+ final PackageInstallerSession session = new PackageInstallerSession(mCallback, mPm,
+ sessionId, userId, installerPackageName, callingUid, params, createdMillis,
+ sessionDir, mInstallThread.getLooper());
+ mSessions.put(sessionId, session);
+
+ writeSessionsAsync();
+ return sessionId;
+ }
+ }
+
+ @Override
+ public IPackageInstallerSession openSession(int sessionId) {
+ synchronized (mSessions) {
+ final PackageInstallerSession session = mSessions.get(sessionId);
+ if (session == null) {
+ throw new IllegalStateException("Missing session " + sessionId);
+ }
+ if (Binder.getCallingUid() != session.installerUid) {
+ throw new SecurityException("Caller has no access to session " + sessionId);
+ }
+ return session;
+ }
+ }
+
+ private int allocateSessionIdLocked() {
+ if (mSessions.get(mNextSessionId) != null) {
+ throw new IllegalStateException("Next session already allocated");
+ }
+ return mNextSessionId++;
+ }
+
+ @Override
+ public int[] getSessions(int userId, String installerPackageName) {
+ final int callingUid = Binder.getCallingUid();
+ mPm.enforceCrossUserPermission(callingUid, userId, false, TAG);
+ mAppOps.checkPackage(callingUid, installerPackageName);
+
+ int[] matching = new int[0];
+ synchronized (mSessions) {
+ for (int i = 0; i < mSessions.size(); i++) {
+ final int key = mSessions.keyAt(i);
+ final PackageInstallerSession session = mSessions.valueAt(i);
+ if (session.userId == userId
+ && session.installerPackageName.equals(installerPackageName)) {
+ matching = ArrayUtils.appendInt(matching, key);
+ }
+ }
+ }
+ return matching;
+ }
+
+ @Override
+ public void uninstall(int userId, String basePackageName, IPackageDeleteObserver observer) {
+ mPm.deletePackageAsUser(basePackageName, observer, userId, 0);
+ }
+
+ @Override
+ public void uninstallSplit(int userId, String basePackageName, String overlayName,
+ IPackageDeleteObserver observer) {
+ // TODO: flesh out once PM has split support
+ throw new UnsupportedOperationException();
+ }
+
+ class Callback {
+ public void onProgressChanged(PackageInstallerSession session) {
+ // TODO: notify listeners
+ }
+
+ public void onSessionInvalid(PackageInstallerSession session) {
+ writeSessionsAsync();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
new file mode 100644
index 0000000..f90d7ab
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -0,0 +1,539 @@
+/*
+ * Copyright (C) 2014 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.server.pm;
+
+import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
+import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
+import static android.content.pm.PackageManager.INSTALL_FAILED_PACKAGE_CHANGED;
+import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageInstallObserver2;
+import android.content.pm.IPackageInstallerSession;
+import android.content.pm.PackageInstallerParams;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageParser;
+import android.content.pm.PackageParser.PackageLite;
+import android.content.pm.Signature;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.SELinux;
+import android.system.ErrnoException;
+import android.system.OsConstants;
+import android.system.StructStat;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.internal.content.NativeLibraryHelper;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
+
+import libcore.io.IoUtils;
+import libcore.io.Libcore;
+import libcore.io.Streams;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+
+public class PackageInstallerSession extends IPackageInstallerSession.Stub {
+ private static final String TAG = "PackageInstaller";
+
+ private final PackageInstallerService.Callback mCallback;
+ private final PackageManagerService mPm;
+ private final Handler mHandler;
+
+ public final int sessionId;
+ public final int userId;
+ public final String installerPackageName;
+ /** UID not persisted */
+ public final int installerUid;
+ public final PackageInstallerParams params;
+ public final long createdMillis;
+ public final File sessionDir;
+
+ private static final int MSG_INSTALL = 0;
+
+ private Handler.Callback mHandlerCallback = new Handler.Callback() {
+ @Override
+ public boolean handleMessage(Message msg) {
+ synchronized (mLock) {
+ if (msg.obj != null) {
+ mRemoteObserver = (IPackageInstallObserver2) msg.obj;
+ }
+
+ try {
+ installLocked();
+ } catch (InstallFailedException e) {
+ Slog.e(TAG, "Install failed: " + e);
+ try {
+ mRemoteObserver.packageInstalled(mPackageName, null, e.error);
+ } catch (RemoteException ignored) {
+ }
+ }
+
+ return true;
+ }
+ }
+ };
+
+ private final Object mLock = new Object();
+
+ private int mProgress;
+
+ private String mPackageName;
+ private int mVersionCode;
+ private Signature[] mSignatures;
+
+ private boolean mMutationsAllowed;
+ private boolean mVerifierConfirmed;
+ private boolean mPermissionsConfirmed;
+ private boolean mInvalid;
+
+ private ArrayList<WritePipe> mPipes = new ArrayList<>();
+
+ private IPackageInstallObserver2 mRemoteObserver;
+
+ public PackageInstallerSession(PackageInstallerService.Callback callback,
+ PackageManagerService pm, int sessionId, int userId, String installerPackageName,
+ int installerUid, PackageInstallerParams params, long createdMillis, File sessionDir,
+ Looper looper) {
+ mCallback = callback;
+ mPm = pm;
+ mHandler = new Handler(looper, mHandlerCallback);
+
+ this.sessionId = sessionId;
+ this.userId = userId;
+ this.installerPackageName = installerPackageName;
+ this.installerUid = installerUid;
+ this.params = params;
+ this.createdMillis = createdMillis;
+ this.sessionDir = sessionDir;
+
+ // Check against any explicitly provided signatures
+ mSignatures = params.signatures;
+
+ // TODO: splice in flag when restoring persisted session
+ mMutationsAllowed = true;
+
+ if (pm.checkPermission(android.Manifest.permission.INSTALL_PACKAGES, installerPackageName)
+ == PackageManager.PERMISSION_GRANTED) {
+ mPermissionsConfirmed = true;
+ }
+ }
+
+ @Override
+ public void updateProgress(int progress) {
+ mProgress = progress;
+ mCallback.onProgressChanged(this);
+ }
+
+ @Override
+ public ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes) {
+ // TODO: relay over to DCS when installing to ASEC
+
+ // Quick sanity check of state, and allocate a pipe for ourselves. We
+ // then do heavy disk allocation outside the lock, but this open pipe
+ // will block any attempted install transitions.
+ final WritePipe pipe;
+ synchronized (mLock) {
+ if (!mMutationsAllowed) {
+ throw new IllegalStateException("Mutations not allowed");
+ }
+
+ pipe = new WritePipe();
+ mPipes.add(pipe);
+ }
+
+ try {
+ // Use installer provided name for now; we always rename later
+ if (!FileUtils.isValidExtFilename(name)) {
+ throw new IllegalArgumentException("Invalid name: " + name);
+ }
+ final File target = new File(sessionDir, name);
+
+ final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(),
+ OsConstants.O_CREAT | OsConstants.O_WRONLY, 00700);
+
+ // If caller specified a total length, allocate it for them. Free up
+ // cache space to grow, if needed.
+ if (lengthBytes > 0) {
+ final StructStat stat = Libcore.os.fstat(targetFd);
+ final long deltaBytes = lengthBytes - stat.st_size;
+ if (deltaBytes > 0) {
+ mPm.freeStorage(deltaBytes);
+ }
+ Libcore.os.posix_fallocate(targetFd, 0, lengthBytes);
+ }
+
+ if (offsetBytes > 0) {
+ Libcore.os.lseek(targetFd, offsetBytes, OsConstants.SEEK_SET);
+ }
+
+ pipe.setTargetFd(targetFd);
+ pipe.start();
+ return pipe.getWriteFd();
+
+ } catch (ErrnoException e) {
+ throw new IllegalStateException("Failed to write", e);
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to write", e);
+ }
+ }
+
+ @Override
+ public void install(IPackageInstallObserver2 observer) {
+ Preconditions.checkNotNull(observer);
+ mHandler.obtainMessage(MSG_INSTALL, observer).sendToTarget();
+ }
+
+ private void installLocked() throws InstallFailedException {
+ if (mInvalid) {
+ throw new InstallFailedException(INSTALL_FAILED_ALREADY_EXISTS, "Invalid session");
+ }
+
+ // Verify that all writers are hands-off
+ if (mMutationsAllowed) {
+ for (WritePipe pipe : mPipes) {
+ if (!pipe.isClosed()) {
+ throw new InstallFailedException(INSTALL_FAILED_PACKAGE_CHANGED,
+ "Files still open");
+ }
+ }
+ mMutationsAllowed = false;
+
+ // TODO: persist disabled mutations before going forward, since
+ // beyond this point we may have hardlinks to the valid install
+ }
+
+ // Verify that stage looks sane with respect to existing application.
+ // This currently only ensures packageName, versionCode, and certificate
+ // consistency.
+ validateInstallLocked();
+
+ Preconditions.checkNotNull(mPackageName);
+ Preconditions.checkNotNull(mSignatures);
+
+ if (!mVerifierConfirmed) {
+ // TODO: async communication with verifier
+ // when they confirm, we'll kick off another install() pass
+ mVerifierConfirmed = true;
+ }
+
+ if (!mPermissionsConfirmed) {
+ // TODO: async confirm permissions with user
+ // when they confirm, we'll kick off another install() pass
+ mPermissionsConfirmed = true;
+ }
+
+ // Unpack any native libraries contained in this session
+ unpackNativeLibraries();
+
+ // Inherit any packages and native libraries from existing install that
+ // haven't been overridden.
+ if (!params.fullInstall) {
+ spliceExistingFilesIntoStage();
+ }
+
+ // TODO: for ASEC based applications, grow and stream in packages
+
+ // We've reached point of no return; call into PMS to install the stage.
+ // Regardless of success or failure we always destroy session.
+ final IPackageInstallObserver2 remoteObserver = mRemoteObserver;
+ final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() {
+ @Override
+ public void packageInstalled(String basePackageName, Bundle extras, int returnCode)
+ throws RemoteException {
+ destroy();
+ remoteObserver.packageInstalled(basePackageName, extras, returnCode);
+ }
+ };
+
+ mPm.installStage(mPackageName, this.sessionDir, localObserver, params.installFlags);
+ }
+
+ /**
+ * Validate install by confirming that all application packages are have
+ * consistent package name, version code, and signing certificates.
+ * <p>
+ * Renames package files in stage to match split names defined inside.
+ */
+ private void validateInstallLocked() throws InstallFailedException {
+ mPackageName = null;
+ mVersionCode = -1;
+ mSignatures = null;
+
+ final File[] files = sessionDir.listFiles();
+ if (ArrayUtils.isEmpty(files)) {
+ throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, "No packages staged");
+ }
+
+ final ArraySet<String> seenSplits = new ArraySet<>();
+
+ // Verify that all staged packages are internally consistent
+ for (File file : files) {
+ final PackageLite info = PackageParser.parsePackageLite(file.getAbsolutePath(),
+ PackageParser.PARSE_GET_SIGNATURES);
+ if (info == null) {
+ throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
+ "Failed to parse " + file);
+ }
+
+ if (!seenSplits.add(info.splitName)) {
+ throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
+ "Split " + info.splitName + " was defined multiple times");
+ }
+
+ // Use first package to define unknown values
+ if (mPackageName != null) {
+ mPackageName = info.packageName;
+ mVersionCode = info.versionCode;
+ }
+ if (mSignatures != null) {
+ mSignatures = info.signatures;
+ }
+
+ assertPackageConsistent(String.valueOf(file), info.packageName, info.versionCode,
+ info.signatures);
+
+ // Take this opportunity to enforce uniform naming
+ final String name;
+ if (info.splitName == null) {
+ name = info.packageName + ".apk";
+ } else {
+ name = info.packageName + "-" + info.splitName + ".apk";
+ }
+ if (!FileUtils.isValidExtFilename(name)) {
+ throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
+ "Invalid filename: " + name);
+ }
+ if (!file.getName().equals(name)) {
+ file.renameTo(new File(file.getParentFile(), name));
+ }
+ }
+
+ // TODO: shift package signature verification to installer; we're
+ // currently relying on PMS to do this.
+ // TODO: teach about compatible upgrade keysets.
+
+ if (params.fullInstall) {
+ // Full installs must include a base package
+ if (!seenSplits.contains(null)) {
+ throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
+ "Full install must include a base package");
+ }
+
+ } else {
+ // Partial installs must be consistent with existing install.
+ final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId);
+ if (app == null) {
+ throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
+ "Missing existing base package for " + mPackageName);
+ }
+
+ final PackageLite info = PackageParser.parsePackageLite(app.sourceDir,
+ PackageParser.PARSE_GET_SIGNATURES);
+ if (info == null) {
+ throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
+ "Failed to parse existing base " + app.sourceDir);
+ }
+
+ assertPackageConsistent("Existing base", info.packageName, info.versionCode,
+ info.signatures);
+ }
+ }
+
+ private void assertPackageConsistent(String tag, String packageName, int versionCode,
+ Signature[] signatures) throws InstallFailedException {
+ if (!mPackageName.equals(packageName)) {
+ throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, tag + " package "
+ + packageName + " inconsistent with " + mPackageName);
+ }
+ if (mVersionCode != versionCode) {
+ throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, tag
+ + " version code " + versionCode + " inconsistent with "
+ + mVersionCode);
+ }
+ if (!Signature.areExactMatch(mSignatures, signatures)) {
+ throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
+ tag + " signatures are inconsistent");
+ }
+ }
+
+ /**
+ * Application is already installed; splice existing files that haven't been
+ * overridden into our stage.
+ */
+ private void spliceExistingFilesIntoStage() throws InstallFailedException {
+ final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId);
+ final File existingDir = new File(app.sourceDir).getParentFile();
+
+ try {
+ linkTreeIgnoringExisting(existingDir, sessionDir);
+ } catch (ErrnoException e) {
+ throw new InstallFailedException(INSTALL_FAILED_INTERNAL_ERROR,
+ "Failed to splice into stage");
+ }
+ }
+
+ /**
+ * Recursively hard link all files from source directory tree to target.
+ * When a file already exists in the target tree, it leaves that file
+ * intact.
+ */
+ private void linkTreeIgnoringExisting(File sourceDir, File targetDir) throws ErrnoException {
+ final File[] sourceContents = sourceDir.listFiles();
+ if (ArrayUtils.isEmpty(sourceContents)) return;
+
+ for (File sourceFile : sourceContents) {
+ final File targetFile = new File(targetDir, sourceFile.getName());
+
+ if (sourceFile.isDirectory()) {
+ targetFile.mkdir();
+ linkTreeIgnoringExisting(sourceFile, targetFile);
+ } else {
+ Libcore.os.link(sourceFile.getAbsolutePath(), targetFile.getAbsolutePath());
+ }
+ }
+ }
+
+ private void unpackNativeLibraries() throws InstallFailedException {
+ final File libDir = new File(sessionDir, "lib");
+
+ if (!libDir.mkdir()) {
+ throw new InstallFailedException(INSTALL_FAILED_INTERNAL_ERROR,
+ "Failed to create " + libDir);
+ }
+
+ try {
+ Libcore.os.chmod(libDir.getAbsolutePath(), 0755);
+ } catch (ErrnoException e) {
+ throw new InstallFailedException(INSTALL_FAILED_INTERNAL_ERROR,
+ "Failed to prepare " + libDir + ": " + e);
+ }
+
+ if (!SELinux.restorecon(libDir)) {
+ throw new InstallFailedException(INSTALL_FAILED_INTERNAL_ERROR,
+ "Failed to set context on " + libDir);
+ }
+
+ // Unpack all native libraries under stage
+ final File[] files = sessionDir.listFiles();
+ if (ArrayUtils.isEmpty(files)) {
+ throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, "No packages staged");
+ }
+
+ for (File file : files) {
+ final NativeLibraryHelper.ApkHandle handle = new NativeLibraryHelper.ApkHandle(file);
+ try {
+ final int abiIndex = NativeLibraryHelper.findSupportedAbi(handle,
+ Build.SUPPORTED_ABIS);
+ if (abiIndex >= 0) {
+ int copyRet = NativeLibraryHelper.copyNativeBinariesIfNeededLI(handle, libDir,
+ Build.SUPPORTED_ABIS[abiIndex]);
+ if (copyRet != INSTALL_SUCCEEDED) {
+ throw new InstallFailedException(copyRet,
+ "Failed to copy native libraries for " + file);
+ }
+ } else if (abiIndex != PackageManager.NO_NATIVE_LIBRARIES) {
+ throw new InstallFailedException(abiIndex,
+ "Failed to copy native libraries for " + file);
+ }
+ } finally {
+ handle.close();
+ }
+ }
+ }
+
+ @Override
+ public void destroy() {
+ try {
+ synchronized (mLock) {
+ mInvalid = true;
+ }
+ FileUtils.deleteContents(sessionDir);
+ sessionDir.delete();
+ } finally {
+ mCallback.onSessionInvalid(this);
+ }
+ }
+
+ private static class WritePipe extends Thread {
+ private final ParcelFileDescriptor[] mPipe;
+
+ private FileDescriptor mTargetFd;
+
+ private volatile boolean mClosed;
+
+ public WritePipe() {
+ try {
+ mPipe = ParcelFileDescriptor.createPipe();
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to create pipe");
+ }
+ }
+
+ public boolean isClosed() {
+ return mClosed;
+ }
+
+ public void setTargetFd(FileDescriptor targetFd) {
+ mTargetFd = targetFd;
+ }
+
+ public ParcelFileDescriptor getWriteFd() {
+ return mPipe[1];
+ }
+
+ @Override
+ public void run() {
+ FileInputStream in = null;
+ FileOutputStream out = null;
+ try {
+ // TODO: look at switching to sendfile(2) to speed up
+ in = new FileInputStream(mPipe[0].getFileDescriptor());
+ out = new FileOutputStream(mTargetFd);
+ Streams.copy(in, out);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to stream data: " + e);
+ } finally {
+ IoUtils.closeQuietly(mPipe[0]);
+ IoUtils.closeQuietly(mTargetFd);
+ mClosed = true;
+ }
+ }
+ }
+
+ private class InstallFailedException extends Exception {
+ private final int error;
+
+ public InstallFailedException(int error, String detailMessage) {
+ super(detailMessage);
+ this.error = error;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index d1333b2..a3ecc05 100755
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -18,6 +18,7 @@ package com.android.server.pm;
import static android.Manifest.permission.GRANT_REVOKE_PERMISSIONS;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
+import static android.Manifest.permission.INSTALL_PACKAGES;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
@@ -59,6 +60,7 @@ import org.xmlpull.v1.XmlSerializer;
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.app.IActivityManager;
+import android.app.PackageInstallObserver;
import android.app.admin.IDevicePolicyManager;
import android.app.backup.IBackupManager;
import android.content.BroadcastReceiver;
@@ -78,6 +80,7 @@ import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageDeleteObserver;
import android.content.pm.IPackageInstallObserver;
import android.content.pm.IPackageInstallObserver2;
+import android.content.pm.IPackageInstaller;
import android.content.pm.IPackageManager;
import android.content.pm.IPackageMoveObserver;
import android.content.pm.IPackageStatsObserver;
@@ -86,6 +89,7 @@ import android.content.pm.ManifestDigest;
import android.content.pm.PackageCleanItem;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInfoLite;
+import android.content.pm.PackageInstallerParams;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser.ActivityIntentInfo;
import android.content.pm.PackageParser;
@@ -179,6 +183,7 @@ import java.util.concurrent.atomic.AtomicLong;
import dalvik.system.DexFile;
import dalvik.system.StaleDexCacheError;
import dalvik.system.VMRuntime;
+
import libcore.io.IoUtils;
/**
@@ -351,6 +356,8 @@ public class PackageManagerService extends IPackageManager.Stub {
// apps.
final File mDrmAppPrivateInstallDir;
+ final File mAppStagingDir;
+
// ----------------------------------------------------------------
// Lock for state used when installing and doing other long running
@@ -457,6 +464,8 @@ public class PackageManagerService extends IPackageManager.Stub {
final SparseArray<PackageVerificationState> mPendingVerification
= new SparseArray<PackageVerificationState>();
+ final PackageInstallerService mInstallerService;
+
HashSet<PackageParser.Package> mDeferredDexOpt = null;
/** Token for keys in mPendingVerification. */
@@ -1339,6 +1348,7 @@ public class PackageManagerService extends IPackageManager.Stub {
mAsecInternalPath = new File(dataDir, "app-asec").getPath();
mUserAppDataDir = new File(dataDir, "user");
mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
+ mAppStagingDir = new File(dataDir, "app-staging");
sUserManager = new UserManagerService(context, this,
mInstallLock, mPackages);
@@ -1701,14 +1711,17 @@ public class PackageManagerService extends IPackageManager.Stub {
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_READY,
SystemClock.uptimeMillis());
- // Now after opening every single application zip, make sure they
- // are all flushed. Not really needed, but keeps things nice and
- // tidy.
- Runtime.getRuntime().gc();
mRequiredVerifierPackage = getRequiredVerifierLPr();
} // synchronized (mPackages)
} // synchronized (mInstallLock)
+
+ mInstallerService = new PackageInstallerService(context, this, mAppStagingDir);
+
+ // Now after opening every single application zip, make sure they
+ // are all flushed. Not really needed, but keeps things nice and
+ // tidy.
+ Runtime.getRuntime().gc();
}
private static void pruneDexFiles(File cacheDir) {
@@ -2252,7 +2265,8 @@ public class PackageManagerService extends IPackageManager.Stub {
if ((flags & PackageManager.GET_UNINSTALLED_PACKAGES) == 0) {
return null;
}
- pkg = new PackageParser.Package(packageName);
+ // TODO: teach about reading split name
+ pkg = new PackageParser.Package(packageName, null);
pkg.applicationInfo.packageName = packageName;
pkg.applicationInfo.flags = ps.pkgFlags | ApplicationInfo.FLAG_IS_DATA_ONLY;
pkg.applicationInfo.publicSourceDir = ps.resourcePathString;
@@ -2350,6 +2364,14 @@ public class PackageManagerService extends IPackageManager.Stub {
});
}
+ void freeStorage(long freeStorageSize) throws IOException {
+ synchronized (mInstallLock) {
+ if (mInstaller.freeCache(freeStorageSize) < 0) {
+ throw new IOException("Failed to free enough space");
+ }
+ }
+ }
+
@Override
public ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) {
if (!sUserManager.exists(userId)) return null;
@@ -2533,10 +2555,9 @@ public class PackageManagerService extends IPackageManager.Stub {
* Checks if the request is from the system or an app that has INTERACT_ACROSS_USERS
* or INTERACT_ACROSS_USERS_FULL permissions, if the userid is not for the caller.
* @param message the message to log on security exception
- * @return
*/
- private void enforceCrossUserPermission(int callingUid, int userId,
- boolean requireFullPermission, String message) {
+ void enforceCrossUserPermission(int callingUid, int userId, boolean requireFullPermission,
+ String message) {
if (userId < 0) {
throw new IllegalArgumentException("Invalid userId " + userId);
}
@@ -7734,6 +7755,16 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
+ void installStage(String basePackageName, File stageDir, IPackageInstallObserver2 observer,
+ int flags) {
+ // TODO: install stage!
+ try {
+ observer.packageInstalled(basePackageName, null,
+ PackageManager.INSTALL_FAILED_INTERNAL_ERROR);
+ } catch (RemoteException ignored) {
+ }
+ }
+
/**
* @hide
*/
@@ -7777,7 +7808,7 @@ public class PackageManagerService extends IPackageManager.Stub {
return PackageManager.INSTALL_SUCCEEDED;
}
- private boolean isUserRestricted(int userId, String restrictionKey) {
+ boolean isUserRestricted(int userId, String restrictionKey) {
Bundle restrictions = sUserManager.getUserRestrictions(userId);
if (restrictions.getBoolean(restrictionKey, false)) {
Log.w(TAG, "User is restricted: " + restrictionKey);
@@ -12900,4 +12931,9 @@ public class PackageManagerService extends IPackageManager.Stub {
Binder.restoreCallingIdentity(token);
}
}
+
+ @Override
+ public IPackageInstaller getPackageInstaller() {
+ return mInstallerService;
+ }
}
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index a016933..3190fb0 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -33,6 +33,7 @@ import android.content.pm.IPackageStatsObserver;
import android.content.pm.InstrumentationInfo;
import android.content.pm.ManifestDigest;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
@@ -718,4 +719,9 @@ public class MockPackageManager extends PackageManager {
public void clearForwardingIntentFilters(int userIdOrig) {
throw new UnsupportedOperationException();
}
+
+ /** {@hide} */
+ public PackageInstaller getPackageInstaller() {
+ throw new UnsupportedOperationException();
+ }
}