当前位置:   article > 正文

开机黑屏几秒后再进入 setup_fallbackhome: user unlocked but no home; let's hop

fallbackhome: user unlocked but no home; let's hope someone enables one soon

  Android N运行于一个安全的模式,也就是Dierect Boot模式(具体可参照) 
  当手机开机,首先进入一个Dierect Boot的模式,
  1、在这个模式下只可以访问device-encrypted store下的数据, 无法访问credential-encrypted store下的数据。当用户解锁后就都可以访问了。 
  2、一般情况下,应用是无法在Direct Boot模式下运行的 
 
  3、如果需要某个app能够在Direct Boot模式下运行,需要注册相关APP的组件。
  应用组件申请在Direct Boot模式下运行:在AndroidManinfest.xml中设置 android:directBootAware="true"。(可参照gms包里的开机向导)


  首次开机进入开机向导前出现黑屏的原因是:
     开机向导没有设置过android:directBootAware="true"属性,因此它必须要等动画结束,ACTION_USER_UNLOCKED发送后,fallbackhome才能退出并加载启动开机向导,这样就会引起黑屏现象
     在开机向导的<applicatio里加了android:directBootAware="true"属性后,问题不存在了
     可以参考gms包里开机向导,有多个activity设置了directBootAware属性

 

https://blog.csdn.net/fu_kevin0606/article/details/65437594 这篇文档

以下为转载内容

DirectBoot功能介绍

DirectBoot功能介绍

当手机已经通电开机但是用户并有解锁锁屏的时候,Android N运行于一个安全的模式,也就是Dierect Boot模式。

为了支持Dierect Boot模式,系统提供了两个存储数据的地方:
1.Credential encrypted storage,默认存储数据的地方,仅在用户解锁手机后可用。
2.Device encrypted storage,主要对应的就是Direct Boot使用的存储空间。在Direct Boot模式下和用户解锁手机后都可以使用的存储空间。

系统把部分系统数据和已经注册了相关权限的Apps的数据保存在device-encrypted store 。其他的数据默认保存到credential-encrypted store。
当手机开机,首先进入一个Dierect Boot的模式,在这个模式下只可以访问device-encrypted store下的数据,无法访问credential-encrypted store下的数据。当用户解锁后就都可以访问了。

一般情况下,应用是无法在Direct Boot模式下运行的
如果需要某个app能够在Direct Boot模式下运行,需要注册相关APP的组件。通常需要在这个模式下运行的app:
1.计划通知的应用,例如Clock
2.重要的用户通知的应用,例如sms
3.提供无障碍服务的应用,例如Talkback

应用组件申请在Direct Boot模式下运行:在AndroidManinfest.xml中设置 android:directBootAware="true"。

应用访问device encrypted storage:
创建Context.createDeviceEncryptedStorageContext().然后通过这个Context来使用device encrypted storage 的存储空间。 

 

  1. Context directBootContext = Context.createDeviceEncryptedStorageContext();
  2. // Access appDataFilename that lives in device encrypted storage
  3. FileInputStream inStream = directBootContext.openFileInput(appDataFilename);
  4. // Use inStream to read content...

应用获取解锁的通知:
监听广播ACTION_USER_UNLOCKED 。
或者接收ACTION_BOOT_COMPLETED ,这个广播的意思是手机开机并且用户解锁。
也可调用UserManager.isUserUnlocked()方法来查询。
应用迁移已经存在的数据:
Context.migrateSharedPreferencesFrom() 
Context.migrateDatabaseFrom()
两种方法在credential encrypted storage 和device encrypted storage存储空间之间去迁移preference 和database的数据.

 

启动FallbackHome流程

在分析7.0过程中发现在启动Launcher之前会先启动一个FallbackHome,之后才会启动Launcher,通过调查发现FallbackHome属于Settings中的一个activity,Settings的android:directBootAware为true,并且FallbackHome在category中配置了Home属性,而Launcher的android:directBootAware为false,所有只有FallbackHome可以在direct boot模式下启动。

 

 
  1. <application android:label="@string/settings_label"
  2. android:icon="@mipmap/ic_launcher_settings"
  3. ............
  4. android:directBootAware="true">
  5. <!-- Triggered when user-selected home app isn't encryption aware -->
  6. <activity android:name=".FallbackHome"
  7. android:excludeFromRecents="true"
  8. android:theme="@style/FallbackHome">
  9. <intent-filter android:priority="-1000">
  10. <action android:name="android.intent.action.MAIN" />
  11. <category android:name="android.intent.category.HOME" />
  12. <category android:name="android.intent.category.DEFAULT" />
  13. </intent-filter>
  14. </activity>

