From 55280a91884b9256e8db6af6a09f28b3feeaa9d8 Mon Sep 17 00:00:00 2001 From: Dianne Hackborn Date: Thu, 7 May 2009 15:53:46 -0700 Subject: Improve shutdown process to send broadcast for applications. This introduces a new class in the base platform for performing a clean shutdown (which was copied from the classes in the policies). It includes new features to send a shutdown broadcast for applications to do cleanup, and ot have the activity manager pause the current activity before proceeding with the shutdown. These facilities are also use to write at the most recent stat files for sync, battery and user activity. --- api/current.xml | 22 ++ core/java/android/app/ActivityManagerNative.java | 22 ++ core/java/android/app/IActivityManager.java | 5 +- core/java/android/content/Intent.java | 10 + core/java/android/content/SyncManager.java | 12 + core/java/android/content/SyncStorageEngine.java | 19 +- core/java/android/os/Power.java | 7 +- .../com/android/internal/app/ShutdownThread.java | 241 +++++++++++++++++++++ core/res/AndroidManifest.xml | 8 + core/res/res/values/strings.xml | 6 + .../android/server/am/ActivityManagerService.java | 58 ++++- .../com/android/server/am/BatteryStatsService.java | 10 +- .../com/android/server/am/UsageStatsService.java | 6 + 13 files changed, 410 insertions(+), 16 deletions(-) create mode 100644 core/java/com/android/internal/app/ShutdownThread.java diff --git a/api/current.xml b/api/current.xml index 7c5e6675482f..f809c09b00a0 100644 --- a/api/current.xml +++ b/api/current.xml @@ -1013,6 +1013,17 @@ visibility="public" > + + + + {@link #ACTION_BATTERY_CHANGED} *
  • {@link #ACTION_POWER_CONNECTED} *
  • {@link #ACTION_POWER_DISCONNECTED} + *
  • {@link #ACTION_SHUTDOWN} * * *

    Standard Categories

    @@ -1269,6 +1270,15 @@ public class Intent implements Parcelable { */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_POWER_DISCONNECTED = "android.intent.action.ACTION_POWER_DISCONNECTED"; + /** + * Broadcast Action: Device is shutting down. + * This is broadcast when the device is being shut down (completely turned + * off, not sleeping). Once the broadcast is complete, the final shutdown + * will proceed and all unsaved data lost. Apps will not normally need + * to handle this, since the forground activity will be paused as well. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_SHUTDOWN = "android.intent.action.ACTION_SHUTDOWN"; /** * Broadcast Action: Indicates low memory condition on the device */ diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java index ade5f3d3e04e..4d2cce881897 100644 --- a/core/java/android/content/SyncManager.java +++ b/core/java/android/content/SyncManager.java @@ -212,6 +212,14 @@ class SyncManager { } }; + private BroadcastReceiver mShutdownIntentReceiver = + new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + Log.w(TAG, "Writing sync state before shutdown..."); + getSyncStorageEngine().writeAllState(); + } + }; + private static final String ACTION_SYNC_ALARM = "android.content.syncmanager.SYNC_ALARM"; private static final String SYNC_POLL_ALARM = "android.content.syncmanager.SYNC_POLL_ALARM"; private final SyncHandler mSyncHandler; @@ -249,6 +257,10 @@ class SyncManager { intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK); context.registerReceiver(mStorageIntentReceiver, intentFilter); + intentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN); + intentFilter.setPriority(100); + context.registerReceiver(mShutdownIntentReceiver, intentFilter); + if (!factoryTest) { mNotificationMgr = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java index 1587462ec0e4..38278496411a 100644 --- a/core/java/android/content/SyncStorageEngine.java +++ b/core/java/android/content/SyncStorageEngine.java @@ -736,7 +736,7 @@ public class SyncStorageEngine extends Handler { } boolean writeStatisticsNow = false; - int day = getCurrentDay(); + int day = getCurrentDayLocked(); if (mDayStats[0] == null) { mDayStats[0] = new DayStats(day); } else if (day != mDayStats[0].day) { @@ -925,7 +925,7 @@ public class SyncStorageEngine extends Handler { } } - private int getCurrentDay() { + private int getCurrentDayLocked() { mCal.setTimeInMillis(System.currentTimeMillis()); final int dayOfYear = mCal.get(Calendar.DAY_OF_YEAR); if (mYear != mCal.get(Calendar.YEAR)) { @@ -1005,6 +1005,21 @@ public class SyncStorageEngine extends Handler { return status; } + public void writeAllState() { + synchronized (mAuthorities) { + // Account info is always written so no need to do it here. + + if (mNumPendingFinished > 0) { + // Only write these if they are out of date. + writePendingOperationsLocked(); + } + + // Just always write these... they are likely out of date. + writeStatusLocked(); + writeStatisticsLocked(); + } + } + /** * Read all account information back in to the initial engine state. */ diff --git a/core/java/android/os/Power.java b/core/java/android/os/Power.java index 47497e547ff1..3679e478126e 100644 --- a/core/java/android/os/Power.java +++ b/core/java/android/os/Power.java @@ -80,10 +80,9 @@ public class Power public static native int setLastUserActivityTimeout(long ms); /** - * Turn the device off. - * - * This method is considered deprecated in favor of - * {@link android.policy.ShutdownThread.shutdownAfterDisablingRadio()}. + * Low-level function turn the device off immediately, without trying + * to be clean. Most people should use + * {@link android.internal.app.ShutdownThread} for a clean shutdown. * * @deprecated * @hide diff --git a/core/java/com/android/internal/app/ShutdownThread.java b/core/java/com/android/internal/app/ShutdownThread.java new file mode 100644 index 000000000000..77d6e20a0fa5 --- /dev/null +++ b/core/java/com/android/internal/app/ShutdownThread.java @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2008 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 com.android.internal.app; + +import android.app.ActivityManagerNative; +import android.app.IActivityManager; +import android.app.ProgressDialog; +import android.app.AlertDialog; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.IBluetoothDevice; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Handler; +import android.os.RemoteException; +import android.os.Power; +import android.os.ServiceManager; +import android.os.SystemClock; +import com.android.internal.telephony.ITelephony; +import android.util.Log; +import android.view.WindowManager; + +public final class ShutdownThread extends Thread { + // constants + private static final String TAG = "ShutdownThread"; + private static final int MAX_NUM_PHONE_STATE_READS = 16; + 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; + + // state tracking + private static Object sIsStartedGuard = new Object(); + private static boolean sIsStarted = false; + + // static instance of this thread + private static final ShutdownThread sInstance = new ShutdownThread(); + + private final Object mBroadcastDoneSync = new Object(); + private boolean mBroadcastDone; + private Context mContext; + private Handler mHandler; + + 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. + */ + public static void shutdown(final Context context, boolean confirm) { + // ensure that only one thread is trying to power down. + // any additional calls are just returned + synchronized (sIsStartedGuard){ + if (sIsStarted) { + Log.d(TAG, "Request to shutdown already running, returning."); + return; + } + } + + Log.d(TAG, "Notifying thread to start radio shutdown"); + + if (confirm) { + final AlertDialog dialog = new AlertDialog.Builder(context) + .setIcon(android.R.drawable.ic_dialog_alert) + .setTitle(com.android.internal.R.string.power_off) + .setMessage(com.android.internal.R.string.shutdown_confirm) + .setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + beginShutdownSequence(context); + } + }) + .setNegativeButton(com.android.internal.R.string.no, null) + .create(); + dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND); + dialog.show(); + } else { + beginShutdownSequence(context); + } + } + + private static void beginShutdownSequence(Context context) { + synchronized (sIsStartedGuard) { + sIsStarted = true; + } + + // throw up an indeterminate system dialog to indicate radio is + // shutting down. + ProgressDialog pd = new ProgressDialog(context); + pd.setTitle(context.getText(com.android.internal.R.string.power_off)); + pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress)); + pd.setIndeterminate(true); + pd.setCancelable(false); + pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + pd.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND); + + pd.show(); + + // start the thread that initiates shutdown + sInstance.mContext = context; + sInstance.mHandler = new Handler() { + }; + sInstance.start(); + } + + void broadcastDone() { + synchronized (mBroadcastDoneSync) { + mBroadcastDone = true; + mBroadcastDoneSync.notifyAll(); + } + } + + /** + * Makes sure we handle the shutdown gracefully. + * Shuts off power regardless of radio and bluetooth state if the alloted time has passed. + */ + public void run() { + boolean bluetoothOff; + boolean radioOff; + + 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(); + } + }; + + Log.i(TAG, "Sending shutdown broadcast..."); + + // First send the high-level shut down broadcast. + mBroadcastDone = 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) { + long delay = endTime - System.currentTimeMillis(); + if (delay <= 0) { + Log.w(TAG, "Shutdown broadcast timed out"); + break; + } + try { + mBroadcastDoneSync.wait(delay); + } catch (InterruptedException e) { + } + } + } + + Log.i(TAG, "Shutting down activity manager..."); + + final IActivityManager am = + ActivityManagerNative.asInterface(ServiceManager.checkService("activity")); + if (am != null) { + try { + am.shutdown(MAX_BROADCAST_TIME); + } catch (RemoteException e) { + } + } + + final ITelephony phone = + ITelephony.Stub.asInterface(ServiceManager.checkService("phone")); + final IBluetoothDevice bluetooth = + IBluetoothDevice.Stub.asInterface(ServiceManager.checkService( + Context.BLUETOOTH_SERVICE)); + + try { + bluetoothOff = bluetooth == null || + bluetooth.getBluetoothState() == BluetoothDevice.BLUETOOTH_STATE_OFF; + if (!bluetoothOff) { + Log.w(TAG, "Disabling Bluetooth..."); + bluetooth.disable(false); // disable but don't persist new state + } + } catch (RemoteException ex) { + Log.e(TAG, "RemoteException during bluetooth shutdown", ex); + bluetoothOff = true; + } + + try { + radioOff = phone == null || !phone.isRadioOn(); + if (!radioOff) { + Log.w(TAG, "Turning off radio..."); + phone.setRadio(false); + } + } catch (RemoteException ex) { + Log.e(TAG, "RemoteException during radio shutdown", ex); + radioOff = true; + } + + Log.i(TAG, "Waiting for Bluetooth and Radio..."); + + // Wait a max of 32 seconds for clean shutdown + for (int i = 0; i < MAX_NUM_PHONE_STATE_READS; i++) { + if (!bluetoothOff) { + try { + bluetoothOff = + bluetooth.getBluetoothState() == BluetoothDevice.BLUETOOTH_STATE_OFF; + } catch (RemoteException ex) { + Log.e(TAG, "RemoteException during bluetooth shutdown", ex); + bluetoothOff = true; + } + } + if (!radioOff) { + try { + radioOff = !phone.isRadioOn(); + } catch (RemoteException ex) { + Log.e(TAG, "RemoteException during radio shutdown", ex); + radioOff = true; + } + } + if (radioOff && bluetoothOff) { + Log.i(TAG, "Radio and Bluetooth shutdown complete."); + break; + } + SystemClock.sleep(PHONE_STATE_POLL_SLEEP_MSEC); + } + + //shutdown power + Log.i(TAG, "Performing low-level shutdown..."); + Power.shutdown(); + } +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 7ebc896059bf..bff6b9dfda5e 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -803,6 +803,14 @@ android:description="@string/permdesc_runSetActivityWatcher" android:protectionLevel="signature" /> + + + + + partial shutdown + + Puts the activity manager into a shutdown + state. Does not perform a complete shutdown. + monitor and control all application launching diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index b04f5a8f4fb0..2be497528ed9 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -706,6 +706,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen */ boolean mSleeping = false; + /** + * Set if we are shutting down the system, similar to sleeping. + */ + boolean mShuttingDown = false; + /** * Set when the system is going to sleep, until we have * successfully paused the current activity and released our wake lock. @@ -868,7 +873,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return; } AppErrorResult res = (AppErrorResult) data.get("result"); - if (!mSleeping) { + if (!mSleeping && !mShuttingDown) { Dialog d = new AppErrorDialog( mContext, res, proc, (Integer)data.get("flags"), @@ -1893,7 +1898,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // If we are not going to sleep, we want to ensure the device is // awake until the next activity is started. - if (!mSleeping) { + if (!mSleeping && !mShuttingDown) { mLaunchingActivity.acquire(); if (!mHandler.hasMessages(LAUNCH_TIMEOUT_MSG)) { // To be safe, don't allow the wake lock to be held for too long. @@ -1972,12 +1977,15 @@ public final class ActivityManagerService extends ActivityManagerNative implemen mPausingActivity = null; } - if (!mSleeping) { + if (!mSleeping && !mShuttingDown) { resumeTopActivityLocked(prev); } else { if (mGoingToSleep.isHeld()) { mGoingToSleep.release(); } + if (mShuttingDown) { + notifyAll(); + } } if (prev != null) { @@ -2243,7 +2251,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // If we are sleeping, and there is no resumed activity, and the top // activity is paused, well that is the state we want. - if (mSleeping && mLastPausedActivity == next && next.state == ActivityState.PAUSED) { + if ((mSleeping || mShuttingDown) + && mLastPausedActivity == next && next.state == ActivityState.PAUSED) { // Make sure we have executed any pending transitions, since there // should be nothing left to do at this point. mWindowManager.executeAppTransition(); @@ -7098,8 +7107,45 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } + public boolean shutdown(int timeout) { + if (checkCallingPermission(android.Manifest.permission.SHUTDOWN) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires permission " + + android.Manifest.permission.SHUTDOWN); + } + + boolean timedout = false; + + synchronized(this) { + mShuttingDown = true; + mWindowManager.setEventDispatching(false); + + if (mResumedActivity != null) { + pauseIfSleepingLocked(); + final long endTime = System.currentTimeMillis() + timeout; + while (mResumedActivity != null || mPausingActivity != null) { + long delay = endTime - System.currentTimeMillis(); + if (delay <= 0) { + Log.w(TAG, "Activity manager shutdown timed out"); + timedout = true; + break; + } + try { + this.wait(); + } catch (InterruptedException e) { + } + } + } + } + + mUsageStatsService.shutdown(); + mBatteryStatsService.shutdown(); + + return timedout; + } + void pauseIfSleepingLocked() { - if (mSleeping) { + if (mSleeping || mShuttingDown) { if (!mGoingToSleep.isHeld()) { mGoingToSleep.acquire(); if (mLaunchingActivity.isHeld()) { @@ -8064,7 +8110,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen + " mBooting=" + mBooting + " mBooted=" + mBooted + " mFactoryTest=" + mFactoryTest); - pw.println(" mSleeping=" + mSleeping); + pw.println(" mSleeping=" + mSleeping + " mShuttingDown=" + mShuttingDown); pw.println(" mGoingToSleep=" + mGoingToSleep); pw.println(" mLaunchingActivity=" + mLaunchingActivity); pw.println(" mDebugApp=" + mDebugApp + "/orig=" + mOrigDebugApp diff --git a/services/java/com/android/server/am/BatteryStatsService.java b/services/java/com/android/server/am/BatteryStatsService.java index ddc3e6829de2..a21893bbb44c 100644 --- a/services/java/com/android/server/am/BatteryStatsService.java +++ b/services/java/com/android/server/am/BatteryStatsService.java @@ -25,8 +25,7 @@ import android.os.IBinder; import android.os.Parcel; import android.os.Process; import android.os.ServiceManager; -import android.telephony.TelephonyManager; -import android.util.PrintWriterPrinter; +import android.util.Log; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -50,6 +49,13 @@ public final class BatteryStatsService extends IBatteryStats.Stub { ServiceManager.addService("batteryinfo", asBinder()); } + public void shutdown() { + Log.w("BatteryStats", "Writing battery stats before shutdown..."); + synchronized (mStats) { + mStats.writeLocked(); + } + } + public static IBatteryStats getService() { if (sService != null) { return sService; diff --git a/services/java/com/android/server/am/UsageStatsService.java b/services/java/com/android/server/am/UsageStatsService.java index b6f91589fae6..866334b2d28e 100755 --- a/services/java/com/android/server/am/UsageStatsService.java +++ b/services/java/com/android/server/am/UsageStatsService.java @@ -17,6 +17,7 @@ package com.android.server.am; import com.android.internal.app.IUsageStats; + import android.content.ComponentName; import android.content.Context; import android.os.Binder; @@ -418,6 +419,11 @@ public final class UsageStatsService extends IUsageStats.Stub { ServiceManager.addService(SERVICE_NAME, asBinder()); } + public void shutdown() { + Log.w(TAG, "Writing usage stats before shutdown..."); + writeStatsToFile(true); + } + public static IUsageStats getService() { if (sService != null) { return sService; -- cgit v1.2.3-59-g8ed1b