平台
RK3566 + Android 11
概述
替换默认主界面, 更换为指定第三方Launcher后, 点击导航栏的RECENT键无效. 究其原因在于, 在旧版本SDK上, 删除Launcher3并不会影响RECENT的功能 , 而在新的SDK上, RECENT功能集成于Launcher3目录下, 删除 Launcher3后, 导致SystemUI调用对应的RECENT界面启动的服务失败.
过程疏理
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); mNavigationBarView = (NavigationBarView) view; final Display display = view.getDisplay(); // It may not have display when running unit test. if (display != null) { mDisplayId = display.getDisplayId(); mIsOnDefaultDisplay = mDisplayId == Display.DEFAULT_DISPLAY; } mNavigationBarView.setComponents(mStatusBarLazy.get().getPanelController()); mNavigationBarView.setDisabledFlags(mDisabledFlags1); mNavigationBarView.setOnVerticalChangedListener(this::onVerticalChanged); mNavigationBarView.setOnTouchListener(this::onNavigationTouch); if (savedInstanceState != null) { mNavigationBarView.getLightTransitionsController().restoreState(savedInstanceState); } mNavigationBarView.setNavigationIconHints(mNavigationIconHints); mNavigationBarView.setWindowVisible(isNavBarWindowVisible()); prepareNavigationBarView(); } private void prepareNavigationBarView() { mNavigationBarView.reorient(); ButtonDispatcher recentsButton = mNavigationBarView.getRecentsButton(); recentsButton.setOnClickListener(this::onRecentsClick); recentsButton.setOnTouchListener(this::onRecentsTouch); recentsButton.setLongClickable(true); recentsButton.setOnLongClickListener(this::onLongPressBackRecents); } private void onRecentsClick(View v) { if (LatencyTracker.isEnabled(getContext())) { LatencyTracker.getInstance(getContext()).onActionStart( LatencyTracker.ACTION_TOGGLE_RECENTS); } mStatusBarLazy.get().awakenDreams(); mCommandQueue.toggleRecentApps(); }
frameworks/base/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@Override public void toggleRecentApps() { // Ensure the device has been provisioned before allowing the user to interact with // recents if (!isUserSetup()) { return; } mImpl.toggleRecentApps(); }
frameworks/base/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
@Override public void toggleRecentApps() { Log.d(TAG, "toggleRecentApps"); // If connected to launcher service, let it handle the toggle logic IOverviewProxy overviewProxy = mOverviewProxyService.getProxy(); if (overviewProxy != null) { final Runnable toggleRecents = () -> { try { if (mOverviewProxyService.getProxy() != null) { mOverviewProxyService.getProxy().onOverviewToggle(); mOverviewProxyService.notifyToggleRecentApps(); } } catch (RemoteException e) { Log.e(TAG, "Cannot send toggle recents through proxy service.", e); } }; // Preload only if device for current user is unlocked if (mStatusBarLazy != null && mStatusBarLazy.get().isKeyguardShowing()) { mStatusBarLazy.get().executeRunnableDismissingKeyguard(() -> { // Flush trustmanager before checking device locked per user mTrustManager.reportKeyguardShowingChanged(); mHandler.post(toggleRecents); }, null, true /* dismissShade */, false /* afterKeyguardGone */, true /* deferred */); } else { toggleRecents.run(); } return; } else { // Do nothing } }
重点看下overviewProxy
frameworks/base/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
private static final String ACTION_QUICKSTEP = "android.intent.action.QUICKSTEP_SERVICE"; @SuppressWarnings("OptionalUsedAsFieldOrParameterType") @Inject public OverviewProxyService(Context context, CommandQueue commandQueue, NavigationBarController navBarController, NavigationModeController navModeController, NotificationShadeWindowController statusBarWinController, SysUiState sysUiState, PipUI pipUI, Optional<Divider> dividerOptional, Optional<Lazy<StatusBar>> statusBarOptionalLazy, BroadcastDispatcher broadcastDispatcher) { super(broadcastDispatcher); mContext = context; mPipUI = pipUI; mStatusBarOptionalLazy = statusBarOptionalLazy; mHandler = new Handler(); mNavBarController = navBarController; mStatusBarWinController = statusBarWinController; mConnectionBackoffAttempts = 0; mDividerOptional = dividerOptional; //frameworks/base/core/res/res/values/config.xml // <string name="config_recentsComponentName" translatable="false">com.android.launcher3/com.android.quickstep.RecentsActivity</string> mRecentsComponentName = ComponentName.unflattenFromString(context.getString( com.android.internal.R.string.config_recentsComponentName)); mQuickStepIntent = new Intent(ACTION_QUICKSTEP) } //绑定服务 private void internalConnectToCurrentUser() { //.............. Intent launcherServiceIntent = new Intent(ACTION_QUICKSTEP) .setPackage(mRecentsComponentName.getPackageName()); try { mBound = mContext.bindServiceAsUser(launcherServiceIntent, mOverviewServiceConnection, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE, UserHandle.of(getCurrentUserId())); } catch (SecurityException e) { Log.e(TAG_OPS, "Unable to bind because of security error", e); } //..... } //绑定成功 private final ServiceConnection mOverviewServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { //......... mOverviewProxy = IOverviewProxy.Stub.asInterface(service); } //由上面OverviewProxyRecentsImpl 调用 public IOverviewProxy getProxy() { return mOverviewProxy; }
近期任务UI的实现有别与旧版本的SDK, 有frameworks/base/packages/SystemUI改到了packages/apps/Launcher3/quickstep
packages/apps/Launcher3/quickstep/AndroidManifest.xml <service android:name="com.android.quickstep.TouchInteractionService" android:permission="android.permission.STATUS_BAR_SERVICE" android:directBootAware="true" > <intent-filter> <action android:name="android.intent.action.QUICKSTEP_SERVICE" /> </intent-filter> </service>
为了使用默认第三方Launcher, 需要注释掉quickstep自带的Launcher
packages/apps/Launcher3/quickstep/AndroidManifest-launcher.xml
<activity android:name="com.android.launcher3.uioverrides.QuickstepLauncher" android:launchMode="singleTask" android:clearTaskOnLaunch="true" android:stateNotNeeded="true" android:windowSoftInputMode="adjustPan" android:screenOrientation="unspecified" android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize" android:resizeableActivity="true" android:resumeWhilePausing="true" android:taskAffinity="" android:enabled="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <!--category android:name="android.intent.category.HOME" /--> <category android:name="android.intent.category.DEFAULT" /> <!--category android:name="android.intent.category.MONKEY"/--> <category android:name="android.intent.category.LAUNCHER_APP" /> </intent-filter> <meta-data android:name="com.android.launcher3.grid.control" android:value="${packageName}.grid_control" /> </activity>
开机奔溃
2022-07-10 04:01:50.179 1472-1472/com.android.launcher3 E/AndroidRuntime: FATAL EXCEPTION: main Process: com.android.launcher3, PID: 1472 java.lang.RuntimeException: Unable to create service com.android.quickstep.TouchInteractionService: java.lang.NullPointerException: Attempt to read from field 'android.content.pm.ActivityInfo android.content.pm.ResolveInfo.activityInfo' on a null object reference at android.app.ActivityThread.handleCreateService(ActivityThread.java:4198) at android.app.ActivityThread.access$1500(ActivityThread.java:237) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1932) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loop(Looper.java:223) at android.app.ActivityThread.main(ActivityThread.java:7664) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947) Caused by: java.lang.NullPointerException: Attempt to read from field 'android.content.pm.ActivityInfo android.content.pm.ResolveInfo.activityInfo' on a null object reference at com.android.quickstep.OverviewComponentObserver.<init>(OverviewComponentObserver.java:82) at com.android.quickstep.TouchInteractionService.onUserUnlocked(TouchInteractionService.java:345) at com.android.quickstep.-$$Lambda$6M6xH0rMyGjroOocJ2F5KYabvuw.run(Unknown Source:2) at com.android.quickstep.RecentsAnimationDeviceState.runOnUserUnlocked(RecentsAnimationDeviceState.java:272) at com.android.quickstep.TouchInteractionService.onCreate(TouchInteractionService.java:301) at android.app.ActivityThread.handleCreateService(ActivityThread.java:4186) at android.app.ActivityThread.access$1500(ActivityThread.java:237) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1932) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loop(Looper.java:223) at android.app.ActivityThread.main(ActivityThread.java:7664) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
packages/apps/Launcher3/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
public OverviewComponentObserver(Context context, RecentsAnimationDeviceState deviceState) { mContext = context; mDeviceState = deviceState; mCurrentHomeIntent = new Intent(Intent.ACTION_MAIN) .addCategory(Intent.CATEGORY_HOME) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); //异常位置: 由于前面注释掉了自带Launcher导致, 将mContext.getPackageName 替换成 三方Launcher的包名可解决. mMyHomeIntent = new Intent(mCurrentHomeIntent).setPackage(/*mContext.getPackageName()*/"com.android.launcherX"); ResolveInfo info = context.getPackageManager().resolveActivity(mMyHomeIntent, 0); ComponentName myHomeComponent = new ComponentName(/*mContext.getPackageName()*/"com.android.launcherX", info.activityInfo.name); mMyHomeIntent.setComponent(myHomeComponent); mConfigChangesMap.append(myHomeComponent.hashCode(), info.activityInfo.configChanges); ComponentName fallbackComponent = new ComponentName(mContext, RecentsActivity.class); mFallbackIntent = new Intent(Intent.ACTION_MAIN) .addCategory(Intent.CATEGORY_DEFAULT) .setComponent(fallbackComponent) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); try { ActivityInfo fallbackInfo = context.getPackageManager().getActivityInfo( mFallbackIntent.getComponent(), 0 /* flags */); mConfigChangesMap.append(fallbackComponent.hashCode(), fallbackInfo.configChanges); } catch (PackageManager.NameNotFoundException ignored) { /* Impossible */ } mContext.registerReceiver(mUserPreferenceChangeReceiver, new IntentFilter(ACTION_PREFERRED_ACTIVITY_CHANGED)); updateOverviewTargets(); }
解决奔溃问题后, 点击recent键返回了HOME, 而不是显示RECENT界面:
packages/apps/Launcher3/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
/** * Update overview intent and {@link BaseActivityInterface} based off the current launcher home * component. */ private void updateOverviewTargets() { ComponentName defaultHome = PackageManagerWrapper.getInstance() .getHomeActivities(new ArrayList<>()); mIsHomeDisabled = mDeviceState.isHomeDisabled(); mIsDefaultHome = Objects.equals(mMyHomeIntent.getComponent(), defaultHome); // Set assistant visibility to 0 from launcher's perspective, ensures any elements that // launcher made invisible become visible again before the new activity control helper // becomes active. if (mActivityInterface != null) { mActivityInterface.onAssistantVisibilityChanged(0.f); } if (SEPARATE_RECENTS_ACTIVITY.get()) { mIsDefaultHome = false; if (defaultHome == null) { defaultHome = mMyHomeIntent.getComponent(); } } //不走if分支, if (false && !mDeviceState.isHomeDisabled() && (defaultHome == null || mIsDefaultHome)) { // User default home is same as out home app. Use Overview integrated in Launcher. mActivityInterface = LauncherActivityInterface.INSTANCE; mIsHomeAndOverviewSame = true; mOverviewIntent = mMyHomeIntent; mCurrentHomeIntent.setComponent(mMyHomeIntent.getComponent()); // Remove any update listener as we don't care about other packages. unregisterOtherHomeAppUpdateReceiver(); } else { // The default home app is a different launcher. Use the fallback Overview instead. mActivityInterface = FallbackActivityInterface.INSTANCE; mIsHomeAndOverviewSame = false; mOverviewIntent = mFallbackIntent; mCurrentHomeIntent.setComponent(defaultHome); // User's default home app can change as a result of package updates of this app (such // as uninstalling the app or removing the "Launcher" feature in an update). // Listen for package updates of this app (and remove any previously attached // package listener). if (defaultHome == null) { unregisterOtherHomeAppUpdateReceiver(); } else if (!defaultHome.getPackageName().equals(mUpdateRegisteredPackage)) { unregisterOtherHomeAppUpdateReceiver(); mUpdateRegisteredPackage = defaultHome.getPackageName(); mContext.registerReceiver(mOtherHomeAppUpdateReceiver, getPackageFilter( mUpdateRegisteredPackage, ACTION_PACKAGE_ADDED, ACTION_PACKAGE_CHANGED, ACTION_PACKAGE_REMOVED)); } } mOverviewChangeListener.accept(mIsHomeAndOverviewSame); }
关键在于使mOverviewIntent = mFallbackIntent;
由SystemUI调用onOverviewToggle:
packages/apps/Launcher3/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@BinderThread @Override public void onOverviewToggle() { TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onOverviewToggle"); mOverviewCommandHelper.onOverviewToggle(); }
packages/apps/Launcher3/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
@BinderThread public void onOverviewToggle() { // If currently screen pinning, do not enter overview if (mDeviceState.isScreenPinningActive()) { return; } ActivityManagerWrapper.getInstance() .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); MAIN_EXECUTOR.execute(new RecentsActivityCommand<>()); }
MAIN_EXECUTOR 和 LooperExecutor
RecentsActivityCommand
private class RecentsActivityCommand<T extends StatefulActivity<?>> implements Runnable { protected final BaseActivityInterface<?, T> mActivityInterface; //..... private ActivityInitListener mListener; public RecentsActivityCommand() { mActivityInterface = mOverviewComponentObserver.getActivityInterface(); //.... } @Override public void run() { //... // Otherwise, start overview. mListener = mActivityInterface.createActivityInitListener(this::onActivityReady); mListener.registerAndStartActivity(mOverviewComponentObserver.getOverviewIntent(), new RemoteAnimationProvider() { @Override public AnimatorSet createWindowAnimation( RemoteAnimationTargetCompat[] appTargets, RemoteAnimationTargetCompat[] wallpaperTargets) { return RecentsActivityCommand.this.createWindowAnimation(appTargets, wallpaperTargets); } }, mContext, MAIN_EXECUTOR.getHandler(), mAnimationProvider.getRecentsLaunchDuration()); }
packages/apps/Launcher3/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
/** * Get the current activity control helper for managing interactions to the overview activity. * * @return the current activity control helper */ public BaseActivityInterface getActivityInterface() { return mActivityInterface; }
OverviewComponentObserver.mActivityInterface = FallbackActivityInterface.INSTANCE;
packages/apps/Launcher3/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
public final class FallbackActivityInterface extends BaseActivityInterface<RecentsState, RecentsActivity> { public static final FallbackActivityInterface INSTANCE = new FallbackActivityInterface(); @Override public ActivityInitListener createActivityInitListener( Predicate<Boolean> onInitListener) { return new ActivityInitListener<>((activity, alreadyOnHome) -> onInitListener.test(alreadyOnHome), RecentsActivity.ACTIVITY_TRACKER); }
packages/apps/Launcher3/quickstep/src/com/android/quickstep/util/ActivityInitListener.java
/** * Starts the given intent with the provided animation. Unlike {@link #register(Intent)}, this * method will not call {@link #init} if the activity already exists, it will only call it when * we get handleIntent() for the provided intent that we're starting. */ public void registerAndStartActivity(Intent intent, RemoteAnimationProvider animProvider, Context context, Handler handler, long duration) { mIsRegistered = true; Bundle options = animProvider.toActivityOptions(handler, duration, context).toBundle(); context.startActivity(addToIntent(new Intent(intent)), options); }
最终context.startActivity 其中的Intent是:mOverviewComponentObserver.getOverviewIntent()
packages/apps/Launcher3/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
/** * Get the current intent for going to the overview activity. * * @return the overview intent */ public Intent getOverviewIntent() { /*参见前面的代码: ComponentName fallbackComponent = new ComponentName(mContext, RecentsActivity.class); mFallbackIntent = new Intent(Intent.ACTION_MAIN) .addCategory(Intent.CATEGORY_DEFAULT) .setComponent(fallbackComponent) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); */ return mOverviewIntent; }
至此RecentsActivity启动完成.
模块编译:
mmm packages/apps/Launcher3/:Launcher3QuickStep -j4
需要注意的是
1.原生的Launcher3需要注释掉, 或让它不参与编译.
2.近期任务UI的存放目录是在Lauuncher3目录下, 而需要参与编译的模块只有Launcher3QuickStep
3.第三方Launcher不能使用com.android.launcher3这个包名, 与上面的模块冲突
参考
SystemUI之QuickStep探索(常规启动篇)
Android11 最近任务Recents功能分析