所以在ActivityManagerService启动Home界面时,从PackageManagerService中获取到的Home界面就是FallbackHome

 

  1. Intent getHomeIntent() {
  2. Intent intent = new Intent(mTopAction, mTopData != null ? Uri.parse(mTopData) : null);
  3. intent.setComponent(mTopComponent);
  4. intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
  5. if (mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
  6. intent.addCategory(Intent.CATEGORY_HOME);
  7. }
  8. return intent;
  9. }
  10. boolean startHomeActivityLocked(int userId, String reason) {
  11. if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL
  12. && mTopAction == null) {
  13. // We are running in factory test mode, but unable to find
  14. // the factory test app, so just sit around displaying the
  15. // error message and don't try to start anything.
  16. return false;
  17. }
  18. Intent intent = getHomeIntent();
  19. ActivityInfo aInfo = resolveActivityInfo(intent, STOCK_PM_FLAGS, userId); //获取Home activity信息
  20. if (aInfo != null) {
  21. intent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name));
  22. // Don't do this if the home app is currently being
  23. // instrumented.
  24. aInfo = new ActivityInfo(aInfo);
  25. aInfo.applicationInfo = getAppInfoForUser(aInfo.applicationInfo, userId);
  26. ProcessRecord app = getProcessRecordLocked(aInfo.processName,
  27. aInfo.applicationInfo.uid, true);
  28. if (app == null || app.instrumentationClass == null) {
  29. intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
  30. mActivityStarter.startHomeActivityLocked(intent, aInfo, reason); //启动FallbackHome
  31. }
  32. } else {
  33. Slog.wtf(TAG, "No home screen found for " + intent, new Throwable());
  34. }
  35. return true;
  36. }
 

接着就会将FallbackHome启动起来,其实这个activity的代码非常简单不到100行,是个透明的activity,创建FallbackHome时注册ACTION_USER_UNLOCKED广播,然后进行判断用户是否都已经解锁,如果没有就结束执行。之后就会等待接收ACTION_USER_UNLOCKED广播,继续判断用户是否已经解锁,如果此时已经解锁,就找Home界面,如果没有找到就发延迟消息500ms再找一次,如果找到Launcher就会将FallbackHome finish掉。

 

下面就要看具体什么时候发送ACTION_USER_UNLOCKED广播了。

代码位置packages/apps/Settings/src/com/android/settings/FallbackHome.java

 
  1. * Copyright (C) 2015 The Android Open Source Project
  2. package com.android.settings;
  3. import android.app.Activity;
  4. public class FallbackHome extends Activity {
  5. private static final String TAG = "FallbackHome";
  6. @Override
  7. protected void onCreate(Bundle savedInstanceState) {
  8. super.onCreate(savedInstanceState);
  9. // Set ourselves totally black before the device is provisioned so that
  10. // we don't flash the wallpaper before SUW
  11. if (Settings.Global.getInt(getContentResolver(),
  12. Settings.Global.DEVICE_PROVISIONED, 0) == 0) {
  13. setTheme(android.R.style.Theme_Black_NoTitleBar_Fullscreen);
  14. }
  15. registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
  16. maybeFinish();
  17. }
  18. @Override
  19. protected void onDestroy() {
  20. super.onDestroy();
  21. unregisterReceiver(mReceiver);
  22. }
  23. private BroadcastReceiver mReceiver = new BroadcastReceiver() {
  24. @Override
  25. public void onReceive(Context context, Intent intent) {
  26. maybeFinish();
  27. }
  28. };
  29. private void maybeFinish() {
  30. if (getSystemService(UserManager.class).isUserUnlocked()) {
  31. final Intent homeIntent = new Intent(Intent.ACTION_MAIN)
  32. .addCategory(Intent.CATEGORY_HOME);
  33. final ResolveInfo homeInfo = getPackageManager().resolveActivity(homeIntent, 0);
  34. if (Objects.equals(getPackageName(), homeInfo.activityInfo.packageName)) {
  35. Log.d(TAG, "User unlocked but no home; let's hope someone enables one soon?");
  36. mHandler.sendEmptyMessageDelayed(0, 500);
  37. } else {
  38. Log.d(TAG, "User unlocked and real home found; let's go!");
  39. finish();
  40. }
  41. }
  42. }
  43. private Handler mHandler = new Handler() {
  44. @Override
  45. public void handleMessage(Message msg) {
  46. maybeFinish();
  47. }
  48. };
  49. }

