From c42e29e0a58d07a95d470780216cdf1d67476bd6 Mon Sep 17 00:00:00 2001 From: Suchi Amalapurapu Date: Mon, 22 Feb 2010 16:03:53 -0800 Subject: [PATCH] Add new handler mechanism in MountService to make unmount api asynchronous. MountService updates state on PackageManager and then tries to kill processes holding file references to media about to be unmounted by invoking api on ACtivityManager. This is retried upto 4 times to make sure all processes holding file references are killed before unmounting the media at specified path. Also changed PackageManger api to return boolean value to indicate if MountService is likely to receive broadcasts related to apps on sd. --- .../android/os/storage/IMountService.aidl | 7 +- .../java/com/android/server/MountService.java | 141 ++++++++++++++++-- .../android/server/PackageManagerService.java | 25 +++- .../unit_tests/PackageManagerTests.java | 2 +- 4 files changed, 156 insertions(+), 19 deletions(-) diff --git a/core/java/android/os/storage/IMountService.aidl b/core/java/android/os/storage/IMountService.aidl index 79a6cfeabe8ef..2b2dcf4633fa2 100644 --- a/core/java/android/os/storage/IMountService.aidl +++ b/core/java/android/os/storage/IMountService.aidl @@ -61,9 +61,12 @@ interface IMountService /** * Safely unmount external storage at given mount point. - * Returns an int consistent with MountServiceResultCode + * The unmount is an asynchronous operation. Applications + * should register StorageEventListener for storage related + * status changes. + * */ - int unmountVolume(String mountPoint, boolean force); + void unmountVolume(String mountPoint, boolean force); /** * Format external storage given a mount point. diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java index fb17538f67775..4485c79ee6a3a 100644 --- a/services/java/com/android/server/MountService.java +++ b/services/java/com/android/server/MountService.java @@ -16,32 +16,29 @@ package com.android.server; +import com.android.server.am.ActivityManagerService; + import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; -import android.content.res.Resources; import android.net.Uri; import android.os.storage.IMountService; import android.os.storage.IMountServiceListener; import android.os.storage.StorageResultCode; +import android.os.Handler; +import android.os.Message; import android.os.RemoteException; import android.os.IBinder; import android.os.Environment; import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; -import android.os.UEventObserver; -import android.os.Handler; -import android.text.TextUtils; import android.util.Log; import java.util.ArrayList; import java.util.HashSet; -import java.io.File; -import java.io.FileReader; - /** * MountService implements back-end services for platform storage * management. @@ -124,6 +121,110 @@ class MountService extends IMountService.Stub */ private HashSet mAsecMountSet = new HashSet(); + private static final int H_UNMOUNT_PM_UPDATE = 1; + private static final int H_UNMOUNT_PM_DONE = 2; + private static final int H_UNMOUNT_MS = 3; + private static final int RETRY_UNMOUNT_DELAY = 30; // in ms + private static final int MAX_UNMOUNT_RETRIES = 4; + + private IntentFilter mPmFilter = new IntentFilter( + Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); + private BroadcastReceiver mPmReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { + mHandler.sendEmptyMessage(H_UNMOUNT_PM_DONE); + } + } + }; + + class UnmountCallBack { + String path; + int retries; + boolean force; + + UnmountCallBack(String path, boolean force) { + retries = 0; + this.path = path; + this.force = force; + } + } + + final private Handler mHandler = new Handler() { + ArrayList mForceUnmounts = new ArrayList(); + + public void handleMessage(Message msg) { + switch (msg.what) { + case H_UNMOUNT_PM_UPDATE: { + UnmountCallBack ucb = (UnmountCallBack) msg.obj; + mForceUnmounts.add(ucb); + mContext.registerReceiver(mPmReceiver, mPmFilter); + boolean hasExtPkgs = mPms.updateExternalMediaStatus(false); + if (!hasExtPkgs) { + // Unregister right away + mHandler.sendEmptyMessage(H_UNMOUNT_PM_DONE); + } + break; + } + case H_UNMOUNT_PM_DONE: { + // Unregister receiver + mContext.unregisterReceiver(mPmReceiver); + UnmountCallBack ucb = mForceUnmounts.get(0); + if (ucb == null || ucb.path == null) { + // Just ignore + return; + } + String path = ucb.path; + boolean done = false; + if (!ucb.force) { + done = true; + } else { + int pids[] = getStorageUsers(path); + if (pids == null || pids.length == 0) { + done = true; + } else { + // Kill processes holding references first + ActivityManagerService ams = (ActivityManagerService) + ServiceManager.getService("activity"); + // Eliminate system process here? + boolean ret = ams.killPidsForMemory(pids); + if (ret) { + // Confirm if file references have been freed. + pids = getStorageUsers(path); + if (pids == null || pids.length == 0) { + done = true; + } + } + } + } + if (done) { + mForceUnmounts.remove(0); + mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_MS, + ucb)); + } else { + if (ucb.retries >= MAX_UNMOUNT_RETRIES) { + Log.i(TAG, "Cannot unmount inspite of " + + MAX_UNMOUNT_RETRIES + " to unmount media"); + // Send final broadcast indicating failure to unmount. + } else { + mHandler.sendMessageDelayed( + mHandler.obtainMessage(H_UNMOUNT_PM_DONE, + ucb.retries++), + RETRY_UNMOUNT_DELAY); + } + } + break; + } + case H_UNMOUNT_MS : { + UnmountCallBack ucb = (UnmountCallBack) msg.obj; + String path = ucb.path; + doUnmountVolume(path, true); + break; + } + } + } + }; + private void waitForReady() { while (mReady == false) { for (int retries = 5; retries > 0; retries--) { @@ -554,14 +655,28 @@ class MountService extends IMountService.Stub return rc; } + /* + * If force is not set, we do not unmount if there are + * processes holding references to the volume about to be unmounted. + * If force is set, all the processes holding references need to be + * killed via the ActivityManager before actually unmounting the volume. + * This might even take a while and might be retried after timed delays + * to make sure we dont end up in an instable state and kill some core + * processes. + */ private int doUnmountVolume(String path, boolean force) { if (!getVolumeState(path).equals(Environment.MEDIA_MOUNTED)) { return VoldResponseCode.OpFailedVolNotMounted; } + // We unmounted the volume. No of the asec containers are available now. + synchronized (mAsecMountSet) { + mAsecMountSet.clear(); + } // Notify PackageManager of potential media removal and deal with // return code later on. The caller of this api should be aware or have been // notified that the applications installed on the media will be killed. + // Redundant probably. But no harm in updating state again. mPms.updateExternalMediaStatus(false); try { mConnector.doCommand(String.format( @@ -814,11 +929,12 @@ class MountService extends IMountService.Stub return doMountVolume(path); } - public int unmountVolume(String path, boolean force) { + public void unmountVolume(String path, boolean force) { validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); waitForReady(); - return doUnmountVolume(path, force); + UnmountCallBack ucb = new UnmountCallBack(path, force); + mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb)); } public int formatVolume(String path) { @@ -1029,6 +1145,13 @@ class MountService extends IMountService.Stub } catch (NativeDaemonConnectorException e) { rc = StorageResultCode.OperationFailedInternalError; } + if (rc == StorageResultCode.OperationSucceeded) { + synchronized (mAsecMountSet) { + if (!mAsecMountSet.contains(newId)) { + mAsecMountSet.add(newId); + } + } + } return rc; } diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java index 9e0d6239216c1..a23fac4450b10 100644 --- a/services/java/com/android/server/PackageManagerService.java +++ b/services/java/com/android/server/PackageManagerService.java @@ -8877,6 +8877,9 @@ class PackageManagerService extends IPackageManager.Stub { return prefix + tmpIdx; } + /* + * Return true if PackageManager does have packages to be updated. + */ public boolean updateExternalMediaStatus(final boolean mediaStatus) { synchronized (mPackages) { if (DEBUG_SD_INSTALL) Log.i(TAG, "updateExternalMediaStatus:: mediaStatus=" + @@ -8885,16 +8888,24 @@ class PackageManagerService extends IPackageManager.Stub { return false; } mMediaMounted = mediaStatus; - final HashMap processCids = - new HashMap(); - final int[] uidArr = getExternalMediaPackages(mediaStatus, processCids); - if (processCids.size() == 0) { + boolean ret = false; + synchronized (mPackages) { + Set appList = mSettings.findPackagesWithFlag(ApplicationInfo.FLAG_ON_SDCARD); + ret = appList != null && appList.size() > 0; + } + if (!ret) { + // No packages will be effected by the sdcard update. Just return. return false; } // Queue up an async operation since the package installation may take a little while. mHandler.post(new Runnable() { public void run() { mHandler.removeCallbacks(this); + // If we are up here that means there are packages to be + // enabled or disabled. + final HashMap processCids = + new HashMap(); + final int[] uidArr = getExternalMediaPackages(mediaStatus, processCids); if (mediaStatus) { if (DEBUG_SD_INSTALL) Log.i(TAG, "Loading packages"); loadMediaPackages(processCids, uidArr); @@ -9019,9 +9030,9 @@ class PackageManagerService extends IPackageManager.Stub { } args.doPostInstall(retCode); } - // Send broadcasts first + // Send a broadcast to let everyone know we are done processing + sendResourcesChangedBroadcast(true, pkgList, uidArr); if (pkgList.size() > 0) { - sendResourcesChangedBroadcast(true, pkgList, uidArr); Runtime.getRuntime().gc(); // If something failed do we clean up here or next install? } @@ -9049,9 +9060,9 @@ class PackageManagerService extends IPackageManager.Stub { } } } + sendResourcesChangedBroadcast(false, pkgList, uidArr); // Send broadcasts if (pkgList.size() > 0) { - sendResourcesChangedBroadcast(false, pkgList, uidArr); Runtime.getRuntime().gc(); } // Do clean up. Just unmount diff --git a/tests/AndroidTests/src/com/android/unit_tests/PackageManagerTests.java b/tests/AndroidTests/src/com/android/unit_tests/PackageManagerTests.java index d161a8838581d..5e3895ad7fc9d 100755 --- a/tests/AndroidTests/src/com/android/unit_tests/PackageManagerTests.java +++ b/tests/AndroidTests/src/com/android/unit_tests/PackageManagerTests.java @@ -403,7 +403,7 @@ public class PackageManagerTests extends AndroidTestCase { return ip; } finally { if (cleanUp) { - cleanUpInstall(ip); + //cleanUpInstall(ip); } } }