添加关机动画

平台:Android6.0 TV
问题:添加关机动画

解决方法:此处选择方法2添加动画

  1. 同开机动画方法一样添加关机动画
  2. 在关机前添加一段动画

关机流程分析

  1. 进入/frameworks/base/packages/SystemUI/src/com/android/systemui/shutdown/ShutDownActivity.java,在此入口仅为本人当前的代码环境,具体入口依据项目,手机版的一般入口为PhoneWindowManager-> GlobalActions
@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();
        //关机按钮操作
        } else if (v == shutDownOV) {
            //Toast.makeText(this, getString(R.string.shutdown), Toast.LENGTH_LONG).show();
            ShutDownHelper.shutdown(this);
            finish();
        //重启按钮操作
        } 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);
        }
    }

由上可以看到按下shutDownOV按钮时进入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
    }

通过PowerManager对象调用其shutdown方法
2. 进入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) {
    //权限的获取
    mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null);

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

此处传入两个参数,confirm-表示按下关机键时关机确认对话框是否弹出;

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

    //根据bool值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. 进入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判断是否弹出关机确认对话框
    if (confirm) {
        final CloseDialogReceiver closer = new CloseDialogReceiver(context);
        if (sConfirmDialog != null) {
            sConfirmDialog.dismiss();
        }
        //创建关机确认对话框
        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) {
                        //执行关机
                        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 {
        //无需对话框直接执行关机
        beginShutdownSequence(context);
    }
}
  1. 进入方法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
        }
    }
    //初始化关机线程
    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
    // 启动关机线程
    sInstance.mHandler = new Handler() {
    };
    sInstance.start();
}
  1. 执行关机线程的run
 /**
 * 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.
            // 用于接收关机广播
            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);
    //等待10s,前面定义的广播接收器收到关机广播时mActionDone设置为true,同时取消等待
    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...");
    // 10s内关闭ActivityManager服务
    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...");
    //关闭PackageManager服务
    final PackageManagerService pm = (PackageManagerService)
        ServiceManager.getService("package");
    if (pm != null) {
        pm.shutdown();
    }
    if (mRebootUpdate) {
        sInstance.setRebootProgress(PACKAGE_MANAGER_STOP_PERCENT, null);
    }

    // Shutdown radios.
    //12s内关闭收音机
    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.
    //20s内关闭MountService服务
    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();
    }
    // 执行重启或关机
    rebootOrShutdown(mContext, mReboot, mRebootReason);
}

run方法中主要完成的工作有:

  • 发送关机广播ACTION_SHUTDOWN
  • 关闭服务
  • 关闭radio
  • 设置关闭震动
  • 调用PowerManagerService的方法关闭电源
  1. 进入rebootOrShutdown方法
/**
 * 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
        //关机前震动
        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.
        // 先休眠一会,震动结束后再关机
        try {
            Thread.sleep(SHUTDOWN_VIBRATE_MS);
        } catch (InterruptedException unused) {
        }
    }

    // Shutdown power
    Log.i(TAG, "Performing low-level shutdown...");
    // 调用PowerManagerService的方法关闭电源
    PowerManagerService.lowLevelShutdown();
}
  1. 调用PowerManagerService的方法关闭电源
/**
 * 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
}

添加动画

根据上述源码分析,应该在/frameworks/base/services/core/java/com/android/server/power/ShutdownThread.java的shutdown()方法中添加对应的播放动画的方法,此处选择通过activity跳转的方式启动动画。在设计之前,原先的思路是放置在framework/base/package/systemui/的目录下,但是操作过程发现当按下power选择关机按钮后log中会出现

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

因此导致在播放动画之前就退出了的现象,另尝试在settings下添加该activity,可以实现播放,因此暂时先放在settings目录下

  1. 动画.gif
    添加关机动画_第1张图片
  2. 实现播放动画的自定义view
public class ShutdownAnimationGifView extends View {
    private long movieStart;

    private Movie movie;

    //此处必须重写该构造方法

    public ShutdownAnimationGifView(Context context,AttributeSet attributeSet) {

        super(context,attributeSet);

        //以文件流(InputStream)读取进gif图片资源

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

    }

    @Override

    protected void onDraw(Canvas canvas) {

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

//第一次播放

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

            //强制重绘

            invalidate();

        }

        super.onDraw(canvas);

    }
}
  1. 动画播放界面layout

<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. 动画播放实现的activity
public class ShutAnimationShow extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        //设置activity为全屏显示
        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. activity在清单文件中的配置
//exported:是否能够被其他应用程序组件调用或跟它交互
//如果自己定义的某个Activity要通过隐式启动,在AndroidManifast.xm那么必须加上android.intent.category.DEFAULT,否则不起作用

<activity android:name=".ShutAnimationShow"
    android:exported="true"
    android:hardwareAccelerated="false">
    <intent-filter>
        
        <category android:name="android.intent.category.DEFAULT" />
        
    intent-filter>
activity>

android:hardwareAccelerated=“false” 该属性解决了gif动画不播放的问题,取消硬件加速器

  1. 在ShutdownThread中通过intent跳转启动
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);
        //参数是包名,类全限定名,注意直接用类名不行
        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);
}

这里使用组建名启动方式启动,跳转时会出现如下log信息:

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

此处为警告可以不做修改,若必须改可以修改为

context.startActivityAsUser(intent, UserHandle.CURRENT);

解释参考https://blog.csdn.net/love_xsq/article/details/50392093

注意
上处代码中添加sleep(3000),若不做休眠处理会出现activity界面闪一下的现象,因为跳转activity与接下来的关机流程几乎同步,故作休眠处理

以上的修改完成的关机动画的添加

你可能感兴趣的:(Android之旅)