diff options
23 files changed, 1444 insertions, 117 deletions
@@ -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(); + } } |