diff options
| -rw-r--r-- | Android.mk | 1 | ||||
| -rw-r--r-- | core/java/android/os/Power.java | 10 | ||||
| -rw-r--r-- | core/java/android/os/storage/IMountService.aidl | 4 | ||||
| -rw-r--r-- | core/java/android/os/storage/IMountShutdownObserver.aidl | 33 | ||||
| -rw-r--r-- | core/java/com/android/internal/app/ShutdownThread.java | 100 | ||||
| -rw-r--r-- | services/java/com/android/server/MountService.java | 137 | ||||
| -rw-r--r-- | services/java/com/android/server/PowerManagerService.java | 45 | ||||
| -rw-r--r-- | services/java/com/android/server/Watchdog.java | 8 | ||||
| -rw-r--r-- | tests/AndroidTests/AndroidManifest.xml | 1 | ||||
| -rwxr-xr-x | tests/AndroidTests/src/com/android/unit_tests/AsecTests.java | 257 |
10 files changed, 489 insertions, 107 deletions
diff --git a/Android.mk b/Android.mk index 35ee3fd9bda5..95f38c5ac17f 100644 --- a/Android.mk +++ b/Android.mk @@ -120,6 +120,7 @@ LOCAL_SRC_FILES += \ core/java/android/os/IMessenger.aidl \ core/java/android/os/storage/IMountService.aidl \ core/java/android/os/storage/IMountServiceListener.aidl \ + core/java/android/os/storage/IMountShutdownObserver.aidl \ core/java/android/os/INetworkManagementService.aidl \ core/java/android/os/INetStatService.aidl \ core/java/android/os/IPermissionController.aidl \ diff --git a/core/java/android/os/Power.java b/core/java/android/os/Power.java index b3df52246f49..5a7921590156 100644 --- a/core/java/android/os/Power.java +++ b/core/java/android/os/Power.java @@ -18,7 +18,6 @@ package android.os; import java.io.IOException; import android.os.ServiceManager; -import android.os.storage.IMountService; /** * Class that provides access to some of the power management functions. @@ -101,15 +100,6 @@ public class Power */ public static void reboot(String reason) throws IOException { - IMountService mSvc = IMountService.Stub.asInterface( - ServiceManager.getService("mount")); - - if (mSvc != null) { - try { - mSvc.shutdown(); - } catch (Exception e) { - } - } rebootNative(reason); } diff --git a/core/java/android/os/storage/IMountService.aidl b/core/java/android/os/storage/IMountService.aidl index ad4cb105c708..75455ab35b44 100644 --- a/core/java/android/os/storage/IMountService.aidl +++ b/core/java/android/os/storage/IMountService.aidl @@ -18,6 +18,7 @@ package android.os.storage; import android.os.storage.IMountServiceListener; +import android.os.storage.IMountShutdownObserver; /** WARNING! Update IMountService.h and IMountService.cpp if you change this file. * In particular, the ordering of the methods below must match the @@ -142,6 +143,7 @@ interface IMountService /** * Shuts down the MountService and gracefully unmounts all external media. + * Invokes call back once the shutdown is complete. */ - void shutdown(); + void shutdown(IMountShutdownObserver observer); } diff --git a/core/java/android/os/storage/IMountShutdownObserver.aidl b/core/java/android/os/storage/IMountShutdownObserver.aidl new file mode 100644 index 000000000000..0aa8a4592ab6 --- /dev/null +++ b/core/java/android/os/storage/IMountShutdownObserver.aidl @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2009 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.os.storage; + +/** + * Callback class for receiving events related + * to shutdown. + * + * @hide - For internal consumption only. + */ +interface IMountShutdownObserver { + /** + * This method is called when the shutdown + * of MountService completed. + * @param statusCode indicates success or failure + * of the shutdown. + */ + void onShutDownComplete(int statusCode); +} diff --git a/core/java/com/android/internal/app/ShutdownThread.java b/core/java/com/android/internal/app/ShutdownThread.java index 37898a1da3d7..51cd0f818ae7 100644 --- a/core/java/com/android/internal/app/ShutdownThread.java +++ b/core/java/com/android/internal/app/ShutdownThread.java @@ -28,12 +28,13 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.os.Handler; +import android.os.Power; import android.os.PowerManager; import android.os.RemoteException; -import android.os.Power; import android.os.ServiceManager; import android.os.SystemClock; import android.os.storage.IMountService; +import android.os.storage.IMountShutdownObserver; import com.android.internal.telephony.ITelephony; import android.util.Log; @@ -46,16 +47,20 @@ public final class ShutdownThread extends Thread { private static final int PHONE_STATE_POLL_SLEEP_MSEC = 500; // maximum time we wait for the shutdown broadcast before going on. private static final int MAX_BROADCAST_TIME = 10*1000; + private static final int MAX_SHUTDOWN_WAIT_TIME = 20*1000; // state tracking private static Object sIsStartedGuard = new Object(); private static boolean sIsStarted = false; + private static boolean mReboot; + private static String mRebootReason; + // static instance of this thread private static final ShutdownThread sInstance = new ShutdownThread(); - private final Object mBroadcastDoneSync = new Object(); - private boolean mBroadcastDone; + private final Object mActionDoneSync = new Object(); + private boolean mActionDone; private Context mContext; private PowerManager mPowerManager; private PowerManager.WakeLock mWakeLock; @@ -64,12 +69,13 @@ public final class ShutdownThread extends Thread { private ShutdownThread() { } - /** + /** * Request a clean shutdown, waiting for subsystems to clean up their * state etc. Must be called from a Looper thread in which its UI * is shown. - * + * * @param context Context used to display the shutdown progress dialog. + * @param confirm true if user confirmation is needed before shutting down. */ public static void shutdown(final Context context, boolean confirm) { // ensure that only one thread is trying to power down. @@ -106,6 +112,21 @@ public final class ShutdownThread extends Thread { } } + /** + * Request a clean shutdown, waiting for subsystems to clean up their + * state etc. Must be called from a Looper thread in which its UI + * is shown. + * + * @param context Context used to display the shutdown progress dialog. + * @param reason code to pass to the kernel (e.g. "recovery"), or null. + * @param confirm true if user confirmation is needed before shutting down. + */ + public static void reboot(final Context context, String reason, boolean confirm) { + mReboot = true; + mRebootReason = reason; + shutdown(context, confirm); + } + private static void beginShutdownSequence(Context context) { synchronized (sIsStartedGuard) { sIsStarted = true; @@ -145,13 +166,13 @@ public final class ShutdownThread extends Thread { sInstance.start(); } - void broadcastDone() { - synchronized (mBroadcastDoneSync) { - mBroadcastDone = true; - mBroadcastDoneSync.notifyAll(); + void actionDone() { + synchronized (mActionDoneSync) { + mActionDone = true; + mActionDoneSync.notifyAll(); } } - + /** * Makes sure we handle the shutdown gracefully. * Shuts off power regardless of radio and bluetooth state if the alloted time has passed. @@ -163,27 +184,27 @@ public final class ShutdownThread extends Thread { BroadcastReceiver br = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // We don't allow apps to cancel this, so ignore the result. - broadcastDone(); + actionDone(); } }; Log.i(TAG, "Sending shutdown broadcast..."); // First send the high-level shut down broadcast. - mBroadcastDone = false; + mActionDone = false; mContext.sendOrderedBroadcast(new Intent(Intent.ACTION_SHUTDOWN), null, br, mHandler, 0, null, null); final long endTime = System.currentTimeMillis() + MAX_BROADCAST_TIME; - synchronized (mBroadcastDoneSync) { - while (!mBroadcastDone) { + synchronized (mActionDoneSync) { + while (!mActionDone) { long delay = endTime - System.currentTimeMillis(); if (delay <= 0) { Log.w(TAG, "Shutdown broadcast timed out"); break; } try { - mBroadcastDoneSync.wait(delay); + mActionDoneSync.wait(delay); } catch (InterruptedException e) { } } @@ -262,17 +283,50 @@ public final class ShutdownThread extends Thread { } // Shutdown MountService to ensure media is in a safe state - try { - if (mount != null) { - mount.shutdown(); - } else { - Log.w(TAG, "MountService unavailable for shutdown"); + IMountShutdownObserver observer = new IMountShutdownObserver.Stub() { + public void onShutDownComplete(int statusCode) throws RemoteException { + Log.w(TAG, "Result code " + statusCode + " from MountService.shutdown"); + actionDone(); + } + }; + + Log.i(TAG, "Shutting down MountService"); + // Set initial variables and time out time. + mActionDone = false; + final long endShutTime = System.currentTimeMillis() + MAX_SHUTDOWN_WAIT_TIME; + synchronized (mActionDoneSync) { + try { + if (mount != null) { + mount.shutdown(observer); + } else { + Log.w(TAG, "MountService unavailable for shutdown"); + } + } catch (Exception e) { + Log.e(TAG, "Exception during MountService shutdown", e); + } + while (!mActionDone) { + long delay = endShutTime - System.currentTimeMillis(); + if (delay <= 0) { + Log.w(TAG, "Shutdown wait timed out"); + break; + } + try { + mActionDoneSync.wait(delay); + } catch (InterruptedException e) { + } + } + } + + if (mReboot) { + Log.i(TAG, "Rebooting, reason: " + mRebootReason); + try { + Power.reboot(mRebootReason); + } catch (Exception e) { + Log.e(TAG, "Reboot failed, will attempt shutdown instead", e); } - } catch (Exception e) { - Log.e(TAG, "Exception during MountService shutdown", e); } - //shutdown power + // Shutdown power Log.i(TAG, "Performing low-level shutdown..."); Power.shutdown(); } diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java index 0974f7f20882..713358d839e1 100644 --- a/services/java/com/android/server/MountService.java +++ b/services/java/com/android/server/MountService.java @@ -26,6 +26,7 @@ import android.content.pm.PackageManager; import android.net.Uri; import android.os.storage.IMountService; import android.os.storage.IMountServiceListener; +import android.os.storage.IMountShutdownObserver; import android.os.storage.StorageResultCode; import android.os.Handler; import android.os.Message; @@ -172,69 +173,110 @@ class MountService extends IMountService.Stub } } + class ShutdownCallBack extends UnmountCallBack { + IMountShutdownObserver observer; + ShutdownCallBack(String path, IMountShutdownObserver observer) { + super(path, true); + this.observer = observer; + } + + @Override + void handleFinished() { + int ret = doUnmountVolume(path, true); + if (observer != null) { + try { + observer.onShutDownComplete(ret); + } catch (RemoteException e) { + Log.w(TAG, "RemoteException when shutting down"); + } + } + } + } + final private Handler mHandler = new Handler() { ArrayList<UnmountCallBack> mForceUnmounts = new ArrayList<UnmountCallBack>(); + boolean mRegistered = false; + + void registerReceiver() { + mRegistered = true; + mContext.registerReceiver(mPmReceiver, mPmFilter); + } + + void unregisterReceiver() { + mRegistered = false; + mContext.unregisterReceiver(mPmReceiver); + } 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); + // Register only if needed. + if (!mRegistered) { + registerReceiver(); + 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; + // Unregister now. + if (mRegistered) { + unregisterReceiver(); } - String path = ucb.path; - boolean done = false; - if (!ucb.force) { - done = true; - } else { - int pids[] = getStorageUsers(path); - if (pids == null || pids.length == 0) { + int size = mForceUnmounts.size(); + int sizeArr[] = new int[size]; + int sizeArrN = 0; + for (int i = 0; i < size; i++) { + UnmountCallBack ucb = mForceUnmounts.get(i); + String path = ucb.path; + boolean done = false; + if (!ucb.force) { 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; + 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. + if (done) { + sizeArr[sizeArrN++] = i; + mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_MS, + ucb)); } else { - mHandler.sendMessageDelayed( - mHandler.obtainMessage(H_UNMOUNT_PM_DONE, - ucb.retries++), - RETRY_UNMOUNT_DELAY); + 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); + } } } + // Remove already processed elements from list. + for (int i = (sizeArrN-1); i >= 0; i--) { + mForceUnmounts.remove(sizeArr[i]); + } break; } case H_UNMOUNT_MS : { @@ -826,7 +868,7 @@ class MountService extends IMountService.Stub } } - public void shutdown() { + public void shutdown(final IMountShutdownObserver observer) { validatePermission(android.Manifest.permission.SHUTDOWN); Log.i(TAG, "Shutting down"); @@ -865,12 +907,9 @@ class MountService extends IMountService.Stub } if (state.equals(Environment.MEDIA_MOUNTED)) { - /* - * If the media is mounted, then gracefully unmount it. - */ - if (doUnmountVolume(path, true) != StorageResultCode.OperationSucceeded) { - Log.e(TAG, "Failed to unmount media for shutdown"); - } + // Post a unmount message. + ShutdownCallBack ucb = new ShutdownCallBack(path, observer); + mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb)); } } diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java index 0189c60742ce..fc6bfcd72a90 100644 --- a/services/java/com/android/server/PowerManagerService.java +++ b/services/java/com/android/server/PowerManagerService.java @@ -17,6 +17,7 @@ package com.android.server; import com.android.internal.app.IBatteryStats; +import com.android.internal.app.ShutdownThread; import com.android.server.am.BatteryStatsService; import android.app.ActivityManagerNative; @@ -41,7 +42,6 @@ import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; -import android.os.storage.IMountService; import android.os.IPowerManager; import android.os.LocalPowerManager; import android.os.Power; @@ -2202,28 +2202,35 @@ class PowerManagerService extends IPowerManager.Stub { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null); - /* - * Manually shutdown the MountService to ensure media is - * put into a safe state. - */ - IMountService mSvc = IMountService.Stub.asInterface( - ServiceManager.getService("mount")); + if (mHandler == null || !ActivityManagerNative.isSystemReady()) { + throw new IllegalStateException("Too early to call reboot()"); + } + + final String finalReason = reason; + Runnable runnable = new Runnable() { + public void run() { + synchronized (this) { + ShutdownThread.reboot(mContext, finalReason, false); + // if we get here we failed + notify(); + } + + } + }; + + mHandler.post(runnable); - if (mSvc != null) { + // block until we reboot or fail. + // throw an exception if we failed to reboot + synchronized (runnable) { try { - mSvc.shutdown(); - } catch (Exception e) { - Slog.e(TAG, "MountService shutdown failed", e); + runnable.wait(); + } catch (InterruptedException e) { } - } else { - Slog.w(TAG, "MountService unavailable for shutdown"); - } - - try { - Power.reboot(reason); - } catch (IOException e) { - Slog.e(TAG, "reboot failed", e); } + + // if we get here we failed + throw new IllegalStateException("unable to reboot!"); } /** diff --git a/services/java/com/android/server/Watchdog.java b/services/java/com/android/server/Watchdog.java index f97f50a45a12..3f64b25796df 100644 --- a/services/java/com/android/server/Watchdog.java +++ b/services/java/com/android/server/Watchdog.java @@ -29,6 +29,7 @@ import android.os.Debug; import android.os.Handler; import android.os.Message; import android.os.Process; +import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; import android.provider.Settings; @@ -676,11 +677,8 @@ public class Watchdog extends Thread { */ void rebootSystem(String reason) { Slog.i(TAG, "Rebooting system because: " + reason); - try { - android.os.Power.reboot(reason); - } catch (IOException e) { - Slog.e(TAG, "Reboot failed!", e); - } + PowerManagerService pms = (PowerManagerService) ServiceManager.getService("power"); + pms.reboot(reason); } /** diff --git a/tests/AndroidTests/AndroidManifest.xml b/tests/AndroidTests/AndroidManifest.xml index e06c3a8162c0..1f0bf4d0feaa 100644 --- a/tests/AndroidTests/AndroidManifest.xml +++ b/tests/AndroidTests/AndroidManifest.xml @@ -35,6 +35,7 @@ <uses-permission android:name="android.permission.ASEC_DESTROY" /> <uses-permission android:name="android.permission.ASEC_MOUNT_UNMOUNT" /> <uses-permission android:name="android.permission.ASEC_RENAME" /> + <uses-permission android:name="android.permission.SHUTDOWN" /> <uses-permission android:name="com.android.unit_tests.permission.TEST_GRANTED" /> <uses-permission android:name="com.google.android.googleapps.permission.ACCESS_GOOGLE_PASSWORD" /> <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH" /> diff --git a/tests/AndroidTests/src/com/android/unit_tests/AsecTests.java b/tests/AndroidTests/src/com/android/unit_tests/AsecTests.java index c7bacd4fdb67..9aed36383a3c 100755 --- a/tests/AndroidTests/src/com/android/unit_tests/AsecTests.java +++ b/tests/AndroidTests/src/com/android/unit_tests/AsecTests.java @@ -16,6 +16,8 @@ package com.android.unit_tests; +import com.android.unit_tests.PackageManagerTests.StorageListener; + import android.os.storage.IMountService.Stub; import android.net.Uri; @@ -41,6 +43,9 @@ import android.os.Environment; import android.os.Handler; import android.os.IBinder; import android.os.storage.IMountService; +import android.os.storage.IMountShutdownObserver; +import android.os.storage.StorageEventListener; +import android.os.storage.StorageManager; import android.os.storage.StorageResultCode; import android.os.RemoteException; import android.os.ServiceManager; @@ -365,4 +370,256 @@ public class AsecTests extends AndroidTestCase { } } + /*------------ Tests for unmounting volume ---*/ + public final long MAX_WAIT_TIME=120*1000; + public final long WAIT_TIME_INCR=20*1000; + boolean getMediaState() { + try { + String mPath = Environment.getExternalStorageDirectory().toString(); + String state = getMs().getVolumeState(mPath); + return Environment.MEDIA_MOUNTED.equals(state); + } catch (RemoteException e) { + return false; + } + } + + boolean mountMedia() { + if (getMediaState()) { + return true; + } + try { + String mPath = Environment.getExternalStorageDirectory().toString(); + int ret = getMs().mountVolume(mPath); + return ret == StorageResultCode.OperationSucceeded; + } catch (RemoteException e) { + return false; + } + } + + class StorageListener extends StorageEventListener { + String oldState; + String newState; + String path; + private boolean doneFlag = false; + + public void action() { + synchronized (this) { + doneFlag = true; + notifyAll(); + } + } + + public boolean isDone() { + return doneFlag; + } + + @Override + public void onStorageStateChanged(String path, String oldState, String newState) { + if (localLOGV) Log.i(TAG, "Storage state changed from " + oldState + " to " + newState); + this.oldState = oldState; + this.newState = newState; + this.path = path; + action(); + } + } + + private boolean unmountMedia() { + if (!getMediaState()) { + return true; + } + String path = Environment.getExternalStorageDirectory().toString(); + StorageListener observer = new StorageListener(); + StorageManager sm = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE); + sm.registerListener(observer); + try { + // Wait on observer + synchronized(observer) { + getMs().unmountVolume(path, false); + 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 packageInstalled callback"); + } + return true; + } + } catch (Exception e) { + return false; + } finally { + sm.unregisterListener(observer); + } + } + public void testUnmount() { + boolean oldStatus = getMediaState(); + Log.i(TAG, "oldStatus="+oldStatus); + try { + // Mount media firsts + if (!getMediaState()) { + mountMedia(); + } + assertTrue(unmountMedia()); + } finally { + // Restore old status + boolean currStatus = getMediaState(); + if (oldStatus != currStatus) { + if (oldStatus) { + // Mount media + mountMedia(); + } else { + unmountMedia(); + } + } + } + } + + class MultipleStorageLis extends StorageListener { + int count = 0; + public void onStorageStateChanged(String path, String oldState, String newState) { + count++; + super.action(); + } + } + /* + * This test invokes unmount multiple time and expects the call back + * to be invoked just once. + */ + public void testUnmountMultiple() { + boolean oldStatus = getMediaState(); + StorageManager sm = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE); + MultipleStorageLis observer = new MultipleStorageLis(); + try { + // Mount media firsts + if (!getMediaState()) { + mountMedia(); + } + String path = Environment.getExternalStorageDirectory().toString(); + sm.registerListener(observer); + // Wait on observer + synchronized(observer) { + for (int i = 0; i < 5; i++) { + getMs().unmountVolume(path, false); + } + long waitTime = 0; + while((!observer.isDone()) && (waitTime < MAX_WAIT_TIME) ) { + observer.wait(WAIT_TIME_INCR); + waitTime += WAIT_TIME_INCR; + } + if(!observer.isDone()) { + failStr("Timed out waiting for packageInstalled callback"); + } + } + assertEquals(observer.count, 1); + } catch (Exception e) { + failStr(e); + } finally { + sm.unregisterListener(observer); + // Restore old status + boolean currStatus = getMediaState(); + if (oldStatus != currStatus) { + if (oldStatus) { + // Mount media + mountMedia(); + } else { + unmountMedia(); + } + } + } + } + + class ShutdownObserver extends IMountShutdownObserver.Stub{ + private boolean doneFlag = false; + int statusCode; + + public void action() { + synchronized (this) { + doneFlag = true; + notifyAll(); + } + } + + public boolean isDone() { + return doneFlag; + } + public void onShutDownComplete(int statusCode) throws RemoteException { + this.statusCode = statusCode; + action(); + } + + } + + boolean invokeShutdown() { + IMountService ms = getMs(); + ShutdownObserver observer = new ShutdownObserver(); + synchronized (observer) { + try { + ms.shutdown(observer); + return true; + } catch (RemoteException e) { + failStr(e); + } + } + return false; + } + + public void testShutdown() { + boolean oldStatus = getMediaState(); + try { + // Mount media firsts + if (!getMediaState()) { + mountMedia(); + } + assertTrue(invokeShutdown()); + } finally { + // Restore old status + boolean currStatus = getMediaState(); + if (oldStatus != currStatus) { + if (oldStatus) { + // Mount media + mountMedia(); + } else { + unmountMedia(); + } + } + } + } + + /* + * This test invokes unmount multiple time and expects the call back + * to be invoked just once. + */ + public void testShutdownMultiple() { + boolean oldStatus = getMediaState(); + try { + // Mount media firsts + if (!getMediaState()) { + mountMedia(); + } + IMountService ms = getMs(); + ShutdownObserver observer = new ShutdownObserver(); + synchronized (observer) { + try { + ms.shutdown(observer); + for (int i = 0; i < 4; i++) { + ms.shutdown(null); + } + } catch (RemoteException e) { + failStr(e); + } + } + } finally { + // Restore old status + boolean currStatus = getMediaState(); + if (oldStatus != currStatus) { + if (oldStatus) { + // Mount media + mountMedia(); + } else { + unmountMedia(); + } + } + } + } + } |