Add new shutdown observer for MountService.
Use new observer before rebooting and shutting down. Add some unit tests for unmount and shutdown code paths Fix registering/unregistering part in MountService Use ShutdownThread in PowerManager.reboot() Add reboot support to ShutdownThread. Remove MountService code from PowerManagerService.java and Power.java. Clean shutdown/reboot is handled exclusively by ShutdownThread now. Change-Id: Iefb157451d3d9c426cb431707b870a873c09123d
This commit is contained in:
committed by
Mike Lockwood
parent
8e461c9add
commit
6ffce2e9a3
@@ -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 \
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
33
core/java/android/os/storage/IMountShutdownObserver.aidl
Normal file
33
core/java/android/os/storage/IMountShutdownObserver.aidl
Normal file
@@ -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);
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Exception during MountService shutdown", e);
|
||||
}
|
||||
|
||||
//shutdown power
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// Shutdown power
|
||||
Log.i(TAG, "Performing low-level shutdown...");
|
||||
Power.shutdown();
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 (mSvc != null) {
|
||||
try {
|
||||
mSvc.shutdown();
|
||||
} catch (Exception e) {
|
||||
Slog.e(TAG, "MountService shutdown failed", e);
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
Slog.w(TAG, "MountService unavailable for shutdown");
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
Power.reboot(reason);
|
||||
} catch (IOException e) {
|
||||
Slog.e(TAG, "reboot failed", e);
|
||||
mHandler.post(runnable);
|
||||
|
||||
// block until we reboot or fail.
|
||||
// throw an exception if we failed to reboot
|
||||
synchronized (runnable) {
|
||||
try {
|
||||
runnable.wait();
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
|
||||
// if we get here we failed
|
||||
throw new IllegalStateException("unable to reboot!");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user