Detailed explanation of DirectBoot blocking boot

Posted by JayNak on Thu, 24 Feb 2022 09:18:54 +0100

DirectBoot blocking boot

Introduction to DirectBoot

When the device is powered on but the user has not unlocked the device, Android 7.0 will run in a safe "direct start" mode. To support this mode, the system provides two storage locations for data:

  • (Credential encrypted storage) Credential encrypted storage, which is the default storage location and is only available after the user unlocks the device.
  • (Device encrypted storage) Device encrypted storage, which can be used in the "direct start" mode and after the user unlocks the device.

By default, applications will not run in Direct Boot mode. If you need to perform operations in this mode, you can register the application components that should run in this mode. Some common application cases include:

  • Apps that have scheduled notifications, such as alarm clock apps.
  • Applications that provide important user notifications, such as SMS applications.
  • Applications that provide barrier free services, such as Talkback.

The application component applies to run in Direct Boot mode: mark the component as encryption aware to register with the system, that is, ` ` android:directBootAware=true 'in the list.

When the device is restarted, the encryption aware component can register to receive the action from the system_ LOCKED_ BOOT_ Completed broadcast message. At this time, the device encrypted storage is available, and the component can perform tasks that need to be run in "Direct Boot" mode, such as triggering the set alarm. Like this:

<receiver
    android:directBootAware="true" >
        ...
    <intent-filter>
        <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
    </intent-filter>
</receiver>

After the user unlocks the device, all components can access Device encrypted storage and Credential encrypted storage.

Application accesses Device encrypted storage by calling Context Createdeviceprotectedstoragecontext() creates another Context instance. All storage API calls issued through this Context access Device encrypted storage. The following example will access the Device encrypted storage and open the existing application data file:

Context directBootContext = appContext.createDeviceProtectedStorageContext();
// Access appDataFilename that lives in device encrypted storage
FileInputStream inStream = directBootContext.openFileInput(appDataFilename);
// Use inStream to read content...

App get unlock notification:

  • If the application has a foreground process that needs to be notified immediately, listen to ACTION_USER_UNLOCKED message.
  • If the application only uses background processes that can perform operations on deferred notifications, listen for ACTION_BOOT_COMPLETED message.

You can also call usermanager Isuserunlocked() directly queries whether the user has unlocked the device.

Apply migration to existing data:

Use context Movesharedppreferencesfrom() and context Movedatabasefrom() can migrate preferences and database data between credential encrypted storage and device encrypted storage. Check the official documents for specific usage.

Start FallbackHome

As mentioned in the previous article, Android 7.0 will start FallbackHome before starting the Launcher. After consulting the data, it is found that FallbackHome belongs to an activity in Settings, android:directBootAware of Settings = true, and FallbackHome has the Home attribute configured in the category, while android:directBootAware of Launcher is false, Therefore, only FallbackHome can be started in direct boot mode.

<application android:label="@string/settings_label"
        android:icon="@mipmap/ic_launcher_settings"
        ............
        android:directBootAware="true">
 
        <!-- Triggered when user-selected home app isn't encryption aware -->
        <activity android:name=".FallbackHome"
                  android:excludeFromRecents="true"
                  android:theme="@style/FallbackHome">
            <intent-filter android:priority="-1000">
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.HOME" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
    	..........
</application>

Therefore, when the ActivityManagerService starts the Home interface, the Home interface obtained from PackageManagerService is FallbackHome. It can be seen that the priority of FallbackHome is - 1000, which is handled in PackageManagerService later.

During Android startup, various system services will be pulled up and its systemReady() function will be called. After the most critical ActivityManagerService is pulled up, a function startHomeActivityLocked() is called in systemReady ().

public void systemReady(final Runnable goingCallback, TimingsTraceLog traceLog) {
...
    startHomeActivityLocked(currentUserId, "systemReady");
...
}

Here is the content of this function:

