Aug 16, 2013

In Depth : Android Shutdown Sequence

Standard

What happened when I long press power button ?
What is shutdown sequence ?
How is it different from desktop linux shutdown sequence?
How to change shutdown menu ?

Many questions pop-up in mind when we think about Android shutdown sequence. Before you read about shutdown sequence I suggest you to read about boot sequence article.


Android is linux based open source operating system, x86 (x86 is a series of computer microprocessor instruction set architectures based on the Intel 8086 CPU.) is most likely system where linux kernel is deployed however all Android devices are running on ARM process (ARM (formerly Advanced RISC Machine, which was formerly Acorn RISC Machine)) except Intel’s Xolo device (http://xolo.in/xolo-x900-features). Xolo comes with Atom 1.6 GHz x86 processor. Android shutdown sequence is different from desktop linux like ubuntu, fedora, etc.  
In this article I am going to explain shutdown sequence for Android only. Please refer "Linux Boot and Shutdown Process" for details of desktop linux shutdown process.

Following diagram illustrate shutdown sequence in detail.



Step 1: Long Press Power Button for 500ms.
Step 2: PhoneWindowManager.java identify Power Button long press and call method named "interceptKeyBeforeQueueing". 
Following code display power key  snippet from the function.


/** {@inheritDoc} */
@Override
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags, boolean isScreenOn) {
....
....
....
case KeyEvent.KEYCODE_POWER: {
     result &= ~ACTION_PASS_TO_USER;
       if (down) {
         if (isScreenOn && !mPowerKeyTriggered
               && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
                   mPowerKeyTriggered = true;
                   mPowerKeyTime = event.getDownTime();
                   interceptScreenshotChord();
            }
               ITelephony telephonyService = getTelephonyService();
                boolean hungUp = false;
               if (telephonyService != null) {
                   try {
                       if (telephonyService.isRinging()) {
                           // Pressing Power while there's a ringing incoming
                           // call should silence the ringer.
                            telephonyService.silenceRinger();
                       } else if ((mIncallPowerBehavior
                                & Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0
                               && telephonyService.isOffhook()) {
                            // Otherwise, if "Power button ends call" is enabled,
                           // the Power button will hang up any current active call.
                            hungUp = telephonyService.endCall();
                       }
                   } catch (RemoteException ex) {
                        Log.w(TAG, "ITelephony threw RemoteException", ex);
                   }
               }
               interceptPowerKeyDown(!isScreenOn || hungUp
                       || mVolumeDownKeyTriggered || mVolumeUpKeyTriggered);
           } else {
               mPowerKeyTriggered = false;
               cancelPendingScreenshotChordAction();
               if (interceptPowerKeyUp(canceled || mPendingPowerKeyUpCanceled)) {
                   result = (result & ~ACTION_WAKE_UP) | ACTION_GO_TO_SLEEP;
               }
               mPendingPowerKeyUpCanceled = false;
           }
          break;
      }
....
....
....
}


Above code handle multiple options like silence ringtone, take screenshots and power off. It will identify appropriate option based on time duration and other key's status. It will call "interceptPowerKeyDown" option by eliminate other options.

Following code display interceptPowerKeyDown function. It will wait for 500 millisecond (ViewConfiguration#getGlobalActionKeyTimeout()) then call mPowerLongPress Thread.


private void interceptPowerKeyDown(boolean handled) {
  mPowerKeyHandled = handled;
  if (!handled) {
       mHandler.postDelayed(mPowerLongPress, ViewConfiguration.getGlobalActionKeyTimeout());
  }
}
Following code represent mPowerLongPress thread
private final Runnable mPowerLongPress = new Runnable() {
        @Override
        public void run() {
            // The context isn't read
            if (mLongPressOnPowerBehavior < 0) {
                mLongPressOnPowerBehavior = mContext.getResources().getInteger(
                        com.android.internal.R.integer.config_longPressOnPowerBehavior);
            }
            int resolvedBehavior = mLongPressOnPowerBehavior;
            if (FactoryTest.isLongPressOnPowerOffEnabled()) {
                resolvedBehavior = LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM;
            }

            switch (resolvedBehavior) {
            case LONG_PRESS_POWER_NOTHING:
                break;
            case LONG_PRESS_POWER_GLOBAL_ACTIONS:
                mPowerKeyHandled = true;
                if (!performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false)) {
                    performAuditoryFeedbackForAccessibilityIfNeed();
                }
                sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
                showGlobalActionsDialog();
                break;
            case LONG_PRESS_POWER_SHUT_OFF:
            case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM:
                mPowerKeyHandled = true;
                performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
                sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
                mWindowManagerFuncs.shutdown(resolvedBehavior == LONG_PRESS_POWER_SHUT_OFF);
                break;
            }
        }
    };
Step 3: Controls goes to GlobalActions.java which is responsible to display dialogbox with various options like (Power Off, Airplan mode, Take screenshot and few toggle buttons), This dialog box has different options are per your OEM provider, model and Android OS version. GlobalAction class has method named showdialog() which is responsible to create object of Dialogbox with options.
void showGlobalActionsDialog() {
    if (mGlobalActions == null) {
        mGlobalActions = new GlobalActions(mContext, mWindowManagerFuncs);
    }
    final boolean keyguardShowing = keyguardIsShowingTq();
    mGlobalActions.showDialog(keyguardShowing, isDeviceProvisioned());
    if (keyguardShowing) {
         // since it took two seconds of long press to bring this up,
        // poke the wake lock so they have some time to see the dialog.
        mKeyguardMediator.userActivity();
    }
}



Step 4: If user select "Power Off" option from the dialogbox then control again goes back to PhoneWindowManager, It will start shutdown process.
Step 5: Shutdown process initiate from ShutdownThread.java file's shoutdowninner() function, It wil display confirmation dialog with ok / cancel button, If user select ok option then actual shutdown process starts.


Step 6: beginShutdownSequence() function called when user select OK option from the dialog.


private static void beginShutdownSequence(Context context) {
        synchronized (sIsStartedGuard) {
            if (sIsStarted) {
                Log.d(TAG, "Shutdown sequence already running, returning.");
                return;
            }
            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.show();

        sInstance.mContext = context;
        sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);

        // make sure we never fall asleep again
        sInstance.mCpuWakeLock = null;
        try {
            sInstance.mCpuWakeLock = sInstance.mPowerManager.newWakeLock(
                    PowerManager.PARTIAL_WAKE_LOCK, TAG + "-cpu");
            sInstance.mCpuWakeLock.setReferenceCounted(false);
            sInstance.mCpuWakeLock.acquire();
        } catch (SecurityException e) {
            Log.w(TAG, "No permission to acquire wake lock", e);
            sInstance.mCpuWakeLock = null;
        }

        // also make sure the screen stays on for better user experience
        sInstance.mScreenWakeLock = null;
        if (sInstance.mPowerManager.isScreenOn()) {
            try {
                sInstance.mScreenWakeLock = sInstance.mPowerManager.newWakeLock(
                        PowerManager.FULL_WAKE_LOCK, TAG + "-screen");
                sInstance.mScreenWakeLock.setReferenceCounted(false);
                sInstance.mScreenWakeLock.acquire();
            } catch (SecurityException e) {
                Log.w(TAG, "No permission to acquire wake lock", e);
                sInstance.mScreenWakeLock = null;
            }
        }

        // start the thread that initiates shutdown
        sInstance.mHandler = new Handler() {
        };
        sInstance.start();
    }
Run method, start actual shutdown process 
 public void run() {
        BroadcastReceiver br = new BroadcastReceiver() {
            @Override public void onReceive(Context context, Intent intent) {
                // We don't allow apps to cancel this, so ignore the result.
                actionDone();
            }
        };

        /*
         * Write a system property in case the system_server reboots before we
         * get to the actual hardware restart. If that happens, we'll retry at
         * the beginning of the SystemServer startup.
         */
        {
            String reason = (mReboot ? "1" : "0") + (mRebootReason != null ? mRebootReason : "");
            SystemProperties.set(SHUTDOWN_ACTION_PROPERTY, reason);
        }

        /*
         * If we are rebooting into safe mode, write a system property
         * indicating so.
         */
        if (mRebootSafeMode) {
            SystemProperties.set(REBOOT_SAFEMODE_PROPERTY, "1");
        }

        Log.i(TAG, "Sending shutdown broadcast...");
        
        // First send the high-level shut down broadcast.
        mActionDone = false;
        Intent intent = new Intent(Intent.ACTION_SHUTDOWN);
        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
        mContext.sendOrderedBroadcastAsUser(intent,
                UserHandle.ALL, null, br, mHandler, 0, null, null);
        
        final long endTime = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME;
        synchronized (mActionDoneSync) {
            while (!mActionDone) {
                long delay = endTime - SystemClock.elapsedRealtime();
                if (delay <= 0) {
                    Log.w(TAG, "Shutdown broadcast timed out");
                    break;
                }
                try {
                    mActionDoneSync.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) {
            }
        }

        // Shutdown radios.
        shutdownRadios(MAX_RADIO_WAIT_TIME);

        // Shutdown MountService to ensure media is in a safe state
        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 = SystemClock.elapsedRealtime() + MAX_SHUTDOWN_WAIT_TIME;
        synchronized (mActionDoneSync) {
            try {
                final IMountService mount = IMountService.Stub.asInterface(
                        ServiceManager.checkService("mount"));
                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 - SystemClock.elapsedRealtime();
                if (delay <= 0) {
                    Log.w(TAG, "Shutdown wait timed out");
                    break;
                }
                try {
                    mActionDoneSync.wait(delay);
                } catch (InterruptedException e) {
                }
            }
        }

        rebootOrShutdown(mReboot, mRebootReason);
    }

Step 7: With rebootOrShutdown() method controls transfer to the native function of com_android_server_power_PowerManagerService.cpp file, and finally control goes to android_reboot.c file which is final step of shutdown sequence.
static void nativeShutdown(JNIEnv *env, jclass clazz) {
    android_reboot(ANDROID_RB_POWEROFF, 0, 0);
}

References
  1. PhoneWindowManager.java
  2. GlobalActions.java
  3. WindowManagerPolicy.java
  4. ShutdownThread.java
  5. PowerManagerService.java
  6. com_android_server_power_PowerManagerService.cpp
  7. android_reboot.c
  8. Linux Boot and Shutdown Process

1 comment:

  1. Pipat Methavanitpong11:46 AM

    Thanks for GREAT article!
    But still I cannot find why emulator window does not close and its animation still keeps going, even kernel prints a message "System Halt" already. I wish this article would help my journey in search of it : )

    ReplyDelete