发送ACTION_USER_UNLOCKED广播
在开机将近尾声时WindowManagerService会调用enableScreenIfNeededLocked函数来判断是否将Screen enable。通过Handler发送ENABLE_SCREEN消息到主线程

 

 
  1. void enableScreenIfNeededLocked() {
  2. if (mDisplayEnabled) {
  3. return;
  4. }
  5. if (!mSystemBooted && !mShowingBootMessages) {
  6. return;
  7. }
  8. mH.sendEmptyMessage(H.ENABLE_SCREEN);
  9. }

在mH的handleMessage中处理消息ENABLE_SCREEN,调用函数performEnableScreen来处理。

 

 
  1. final class H extends Handler {
  2. ........
  3. public static final int ENABLE_SCREEN = 16;
  4. ........
  5. @Override
  6. public void handleMessage(Message msg) {
  7. case ENABLE_SCREEN: {
  8. performEnableScreen();
  9. break;
  10. }
  11. ........
  12. }

在performEnableScreen函数中判断是否要enable Screen的两个主要因素有两个:
1.checkWaitingForWindowsLocked所有Windows是否绘制完成
2.checkBootAnimationCompleteLocked开机动画时候完成
如果都完成了会通知AMS开机动画完成了,并且要enable Screen了。

 

 
  1. public void performEnableScreen() {
  2. synchronized(mWindowMap) {
  3. if (mDisplayEnabled) { //如果设备已经enabled,返回
  4. return;
  5. }
  6. if (!mSystemBooted && !mShowingBootMessages) { //如果不是系统启动,并且没有启动信息,返回
  7. return;
  8. }
  9. // Don't enable the screen until all existing windows have been drawn.
  10. if (!mForceDisplayEnabled && checkWaitingForWindowsLocked()) { //如果不是强制设备enable,并且Windows还没有绘制完成,返回
  11. return;
  12. }
  13. ...........
  14. if (!mForceDisplayEnabled && !checkBootAnimationCompleteLocked()) { //如果不是强制设备enable,并且开机动画还没有结束,返回
  15. return;
  16. }
  17. EventLog.writeEvent(EventLogTags.WM_BOOT_ANIMATION_DONE, SystemClock.uptimeMillis());
  18. mDisplayEnabled = true;
  19. if (DEBUG_SCREEN_ON || DEBUG_BOOT) Slog.i(TAG_WM, "******************** ENABLING SCREEN!");
  20. // Enable input dispatch.
  21. mInputMonitor.setEventDispatchingLw(mEventDispatchingEnabled);
  22. }
  23. try {
  24. mActivityManager.bootAnimationComplete(); //通知ActivityManagerService开机动画完成
  25. } catch (RemoteException e) {
  26. }
  27. mPolicy.enableScreenAfterBoot(); //通知ActivityManagerService Screen可以enable
  28. // Make sure the last requested orientation has been applied.
  29. updateRotationUnchecked(false, false);
  30. }

检查Windows是否绘制完成主要是检查是否有启动message,是否有Wallpaper,Wallpaper是否可用,是否有Keyguard进行判断。

 
  1. private boolean checkWaitingForWindowsLocked() {
  2. boolean haveBootMsg = false; //是否有启动message
  3. boolean haveApp = false; //是否有APP
  4. // if the wallpaper service is disabled on the device, we're never going to have
  5. // wallpaper, don't bother waiting for it
  6. boolean haveWallpaper = false; //是否有Wallpaper
  7. boolean wallpaperEnabled = mContext.getResources().getBoolean(
  8. com.android.internal.R.bool.config_enableWallpaperService)
  9. && !mOnlyCore; //Wallpaper是否可用
  10. boolean haveKeyguard = true; //是否有Keyguard
  11. // TODO(multidisplay): Expand to all displays?
  12. final WindowList windows = getDefaultWindowListLocked(); //获取所有的Windows
  13. final int N = windows.size();
  14. for (int i=0; i<N; i++) {
  15. WindowState w = windows.get(i);
  16. if (w.isVisibleLw() && !w.mObscured && !w.isDrawnLw()) {
  17. return true;
  18. }
  19. if (w.isDrawnLw()) { 判断Window的属性
  20. if (w.mAttrs.type == TYPE_BOOT_PROGRESS) {
  21. haveBootMsg = true;
  22. } else if (w.mAttrs.type == TYPE_APPLICATION) {
  23. haveApp = true;
  24. } else if (w.mAttrs.type == TYPE_WALLPAPER) {
  25. haveWallpaper = true;
  26. } else if (w.mAttrs.type == TYPE_STATUS_BAR) {
  27. haveKeyguard = mPolicy.isKeyguardDrawnLw();
  28. }
  29. }
  30. }
  31. // If we are turning on the screen to show the boot message,
  32. // don't do it until the boot message is actually displayed.
  33. if (!mSystemBooted && !haveBootMsg) {
  34. return true;
  35. }
  36. // If we are turning on the screen after the boot is completed
  37. // normally, don't do so until we have the application and
  38. // wallpaper.
  39. if (mSystemBooted && ((!haveApp && !haveKeyguard) ||
  40. (wallpaperEnabled && !haveWallpaper))) {
  41. return true;
  42. }
  43. return false;
  44. }

检查开机动画是否完成,主要就是判断开机动画服务是否在运行,如果仍然在运行,就会发送一个200ms的延迟消息CHECK_IF_BOOT_ANIMATION_FINISHED,每200ms都再检查一次

 
  1. private boolean checkBootAnimationCompleteLocked() {
  2. if (SystemService.isRunning(BOOT_ANIMATION_SERVICE)) {
  3. mH.removeMessages(H.CHECK_IF_BOOT_ANIMATION_FINISHED);
  4. mH.sendEmptyMessageDelayed(H.CHECK_IF_BOOT_ANIMATION_FINISHED,
  5. BOOT_ANIMATION_POLL_INTERVAL);
  6. if (DEBUG_BOOT) Slog.i(TAG_WM, "checkBootAnimationComplete: Waiting for anim complete");
  7. return false;
  8. }
  9. if (DEBUG_BOOT) Slog.i(TAG_WM, "checkBootAnimationComplete: Animation complete!");
  10. return true;
  11. }

在处理CHECK_IF_BOOT_ANIMATION_FINISHED消息时,会再次判断开机动画是否完成,如果完成了就会调用performEnableScreen往下面执行,否则的还是每隔200ms发一次消息检查开机动画是否完成。

  1. case CHECK_IF_BOOT_ANIMATION_FINISHED: {
  2. final boolean bootAnimationComplete;
  3. synchronized (mWindowMap) {
  4. if (DEBUG_BOOT) Slog.i(TAG_WM, "CHECK_IF_BOOT_ANIMATION_FINISHED:");
  5. bootAnimationComplete = checkBootAnimationCompleteLocked();
  6. }
  7. if (bootAnimationComplete) {
  8. performEnableScreen();
  9. }
  10. }

当开机动画完成后就会调用AMS的bootAnimationComplete函数。

 
  1. @Override
  2. public void bootAnimationComplete() {
  3. final boolean callFinishBooting;
  4. synchronized (this) {
  5. callFinishBooting = mCallFinishBooting;
  6. mBootAnimationComplete = true; //设置mBootAnimationComplete为true
  7. }
  8. if (callFinishBooting) {
  9. Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "FinishBooting");
  10. finishBooting(); //调用finishBooting
  11. Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
  12. }
  13. }