boolean startHomeActivityLocked(int userId, String reason) {
    if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL
        && mTopAction == null) {
        // We are running in factory test mode, but unable to find
        // the factory test app, so just sit around displaying the
        // error message and don't try to start anything.
        return false;
    }
    //Look here, look here, look at the important place here, say it three times!!!
    Intent intent = getHomeIntent();
    //Get Home activity information
    ActivityInfo aInfo = resolveActivityInfo(intent, STOCK_PM_FLAGS, userId);
    if (aInfo != null) {
        intent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name));
        // Don't do this if the home app is currently being
        // instrumented.
        aInfo = new ActivityInfo(aInfo);
        aInfo.applicationInfo = getAppInfoForUser(aInfo.applicationInfo, userId);
        ProcessRecord app = getProcessRecordLocked(aInfo.processName,
                                                   aInfo.applicationInfo.uid, true);
        if (app == null || app.instr == null) {
            intent.setFlags(intent.getFlags() | FLAG_ACTIVITY_NEW_TASK);
            final int resolvedUserId = UserHandle.getUserId(aInfo.applicationInfo.uid);
            // For ANR debugging to verify if the user activity is the one that actually
            // launched.
            final String myReason = reason + ":" + userId + ":" + resolvedUserId;
            //Start FallbackHome
            mActivityStartController.startHomeActivity(intent, aInfo, myReason);
        }
    } else {
        Slog.wtf(TAG, "No home screen found for " + intent, new Throwable());
    }

    return true;
}

You can see that you need to construct an Intent, which is obtained through the getHomeIntent() method. The following is the content of this method:

Intent getHomeIntent() {
        Intent intent = new Intent(mTopAction, mTopData != null ? Uri.parse(mTopData) : null);
        intent.setComponent(mTopComponent);
        intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
        if (mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
            intent.addCategory(Intent.CATEGORY_HOME);
        }
        return intent;
    }

Here will be constructed with a category_ The intent of home, which is used to query the corresponding ActivityInfo from PackageManagerService.

Then, the FallbackHome will be started, and the action will be registered when the FallbackHome is created_ USER_ Unlock broadcast, and then judge whether all users have been unlocked. If not, end the execution. After that, it will wait to receive the action_ USER_ The unlocked broadcast continues to judge whether the user has been unlocked. If it has been unlocked at this time, find the Home interface. If it has not been found, send a delay message of 500ms and find it again. If it finds the Launcher, it will finish the FallbackHome.

Here is fallbackhome Java code (packages/apps/Settings/src/com/android/settings/FallbackHome.java)

private void maybeFinish() {
    if (getSystemService(UserManager.class).isUserUnlocked()) {
        final Intent homeIntent = new Intent(Intent.ACTION_MAIN)
            .addCategory(Intent.CATEGORY_HOME);
        final ResolveInfo homeInfo = getPackageManager().resolveActivity(homeIntent, 0);
        if (Objects.equals(getPackageName(), homeInfo.activityInfo.packageName)) {
            if (UserManager.isSplitSystemUser()
                && UserHandle.myUserId() == UserHandle.USER_SYSTEM) {
                // This avoids the situation where the system user has no home activity after
                // SUW and this activity continues to throw out warnings. See b/28870689.
                return;
            }
            Log.d(TAG, "User unlocked but no home; let's hope someone enables one soon?");
            mHandler.sendEmptyMessageDelayed(0, 500);
        } else {
            Log.d(TAG, "User unlocked and real home found; let's go!");
            getSystemService(PowerManager.class).userActivity(
                SystemClock.uptimeMillis(), false);
            finish();
        }
    }
}

At the end of startup, WindowManagerService will call its enablesifneededlocked() function to determine whether to enable screen. Send enable via Handler_ Screen message to main thread

void enableScreenIfNeededLocked() {
    if (mDisplayEnabled) {
        return;
    }
    if (!mSystemBooted && !mShowingBootMessages) {
        return;
    }

    mH.sendEmptyMessage(H.ENABLE_SCREEN);
}

Process message enable in handleMessage of mH_ Screen, call the function performEnableScreen to process. This function is the internal class H of WindowManagerService. The code is as follows

