Add shutdown animation

Keywords: Android Java Mobile Windows

Platform: Android 6.0 TV
Question: Add Close Animation

Solution: Select Method 2 here to add animation

  1. Add shutdown animation as in boot-up animation
  2. Add an animation before shutdown

Shutdown process analysis

  1. Enter / frameworks/base/packages/SystemUI/src/com/android/systemui/shutdown/ShutDownActivity.java. This entry is only my current code environment. The specific entry is based on the project. The general entry for mobile version is Phone Windows Manager - > Global Actions.
@Override
    public void onClick(View v) {
        /*if (v == soundIntelligentOV) {
            ShutDownHelper.gotoSmartBox(this);
            //Toast.makeText(this, getString(R.string.smart_box), Toast.LENGTH_LONG).show();
        } else */if (v == soundBlueToothOV) {
            ShutDownHelper.gotoBluetoohBox(this);
            //Toast.makeText(this, getString(R.string.bluetooth_box), Toast.LENGTH_LONG).show();
            finish();
        //Shutdown button operation
        } else if (v == shutDownOV) {
            //Toast.makeText(this, getString(R.string.shutdown), Toast.LENGTH_LONG).show();
            ShutDownHelper.shutdown(this);
            finish();
        //Restart button operation
        } else if (v == restartOV) {
            ShutDownHelper.reboot(this);
            //Toast.makeText(this, getString(R.string.reboot), Toast.LENGTH_LONG).show();
            ShutDownHelper.reboot(this);
            finish();
        } else if (v == delayShutDownOV) {
            if (delayShutDownOV.getTitle().equals(getString(R.string.description_cancel))) {
                mAutoDelayOptionView.cancleAutoDelay();
                finish();
            }else {
                Toast.makeText(this, getString(R.string.delay_shutdown), Toast.LENGTH_LONG).show();
                mAutoDelayOptionView.DelayShutDown();
            }
            Log.d("sgq","ssss***"+34/15);
        }
    }

From above, you can see that when you press the shutDownOV button, you enter ShutDownHelper.shutdown(this);

public static void shutdown(Context context) {
        // PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        // pm.goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, 0);
        // ((Activity)context).finish();
        // try {
        //     Runtime.getRuntime().exec("reboot");
        // } catch (IOException e) {
        //     e.printStackTrace();
        // }
        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        pm.shutdown(false,false);
        //change by xzh,reboot enter AC on standby
    }

Call its shutdown method through the PowerManager object
2. Enter frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java

/**
 * Shuts down the device.
 *
 * @param confirm If true, shows a shutdown confirmation dialog.
 * @param wait If true, this call waits for the shutdown to complete and does not return.
 */
@Override // Binder call
public void shutdown(boolean confirm, boolean wait) {
    //Access to permissions
    mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null);

    final long ident = Binder.clearCallingIdentity();
    try {
        shutdownOrRebootInternal(true, confirm, null, wait);
    } finally {
        Binder.restoreCallingIdentity(ident);
    }
}

Two parameters are passed in here, confirm - indicating whether the shutdown confirmation dialog pops up when the shutdown key is pressed.

private void shutdownOrRebootInternal(final boolean shutdown, final boolean confirm,
            final String reason, boolean wait) {
    if (mHandler == null || !mSystemReady) {
        throw new IllegalStateException("Too early to call shutdown() or reboot()");
    }

    //Judging whether shutdown or restart should be performed based on bool value shutdown
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            synchronized (this) {
                if (shutdown) {
                    ShutdownThread.shutdown(mContext, confirm);
                } else {
                    ShutdownThread.reboot(mContext, reason, confirm);
                }
            }
        }
    };

    // ShutdownThread must run on a looper capable of displaying the UI.
    Message msg = Message.obtain(mHandler, runnable);
    msg.setAsynchronous(true);
    mHandler.sendMessage(msg);

    // PowerManager.reboot() is documented not to return so just wait for the inevitable.
    if (wait) {
        synchronized (runnable) {
            while (true) {
                try {
                    runnable.wait();
                } catch (InterruptedException e) {
                }
            }
        }
    }
}
  1. Enter ShutdownThread.shutdown(mContext, confirm); frameworks/base/services/core/java/com/android/server/power/ShutdownThread.java
