diff options
author | Jeff Sharkey <jsharkey@android.com> | 2015-04-23 19:36:02 -0700 |
---|---|---|
committer | Jeff Sharkey <jsharkey@android.com> | 2015-04-23 20:32:17 -0700 |
commit | 620b32b316fd4f1bab4eef55ec8802d14a55e7dd (patch) | |
tree | afedbccf5c135e8d19ba52db199316ab0932a277 /services | |
parent | 61044cfd9c7f02d3806338a6b8f137f50e1b1e0f (diff) | |
download | frameworks_base-620b32b316fd4f1bab4eef55ec8802d14a55e7dd.zip frameworks_base-620b32b316fd4f1bab4eef55ec8802d14a55e7dd.tar.gz frameworks_base-620b32b316fd4f1bab4eef55ec8802d14a55e7dd.tar.bz2 |
Package and storage movement callbacks.
Since package and primary storage movement can take quite awhile,
we want to have SystemUI surface progress and allow the Settings
app to be torn down while the movement proceeds in the background.
Movement requests now return a unique ID that identifies an ongoing
operation, and interested parties can observe ongoing progress and
final status. Internally, progress and status are overloaded so
the values 0-100 are progress, and any values outside that range
are terminal status.
Add explicit constants for special-cased volume UUIDs, and change
the APIs to accept VolumeInfo to reduce confusion. Internally the
UUID value "null" means internal storage, and "primary_physical"
means the current primary physical volume. These values are used
for both package and primary storage movement destinations.
Persist the current primary storage location in MountService
metadata, since it can be moved over time.
Surface disk scanned events with separate volume count so we can
determine when it's partitioned successfully. Also send broadcast
to support TvSettings launching into adoption flow.
Bug: 19993667
Change-Id: Ic8a4034033c3cb3262023dba4a642efc6795af10
Diffstat (limited to 'services')
3 files changed, 232 insertions, 55 deletions
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java index f88802a..89a7173 100644 --- a/services/core/java/com/android/server/MountService.java +++ b/services/core/java/com/android/server/MountService.java @@ -232,7 +232,12 @@ class MountService extends IMountService.Stub public static final int FstrimCompleted = 700; } + private static final int VERSION_INIT = 1; + private static final int VERSION_ADD_PRIMARY = 2; + private static final String TAG_VOLUMES = "volumes"; + private static final String ATTR_VERSION = "version"; + private static final String ATTR_PRIMARY_STORAGE_UUID = "primaryStorageUuid"; private static final String TAG_VOLUME = "volume"; private static final String ATTR_TYPE = "type"; private static final String ATTR_FS_UUID = "fsUuid"; @@ -302,6 +307,8 @@ class MountService extends IMountService.Stub /** Map from UUID to metadata */ @GuardedBy("mLock") private ArrayMap<String, VolumeMetadata> mMetadata = new ArrayMap<>(); + @GuardedBy("mLock") + private String mPrimaryStorageUuid; /** Map from disk ID to latches */ @GuardedBy("mLock") @@ -943,22 +950,25 @@ class MountService extends IMountService.Stub } private void onDiskScannedLocked(DiskInfo disk) { + final Intent intent = new Intent(DiskInfo.ACTION_DISK_SCANNED); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL, + android.Manifest.permission.WRITE_MEDIA_STORAGE); + final CountDownLatch latch = mDiskScanLatches.remove(disk.id); if (latch != null) { latch.countDown(); } - boolean empty = true; + int volumeCount = 0; for (int i = 0; i < mVolumes.size(); i++) { final VolumeInfo vol = mVolumes.valueAt(i); if (Objects.equals(disk.id, vol.getDiskId())) { - empty = false; + volumeCount++; } } - if (empty) { - mCallbacks.notifyDiskUnsupported(disk); - } + mCallbacks.notifyDiskScanned(disk, volumeCount); } private void onVolumeCreatedLocked(VolumeInfo vol) { @@ -1022,8 +1032,8 @@ class MountService extends IMountService.Stub if (isBroadcastWorthy(vol)) { final Intent intent = new Intent(VolumeInfo.ACTION_VOLUME_STATE_CHANGED); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - // TODO: require receiver to hold permission - mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL, + android.Manifest.permission.WRITE_MEDIA_STORAGE); } final String oldStateEnv = VolumeInfo.getEnvironmentForState(oldState); @@ -1166,7 +1176,21 @@ class MountService extends IMountService.Stub while ((type = in.next()) != END_DOCUMENT) { if (type == START_TAG) { final String tag = in.getName(); - if (TAG_VOLUME.equals(tag)) { + if (TAG_VOLUMES.equals(tag)) { + final int version = readIntAttribute(in, ATTR_VERSION, VERSION_INIT); + if (version >= VERSION_ADD_PRIMARY) { + mPrimaryStorageUuid = readStringAttribute(in, + ATTR_PRIMARY_STORAGE_UUID); + } else { + if (SystemProperties.getBoolean(StorageManager.PROP_PRIMARY_PHYSICAL, + false)) { + mPrimaryStorageUuid = StorageManager.UUID_PRIMARY_PHYSICAL; + } else { + mPrimaryStorageUuid = StorageManager.UUID_PRIVATE_INTERNAL; + } + } + + } else if (TAG_VOLUME.equals(tag)) { final VolumeMetadata meta = VolumeMetadata.read(in); mMetadata.put(meta.fsUuid, meta); } @@ -1192,6 +1216,8 @@ class MountService extends IMountService.Stub out.setOutput(fos, "utf-8"); out.startDocument(null, true); out.startTag(null, TAG_VOLUMES); + writeIntAttribute(out, ATTR_VERSION, VERSION_ADD_PRIMARY); + writeStringAttribute(out, ATTR_PRIMARY_STORAGE_UUID, mPrimaryStorageUuid); final int size = mMetadata.size(); for (int i = 0; i < size; i++) { final VolumeMetadata meta = mMetadata.valueAt(i); @@ -1398,6 +1424,24 @@ class MountService extends IMountService.Stub } @Override + public String getPrimaryStorageUuid() throws RemoteException { + synchronized (mLock) { + return mPrimaryStorageUuid; + } + } + + @Override + public void setPrimaryStorageUuid(String volumeUuid) throws RemoteException { + synchronized (mLock) { + Slog.d(TAG, "Changing primary storage UUID to " + volumeUuid); + mPrimaryStorageUuid = volumeUuid; + writeMetadataLocked(); + + // TODO: reevaluate all volumes we know about! + } + } + + @Override public int[] getStorageUsers(String path) { enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); waitForReady(); @@ -2700,7 +2744,7 @@ class MountService extends IMountService.Stub private static final int MSG_STORAGE_STATE_CHANGED = 1; private static final int MSG_VOLUME_STATE_CHANGED = 2; private static final int MSG_VOLUME_METADATA_CHANGED = 3; - private static final int MSG_DISK_UNSUPPORTED = 4; + private static final int MSG_DISK_SCANNED = 4; private final RemoteCallbackList<IMountServiceListener> mCallbacks = new RemoteCallbackList<>(); @@ -2748,8 +2792,8 @@ class MountService extends IMountService.Stub callback.onVolumeMetadataChanged((VolumeInfo) args.arg1); break; } - case MSG_DISK_UNSUPPORTED: { - callback.onDiskUnsupported((DiskInfo) args.arg1); + case MSG_DISK_SCANNED: { + callback.onDiskScanned((DiskInfo) args.arg1, args.argi2); break; } } @@ -2777,10 +2821,11 @@ class MountService extends IMountService.Stub obtainMessage(MSG_VOLUME_METADATA_CHANGED, args).sendToTarget(); } - private void notifyDiskUnsupported(DiskInfo disk) { + private void notifyDiskScanned(DiskInfo disk, int volumeCount) { final SomeArgs args = SomeArgs.obtain(); args.arg1 = disk; - obtainMessage(MSG_DISK_UNSUPPORTED, args).sendToTarget(); + args.argi2 = volumeCount; + obtainMessage(MSG_DISK_SCANNED, args).sendToTarget(); } } diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 89fa320..a406175 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -717,7 +717,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } else { final VolumeInfo vol = mStorage.findVolumeByUuid(volumeUuid); if (vol != null && vol.type == VolumeInfo.TYPE_PRIVATE - && vol.state == VolumeInfo.STATE_MOUNTED) { + && vol.isMountedWritable()) { return new File(vol.path, "app"); } else { throw new FileNotFoundException("Failed to find volume for UUID " + volumeUuid); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 1c339f5..bd22524 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -49,12 +49,10 @@ import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATIO import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK; import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER; import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED; -import static android.content.pm.PackageManager.MOVE_EXTERNAL_MEDIA; import static android.content.pm.PackageManager.MOVE_FAILED_DOESNT_EXIST; import static android.content.pm.PackageManager.MOVE_FAILED_INTERNAL_ERROR; import static android.content.pm.PackageManager.MOVE_FAILED_OPERATION_PENDING; import static android.content.pm.PackageManager.MOVE_FAILED_SYSTEM_PACKAGE; -import static android.content.pm.PackageManager.MOVE_INTERNAL; import static android.content.pm.PackageParser.isApkFile; import static android.os.Process.PACKAGE_INFO_GID; import static android.os.Process.SYSTEM_UID; @@ -144,6 +142,7 @@ import android.os.Message; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Process; +import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.SELinux; import android.os.ServiceManager; @@ -174,6 +173,7 @@ import android.util.PrintStreamPrinter; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; +import android.util.SparseIntArray; import android.util.Xml; import android.view.Display; @@ -189,11 +189,14 @@ import com.android.internal.app.ResolverActivity; import com.android.internal.content.NativeLibraryHelper; import com.android.internal.content.PackageHelper; import com.android.internal.os.IParcelFileDescriptorFactory; +import com.android.internal.os.SomeArgs; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.IndentingPrintWriter; +import com.android.internal.util.Preconditions; import com.android.server.EventLogTags; +import com.android.server.FgThread; import com.android.server.IntentResolver; import com.android.server.LocalServices; import com.android.server.ServiceThread; @@ -237,6 +240,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; /** @@ -504,6 +508,10 @@ public class PackageManagerService extends IPackageManager.Stub { final PackageInstallerService mInstallerService; private final PackageDexOptimizer mPackageDexOptimizer; + + private AtomicInteger mNextMoveId = new AtomicInteger(); + private final MoveCallbacks mMoveCallbacks; + // Cache of users who need badging. SparseBooleanArray mUserNeedsBadging = new SparseBooleanArray(); @@ -1698,6 +1706,7 @@ public class PackageManagerService extends IPackageManager.Stub { mInstaller = installer; mPackageDexOptimizer = new PackageDexOptimizer(this); + mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper()); getDefaultDisplayMetrics(context, mMetrics); @@ -14137,49 +14146,25 @@ public class PackageManagerService extends IPackageManager.Stub { } @Override - public void movePackage(final String packageName, final IPackageMoveObserver observer, - final int flags) { + public int movePackage(final String packageName, final String volumeUuid) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MOVE_PACKAGE, null); - final int installFlags; - if ((flags & MOVE_INTERNAL) != 0) { - installFlags = INSTALL_INTERNAL; - } else if ((flags & MOVE_EXTERNAL_MEDIA) != 0) { - installFlags = INSTALL_EXTERNAL; - } else { - throw new IllegalArgumentException("Unsupported move flags " + flags); - } - - try { - movePackageInternal(packageName, null, installFlags, false, observer); - } catch (PackageManagerException e) { - Slog.d(TAG, "Failed to move " + packageName, e); - try { - observer.packageMoved(packageName, e.error); - } catch (RemoteException ignored) { - } - } - } - - @Override - public void movePackageAndData(final String packageName, final String volumeUuid, - final IPackageMoveObserver observer) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MOVE_PACKAGE, null); + final int moveId = mNextMoveId.getAndIncrement(); try { - movePackageInternal(packageName, volumeUuid, INSTALL_INTERNAL, true, observer); + movePackageInternal(packageName, volumeUuid, moveId); } catch (PackageManagerException e) { Slog.d(TAG, "Failed to move " + packageName, e); - try { - observer.packageMoved(packageName, e.error); - } catch (RemoteException ignored) { - } + mMoveCallbacks.notifyStatusChanged(moveId, PackageManager.MOVE_FAILED_INTERNAL_ERROR); } + return moveId; } - private void movePackageInternal(final String packageName, String volumeUuid, int installFlags, - boolean andData, final IPackageMoveObserver observer) throws PackageManagerException { + private void movePackageInternal(final String packageName, final String volumeUuid, + final int moveId) throws PackageManagerException { final UserHandle user = new UserHandle(UserHandle.getCallingUserId()); + final PackageManager pm = mContext.getPackageManager(); + final boolean currentAsec; final String currentVolumeUuid; final File codeFile; final String installerPackageName; @@ -14205,8 +14190,13 @@ public class PackageManagerService extends IPackageManager.Stub { // TODO: yell if already in desired location + mMoveCallbacks.notifyStarted(moveId, + String.valueOf(pm.getApplicationLabel(pkg.applicationInfo))); + pkg.mOperationPending = true; + currentAsec = pkg.applicationInfo.isForwardLocked() + || pkg.applicationInfo.isExternalAsec(); currentVolumeUuid = ps.volumeUuid; codeFile = new File(pkg.codePath); installerPackageName = ps.installerPackageName; @@ -14215,10 +14205,36 @@ public class PackageManagerService extends IPackageManager.Stub { seinfo = pkg.applicationInfo.seinfo; } - if (andData) { - Slog.d(TAG, "Moving " + packageName + " private data from " + currentVolumeUuid + " to " - + volumeUuid); + int installFlags; + final boolean moveData; + + if (Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, volumeUuid)) { + installFlags = INSTALL_INTERNAL; + moveData = !currentAsec; + } else if (Objects.equals(StorageManager.UUID_PRIMARY_PHYSICAL, volumeUuid)) { + installFlags = INSTALL_EXTERNAL; + moveData = false; + } else { + final StorageManager storage = mContext.getSystemService(StorageManager.class); + final VolumeInfo volume = storage.findVolumeByUuid(volumeUuid); + if (volume == null || volume.getType() != VolumeInfo.TYPE_PRIVATE + || !volume.isMountedWritable()) { + throw new PackageManagerException(MOVE_FAILED_INTERNAL_ERROR, + "Move location not mounted private volume"); + } + + Preconditions.checkState(!currentAsec); + + installFlags = INSTALL_INTERNAL; + moveData = true; + } + + Slog.d(TAG, "Moving " + packageName + " from " + currentVolumeUuid + " to " + volumeUuid); + mMoveCallbacks.notifyStatusChanged(moveId, 10, -1); + + if (moveData) { synchronized (mInstallLock) { + // TODO: split this into separate copy and delete operations if (mInstaller.moveUserDataDirs(currentVolumeUuid, volumeUuid, packageName, appId, seinfo) != 0) { synchronized (mPackages) { @@ -14234,6 +14250,8 @@ public class PackageManagerService extends IPackageManager.Stub { } } + mMoveCallbacks.notifyStatusChanged(moveId, 50); + final IPackageInstallObserver2 installObserver = new IPackageInstallObserver2.Stub() { @Override public void onUserActionRequired(Intent intent) throws RemoteException { @@ -14259,13 +14277,16 @@ public class PackageManagerService extends IPackageManager.Stub { final int status = PackageManager.installStatusToPublicStatus(returnCode); switch (status) { case PackageInstaller.STATUS_SUCCESS: - observer.packageMoved(packageName, PackageManager.MOVE_SUCCEEDED); + mMoveCallbacks.notifyStatusChanged(moveId, + PackageManager.MOVE_SUCCEEDED); break; case PackageInstaller.STATUS_FAILURE_STORAGE: - observer.packageMoved(packageName, PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE); + mMoveCallbacks.notifyStatusChanged(moveId, + PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE); break; default: - observer.packageMoved(packageName, PackageManager.MOVE_FAILED_INTERNAL_ERROR); + mMoveCallbacks.notifyStatusChanged(moveId, + PackageManager.MOVE_FAILED_INTERNAL_ERROR); break; } } @@ -14283,6 +14304,39 @@ public class PackageManagerService extends IPackageManager.Stub { } @Override + public int movePrimaryStorage(String volumeUuid) throws RemoteException { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MOVE_PACKAGE, null); + + final int moveId = mNextMoveId.getAndIncrement(); + + // TODO: ask mountservice to take down both, connect over to DCS to + // migrate, and then bring up new storage + + return moveId; + } + + @Override + public int getMoveStatus(int moveId) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null); + return mMoveCallbacks.mLastStatus.get(moveId); + } + + @Override + public void registerMoveCallback(IPackageMoveObserver callback) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null); + mMoveCallbacks.register(callback); + } + + @Override + public void unregisterMoveCallback(IPackageMoveObserver callback) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null); + mMoveCallbacks.unregister(callback); + } + + @Override public boolean setInstallLocation(int loc) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS, null); @@ -14605,4 +14659,82 @@ public class PackageManagerService extends IPackageManager.Stub { } } } + + private static class MoveCallbacks extends Handler { + private static final int MSG_STARTED = 1; + private static final int MSG_STATUS_CHANGED = 2; + + private final RemoteCallbackList<IPackageMoveObserver> + mCallbacks = new RemoteCallbackList<>(); + + private final SparseIntArray mLastStatus = new SparseIntArray(); + + public MoveCallbacks(Looper looper) { + super(looper); + } + + public void register(IPackageMoveObserver callback) { + mCallbacks.register(callback); + } + + public void unregister(IPackageMoveObserver callback) { + mCallbacks.unregister(callback); + } + + @Override + public void handleMessage(Message msg) { + final SomeArgs args = (SomeArgs) msg.obj; + final int n = mCallbacks.beginBroadcast(); + for (int i = 0; i < n; i++) { + final IPackageMoveObserver callback = mCallbacks.getBroadcastItem(i); + try { + invokeCallback(callback, msg.what, args); + } catch (RemoteException ignored) { + } + } + mCallbacks.finishBroadcast(); + args.recycle(); + } + + private void invokeCallback(IPackageMoveObserver callback, int what, SomeArgs args) + throws RemoteException { + switch (what) { + case MSG_STARTED: { + callback.onStarted(args.argi1, (String) args.arg2); + break; + } + case MSG_STATUS_CHANGED: { + callback.onStatusChanged(args.argi1, args.argi2, (long) args.arg3); + break; + } + } + } + + private void notifyStarted(int moveId, String title) { + Slog.v(TAG, "Move " + moveId + " started with title " + title); + + final SomeArgs args = SomeArgs.obtain(); + args.argi1 = moveId; + args.arg2 = title; + obtainMessage(MSG_STARTED, args).sendToTarget(); + } + + private void notifyStatusChanged(int moveId, int status) { + notifyStatusChanged(moveId, status, -1); + } + + private void notifyStatusChanged(int moveId, int status, long estMillis) { + Slog.v(TAG, "Move " + moveId + " status " + status); + + final SomeArgs args = SomeArgs.obtain(); + args.argi1 = moveId; + args.argi2 = status; + args.arg3 = estMillis; + obtainMessage(MSG_STATUS_CHANGED, args).sendToTarget(); + + synchronized (mLastStatus) { + mLastStatus.put(moveId, status); + } + } + } } |