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:
Suchi Amalapurapu
2010-03-08 14:48:40 -08:00
committed by Mike Lockwood
parent 8e461c9add
commit 6ffce2e9a3
10 changed files with 490 additions and 108 deletions

View File

@@ -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 \

View File

@@ -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);
}

View File

@@ -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);
}

View 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);
}

View File

@@ -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();
}

View File

@@ -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));
}
}

View File

@@ -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!");
}
/**

View File

@@ -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);
}
/**

View File

@@ -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" />

View File

@@ -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();
}
}
}
}
}