/**
 * 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) {
    mReboot = false;
    mRebootSafeMode = false;
    shutdownInner(context, confirm);
}
static void shutdownInner(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;
        }
    }
    ...
    //confirm determines whether the shutdown confirmation dialog box pops up
    if (confirm) {
        final CloseDialogReceiver closer = new CloseDialogReceiver(context);
        if (sConfirmDialog != null) {
            sConfirmDialog.dismiss();
        }
        //Create shutdown confirmation dialog box
        sConfirmDialog = new AlertDialog.Builder(context)
                .setTitle(mRebootSafeMode
                        ? com.android.internal.R.string.reboot_safemode_title
                        : com.android.internal.R.string.power_off)
                .setMessage(resourceId)
                .setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        //Execution shutdown
                        beginShutdownSequence(context);
                    }
                })
                .setNegativeButton(com.android.internal.R.string.no, null)
                .create();
        closer.dialog = sConfirmDialog;
        sConfirmDialog.setOnDismissListener(closer);
        sConfirmDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
        sConfirmDialog.show();
    } else {
        //Direct shutdown without dialog box
        beginShutdownSequence(context);
    }
}
  1. Entry method beginShutdownSequence(context)
private static void beginShutdownSequence(Context context) {
    synchronized (sIsStartedGuard) {
        if (sIsStarted) {
            Log.d(TAG, "Shutdown sequence already running, returning.");
            return;
        }
        sIsStarted = true;
    }

    // Throw up a system dialog to indicate the device is rebooting / shutting down.
    ProgressDialog pd = new ProgressDialog(context);

    // Path 1: Reboot to recovery and install the update
    //   Condition: mRebootReason == REBOOT_RECOVERY and mRebootUpdate == True
    //   (mRebootUpdate is set by checking if /cache/recovery/uncrypt_file exists.)
    //   UI: progress bar
    //
    // Path 2: Reboot to recovery for factory reset
    //   Condition: mRebootReason == REBOOT_RECOVERY
    //   UI: spinning circle only (no progress bar)
    //
    // Path 3: Regular reboot / shutdown
    //   Condition: Otherwise
    //   UI: spinning circle only (no progress bar)
    if (PowerManager.REBOOT_RECOVERY.equals(mRebootReason)) {
        mRebootUpdate = new File(UNCRYPT_PACKAGE_FILE).exists();
        if (mRebootUpdate) {
            pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_update_title));
            pd.setMessage(context.getText(
                    com.android.internal.R.string.reboot_to_update_prepare));
            pd.setMax(100);
            pd.setProgressNumberFormat(null);
            pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
            pd.setProgress(0);
            pd.setIndeterminate(false);
            //add by shaoguangqing for shutdown UI on
            pd.setCancelable(false);
            pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
            pd.show();
            //add by shaoguangqing for shutdown UI off
        }
    }
    //Initialize shutdown threads
    sInstance.mProgressDialog = pd;
    Log.i("shutdownthread qff","beginShutdownSequence sInstance.mProgressDialog= "+sInstance.mProgressDialog);
    sInstance.mContext = context;
    sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);

    // make sure we never fall asleep again
    sInstance.mCpuWakeLock = null;
    try {
        Log.i("shutdownthread qff","beginShutdownSequence sInstance.mCpuWakeLock---------");
        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
    // Start shutdown thread
    sInstance.mHandler = new Handler() {
    };
    sInstance.start();
}
  1. run executing shutdown threads
 /**
 * 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() {
    BroadcastReceiver br = new BroadcastReceiver() {
        @Override public void onReceive(Context context, Intent intent) {
            // We don't allow apps to cancel this, so ignore the result.
            // Used to receive shutdown broadcasting
            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);
    //Wait 10 seconds. When the previously defined broadcast receiver receives the shutdown broadcast, mActionDone is set to true and the wait is cancelled.
    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;
            } else if (mRebootUpdate) {
                int status = (int)((MAX_BROADCAST_TIME - delay) * 1.0 *
                        BROADCAST_STOP_PERCENT / MAX_BROADCAST_TIME);
                sInstance.setRebootProgress(status, null);
            }
            try {
                mActionDoneSync.wait(Math.min(delay, PHONE_STATE_POLL_SLEEP_MSEC));
            } catch (InterruptedException e) {
            }
        }
    }
    if (mRebootUpdate) {
        sInstance.setRebootProgress(BROADCAST_STOP_PERCENT, null);
    }

    Log.i(TAG, "Shutting down activity manager...");
    // Close Activity Manager Service within 10s
    final IActivityManager am =
        ActivityManagerNative.asInterface(ServiceManager.checkService("activity"));
    if (am != null) {
        try {
            am.shutdown(MAX_BROADCAST_TIME);
        } catch (RemoteException e) {
        }
    }
    if (mRebootUpdate) {
        sInstance.setRebootProgress(ACTIVITY_MANAGER_STOP_PERCENT, null);
    }

    Log.i(TAG, "Shutting down package manager...");
    //Close PackageManager Service
    final PackageManagerService pm = (PackageManagerService)
        ServiceManager.getService("package");
    if (pm != null) {
        pm.shutdown();
    }
    if (mRebootUpdate) {
        sInstance.setRebootProgress(PACKAGE_MANAGER_STOP_PERCENT, null);
    }

    // Shutdown radios.
    //Turn off the radio within 12 seconds
    shutdownRadios(MAX_RADIO_WAIT_TIME);
    if (mRebootUpdate) {
        sInstance.setRebootProgress(RADIO_STOP_PERCENT, null);
    }

    // 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.
    //Close MountService within 20 seconds
    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;
            } else if (mRebootUpdate) {
                int status = (int)((MAX_SHUTDOWN_WAIT_TIME - delay) * 1.0 *
                        (MOUNT_SERVICE_STOP_PERCENT - RADIO_STOP_PERCENT) /
                        MAX_SHUTDOWN_WAIT_TIME);
                status += RADIO_STOP_PERCENT;
                sInstance.setRebootProgress(status, null);
            }
            try {
                mActionDoneSync.wait(Math.min(delay, PHONE_STATE_POLL_SLEEP_MSEC));
            } catch (InterruptedException e) {
            }
        }
    }
    if (mRebootUpdate) {
        sInstance.setRebootProgress(MOUNT_SERVICE_STOP_PERCENT, null);

        // If it's to reboot to install update, invoke uncrypt via init service.
        uncrypt();
    }
    // Execute restart or shutdown
    rebootOrShutdown(mContext, mReboot, mRebootReason);
}

The main work done in the run method is as follows:

  • Send off broadcast ACTION_SHUTDOWN
  • Shut down service
  • Close radio
  • Set Close Vibration
  • Call the PowerManagerService method to turn off the power supply
  1. Enter rebootOrShutdown method
/**
 * Do not call this directly. Use {@link #reboot(Context, String, boolean)}
 * or {@link #shutdown(Context, boolean)} instead.
 *
 * @param context Context used to vibrate or null without vibration
 * @param reboot true to reboot or false to shutdown
 * @param reason reason for reboot
 */
