我们知道Car Model 属于 No Launcher 模式,但真的是没有 Launcher应用吗?我们一步一步分析:
Car Module Navigation Bar
packages/services/Car/car_product/overlay/frameworks/base/packages/SystemUI/res/values/arrays_car.xml
<resources>
<!-- There needs to be correspondence per index between these arrays, which means that if there
isn't a longpress action associated with a shortcut item, put in an empty item to make
sure everything lines up.
-->
<array name="car_facet_icons">
<item>@drawable/car_ic_navigation</item>
<item>@drawable/car_ic_phone</item>
<item>@drawable/car_ic_overview</item>
<item>@drawable/car_ic_music</item>
<item>@drawable/car_ic_car</item>
</array>
<array name="car_facet_intent_uris">
<!-- Launch the lenspicker for all the facets. The lens picker will trampoline into the last run app or display a list of valid apps -->
<item>intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x14000000;package=com.android.support.car.lenspicker;end</item>
<item>intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x14000000;package=com.android.support.car.lenspicker;end</item>
<item>intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x14000000;package=com.android.support.car.lenspicker;S.system_command=toggle_notifications;end</item>
<item>intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x14000000;package=com.android.support.car.lenspicker;end</item>
<item>intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x14000000;package=com.android.support.car.lenspicker;end</item>
</array>
<array name="car_facet_longpress_intent_uris">
<item></item>
<item></item>
<item></item>
<item></item>
<!-- Long pressing the overflow triggers a bug report -->
<item>intent:#Intent;component=com.google.android.car.bugreport/.BugReportActivity;end</item>
</array>
<array name="car_facet_category_filters">
<item>android.intent.category.APP_MAPS</item>
<item>android.intent.category.APP_MESSAGING</item>
<item></item>
<item>android.intent.category.APP_MUSIC</item>
<item></item>
</array>
<array name="car_facet_package_filters">
<item>com.android.car.mapsplaceholder</item>
<item>com.android.car.dialer</item>
<item>com.android.car.overview</item>
<item></item>
<item>com.android.car.hvac;com.android.settings;com.android.car.settings;com.android.vending;com.google.android.car.bugreport;com.google.android.car.kitchensink;com.android.car.systemupdater;org.chromium.webview_shell;com.android.contacts;org.codeaurora.bluetooth.bttestapp;com.google.android.projection.sink</item>
</array>
</resources>
我们通过这个配置文件可以看出这个文件配置了Car Module 中NavigationBar 上的按钮个数,以及按钮点击之后所携带的intent参数,Uri,Category, Package Filters。
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java
/**
* Handles a click on a facet. A click will trigger the given Intent.
*
* @param index The index of the facet that was clicked.
*/
private void onFacetClicked(Intent intent, int index) {
String packageName = intent.getPackage();
if (packageName == null) {
return;
}
intent.putExtra(EXTRA_FACET_CATEGORIES, mFacetCategories.get(index));
intent.putExtra(EXTRA_FACET_PACKAGES, mFacetPackages.get(index));
// The facet is identified by the index in which it was added to the nav bar.
// This value can be used to determine which facet was selected
intent.putExtra(EXTRA_FACET_ID, Integer.toString(index));
// If the current facet is clicked, we want to launch the picker by default
// rather than the "preferred/last run" app.
intent.putExtra(EXTRA_FACET_LAUNCH_PICKER, index == mCurrentFacetIndex);
int stackId = StackId.FULLSCREEN_WORKSPACE_STACK_ID;
if (intent.getCategories().contains(Intent.CATEGORY_HOME)) {
stackId = StackId.HOME_STACK_ID;
}
setCurrentFacet(index);
mStatusBar.startActivityOnStack(intent, stackId);
}
可以发现NavigationBar 启动Map, dialer, 等等其他应用,都是先去启动包名为com.android.support.car.lenspicker这个应用。
packages/apps/Car/LensPicker/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.support.car.lenspicker" >
<uses-sdk
android:minSdkVersion="22"
android:targetSdkVersion='23'/>
<!-- Permission to allow the LensPicker to set a default activity for an Intent. -->
<uses-permission android:name="android.permission.SET_PREFERRED_APPLICATIONS" />
<uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />
<application
android:label="@string/app_label" >
<activity android:name=".LensPickerActivity"
android:theme="@style/Theme.FloatingLensPicker"
android:label="LensPickerActivity"
android:resizeableActivity="true"
android:excludeFromRecents="true"
android:launchMode="singleTask"
android:noHistory="true" />
<activity android:name=".LensResolverActivity"
android:theme="@style/Theme.FloatingLensPicker"
android:label="LensResolverActivity"
android:resizeableActivity="true"
android:excludeFromRecents="true"
android:launchMode="singleTask"
android:noHistory="true" />
<activity android:name=".LensPickerTrampolineActivity"
android:theme="@style/Theme.FloatingLensPicker"
android:label="LensPickerTrampolineActivity"
android:resizeableActivity="true"
android:excludeFromRecents="true"
android:noHistory="true" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
</manifest>
我们可以看到 <category android:name="android.intent.category.HOME" />它是一个Launcher应用。
packages/apps/Car/LensPicker/src/com/android/support/car/lenspicker/LensPickerTrampolineActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
PackageManager packageManager = getPackageManager();
mSharedPrefs = LensPickerUtils.getFacetSharedPrefs(this);
Intent intent = getIntent();
String systemCommand =
intent.getStringExtra(LensPickerConstants.EXTRA_FACET_SYSTEM_COMMAND);
if (systemCommand != null && executeSystemCommand(systemCommand)) {
finish();
return;
}
// Hide the shade if switching to a different facet.
hideNotificationsShade();
String facetId = intent.getStringExtra(LensPickerConstants.EXTRA_FACET_ID);
// If no facetId was passed to this activity, then that means that we cannot retrieve the
// application categories to determine an activity to launch. Thus, just launch the
// default application.
if (TextUtils.isEmpty(facetId)) {
launchLastRunOrDefaultApplication();
finish();
return;
}
String facetKey = LensPickerUtils.getFacetKey(facetId);
String savedPackageName = mSharedPrefs.getString(facetKey, null /* defaultValue */);
String[] categories = intent.getStringArrayExtra(
LensPickerConstants.EXTRA_FACET_CATEGORIES);
String[] packages = intent.getStringArrayExtra(LensPickerConstants.EXTRA_FACET_PACKAGES);
boolean alwaysLaunchPicker = intent.getBooleanExtra(
LensPickerConstants.EXTRA_FACET_LAUNCH_PICKER, false);
Intent launchIntent;
if (!alwaysLaunchPicker && savedPackageName != null) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Launching saved package: " + savedPackageName);
}
launchIntent = packageManager.getLaunchIntentForPackage(savedPackageName);
} else {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Delegating to LensPickerActivity to handle application launch.");
}
launchIntent = new Intent(this, LensPickerActivity.class);
launchIntent.putExtra(LensPickerConstants.EXTRA_FACET_PACKAGES, packages);
launchIntent.putExtra(LensPickerConstants.EXTRA_FACET_CATEGORIES, categories);
launchIntent.putExtra(LensPickerConstants.EXTRA_FACET_ID, facetId);
}
startActivity(launchIntent);
finish();
}
从Intent中取出package,categrory,facet 信息,然后启动LensPickerActivity
packages/apps/Car/LensPicker/src/com/android/support/car/lenspicker/LensPickerActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPackageManager = getPackageManager();
mSharedPrefs = LensPickerUtils.getFacetSharedPrefs(this);
setContentView(R.layout.lens_list);
mPagedListView = (PagedListView) findViewById(R.id.list_view);
// Set this to light mode, since the scroll bar buttons always appear
// on top of a dark scrim.
mPagedListView.setLightMode();
findViewById(R.id.dismiss_area).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
}
@Override
protected void onResume() {
super.onResume();
Intent intent = getIntent();
String[] categories = intent.getStringArrayExtra(
LensPickerConstants.EXTRA_FACET_CATEGORIES);
String[] packages = intent.getStringArrayExtra(LensPickerConstants.EXTRA_FACET_PACKAGES);
String facetId = intent.getStringExtra(LensPickerConstants.EXTRA_FACET_ID);
List<ResolveInfo> resolveInfos = getComponents(packages, categories);
if (resolveInfos != null && resolveInfos.size() == 1) {
// Directly launch the package rather than showing a list of 1.
ResolveInfo rInfo = resolveInfos.get(0);
String packageName = LensPickerUtils.getPackageName(rInfo);
Intent launchIntent = LensPickerUtils.getLaunchIntent(packageName, rInfo,
mPackageManager);
if (launchIntent != null) {
launch(facetId, packageName, launchIntent);
} else {
Log.e(TAG, "Failed to get launch intent for package" + packageName);
}
finish();
return;
}
mPagedListView.setAdapter(new LensPickerAdapter(this, resolveInfos, facetId,
this /* LensPickerSelectionHandler */));
}
@Override
protected void onPause() {
super.onPause();
if (mLastLaunchedFacetId == null || mLastLaunchedPackageName == null
|| mLastLaunchedIntent == null) {
return;
}
LensPickerUtils.saveLastLaunchedAppInfo(mSharedPrefs, mLastLaunchedFacetId,
mLastLaunchedPackageName, mLastLaunchedIntent);
}
在onResume 方法中去启动Intent中保留的应用。
第一:以上我们可以得到 Car Model No Launcher 启动流程:
启动目标应用 --> 跳转LensPicker应用 --> 启动目标应用
第二:让我们无感它的切换过程,认定它是No Launcher 模式主要是 LensPicker AndroidManifest.xml 文件中配置的这两个属性:
android:excludeFromRecents="true"
该属性让我们在Recent 中无法查看到这个应用进程的信息
android:noHistory="true"
该属性清除了它自身的启动路径TaskRecord,
注意:让我们使用Back 按键时直接在相邻的两个应用中进行TaskRecord切换,避免再次经过LensPicker做启动,同时也减轻了LensPicker的工作,不需要保留所有的启动记录,而是让专业的人去做专业的事(AMS, ActivityStack, TaskRecord...)。
第三:而LensPicker只保留了当前最新的启动应用记录,当用户触发Home 按键,会回到LensPickerActivity页面中,而LensPickerActivity保留了最新的应用启动记录,会跳转到目标应用,这就实现了Car Model No Launcher模式。
下面我们贴一下栈信息,印证我们的想法:
ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)
Display #0 (activities from top to bottom):
Stack #1:
mFullscreen=true
isSleeping=false
mBounds=null
Task id #217
mFullscreen=true
mBounds=null
mMinWidth=-1
mMinHeight=-1
mLastNonFullscreenBounds=null
* TaskRecord{c4be22 #217 A=com.android.car.radio U=0 StackId=1 sz=1}
userId=0 effectiveUid=u0a11 mCallingUid=u0a44 mUserSetupComplete=true mCallingPackage=com.android.support.car.lenspicker
affinity=com.android.car.radio
intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 pkg=com.android.car.radio cmp=com.android.car.radio/.CarRadioActivity}
realActivity=com.android.car.radio/.CarRadioActivity
autoRemoveRecents=false isPersistable=true numFullscreen=1 taskType=0 mTaskToReturnTo=1
rootWasReset=false mNeverRelinquishIdentity=true mReuseTask=false mLockTaskAuth=LOCK_TASK_AUTH_PINNABLE
Activities=[ActivityRecord{aa50792 u0 com.android.car.radio/.CarRadioActivity t217}]
askedCompatMode=false inRecents=true isAvailable=true
lastThumbnail=null lastThumbnailFile=/data/system_ce/0/recent_images/217_task_thumbnail.png
stackId=1
hasBeenVisible=true mResizeMode=RESIZE_MODE_RESIZEABLE mSupportsPictureInPicture=false isResizeable=true firstActiveTime=1564637925171 lastActiveTime=1564637925203 (inactive for 29s)
* Hist #0: ActivityRecord{aa50792 u0 com.android.car.radio/.CarRadioActivity t217}
packageName=com.android.car.radio processName=com.android.car.radio
launchedFromUid=10044 launchedFromPackage=com.android.support.car.lenspicker userId=0
app=ProcessRecord{cdd5c6d 2175:com.android.car.radio/u0a11}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 pkg=com.android.car.radio cmp=com.android.car.radio/.CarRadioActivity }
frontOfTask=true task=TaskRecord{c4be22 #217 A=com.android.car.radio U=0 StackId=1 sz=1}
taskAffinity=com.android.car.radio
realActivity=com.android.car.radio/.CarRadioActivity
baseDir=/system/priv-app/CarRadioApp/CarRadioApp.apk
dataDir=/data/user/0/com.android.car.radio
stateNotNeeded=false componentSpecified=true mActivityType=0
compat={160dpi always-compat} labelRes=0x7f0d0028 icon=0x7f020076 theme=0x7f0e01a6
mLastReportedConfigurations:
mGlobalConfig={1.0 310mcc260mnc [en_US] ldltr sw990dp w1964dp h878dp 160dpi xlrg long land car finger -keyb/v/h tball/v appBounds=Rect(0, 0 - 1964, 934) s.7}
mOverrideConfig={1.0 310mcc260mnc [en_US] ldltr sw990dp w1964dp h878dp 160dpi xlrg long land car finger -keyb/v/h tball/v appBounds=Rect(0, 0 - 1964, 934) s.7}
CurrentConfiguration={1.0 310mcc260mnc [en_US] ldltr sw990dp w1964dp h878dp 160dpi xlrg long land car finger -keyb/v/h tball/v appBounds=Rect(0, 0 - 1964, 934) s.7}
taskDescription: iconFilename=null label="null" primaryColor=fff5f5f5
backgroundColor=fffafafa
statusBarColor=ff283593
navigationBarColor=ff000000
launchFailed=false launchCount=1 lastLaunchTime=-29s467ms
haveState=false icicle=null
state=RESUMED stopped=false delayedResume=false finishing=false
keysPaused=false inHistory=true visible=true sleeping=false idle=true mStartingWindowState=STARTING_WINDOW_SHOWN
fullscreen=true noDisplay=false immersive=false launchMode=2
frozenBeforeDestroy=false forceNewConfig=false
mActivityType=APPLICATION_ACTIVITY_TYPE
waitingVisible=false nowVisible=true lastVisibleTime=-26s401ms
connections=[ConnectionRecord{9ddac5 u0 CR com.android.car.radio/.RadioService:@eecd3c}]
resizeMode=RESIZE_MODE_RESIZEABLE
mLastReportedMultiWindowMode=false mLastReportedPictureInPictureMode=false
Running activities (most recent first):
TaskRecord{c4be22 #217 A=com.android.car.radio U=0 StackId=1 sz=1}
Run #0: ActivityRecord{aa50792 u0 com.android.car.radio/.CarRadioActivity t217}
mResumedActivity: ActivityRecord{aa50792 u0 com.android.car.radio/.CarRadioActivity t217}
Stack #0:
mFullscreen=true
isSleeping=false
mBounds=null
mLastPausedActivity: ActivityRecord{21dc322 u0 com.android.support.car.lenspicker/.LensPickerTrampolineActivity t-1 f}
mLastNoHistoryActivity: ActivityRecord{21dc322 u0 com.android.support.car.lenspicker/.LensPickerTrampolineActivity t-1 f}
ResumedActivity: ActivityRecord{aa50792 u0 com.android.car.radio/.CarRadioActivity t217}
mFocusedStack=ActivityStack{5ff80b3 stackId=1, 1 tasks} mLastFocusedStack=ActivityStack{5ff80b3 stackId=1, 1 tasks}
mCurTaskIdForUser={0=217}
mUserStackInFront={}
mStacks={0=ActivityStack{4728670 stackId=0, 0 tasks}, 1=ActivityStack{5ff80b3 stackId=1, 1 tasks}}
mLockTaskModeState=NONE mLockTaskPackages (userId:packages)=
0:[]
mLockTaskModeTasks[]
KeyguardController:
mKeyguardShowing=false
mKeyguardGoingAway=false
mOccluded=false
mDismissingKeyguardActivity=null
mDismissalRequested=false
mVisibilityTransactionDepth=0