在finishBooting中通过mUserController调用sendBootCompletedLocked函数

 
  1. final void finishBooting() {
  2. synchronized (this) {
  3. if (!mBootAnimationComplete) {
  4. mCallFinishBooting = true;
  5. return;
  6. }
  7. mCallFinishBooting = false;
  8. }
  9. ................
  10. // Let system services know.
  11. mSystemServiceManager.startBootPhase(SystemService.PHASE_BOOT_COMPLETED);
  12. ...............
  13. mUserController.sendBootCompletedLocked(
  14. new IIntentReceiver.Stub() {
  15. @Override
  16. public void performReceive(Intent intent, int resultCode,
  17. String data, Bundle extras, boolean ordered,
  18. boolean sticky, int sendingUser) {
  19. synchronized (ActivityManagerService.this) {
  20. requestPssAllProcsLocked(SystemClock.uptimeMillis(),
  21. true, false);
  22. }
  23. }
  24. });

UserController.java代码位置frameworks/base/services/core/java/com/android/server/am/
具体流程图如下:

经过一系列的代码跳转,最终调用UserController的finishUserUnlocked函数来发送ACTION_USER_UNLOCKED广播。

 

  1. void finishUserUnlocked(final UserState uss) {
  2. .................
  3. final Intent unlockedIntent = new Intent(Intent.ACTION_USER_UNLOCKED);
  4. unlockedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
  5. unlockedIntent.addFlags(
  6. Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
  7. mService.broadcastIntentLocked(null, null, unlockedIntent, null, null, 0, null,
  8. null, null, AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
  9. userId);
  10. .................
  11. }

当FallbackHome接收到ACTION_USER_UNLOCKED广播后,并且此时用户已经解锁,就会将将FallbackHome finish掉,启动launcher。
问题分析

就是因为现在启动Launcher时多了一个流程,导致启动launcher比原来6.0要慢。通过查看开机log可以看到从启动FallbackHome到启动google桌面花费了4s

 

  1. 18:10:50.653 769 1910 I ActivityManager: START u0 {act=android.intent.action.MAIN cat=[android.intent.category.HOME] flg=0x10000100 cmp=com.android.settings/.FallbackHome} from uid 0 on display 0
  2. 18:10:54.586 2029 2029 D FallbackHome: User unlocked and real home found; let's go!
  3. 18:10:54.615 769 2207 I ActivityManager: START u0 {act=android.intent.action.MAIN cat=[android.intent.category.HOME] flg=0x10000100 cmp=com.google.android.setupwizard/.SetupWizardActivity} from uid 0 on display 0

如果启动FallbackHome到启动launcher之间相隔的时间再长一点就可能发生开机过程中显示launcher时发生几秒的黑屏
小结

Android 7.0新增了DirectBoot功能,AOSP中为实现该功能修改了开机代码流程,并且这部分流程并未根据设备是否支持DirectBoot做区分,只是流程上做了兼容,确保不支持DirectBoot的设备在这套流程下也能正常开机。
在这套流程下,用户解锁后才可进入非directBootAware应用,包括Launcher。com.android.settings/.FallbackHome中判断用户解锁状态,已解锁才会Finish掉去启动Launcher,未解锁就等待ACTION_USER_UNLOCKED广播后再去启动Launcher。非DirectBoot模式下耗时4s就是在等待finishBooting后的系统广播ACTION_USER_UNLOCKED。
目前已从APP和PackageManagerService的角度尝试修改,在开机流程中绕过FallbackHome,但验证失败:
1)去除FallbackHome的android.intent.category.Home属性会导致停留在开机动画之后的界面。因为此时仍旧处于未解锁状态,且Launcher非directBootAware应用,PMS中的限制导致此时无法启动Launcher;
2)修改FallbackHome和Launcher的优先级仍旧先启动FallbackHome;
3)将Launcher标记为directBootAware应用会导致开机后Launcher crash。因为Launcher中的widget仍旧是非directBootAware的,此时仍旧无法启动,除非将widget相关的APP都标记为directBootAware;
4)PMS依赖手机当前的状态,需要user解锁才能正常查询。如果强制修改,不考虑DirectBoot和当前启动状态,即使当前user未解锁,依然可以查询符合条件的component,修改后会有无法开机的现象。因为Launcher不是directBootAware的,当前手机user尚未解锁,涉及存储相关的解锁也未进行。