public static void rebootOrShutdown(final Context context, boolean reboot, String reason) {
    if (reboot) {
        Log.i(TAG, "Rebooting, reason: " + reason);
        PowerManagerService.lowLevelReboot(reason);
        Log.e(TAG, "Reboot failed, will attempt shutdown instead");
    } else if (SHUTDOWN_VIBRATE_MS > 0 && context != null) {
        // vibrate before shutting down
        //Vibration before shutdown
        Vibrator vibrator = new SystemVibrator(context);
        try {
            vibrator.vibrate(SHUTDOWN_VIBRATE_MS, VIBRATION_ATTRIBUTES);
        } catch (Exception e) {
            // Failure to vibrate shouldn't interrupt shutdown.  Just log it.
            Log.w(TAG, "Failed to vibrate during shutdown.", e);
        }

        // vibrator is asynchronous so we need to wait to avoid shutting down too soon.
        // Sleep for a while before shutting down after shaking.
        try {
            Thread.sleep(SHUTDOWN_VIBRATE_MS);
        } catch (InterruptedException unused) {
        }
    }

    // Shutdown power
    Log.i(TAG, "Performing low-level shutdown...");
    // Call the PowerManagerService method to turn off the power supply
    PowerManagerService.lowLevelShutdown();
}
  1. Call the PowerManagerService method to turn off the power supply
/**
 * Low-level function turn the device off immediately, without trying
 * to be clean.  Most people should use {@link ShutdownThread} for a clean shutdown.
 */
public static void lowLevelShutdown() {
    // MStar Android Patch Begin
    //SystemProperties.set("sys.powerctl", "shutdown");
    nativeShutdown();
    // MStar Android Patch End
}

Add animation

According to the above source code analysis, the corresponding method of playing animation should be added in shutdown() method of / frameworks/base/services/core/java/com/android/server/power/ShutdownThread.java, where the animation can be started by activity jump. Before the design, the original idea was to place it in the framework / base / package / system Mui / directory, but the operation process found that when power was pressed to select the shutdown button, the log would appear.