final class H extends Handler {
    ........
    public static final int ENABLE_SCREEN = 16;
    ........
    @Override
    public void handleMessage(Message msg) {

    case ENABLE_SCREEN: {
        performEnableScreen();
        break;
    }
        ........
}

In the performEnableScreen function, there are two main factors to judge whether to enable Screen:

  1. checkWaitingForWindowsLocked all Windows are drawn
  2. checkBootAnimationCompleteLocked when the boot animation is completed

If all are completed, AMS will be notified that the startup animation is completed and that it is time to enable Screen.

Some performEnableScreen method codes are as follows:

public void performEnableScreen() {
    synchronized(mWindowMap) {
        if (mDisplayEnabled) {   //If the device has been enabled, return
            return;
        }
        if (!mSystemBooted && !mShowingBootMessages) {   //If the system is not started and there is no startup information, return
            return;
        }

        // Don't enable the screen until all existing windows have been drawn.
        if (!mForceDisplayEnabled && checkWaitingForWindowsLocked()) {    //If the device is not forced to enable and Windows has not finished drawing, return
            return;
        }

        ...........

            if (!mForceDisplayEnabled && !checkBootAnimationCompleteLocked()) {    //If the device is not forced to enable and the startup animation has not ended, return to
                return;
            }

        EventLog.writeEvent(EventLogTags.WM_BOOT_ANIMATION_DONE, SystemClock.uptimeMillis());
        mDisplayEnabled = true;
        if (DEBUG_SCREEN_ON || DEBUG_BOOT) Slog.i(TAG_WM, "******************** ENABLING SCREEN!");

        // Enable input dispatch.
        mInputMonitor.setEventDispatchingLw(mEventDispatchingEnabled);
    }

    try {
        mActivityManager.bootAnimationComplete();   //Notify ActivityManagerService that the startup animation is complete
    } catch (RemoteException e) {
    }

    mPolicy.enableScreenAfterBoot();    //Notify activitymanagerservice that the screen can be enable d

    // Make sure the last requested orientation has been applied.
    updateRotationUnchecked(false, false);
}

In the above method, check whether Windows drawing is completed mainly to check whether there is a startup message, whether there is a Wallpaper, whether the Wallpaper is available, and whether there is a Keyguard. That is, a series of judgments are made in the method checkWaitingForWindowsLocked. The method code is as follows:

 private boolean checkWaitingForWindowsLocked() {

     boolean haveBootMsg = false;    //Is there a start message
     boolean haveApp = false;       //Is there an APP
     // if the wallpaper service is disabled on the device, we're never going to have
     // wallpaper, don't bother waiting for it
     boolean haveWallpaper = false;    //Is there a Wallpaper
     boolean wallpaperEnabled = mContext.getResources().getBoolean(
         com.android.internal.R.bool.config_enableWallpaperService)
         && !mOnlyCore;   //Is Wallpaper available
     boolean haveKeyguard = true;   //Is there a Keyguard
     // TODO(multidisplay): Expand to all displays?
     final WindowList windows = getDefaultWindowListLocked();   //Get all Windows
     final int N = windows.size();
     for (int i=0; i<N; i++) {
         WindowState w = windows.get(i);
         if (w.isVisibleLw() && !w.mObscured && !w.isDrawnLw()) {
             return true;
         }
         if (w.isDrawnLw()) {   judge Window Properties of
             if (w.mAttrs.type == TYPE_BOOT_PROGRESS) {
                 haveBootMsg = true;
             } else if (w.mAttrs.type == TYPE_APPLICATION) {
                 haveApp = true;
             } else if (w.mAttrs.type == TYPE_WALLPAPER) {
                 haveWallpaper = true;
             } else if (w.mAttrs.type == TYPE_STATUS_BAR) {
                 haveKeyguard = mPolicy.isKeyguardDrawnLw();
             }
         }
     }

     // If we are turning on the screen to show the boot message,
     // don't do it until the boot message is actually displayed.
     if (!mSystemBooted && !haveBootMsg) {
         return true;
     }

     // If we are turning on the screen after the boot is completed
     // normally, don't do so until we have the application and
     // wallpaper.
     if (mSystemBooted && ((!haveApp && !haveKeyguard) ||
                           (wallpaperEnabled && !haveWallpaper))) {
         return true;
     }

     return false;
 }

After checking whether the Windows drawing is completed, you should also check whether the startup animation is completed, mainly to judge whether the startup animation service is running. If it is still running, a 200ms delay message check will be sent_ IF_ BOOT_ ANIMATION_ Finished, check again every 200ms. The method is checkBootAnimationCompleteLocked. Here is its code:

private boolean checkBootAnimationCompleteLocked() {
    if (SystemService.isRunning(BOOT_ANIMATION_SERVICE)) {
        mH.removeMessages(H.CHECK_IF_BOOT_ANIMATION_FINISHED);
        mH.sendEmptyMessageDelayed(H.CHECK_IF_BOOT_ANIMATION_FINISHED,
                                   BOOT_ANIMATION_POLL_INTERVAL);
        if (DEBUG_BOOT) Slog.i(TAG_WM, "checkBootAnimationComplete: Waiting for anim complete");
        return false;
    }
    if (DEBUG_BOOT) Slog.i(TAG_WM, "checkBootAnimationComplete: Animation complete!");
    return true;
}

Processing check_ IF_ BOOT_ ANIMATION_ When the finished message is sent, it will judge whether the startup animation is completed again. If it is completed, it will call performEnableScreen to execute below. Otherwise, it will send a message every 200ms to check whether the startup animation is completed.

case CHECK_IF_BOOT_ANIMATION_FINISHED: {
    final boolean bootAnimationComplete;
    synchronized (mWindowMap) {
        if (DEBUG_BOOT) Slog.i(TAG_WM, "CHECK_IF_BOOT_ANIMATION_FINISHED:");
        bootAnimationComplete = checkBootAnimationCompleteLocked();
    }
    if (bootAnimationComplete) {
        performEnableScreen();
    }
}

When the boot animation is completed, the bootAnimationComplete function of AMS will be called. The method code is as follows:

@Override
public void bootAnimationComplete() {
    final boolean callFinishBooting;
    synchronized (this) {
        callFinishBooting = mCallFinishBooting;
        mBootAnimationComplete = true;   //Set mBootAnimationComplete to true
    }
    if (callFinishBooting) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "FinishBooting");
        finishBooting();     //Call finishBooting here
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    }
}

In finishBooting, call sendBootCompletedLocked function through mUserController

final void finishBooting() {
    synchronized (this) {
        if (!mBootAnimationComplete) {
            mCallFinishBooting = true;
            return;
        }
        mCallFinishBooting = false;
    }
    ................
    // Let system services know.
    mSystemServiceManager.startBootPhase(SystemService.PHASE_BOOT_COMPLETED);
    ...............
    mUserController.sendBootCompletedLocked(
        new IIntentReceiver.Stub() {
            @Override
            public void performReceive(Intent intent, int resultCode,String data, Bundle extras, boolean ordered,boolean sticky, 										int sendingUser) {
                synchronized (ActivityManagerService.this) {
                    requestPssAllProcsLocked(SystemClock.uptimeMillis(),true, false);
                }
            }
    });

UserController.java code location: frameworks/base/services/core/java/com/android/server/am/
The specific flow chart is as follows:

After a series of code jumps, finally call the finishUserUnlocked function of UserController to send ACTION_USER_UNLOCKED broadcast.

void finishUserUnlocked(final UserState uss) {
    .................
    final Intent unlockedIntent = new Intent(Intent.ACTION_USER_UNLOCKED);
    unlockedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
    unlockedIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
    mService.broadcastIntentLocked(null, null, unlockedIntent, null, null, 0, null,
                                   null, null, AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
                                   userId);
    .................
}

When FallbackHome receives action_ USER_ After the unlocked broadcast and the user has unlocked it, the FallbackHomefinish will be turned off and the launcher will be started.

Here, if there is no accident, you will see the tabletop of your mind!!!

Topics: Android