开机绕过FallbackHome涉及的修改面很多,并非通过修改APP或PMS可以实现,还涉及存储区域解锁以及用户状态和ACTION_USER_UNLOCKED广播的修改,对AOSP开机流程改动较大,暂时尚未有较好的优化方案,欢迎大神指教。

=============================================

 1. 现在发现一个问题,关机闹钟的问题,设置闹钟后关机,等快到闹钟的时候自动开机了,就提示闹钟应用停止,然后一直处于Andriod 正在启动“”的状态,看log是因为在maybeFinisth()的时候一直找不到launcher,
     只有FallbackHome被匹配到了,目前还在跟踪,比较奇怪,手机根本没有设置任何锁屏密码。

     ss:明白了,vender下面的PowerOnAlert没有编译进系统。


     2.我现在采取的策略是让开机动画延迟3s结束,可以规避该问题。 
     具体实现在WindowManagerService.java的performEnableScreen函数中,延迟3s通知surfaceFlinger开机完成。 
     mH.postDelayed(new Runnable() { @Override public void run() { try { IBinder surfaceFlinger = ServiceManager.getService("SurfaceFlinger"); if (surfaceFlinger != null)
     { ...... surfaceFlinger.transact(IBinder.FIRST_CALL_TRANSACTION, // BOOT_FINISHED data, null, 0); data.recycle(); ......... } }, 3000); 
     并且在后面将下面代码注销,不让检查开机动画完成,继续向下执行。 
     /*if (!mForceDisplayEnabled && !checkBootAnimationCompleteLocked()) 
     { if (DEBUG_BOOT) Slog.i(TAG_WM, "performEnableScreen: Waiting for anim complete"); return }*/ 采用该方案尚未发现有什么副作用。

 

 

 

 

 

 

 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/273571
推荐阅读
相关标签
  

闽ICP备14008679号