2140 04-19 17: 14: 31 4868 4997 I ActivityManager: moveTaskToBack: TaskRecord {6fbcc5 #4 A=com.android.systemui U=0 sz=2}

As a result, the phenomenon of quitting before playing the animation can be realized by adding the activity under settings. Therefore, it should be placed in the settings directory for the time being.

  1. Animation.gif
  2. Realize the customized view of playing animation
public class ShutdownAnimationGifView extends View {
    private long movieStart;

    private Movie movie;

    //This constructor must be overridden here

    public ShutdownAnimationGifView(Context context,AttributeSet attributeSet) {

        super(context,attributeSet);

        //Read gif image resources in InputStream

        movie=Movie.decodeStream(getResources().openRawResource(R.drawable.shutdownanimation));

    }

    @Override

    protected void onDraw(Canvas canvas) {

        long curTime=android.os.SystemClock.uptimeMillis();

//First broadcast

        if (movieStart == 0) {

            movieStart = curTime;

        }

        if (movie != null) {

            int duraction = movie.duration();

            int relTime = (int) ((curTime-movieStart)%duraction);

            movie.setTime(relTime);

            movie.draw(canvas, 0, 0);

            //Compulsory repainting

            invalidate();

        }

        super.onDraw(canvas);

    }
}
  1. Animation playback interface layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:attr="http://schemas.android.com/apk/res/com.android.tv.settings"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <com.android.tv.settings.ShutdownAnimationGifView
        android:id="@+id/shutanimation_show"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"/>
</LinearLayout>
  1. Activities implemented by animation playback
public class ShutAnimationShow extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        //Set activity for full screen display
        this.requestWindowFeature(Window.FEATURE_NO_TITLE);
        this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);
        super.onCreate(savedInstanceState);
        android.util.Log.i("ShutAnimationShow","qffffffffffffffff");
        setContentView(R.layout.activity_shutanimation_show_ui);
    }
}
  1. Configuration of activity in manifest file
//Export: Can it be invoked or interacted with by other application components
//If an Activity defined by oneself is to be started implicitly, android.intent.category.DEFAULT must be added to Android Manifast. xm, otherwise it will not work.
<!--add shutdownanimation-->
<activity android:name=".ShutAnimationShow"
    android:exported="true"
    android:hardwareAccelerated="false">
    <intent-filter>
        <!--<action android:name="shutdown.ShutAnimationShow.action" />-->
        <category android:name="android.intent.category.DEFAULT" />
        <!--<category android:name="shutdown.ShutAnimationShow.category" />-->
    </intent-filter>
</activity>

Android: Hardware Accelerated= "false" This property solves the problem of gif animation not playing, and cancels the hardware accelerator.

  1. Start with intent jump in HutdownThread
public static void shutdown(final Context context, boolean confirm) {
    mReboot = false;
    mRebootSafeMode = false;
    try {
        Intent intent = new Intent();
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        //The parameter is the package name and the class is fully qualified. Note that it is not possible to use the class name directly.
        ComponentName name = new ComponentName("com.android.tv.settings",
                "com.android.tv.settings.ShutAnimationShow");
        Log.i("shutdownthread qff","beginShutdownSequence--------- ");
        intent.setComponent(name);
        context.startActivity(intent);
        //context.startActivityAsUser(intent, UserHandle.CURRENT);
        sleep(3000);
    }catch (Exception e){
        e.printStackTrace();
    }
    shutdownInner(context, confirm);
}

Here, the start-up mode is used, and the following log information will appear when the jump occurs:

04-19 14:37:53.463  4407  4530 W ContextImpl: Calling a method in the system process without a qualified user: android.app.ContextImpl.startActivity:658 com.android.server.power.ShutdownThread.beginShutdownSequence:310

This is a warning that can be changed without modification. If necessary, it can be changed to

context.startActivityAsUser(intent, UserHandle.CURRENT);

Interpretation reference https://blog.csdn.net/love_xsq/article/details/50392093

Be careful
sleep(3000) is added to the code above. If you do not do sleep processing, the activity interface will flash. Because jumping activity is almost synchronized with the next shutdown process, it is pretended to be sleep processing.

The above modification completes the addition of shutdown animation

Posted by kalebaustin on Sat, 18 May 2019 23:35:22 -0700