summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeff Sharkey <jsharkey@android.com>2015-04-23 19:36:02 -0700
committerJeff Sharkey <jsharkey@android.com>2015-04-23 20:32:17 -0700
commit620b32b316fd4f1bab4eef55ec8802d14a55e7dd (patch)
treeafedbccf5c135e8d19ba52db199316ab0932a277
parent61044cfd9c7f02d3806338a6b8f137f50e1b1e0f (diff)
downloadframeworks_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
-rw-r--r--cmds/pm/src/com/android/commands/pm/Pm.java45
-rw-r--r--core/java/android/app/ApplicationPackageManager.java201
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl10
-rw-r--r--core/java/android/content/pm/IPackageMoveObserver.aidl4
-rw-r--r--core/java/android/content/pm/PackageManager.java44
-rw-r--r--core/java/android/os/storage/DiskInfo.java17
-rw-r--r--core/java/android/os/storage/IMountService.java52
-rw-r--r--core/java/android/os/storage/IMountServiceListener.java16
-rw-r--r--core/java/android/os/storage/StorageEventListener.java2
-rw-r--r--core/java/android/os/storage/StorageManager.java47
-rw-r--r--core/java/android/os/storage/VolumeInfo.java4
-rw-r--r--core/java/com/android/internal/util/Preconditions.java6
-rw-r--r--core/tests/coretests/src/android/content/pm/PackageManagerTests.java82
-rw-r--r--services/core/java/com/android/server/MountService.java71
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java2
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java214
-rw-r--r--test-runner/src/android/test/mock/MockPackageManager.java46
17 files changed, 631 insertions, 232 deletions
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index d5cc8cc..b84b1e2 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -51,9 +51,11 @@ import android.os.Bundle;
import android.os.IUserManager;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.text.TextUtils;
+import android.text.format.DateUtils;
import android.util.Log;
import libcore.io.IoUtils;
@@ -1283,20 +1285,6 @@ public final class Pm {
}
}
- class LocalPackageMoveObserver extends IPackageMoveObserver.Stub {
- boolean finished;
- int returnCode;
-
- @Override
- public void packageMoved(String packageName, int returnCode) throws RemoteException {
- synchronized (this) {
- this.finished = true;
- this.returnCode = returnCode;
- notifyAll();
- }
- }
- }
-
public int runMove() {
final String packageName = nextArg();
String volumeUuid = nextArg();
@@ -1304,24 +1292,21 @@ public final class Pm {
volumeUuid = null;
}
- final LocalPackageMoveObserver obs = new LocalPackageMoveObserver();
try {
- mPm.movePackageAndData(packageName, volumeUuid, obs);
+ final int moveId = mPm.movePackage(packageName, volumeUuid);
- synchronized (obs) {
- while (!obs.finished) {
- try {
- obs.wait();
- } catch (InterruptedException e) {
- }
- }
- if (obs.returnCode == PackageManager.MOVE_SUCCEEDED) {
- System.out.println("Success");
- return 0;
- } else {
- System.err.println("Failure [" + obs.returnCode + "]");
- return 1;
- }
+ int status = mPm.getMoveStatus(moveId);
+ while (!PackageManager.isMoveStatusFinished(status)) {
+ SystemClock.sleep(DateUtils.SECOND_IN_MILLIS);
+ status = mPm.getMoveStatus(moveId);
+ }
+
+ if (status == PackageManager.MOVE_SUCCEEDED) {
+ System.out.println("Success");
+ return 0;
+ } else {
+ System.err.println("Failure [" + status + "]");
+ return 1;
}
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index dfe7e18..10f5960 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -62,6 +62,9 @@ import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemProperties;
@@ -81,7 +84,9 @@ import com.android.internal.util.UserIcons;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
+import java.util.Objects;
/*package*/
final class ApplicationPackageManager extends PackageManager {
@@ -98,6 +103,9 @@ final class ApplicationPackageManager extends PackageManager {
@GuardedBy("mLock")
private PackageInstaller mInstaller;
+ @GuardedBy("mDelegates")
+ private final ArrayList<MoveCallbackDelegate> mDelegates = new ArrayList<>();
+
UserManager getUserManager() {
synchronized (mLock) {
if (mUserManager == null) {
@@ -1410,57 +1418,100 @@ final class ApplicationPackageManager extends PackageManager {
}
@Override
- public void movePackage(String packageName, IPackageMoveObserver observer, int flags) {
+ public String getInstallerPackageName(String packageName) {
+ try {
+ return mPM.getInstallerPackageName(packageName);
+ } catch (RemoteException e) {
+ // Should never happen!
+ }
+ return null;
+ }
+
+ @Override
+ public int getMoveStatus(int moveId) {
try {
- mPM.movePackage(packageName, observer, flags);
+ return mPM.getMoveStatus(moveId);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
}
@Override
- public void movePackageAndData(String packageName, String volumeUuid,
- IPackageMoveObserver observer) {
+ public void registerMoveCallback(MoveCallback callback, Handler handler) {
+ synchronized (mDelegates) {
+ final MoveCallbackDelegate delegate = new MoveCallbackDelegate(callback,
+ handler.getLooper());
+ try {
+ mPM.registerMoveCallback(delegate);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ mDelegates.add(delegate);
+ }
+ }
+
+ @Override
+ public void unregisterMoveCallback(MoveCallback callback) {
+ synchronized (mDelegates) {
+ for (Iterator<MoveCallbackDelegate> i = mDelegates.iterator(); i.hasNext();) {
+ final MoveCallbackDelegate delegate = i.next();
+ if (delegate.mCallback == callback) {
+ try {
+ mPM.unregisterMoveCallback(delegate);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ i.remove();
+ }
+ }
+ }
+ }
+
+ @Override
+ public int movePackage(String packageName, VolumeInfo vol) {
try {
- mPM.movePackageAndData(packageName, volumeUuid, observer);
+ final String volumeUuid;
+ if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(vol.id)) {
+ volumeUuid = StorageManager.UUID_PRIVATE_INTERNAL;
+ } else if (vol.isPrimaryPhysical()) {
+ volumeUuid = StorageManager.UUID_PRIMARY_PHYSICAL;
+ } else {
+ volumeUuid = Preconditions.checkNotNull(vol.fsUuid);
+ }
+
+ return mPM.movePackage(packageName, volumeUuid);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
}
@Override
- public @NonNull VolumeInfo getApplicationCurrentVolume(ApplicationInfo app) {
+ public @Nullable VolumeInfo getPackageCurrentVolume(ApplicationInfo app) {
final StorageManager storage = mContext.getSystemService(StorageManager.class);
if (app.isInternal()) {
- return Preconditions.checkNotNull(
- storage.findVolumeById(VolumeInfo.ID_PRIVATE_INTERNAL));
+ return storage.findVolumeById(VolumeInfo.ID_PRIVATE_INTERNAL);
} else if (app.isExternalAsec()) {
- final List<VolumeInfo> vols = storage.getVolumes();
- for (VolumeInfo vol : vols) {
- if ((vol.getType() == VolumeInfo.TYPE_PUBLIC) && vol.isPrimary()) {
- return vol;
- }
- }
- throw new IllegalStateException("Failed to find primary public volume");
+ return storage.getPrimaryPhysicalVolume();
} else {
- return Preconditions.checkNotNull(storage.findVolumeByUuid(app.volumeUuid));
+ return storage.findVolumeByUuid(app.volumeUuid);
}
}
@Override
- public @NonNull List<VolumeInfo> getApplicationCandidateVolumes(ApplicationInfo app) {
+ public @NonNull List<VolumeInfo> getPackageCandidateVolumes(ApplicationInfo app) {
final StorageManager storage = mContext.getSystemService(StorageManager.class);
+ final VolumeInfo currentVol = getPackageCurrentVolume(app);
final List<VolumeInfo> vols = storage.getVolumes();
final List<VolumeInfo> candidates = new ArrayList<>();
for (VolumeInfo vol : vols) {
- if (isCandidateVolume(app, vol)) {
+ if (Objects.equals(vol, currentVol) || isPackageCandidateVolume(app, vol)) {
candidates.add(vol);
}
}
return candidates;
}
- private static boolean isCandidateVolume(ApplicationInfo app, VolumeInfo vol) {
+ private static boolean isPackageCandidateVolume(ApplicationInfo app, VolumeInfo vol) {
// Private internal is always an option
if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(vol.getId())) {
return true;
@@ -1473,10 +1524,14 @@ final class ApplicationPackageManager extends PackageManager {
return false;
}
- // Moving into an ASEC on public primary is only an option when app is
- // internal, or already in ASEC
- if ((vol.getType() == VolumeInfo.TYPE_PUBLIC) && vol.isPrimary()) {
- return app.isInternal() || app.isExternalAsec();
+ // Gotta be able to write there
+ if (!vol.isMountedWritable()) {
+ return false;
+ }
+
+ // Moving into an ASEC on public primary is only option internal
+ if (vol.isPrimaryPhysical()) {
+ return app.isInternal();
}
// Otherwise we can move to any private volume
@@ -1484,13 +1539,66 @@ final class ApplicationPackageManager extends PackageManager {
}
@Override
- public String getInstallerPackageName(String packageName) {
+ public int movePrimaryStorage(VolumeInfo vol) {
try {
- return mPM.getInstallerPackageName(packageName);
+ final String volumeUuid;
+ if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(vol.id)) {
+ volumeUuid = StorageManager.UUID_PRIVATE_INTERNAL;
+ } else if (vol.isPrimaryPhysical()) {
+ volumeUuid = StorageManager.UUID_PRIMARY_PHYSICAL;
+ } else {
+ volumeUuid = Preconditions.checkNotNull(vol.fsUuid);
+ }
+
+ return mPM.movePrimaryStorage(volumeUuid);
} catch (RemoteException e) {
- // Should never happen!
+ throw e.rethrowAsRuntimeException();
}
- return null;
+ }
+
+ public @Nullable VolumeInfo getPrimaryStorageCurrentVolume() {
+ final StorageManager storage = mContext.getSystemService(StorageManager.class);
+ final String volumeUuid = storage.getPrimaryStorageUuid();
+ if (Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, volumeUuid)) {
+ return storage.findVolumeById(VolumeInfo.ID_PRIVATE_INTERNAL);
+ } else if (Objects.equals(StorageManager.UUID_PRIMARY_PHYSICAL, volumeUuid)) {
+ return storage.getPrimaryPhysicalVolume();
+ } else {
+ return storage.findVolumeByUuid(volumeUuid);
+ }
+ }
+
+ public @NonNull List<VolumeInfo> getPrimaryStorageCandidateVolumes() {
+ final StorageManager storage = mContext.getSystemService(StorageManager.class);
+ final VolumeInfo currentVol = getPrimaryStorageCurrentVolume();
+ final List<VolumeInfo> vols = storage.getVolumes();
+ final List<VolumeInfo> candidates = new ArrayList<>();
+ for (VolumeInfo vol : vols) {
+ if (Objects.equals(vol, currentVol) || isPrimaryStorageCandidateVolume(vol)) {
+ candidates.add(vol);
+ }
+ }
+ return candidates;
+ }
+
+ private static boolean isPrimaryStorageCandidateVolume(VolumeInfo vol) {
+ // Private internal is always an option
+ if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(vol.getId())) {
+ return true;
+ }
+
+ // Gotta be able to write there
+ if (!vol.isMountedWritable()) {
+ return false;
+ }
+
+ // We can move to public volumes on legacy devices
+ if ((vol.getType() == VolumeInfo.TYPE_PUBLIC) && vol.getDisk().isDefaultPrimary()) {
+ return true;
+ }
+
+ // Otherwise we can move to any private volume
+ return (vol.getType() == VolumeInfo.TYPE_PRIVATE);
}
@Override
@@ -1941,6 +2049,45 @@ final class ApplicationPackageManager extends PackageManager {
return null;
}
+ /** {@hide} */
+ private static class MoveCallbackDelegate extends IPackageMoveObserver.Stub implements
+ Handler.Callback {
+ private static final int MSG_STARTED = 1;
+ private static final int MSG_STATUS_CHANGED = 2;
+
+ final MoveCallback mCallback;
+ final Handler mHandler;
+
+ public MoveCallbackDelegate(MoveCallback callback, Looper looper) {
+ mCallback = callback;
+ mHandler = new Handler(looper, this);
+ }
+
+ @Override
+ public boolean handleMessage(Message msg) {
+ final int moveId = msg.arg1;
+ switch (msg.what) {
+ case MSG_STARTED:
+ mCallback.onStarted(moveId, (String) msg.obj);
+ return true;
+ case MSG_STATUS_CHANGED:
+ mCallback.onStatusChanged(moveId, msg.arg2, (long) msg.obj);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onStarted(int moveId, String title) {
+ mHandler.obtainMessage(MSG_STARTED, moveId, 0, title).sendToTarget();
+ }
+
+ @Override
+ public void onStatusChanged(int moveId, int status, long estMillis) {
+ mHandler.obtainMessage(MSG_STATUS_CHANGED, moveId, status, estMillis).sendToTarget();
+ }
+ }
+
private final ContextImpl mContext;
private final IPackageManager mPM;
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 447c668..ae59bfc 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -50,7 +50,6 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.content.IntentSender;
-import com.android.internal.os.IResultReceiver;
/**
* See {@link PackageManager} for documentation on most of the APIs
@@ -431,8 +430,13 @@ interface IPackageManager {
PackageCleanItem nextPackageToClean(in PackageCleanItem lastPackage);
- void movePackage(String packageName, IPackageMoveObserver observer, int flags);
- void movePackageAndData(String packageName, String volumeUuid, IPackageMoveObserver observer);
+ int getMoveStatus(int moveId);
+
+ void registerMoveCallback(in IPackageMoveObserver callback);
+ void unregisterMoveCallback(in IPackageMoveObserver callback);
+
+ int movePackage(in String packageName, in String volumeUuid);
+ int movePrimaryStorage(in String volumeUuid);
boolean addPermissionAsync(in PermissionInfo info);
diff --git a/core/java/android/content/pm/IPackageMoveObserver.aidl b/core/java/android/content/pm/IPackageMoveObserver.aidl
index baa1595..50ab3b5 100644
--- a/core/java/android/content/pm/IPackageMoveObserver.aidl
+++ b/core/java/android/content/pm/IPackageMoveObserver.aidl
@@ -22,6 +22,6 @@ package android.content.pm;
* @hide
*/
oneway interface IPackageMoveObserver {
- void packageMoved(in String packageName, int returnCode);
+ void onStarted(int moveId, String title);
+ void onStatusChanged(int moveId, int status, long estMillis);
}
-
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index e4108b1..e1c271d 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -42,6 +42,7 @@ import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
+import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.storage.VolumeInfo;
@@ -875,7 +876,8 @@ public abstract class PackageManager {
*
* @hide
*/
- public static final int MOVE_SUCCEEDED = 1;
+ public static final int MOVE_SUCCEEDED = -100;
+
/**
* Error code that is passed to the {@link IPackageMoveObserver} by
* {@link #movePackage(android.net.Uri, IPackageMoveObserver)}
@@ -941,6 +943,7 @@ public abstract class PackageManager {
* been installed on external media.
* @hide
*/
+ @Deprecated
public static final int MOVE_INTERNAL = 0x00000001;
/**
@@ -948,8 +951,12 @@ public abstract class PackageManager {
* the package should be moved to external media.
* @hide
*/
+ @Deprecated
public static final int MOVE_EXTERNAL_MEDIA = 0x00000002;
+ /** {@hide} */
+ public static final String EXTRA_MOVE_ID = "android.content.pm.extra.MOVE_ID";
+
/**
* Usable by the required verifier as the {@code verificationCode} argument
* for {@link PackageManager#verifyPendingInstall} to indicate that it will
@@ -4183,17 +4190,42 @@ public abstract class PackageManager {
* @hide
*/
@Deprecated
- public abstract void movePackage(String packageName, IPackageMoveObserver observer, int flags);
+ public void movePackage(String packageName, IPackageMoveObserver observer, int flags) {
+ throw new UnsupportedOperationException();
+ }
/** {@hide} */
- public abstract void movePackageAndData(String packageName, String volumeUuid,
- IPackageMoveObserver observer);
+ public static boolean isMoveStatusFinished(int status) {
+ return (status < 0 || status > 100);
+ }
+
+ /** {@hide} */
+ public static abstract class MoveCallback {
+ public abstract void onStarted(int moveId, String title);
+ public abstract void onStatusChanged(int moveId, int status, long estMillis);
+ }
/** {@hide} */
- public abstract @Nullable VolumeInfo getApplicationCurrentVolume(ApplicationInfo app);
+ public abstract int getMoveStatus(int moveId);
/** {@hide} */
- public abstract @NonNull List<VolumeInfo> getApplicationCandidateVolumes(ApplicationInfo app);
+ public abstract void registerMoveCallback(MoveCallback callback, Handler handler);
+ /** {@hide} */
+ public abstract void unregisterMoveCallback(MoveCallback callback);
+
+ /** {@hide} */
+ public abstract int movePackage(String packageName, VolumeInfo vol);
+ /** {@hide} */
+ public abstract @Nullable VolumeInfo getPackageCurrentVolume(ApplicationInfo app);
+ /** {@hide} */
+ public abstract @NonNull List<VolumeInfo> getPackageCandidateVolumes(ApplicationInfo app);
+
+ /** {@hide} */
+ public abstract int movePrimaryStorage(VolumeInfo vol);
+ /** {@hide} */
+ public abstract @Nullable VolumeInfo getPrimaryStorageCurrentVolume();
+ /** {@hide} */
+ public abstract @NonNull List<VolumeInfo> getPrimaryStorageCandidateVolumes();
/**
* Returns the device identity that verifiers can use to associate their scheme to a particular
diff --git a/core/java/android/os/storage/DiskInfo.java b/core/java/android/os/storage/DiskInfo.java
index 64f2a05..9623695 100644
--- a/core/java/android/os/storage/DiskInfo.java
+++ b/core/java/android/os/storage/DiskInfo.java
@@ -36,7 +36,10 @@ import java.util.Objects;
* @hide
*/
public class DiskInfo implements Parcelable {
- public static final String EXTRA_DISK_ID = "android.os.storage.extra.DISK_ID";
+ public static final String ACTION_DISK_SCANNED =
+ "android.os.storage.action.DISK_SCANNED";
+ public static final String EXTRA_DISK_ID =
+ "android.os.storage.extra.DISK_ID";
public static final int FLAG_ADOPTABLE = 1 << 0;
public static final int FLAG_DEFAULT_PRIMARY = 1 << 1;
@@ -96,6 +99,14 @@ public class DiskInfo implements Parcelable {
}
}
+ public boolean isAdoptable() {
+ return (flags & FLAG_ADOPTABLE) != 0;
+ }
+
+ public boolean isDefaultPrimary() {
+ return (flags & FLAG_DEFAULT_PRIMARY) != 0;
+ }
+
public boolean isSd() {
return (flags & FLAG_SD) != 0;
}
@@ -104,10 +115,6 @@ public class DiskInfo implements Parcelable {
return (flags & FLAG_USB) != 0;
}
- public boolean isAdoptable() {
- return (flags & FLAG_ADOPTABLE) != 0;
- }
-
@Override
public String toString() {
final CharArrayWriter writer = new CharArrayWriter();
diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java
index 0a8187e..0b1031c 100644
--- a/core/java/android/os/storage/IMountService.java
+++ b/core/java/android/os/storage/IMountService.java
@@ -1063,6 +1063,38 @@ public interface IMountService extends IInterface {
_data.recycle();
}
}
+
+ @Override
+ public String getPrimaryStorageUuid() throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ String _result;
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ mRemote.transact(Stub.TRANSACTION_getPrimaryStorageUuid, _data, _reply, 0);
+ _reply.readException();
+ _result = _reply.readString();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ return _result;
+ }
+
+ @Override
+ public void setPrimaryStorageUuid(String volumeUuid) throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeString(volumeUuid);
+ mRemote.transact(Stub.TRANSACTION_setPrimaryStorageUuid, _data, _reply, 0);
+ _reply.readException();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ }
}
private static final String DESCRIPTOR = "IMountService";
@@ -1169,6 +1201,9 @@ public interface IMountService extends IInterface {
static final int TRANSACTION_setVolumeNickname = IBinder.FIRST_CALL_TRANSACTION + 52;
static final int TRANSACTION_setVolumeUserFlags = IBinder.FIRST_CALL_TRANSACTION + 53;
+ static final int TRANSACTION_getPrimaryStorageUuid = IBinder.FIRST_CALL_TRANSACTION + 54;
+ static final int TRANSACTION_setPrimaryStorageUuid = IBinder.FIRST_CALL_TRANSACTION + 55;
+
/**
* Cast an IBinder object into an IMountService interface, generating a
* proxy if needed.
@@ -1669,6 +1704,20 @@ public interface IMountService extends IInterface {
reply.writeNoException();
return true;
}
+ case TRANSACTION_getPrimaryStorageUuid: {
+ data.enforceInterface(DESCRIPTOR);
+ String volumeUuid = getPrimaryStorageUuid();
+ reply.writeNoException();
+ reply.writeString(volumeUuid);
+ return true;
+ }
+ case TRANSACTION_setPrimaryStorageUuid: {
+ data.enforceInterface(DESCRIPTOR);
+ String volumeUuid = data.readString();
+ setPrimaryStorageUuid(volumeUuid);
+ reply.writeNoException();
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
}
@@ -1969,4 +2018,7 @@ public interface IMountService extends IInterface {
public void setVolumeNickname(String volId, String nickname) throws RemoteException;
public void setVolumeUserFlags(String volId, int flags, int mask) throws RemoteException;
+
+ public String getPrimaryStorageUuid() throws RemoteException;
+ public void setPrimaryStorageUuid(String volumeUuid) throws RemoteException;
}
diff --git a/core/java/android/os/storage/IMountServiceListener.java b/core/java/android/os/storage/IMountServiceListener.java
index 8e878a4..fcb4779 100644
--- a/core/java/android/os/storage/IMountServiceListener.java
+++ b/core/java/android/os/storage/IMountServiceListener.java
@@ -98,10 +98,11 @@ public interface IMountServiceListener extends IInterface {
reply.writeNoException();
return true;
}
- case TRANSACTION_onDiskUnsupported: {
+ case TRANSACTION_onDiskScanned: {
data.enforceInterface(DESCRIPTOR);
final DiskInfo disk = (DiskInfo) data.readParcelable(null);
- onDiskUnsupported(disk);
+ final int volumeCount = data.readInt();
+ onDiskScanned(disk, volumeCount);
reply.writeNoException();
return true;
}
@@ -207,13 +208,14 @@ public interface IMountServiceListener extends IInterface {
}
@Override
- public void onDiskUnsupported(DiskInfo disk) throws RemoteException {
+ public void onDiskScanned(DiskInfo disk, int volumeCount) throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeParcelable(disk, 0);
- mRemote.transact(Stub.TRANSACTION_onDiskUnsupported, _data, _reply,
+ _data.writeInt(volumeCount);
+ mRemote.transact(Stub.TRANSACTION_onDiskScanned, _data, _reply,
android.os.IBinder.FLAG_ONEWAY);
_reply.readException();
} finally {
@@ -224,12 +226,10 @@ public interface IMountServiceListener extends IInterface {
}
static final int TRANSACTION_onUsbMassStorageConnectionChanged = (IBinder.FIRST_CALL_TRANSACTION + 0);
-
static final int TRANSACTION_onStorageStateChanged = (IBinder.FIRST_CALL_TRANSACTION + 1);
-
static final int TRANSACTION_onVolumeStateChanged = (IBinder.FIRST_CALL_TRANSACTION + 2);
static final int TRANSACTION_onVolumeMetadataChanged = (IBinder.FIRST_CALL_TRANSACTION + 3);
- static final int TRANSACTION_onDiskUnsupported = (IBinder.FIRST_CALL_TRANSACTION + 4);
+ static final int TRANSACTION_onDiskScanned = (IBinder.FIRST_CALL_TRANSACTION + 4);
}
/**
@@ -255,5 +255,5 @@ public interface IMountServiceListener extends IInterface {
public void onVolumeMetadataChanged(VolumeInfo vol) throws RemoteException;
- public void onDiskUnsupported(DiskInfo disk) throws RemoteException;
+ public void onDiskScanned(DiskInfo disk, int volumeCount) throws RemoteException;
}
diff --git a/core/java/android/os/storage/StorageEventListener.java b/core/java/android/os/storage/StorageEventListener.java
index ad2fae0..6a0140e 100644
--- a/core/java/android/os/storage/StorageEventListener.java
+++ b/core/java/android/os/storage/StorageEventListener.java
@@ -44,6 +44,6 @@ public class StorageEventListener {
public void onVolumeMetadataChanged(VolumeInfo vol) {
}
- public void onDiskUnsupported(DiskInfo disk) {
+ public void onDiskScanned(DiskInfo disk, int volumeCount) {
}
}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index f101352..747fb40 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -73,6 +73,11 @@ public class StorageManager {
public static final String PROP_FORCE_ADOPTABLE = "persist.fw.force_adoptable";
/** {@hide} */
+ public static final String UUID_PRIVATE_INTERNAL = null;
+ /** {@hide} */
+ public static final String UUID_PRIMARY_PHYSICAL = "primary_physical";
+
+ /** {@hide} */
public static final int FLAG_ALL_METADATA = 1 << 0;
private final Context mContext;
@@ -89,7 +94,7 @@ public class StorageManager {
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;
final StorageEventListener mCallback;
final Handler mHandler;
@@ -116,8 +121,8 @@ public class StorageManager {
mCallback.onVolumeMetadataChanged((VolumeInfo) args.arg1);
args.recycle();
return true;
- case MSG_DISK_UNSUPPORTED:
- mCallback.onDiskUnsupported((DiskInfo) args.arg1);
+ case MSG_DISK_SCANNED:
+ mCallback.onDiskScanned((DiskInfo) args.arg1, args.argi2);
args.recycle();
return true;
}
@@ -156,10 +161,11 @@ public class StorageManager {
}
@Override
- public void onDiskUnsupported(DiskInfo disk) {
+ public void onDiskScanned(DiskInfo disk, int volumeCount) {
final SomeArgs args = SomeArgs.obtain();
args.arg1 = disk;
- mHandler.obtainMessage(MSG_DISK_UNSUPPORTED, args).sendToTarget();
+ args.argi2 = volumeCount;
+ mHandler.obtainMessage(MSG_DISK_SCANNED, args).sendToTarget();
}
}
@@ -534,17 +540,26 @@ public class StorageManager {
/** {@hide} */
public @Nullable String getBestVolumeDescription(VolumeInfo vol) {
String descrip = vol.getDescription();
-
if (vol.disk != null) {
if (TextUtils.isEmpty(descrip)) {
descrip = vol.disk.getDescription();
}
}
-
return descrip;
}
/** {@hide} */
+ public @Nullable VolumeInfo getPrimaryPhysicalVolume() {
+ final List<VolumeInfo> vols = getVolumes();
+ for (VolumeInfo vol : vols) {
+ if (vol.isPrimaryPhysical()) {
+ return vol;
+ }
+ }
+ return null;
+ }
+
+ /** {@hide} */
public void mount(String volId) {
try {
mMountService.mount(volId);
@@ -628,6 +643,24 @@ public class StorageManager {
}
/** {@hide} */
+ public String getPrimaryStorageUuid() {
+ try {
+ return mMountService.getPrimaryStorageUuid();
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /** {@hide} */
+ public void setPrimaryStorageUuid(String volumeUuid) {
+ try {
+ mMountService.setPrimaryStorageUuid(volumeUuid);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /** {@hide} */
public @Nullable StorageVolume getStorageVolume(File file) {
return getStorageVolume(getVolumeList(), file);
}
diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java
index f3498d5..4e9cfc7 100644
--- a/core/java/android/os/storage/VolumeInfo.java
+++ b/core/java/android/os/storage/VolumeInfo.java
@@ -242,6 +242,10 @@ public class VolumeInfo implements Parcelable {
return (mountFlags & MOUNT_FLAG_PRIMARY) != 0;
}
+ public boolean isPrimaryPhysical() {
+ return isPrimary() && (getType() == TYPE_PUBLIC);
+ }
+
public boolean isVisible() {
return (mountFlags & MOUNT_FLAG_VISIBLE) != 0;
}
diff --git a/core/java/com/android/internal/util/Preconditions.java b/core/java/com/android/internal/util/Preconditions.java
index 414b7bc..b692a18 100644
--- a/core/java/com/android/internal/util/Preconditions.java
+++ b/core/java/com/android/internal/util/Preconditions.java
@@ -24,6 +24,12 @@ import java.util.Collection;
*/
public class Preconditions {
+ public static void checkArgument(boolean expression) {
+ if (!expression) {
+ throw new IllegalArgumentException();
+ }
+ }
+
/**
* Ensures that an object reference passed as a parameter to the calling
* method is not null.
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
index 279bfbf..baa772e 100644
--- a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
@@ -1587,91 +1587,13 @@ public class PackageManagerTests extends AndroidTestCase {
}
}
- private class PackageMoveObserver extends IPackageMoveObserver.Stub {
- public int returnCode;
-
- private boolean doneFlag = false;
-
- public String packageName;
-
- public PackageMoveObserver(String pkgName) {
- packageName = pkgName;
- }
-
- public void packageMoved(String packageName, int returnCode) {
- Log.i("DEBUG_MOVE::", "pkg = " + packageName + ", " + "ret = " + returnCode);
- if (!packageName.equals(this.packageName)) {
- return;
- }
- synchronized (this) {
- this.returnCode = returnCode;
- doneFlag = true;
- notifyAll();
- }
- }
-
- public boolean isDone() {
- return doneFlag;
- }
- }
-
public boolean invokeMovePackage(String pkgName, int flags, GenericReceiver receiver)
throws Exception {
- PackageMoveObserver observer = new PackageMoveObserver(pkgName);
- final boolean received = false;
- mContext.registerReceiver(receiver, receiver.filter);
- try {
- // Wait on observer
- synchronized (observer) {
- synchronized (receiver) {
- getPm().movePackage(pkgName, observer, flags);
- long waitTime = 0;
- while ((!observer.isDone()) && (waitTime < MAX_WAIT_TIME)) {
- observer.wait(WAIT_TIME_INCR);
- waitTime += WAIT_TIME_INCR;
- }
- if (!observer.isDone()) {
- throw new Exception("Timed out waiting for pkgmove callback");
- }
- if (observer.returnCode != PackageManager.MOVE_SUCCEEDED) {
- return false;
- }
- // Verify we received the broadcast
- waitTime = 0;
- while ((!receiver.isDone()) && (waitTime < MAX_WAIT_TIME)) {
- receiver.wait(WAIT_TIME_INCR);
- waitTime += WAIT_TIME_INCR;
- }
- if (!receiver.isDone()) {
- throw new Exception("Timed out waiting for MOVE notifications");
- }
- return receiver.received;
- }
- }
- } finally {
- mContext.unregisterReceiver(receiver);
- }
+ throw new UnsupportedOperationException();
}
private boolean invokeMovePackageFail(String pkgName, int flags, int errCode) throws Exception {
- PackageMoveObserver observer = new PackageMoveObserver(pkgName);
- try {
- // Wait on observer
- synchronized (observer) {
- getPm().movePackage(pkgName, observer, flags);
- long waitTime = 0;
- while ((!observer.isDone()) && (waitTime < MAX_WAIT_TIME)) {
- observer.wait(WAIT_TIME_INCR);
- waitTime += WAIT_TIME_INCR;
- }
- if (!observer.isDone()) {
- throw new Exception("Timed out waiting for pkgmove callback");
- }
- assertEquals(errCode, observer.returnCode);
- }
- } finally {
- }
- return true;
+ throw new UnsupportedOperationException();
}
private int getDefaultInstallLoc() {
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);
+ }
+ }
+ }
}
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index 276b713..9efea0d 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -16,7 +16,6 @@
package android.test.mock;
-import android.annotation.NonNull;
import android.app.PackageInstallObserver;
import android.content.ComponentName;
import android.content.Intent;
@@ -29,7 +28,6 @@ import android.content.pm.FeatureInfo;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageDeleteObserver;
import android.content.pm.IPackageInstallObserver;
-import android.content.pm.IPackageMoveObserver;
import android.content.pm.IPackageStatsObserver;
import android.content.pm.InstrumentationInfo;
import android.content.pm.IntentFilterVerificationInfo;
@@ -46,11 +44,14 @@ import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.VerificationParams;
import android.content.pm.VerifierDeviceIdentity;
+import android.content.pm.PackageManager.MoveCallback;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.Handler;
+import android.os.ResultReceiver;
import android.os.UserHandle;
import android.os.storage.VolumeInfo;
@@ -487,33 +488,62 @@ public class MockPackageManager extends PackageManager {
throw new UnsupportedOperationException();
}
+ @Override
+ public String getInstallerPackageName(String packageName) {
+ throw new UnsupportedOperationException();
+ }
+
/** {@hide} */
@Override
- public void movePackage(String packageName, IPackageMoveObserver observer, int flags) {
+ public int getMoveStatus(int moveId) {
throw new UnsupportedOperationException();
}
/** {@hide} */
@Override
- public void movePackageAndData(String packageName, String volumeUuid,
- IPackageMoveObserver observer) {
+ public void registerMoveCallback(MoveCallback callback, Handler handler) {
throw new UnsupportedOperationException();
}
/** {@hide} */
@Override
- public @NonNull VolumeInfo getApplicationCurrentVolume(ApplicationInfo app) {
+ public void unregisterMoveCallback(MoveCallback callback) {
throw new UnsupportedOperationException();
}
/** {@hide} */
@Override
- public @NonNull List<VolumeInfo> getApplicationCandidateVolumes(ApplicationInfo app) {
+ public int movePackage(String packageName, VolumeInfo vol) {
throw new UnsupportedOperationException();
}
+ /** {@hide} */
@Override
- public String getInstallerPackageName(String packageName) {
+ public VolumeInfo getPackageCurrentVolume(ApplicationInfo app) {
+ throw new UnsupportedOperationException();
+ }
+
+ /** {@hide} */
+ @Override
+ public List<VolumeInfo> getPackageCandidateVolumes(ApplicationInfo app) {
+ throw new UnsupportedOperationException();
+ }
+
+ /** {@hide} */
+ @Override
+ public int movePrimaryStorage(VolumeInfo vol) {
+ throw new UnsupportedOperationException();
+ }
+
+ /** {@hide} */
+ @Override
+ public VolumeInfo getPrimaryStorageCurrentVolume() {
+ throw new UnsupportedOperationException();
+ }
+
+ /** {@hide} */
+ @Override
+ public List<VolumeInfo> getPrimaryStorageCandidateVolumes() {
throw new UnsupportedOperationException();
}