/* * Copyright (c) 2011, The Linux Foundation. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * Neither the name of The Linux Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.android.server; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.os.PowerManager; import android.provider.Settings; import android.os.SystemClock; import android.content.BroadcastReceiver; import android.os.Handler; import android.os.Message; import android.util.Log; import android.content.Context; import android.view.KeyEvent; import android.app.ActivityManager; import java.util.List; import android.content.res.Configuration; import android.view.Display; import android.os.SystemProperties; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; /** * SuspendAcitivity provides the Warmboot feature which enables Device to enter into * and resume from "suspend2ram" state */ public class SuspendActivity extends Activity { private static final String TAG = "SuspendActivity"; private static final String INPUT_METHOD_SERVICE = "com.android.inputmethod.latin"; // Airplane Mode needs to be set to enter "TCXO Shutdown" (complete-suspend) state. With out this, device can enter only "Power-collapse" state private boolean mAirplaneModeSetBy = false; // Indicates whether this Activity was already paused or not */ private boolean mWasPaused = false; // Indicates whether this Activity was already resumed & state-restored or not private boolean mStateRestored = false; // Splash screen events private static final int START_BOOT_SPLASH = 1; private static final int STOP_BOOT_SPLASH = 2; // Splash screen state handling private SplashHandler mHandler = new SplashHandler(); class SplashHandler extends Handler { /** * Handler's Main function which looks after the transitions */ public void handleMessage(Message msg) { switch( msg.what ) { case START_BOOT_SPLASH: Log.d(TAG, "handleMessage() Event : START_BOOT_SPLASH"); SystemProperties.set( "ctl.start", "bootanim"); break; case STOP_BOOT_SPLASH: Log.d(TAG, "handleMessage() Event : STOP_BOOT_SPLASH"); SystemProperties.set( "ctl.stop", "bootanim"); break; } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Display getOrient = getWindowManager().getDefaultDisplay(); mWasPaused = false; Log.d(TAG, "onCreate() : " + SystemClock.elapsedRealtime() + " WIDTH = " + getOrient.getWidth() + ", HEIGHT = " + getOrient.getHeight() ); setSuspendState(); } @Override protected void onStart( ) { super.onStart( ); Log.d(TAG, "onStart() : " + SystemClock.elapsedRealtime()); } @Override protected void onRestart( ) { super.onRestart( ); Log.d(TAG, "onRestart() : " + SystemClock.elapsedRealtime()); } @Override protected void onResume( ) { super.onResume( ); Log.d(TAG, "onResume() - mWasPaused = " + mWasPaused + " : " + SystemClock.elapsedRealtime()); // RESUME needs to be handled only after PAUSE was done if ( mWasPaused ) { mHandler.sendMessage( Message.obtain(mHandler, START_BOOT_SPLASH) ); mHandler.sendMessageDelayed( Message.obtain(mHandler, STOP_BOOT_SPLASH), 2000 ); // Activity can just quit as the device is back now restoreOriginalState(); // Go to "Home-Screen" when device resumes Intent intent = new Intent(Intent.ACTION_MAIN); intent.addCategory(Intent.CATEGORY_HOME); startActivity(intent); // Destory this activity as Device is resumed & restored to original state finish(); mStateRestored = true; } } @Override protected void onPause( ) { super.onPause( ); Log.d(TAG, "onPause() : " + SystemClock.elapsedRealtime()); mWasPaused = true; } @Override protected void onStop( ) { super.onStop( ); Log.d(TAG, "onStop() : " + SystemClock.elapsedRealtime()); // Incase, if something stops/kills us before RESUME was handled. if ( mStateRestored == false ) { // Activity can just quit as the device is back now restoreOriginalState(); // Destory this activity as Device is resumed & restored to original state finish(); mStateRestored = true; } } @Override protected void onDestroy( ) { super.onDestroy( ); Log.d(TAG, "onDestroy() : " + SystemClock.elapsedRealtime()); } /** * Ensure smooth state for the device to enter SUSPEND smoothly */ private void setSuspendState() { Log.d( TAG, "setSuspendState()"); // Should n't suspend during Monkey-run ( Similar to shutdown scenario ) boolean isDebuggableMonkeyBuild = SystemProperties.getBoolean("ro.monkey", false); if (isDebuggableMonkeyBuild) { Log.d(TAG, "Rejected suspend as monkey is detected to be running. Destroying activity.."); // Destory this activity as Device is NOT suspending finish(); return; } Log.d(TAG, "Notifying thread to start radio shutdown"); forceKillActiveServices(); // Disable keypad , Enable airplane-mode as we are entering RESUME state setKeypadEnabled(false); setAirplaneModeEnabled(true); PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE); pm.goToSleep( SystemClock.uptimeMillis() ); } /** * Does n't need to do anything wrt Device-Resume., This activity's onResume() is called soon after * the Device-Resume happens by POWER-key press., * So, if the Airplane Mode was earlier enabled by us, lets disable it and bring back the state to * the Device's original state. */ private void restoreOriginalState() { Log.d(TAG, "restoreOriginalState() : " + SystemClock.elapsedRealtime()); // Enable keypad , Disable airplane-mode as we are entering RESUME state setKeypadEnabled(true); setAirplaneModeEnabled(false); } /** * Checks whether any active services are present., Its better to force-stop them( and their originating process'es) * to ensure smooth sleep (power-collapse) state is entered., * One Example - MediaplaybackService., Music Application doesn't handle onPause/suspend properly., * Force-Stop == stops the process gracefully and then restarts it in another 5 secs */ private void forceKillActiveServices() { Log.d(TAG, "forceKillActiveServices() : " + SystemClock.elapsedRealtime() ); List runningServices; ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); // AM's API getRunningServices() requires a ?int? parameter. Here, we are getting first 50 services used/active by user applications. // Though this actul figure is quite less runningServices = activityManager.getRunningServices(50); for (int i = 0; i < runningServices.size(); i++) { ActivityManager.RunningServiceInfo si = runningServices.get(i); // Don't touch NON-started services if (!si.started && si.clientLabel == 0) { Log.i( TAG, "Ignore [ !si.started && si.clientLabel == 0 ] : Process = " + si.process + " with component " + si.service.getClassName() ); continue; } // Don't touch PERSISTENT services. They are already part of system and wont trouble much if ((si.flags&ActivityManager.RunningServiceInfo.FLAG_PERSISTENT_PROCESS) != 0) { Log.i( TAG, "Ignore [ FLAG_PERSISTENT_PROCESS ] : Process = " + si.process + " with component " + si.service.getClassName() + " clientLabel = " + si.clientLabel ); continue; } // Don't touch Inputmethod.latin service. If stopped, this (current foreground) activity might be destroyed & recreated. if ( si.process.startsWith(INPUT_METHOD_SERVICE) ) { Log.i( TAG, "No POINT in killing System-Input Process = " + si.process + " with component " + si.service.getClassName() + " clientLabel = " + si.clientLabel ); continue; } try { activityManager.forceStopPackage(si.process); // activityManager.killBackgroundProcesses(si.process); Log.i( TAG, "forceStopPackage [SUCCESS] : Process = " + si.process + " with component " + si.service.getClassName() + " clientLabel = " + si.clientLabel ); } catch (Exception e) { Log.i( TAG, "forceStopPackage [FAIL] ! : Process = " + si.process + " with component " + si.service.getClassName() + " clientLabel = " + si.clientLabel ); } } } /** * Disable Keypad (Except POWER-Key) when device enters suspend state */ private void setKeypadEnabled( boolean on ) { Log.d( TAG, "setKeypadEnabled() : on = " + on); String disableStr = (on ? "0" : "1" ); SystemProperties.set( "hw.keyboard.disable" , disableStr ); } /** * Enable Airplane-mode when device enters suspend state., Restore to original state when device resumes. */ private void setAirplaneModeEnabled( boolean on ) { Log.d( TAG, "setAirplaneModeEnabled() : on = " + on); if ( on ) { int airplaneMode = Settings.System.getInt( getContentResolver(), Settings.System.AIRPLANE_MODE_ON, 0 ); mAirplaneModeSetBy = ( airplaneMode == 0 ); changeAirplaneModeSystemSetting(true); if ( mAirplaneModeSetBy ) { Log.d( TAG, "NO airplaneMode set. Lets enable it!"); } else { Log.d( TAG, "AirplaneMode already set. Lets not worry :-)"); } } else { if ( mAirplaneModeSetBy ) { Log.d( TAG, "Suspend has enabled Airplane mode. Lets disable it!" ); changeAirplaneModeSystemSetting(false); } else { Log.d( TAG, "AirplaneMode NOT set by SUSPEND. Lets not worry :-)"); } } } /** * Change the airplane mode system setting * Copied from "frameworks/policies/base/phone/com/android/internal/policy/impl/GlobalActions.java" */ private void changeAirplaneModeSystemSetting( boolean on ) { Log.d( TAG, "changeAirplaneModeSystemSetting() : " + SystemClock.elapsedRealtime()); Settings.System.putInt( getContentResolver(), Settings.System.AIRPLANE_MODE_ON, on ? 1 : 0); Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); intent.putExtra("state", on); sendBroadcast(intent); } }