Android 13 about launcher3 (1)
Android 13 Launcher3
android13#launcher3#分屏相关
Launcher3修改 wm density界面布局不改变
/packages/apps/Launcher3/src/com/android/launcher3/InvariantDeviceProfile.java
Launcher的默认配置加载类,通过InvariantDeviceProfile方法可以看出,
CellLayout显示的应用行数和列数可以通过findClosestDeviceProfiles查询XML配置来读取配参
Android 13 diff
index 02ebb15cd1..36ce9bf7bf 100644
--- a/packages/apps/Launcher3/src/com/android/launcher3/util/DisplayController.java
+++ b/packages/apps/Launcher3/src/com/android/launcher3/util/DisplayController.java
@@ -284,7 +284,7 @@ public class DisplayController implements ComponentCallbacks, SafeCloseable {
if (change != 0) {
mInfo = newInfo;
final int flags = change;
- MAIN_EXECUTOR.execute(() -> notifyChange(displayInfoContext, flags));
+ //MAIN_EXECUTOR.execute(() -> notifyChange(displayInfoContext, flags));
}
}
Android 11 diff
./packages/apps/Launcher3/src/com/android/launcher3/util/ConfigMonitor.java
@Override
public void onDisplayChanged(int displayId) {
if (displayId != mDisplayId) {
return;
}
Display display = getDefaultDisplay(mContext);
display.getRealSize(mTmpPoint1);
if (!mRealSize.equals(mTmpPoint1) && !mRealSize.equals(mTmpPoint1.y, mTmpPoint1.x)) {
LogUtils.d(TAG, String.format("Display size changed from %s to %s", mRealSize, mTmpPoint1));
notifyChange();
return;
}
display.getCurrentSizeRange(mTmpPoint1, mTmpPoint2);
if (!mSmallestSize.equals(mTmpPoint1) || !mLargestSize.equals(mTmpPoint2)) {
LogUtils.d(TAG, String.format("Available size changed from [%s, %s] to [%s, %s]",
mSmallestSize, mLargestSize, mTmpPoint1, mTmpPoint2));
notifyChange();
}
}
public synchronized void notifyChange() {
if (mCallback != null) {
Consumer<Context> callback = mCallback;
mCallback = null;
new MainThreadExecutor().execute(() -> {
//add text
//LauncherAppMonitor.getInstance(mContext).onUIConfigChanged();
//callback.accept(mContext);
//add text
});
}
}
Launcher3 的Hotseat选择显示在底部/右边
Launcher3的HotSeat的位置,是由DeviceProfile::isVerticalBarLayout里面的两个变量isLandscape && transposeLayoutWithOrientation去决定的.
如果想强制HotSeat位于底部,把isVerticalBarLayout的返回值默认为false.
/packages/apps/Launcher3/src/com/android/launcher3/DeviceProfile.java
/**
* When {@code true}, the device is in landscape mode and the hotseat is on the right column. 右边
* When {@code false}, either device is in portrait mode or the device is in landscape mode and
* the hotseat is on the bottom row. 底部
*/
public boolean isVerticalBarLayout() {
return isLandscape && transposeLayoutWithOrientation;
}
原生设计:workspace和allapps界面图标大小、文字大小、间隔大小完全一致.
如果横屏时高度不够,可能会影响图标的展示(因为它俩在同一个界面,如果HotSeat展示在下方,会导致workspace空间被压缩),可以优化垂直方向的高度的思路.
Android 11 Launcher3旋转Hotseat显示在底部
Launcher3图标布局原理解析【原创】
Launcher3 的Hotseat的高度
<dimen name="dynamic_grid_hotseat_extra_vertical_size">5dp</dimen>
Android 13 关于旋转屏幕之后,按home键回Launcher3失效
//自动旋转
public static final String ACCELEROMETER_ROTATION = "accelerometer_rotation";
adb shell settings get system accelerometer_rotation --> 0 off 1 on
Android 12之后,Launcher3有两个模式,一个是平板,另一个是手机.
手机模式:桌面默认是不可以旋转的.
桌面设置页面:桌面空白处长按,进入home settings,有个allow home screen rotation就是旋转开关,手机模式默认是关闭的,平板模式这个偏好隐藏了.
./packages/apps/Launcher3/res/values/config.xml
<string name="settings_fragment_name" translatable="false">com.android.launcher3.settings.SettingsActivity$LauncherSettingsFragment</string>
./packages/apps/Launcher3/src/com/android/launcher3/settings/SettingsActivity.java
public static class LauncherSettingsFragment extends PreferenceFragmentCompat
{
protected boolean initPreference(Preference preference) {
switch (preference.getKey()) {
case NOTIFICATION_DOTS_PREFERENCE_KEY:
return !WidgetsModel.GO_DISABLE_NOTIFICATION_DOTS;
case ALLOW_ROTATION_PREFERENCE_KEY:
DisplayController.Info info =
DisplayController.INSTANCE.get(getContext()).getInfo();
if (info.isTablet(info.realBounds)) {
// Launcher supports rotation by default. No need to show this setting.
//是平板,不显示这个选项
return false;
}
// Initialize the UI once //非平板,默认显示,
preference.setDefaultValue(RotationHelper.getAllowRotationDefaultValue(info));//控制是否旋转
return true;
...
}
}
./packages/apps/Launcher3/src/com/android/launcher3/states/RotationHelper.java
public static boolean getAllowRotationDefaultValue(DisplayController.Info info) {
android.util.Log.d("tag","info.currentSize.x: " +info.currentSize.x);
android.util.Log.d("tag","info.currentSize.y: " +info.currentSize.y);
float originalSmallestWidth = dpiFromPx(Math.min(info.currentSize.x, info.currentSize.y),
DENSITY_DEVICE_STABLE);
return originalSmallestWidth >= MIN_TABLET_WIDTH;//MIN_TABLET_WIDTH = 600;
}
public static float dpiFromPx(float size, int densityDpi) {
float densityRatio = (float) densityDpi / DisplayMetrics.DENSITY_DEFAULT;//DENSITY_DEFAULT=160;
return (size / densityRatio);
}
//关于DENSITY_DEVICE_STABLE
private static int getDeviceDensity() {
//ro.sf.lcd_density的值在初始化进程的时候从build.prop里读取写入一次,之后不会修改
//qemu.sf.lcd_density覆写上边的值,目的是在模拟器的时候可以动态修改
return SystemProperties.getInt("qemu.sf.lcd_density",
SystemProperties.getInt("ro.sf.lcd_density", DENSITY_DEFAULT));
}
initialize()->初始化setIgnoreAutoRotateSettings
private void setIgnoreAutoRotateSettings(boolean ignoreAutoRotateSettings,
DisplayController.Info info) {
// On large devices we do not handle auto-rotate differently.
mIgnoreAutoRotateSettings = ignoreAutoRotateSettings;
if (!mIgnoreAutoRotateSettings) {
mHomeRotationEnabled = LauncherPrefs.get(mActivity).get(ALLOW_ROTATION);
LauncherPrefs.get(mActivity).addListener(this, ALLOW_ROTATION);
} else {
LauncherPrefs.get(mActivity).removeListener(this, ALLOW_ROTATION);
}
}
//手动控制屏幕旋转的方式
private void setOritation(int i) {
try {
IWindowManager windowManagerService = WindowManagerGlobal.getWindowManagerService();
if (i == 0) {
windowManagerService.freezeRotation(0);
Settings.System.putInt(this.mContext.getContentResolver(), "accelerometer_rotation", 0);//关闭自动旋转
} else if (i == 90) {
windowManagerService.freezeRotation(1);
Settings.System.putInt(this.mContext.getContentResolver(), "accelerometer_rotation", 0);
} else if (i == 180) {
windowManagerService.freezeRotation(2);
Settings.System.putInt(this.mContext.getContentResolver(), "accelerometer_rotation", 0);
} else if (i == 270) {
windowManagerService.freezeRotation(3);
Settings.System.putInt(this.mContext.getContentResolver(), "accelerometer_rotation", 0);
} else if (i == -1) {
Settings.System.putInt(this.mContext.getContentResolver(), "accelerometer_rotation", 1);//打开自动旋转
}
} catch (Exception e) {
...
}
}
Settings.System.getInt(mContext.getContentResolver(), Settings.System.USER_ROTATION, 0);
adb shell settings get system user_rotation(修改后查询)
//修改 默认打开主界面旋转
+++ b/packages/apps/Launcher3/res/xml/launcher_preferences.xml
@@ -45,7 +45,7 @@
android:key="pref_allowRotation"
android:title="@string/allow_rotation_title"
android:summary="@string/allow_rotation_desc"
- android:defaultValue="false"
+ android:defaultValue="true"
android:persistent="true"
launcher:logIdOn="615"
launcher:logIdOff="616" />
diff --git a/packages/apps/Launcher3/src/com/android/launcher3/states/RotationHelper.java b/packages/apps/Launcher3/src/com/android/launcher3/states/RotationHelper.java
index 7b4e2485ce..6693f9f123 100644
--- a/packages/apps/Launcher3/src/com/android/launcher3/states/RotationHelper.java
+++ b/packages/apps/Launcher3/src/com/android/launcher3/states/RotationHelper.java
@@ -55,7 +55,7 @@ public class RotationHelper implements OnSharedPreferenceChangeListener,
// original dimensions to determine if rotation is allowed of not.
float originalSmallestWidth = dpiFromPx(Math.min(info.currentSize.x, info.currentSize.y),
DENSITY_DEVICE_STABLE);
- return originalSmallestWidth >= MIN_TABLET_WIDTH;
+ return true;//originalSmallestWidth >= MIN_TABLET_WIDTH;//add text
}
从源头隐藏某个apk不在最近应用(RecentView)显示
//这个改法:再需要隐藏的app,按Recent,有动画时显得卡顿,建议可以在Launcher3内尝试隐藏
frameworks/base/services/core/java/com/android/server/wm/RecentTasks.java
/**
* @return the list of recent tasks for presentation.
*/
private ArrayList<ActivityManager.RecentTaskInfo> getRecentTasksImpl(int maxNum, int flags,
boolean getTasksAllowed, int userId, int callingUid) {
final boolean withExcluded = (flags & RECENT_WITH_EXCLUDED) != 0;
if (!isUserRunning(userId, FLAG_AND_UNLOCKED)) {
Slog.i(TAG, "user " + userId + " is still locked. Cannot load recents");
return new ArrayList<>();
}
loadUserRecentsLocked(userId);
final Set<Integer> includedUsers = getProfileIds(userId);
includedUsers.add(Integer.valueOf(userId));
final ArrayList<ActivityManager.RecentTaskInfo> res = new ArrayList<>();
final int size = mTasks.size();
int numVisibleTasks = 0;
for (int i = 0; i < size; i++) {
final Task task = mTasks.get(i);
if (isVisibleRecentTask(task)) {
numVisibleTasks++;
if (isInVisibleRange(task, i, numVisibleTasks, withExcluded)) {
// Fall through
} else {
// Not in visible range
continue;
}
} else {
// Not visible
continue;
}
// Skip remaining tasks once we reach the requested size
if (res.size() >= maxNum) {
continue;
}
// Only add calling user or related users recent tasks
if (!includedUsers.contains(Integer.valueOf(task.mUserId))) {
if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, not user: " + task);
continue;
}
...
//add text
ComponentName cn = task.intent.getComponent();
if(cn != null && cn.getPackageName().equals("com.xxx.xxx")){
continue;
}else{
res.add(createRecentTaskInfo(task, true /* stripExtras */));
}
//add text
}
return res;
}
Android T Launcher3
Android T Launcher3_recentView
T replace default launcher
关于3.0Ver 利用role不需要删除旧Launcher栈的补充.
case 1: 单独使用role,确实不需要删除旧Launcher栈,唯一需要注意点的是要在Launcher启动之前,把android.app.role.HOME替换,
所以需要在framework里某个必经之路上做文章..
//about home role
packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/role/model/HomeRoleBehavior.java
packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/role/ui/ManageRoleHolderStateLiveData.java
packages/modules/Permission/service/java/com/android/role/RoleService.java
addRoleHolderAsUser(...)
removeRoleHolderAsUser(...)
getRoleHoldersAsUser(...)
frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
+import android.app.role.RoleManager;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
@Override
public void systemReady() {
// In normal flow, systemReady is called before other system services are ready.
// So it is better not to bind keyguard here.
mKeyguardDelegate.onSystemReady();
...
mAutofillManagerInternal = LocalServices.getService(AutofillManagerInternal.class);
mGestureLauncherService = LocalServices.getService(GestureLauncherService.class);
+ setDefaultLauncherItemToHomeRole(mContext);//add
}
public void setDefaultLauncherItemToHomeRole(Context context) {
RoleManager roleManager = context.getSystemService(RoleManager.class);
String packageName = SystemProperties.get("persist.xx.def_xxx_launcher", null);
List<String> list = roleManager_1.getRoleHolders("android.app.role.HOME");
boolean repeat_flag = false;
for (int i = 0; i < list.size(); i++) {
if (list.get(i).equals(packageName)) {
flag = true;
}
}
if (repeat_flag) return;
if (packageName != null && !"".equals(packageName)) {
String roleName = "android.app.role.HOME";
boolean add = true;
int flags = 0;
UserHandle user = Process.myUserHandle();
Log.d("tag", "role: " + roleName + ", package: " + packageName);
Executor executor = context.getMainExecutor();
Consumer<Boolean> callback = successful -> {
if (successful) {
Log.d("tag", "Success , role: " + roleName + ", package: " + packageName);
} else {
Log.d("tag", "Failed , role: " + roleName + ", package: " + packageName);
}
};
roleManager.addRoleHolderAsUser(roleName, packageName, flags, user, executor, callback);
}
}
case 2:ResolverActivity
frameworks/base/core/java/com/android/internal/app/ResolverActivity.java
onCreate()->rebuildList()->onPostListReady()->isAutolaunching()...
protected void onCreate(Bundle savedInstanceState, Intent intent,
CharSequence title, int defaultTitleRes, Intent[] initialIntents,
List<ResolveInfo> rList, boolean supportsAlwaysUseOption) {
setTheme(appliedThemeResId());
super.onCreate(savedInstanceState);
//add text
if(true){
setDefaultLauncher("com.xx.xxx");
finish();
return;
}
//add text
mQuietModeManager = createQuietModeManager();
...
}
//add text
private List<ResolveInfo> getResolveInfoList() {
PackageManager pm = getPackageManager();
Intent intent = new Intent(Intent.ACTION_MAIN, null);
intent.addCategory(Intent.CATEGORY_HOME);
intent.addCategory(Intent.CATEGORY_DEFAULT);
return pm.queryIntentActivities(intent, 0);
}
private ResolveInfo getCurrentLauncher() {
PackageManager pm = getPackageManager();
Intent intent = new Intent(Intent.ACTION_MAIN, null);
intent.addCategory(Intent.CATEGORY_HOME);
intent.addCategory(Intent.CATEGORY_DEFAULT);
return pm.resolveActivity(intent, 0);
}
private void setDefaultLauncher(String packageName) {
PackageManager pm = getPackageManager();
ResolveInfo oldCurrentLauncher = getCurrentLauncher();
List<ResolveInfo> packageInfos = getResolveInfoList();
ResolveInfo customerLauncher = null;
for (ResolveInfo ri : packageInfos) {
if (!TextUtils.isEmpty(ri.activityInfo.packageName) && !TextUtils.isEmpty(packageName)
&& TextUtils.equals(ri.activityInfo.packageName, packageName)) {
customerLauncher = ri;
}
}
if (customerLauncher == null) return;
pm.clearPackagePreferredActivities(oldCurrentLauncher.activityInfo.packageName);//clear old Launcher
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_MAIN);
intentFilter.addCategory(Intent.CATEGORY_HOME);
intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
ComponentName componentName = new ComponentName(customerLauncher.activityInfo.packageName,
customerLauncher.activityInfo.name);
ComponentName[] componentNames = new ComponentName[packageInfos.size()];
int defaultMatch = 0;
for (int i = 0; i < packageInfos.size(); i++) {
ResolveInfo resolveInfo = packageInfos.get(i);
componentNames[i] = new ComponentName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name);
if (defaultMatch < resolveInfo.match) {
defaultMatch = resolveInfo.match;
}
}
pm.clearPackagePreferredActivities(oldCurrentLauncher.activityInfo.packageName);
pm.addPreferredActivity(intentFilter, defaultMatch, componentNames, componentName);
}
//add text
Android13设置默认Launcher分析
Android13设置默认Launcher分析
RK3588 Android13 预安装自己的apk应用及把这个应用设置为默认桌面
Android R设置默认桌面
Android 12 replace default launcher
利用PMS实现默认第三方apk为主Launcher
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
+
+ private void setTargetActivityAsLauncher(Intent intent,List<ResolveInfo> query, int userId){
+ final int N =query.size();
+
+ ComponentName[] set = new ComponentName[N];
+ ComponentName componentName = null;
+ int bestMatch = 0;
+ for(int i = 0;i < N; i++){
+ ResolveInfo r = query.get(i);
+ set[i] = new ComponentName(r.activityInfo.packageName, r.activityInfo.name);
+
+ if (r.match > bestMatch) bestMatch = r.match;
+ if("xx.xx.xxx".equals(r.activityInfo.packageName)){ /*modify target apk packageName */
+ componentName=set[i];
+ }
+ }
+ IntentFilter filter = new IntentFilter();
+ if (intent.getAction() != null) {
+ filter.addAction(intent.getAction());
+ }
+ Set<String> categories = intent.getCategories();
+ if (categories != null) {
+ for (String cat : categories) {
+ filter.addCategory(cat);
+ }
+ }
+ filter.addCategory(Intent.CATEGORY_DEFAULT);
+ if(null!=componentName){
+ addPreferredActivity(filter, bestMatch,set,componentName,userId,false);
+ }
+
+ }
private ResolveInfo chooseBestActivity(Intent intent, String resolvedType,
int flags, int privateResolveFlags, List<ResolveInfo> query, int userId,
public class PackageManagerService extends IPackageManager.Stub
if (N == 1) {
return query.get(0);
} else if (N > 1) {
+ setTargetActivityAsLauncher(intent,query,userId);
final boolean debug = ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
// If there is more than one activity with the same priority,
// then let the user decide between them.
Android T update wm density 导致 Launcher3 ANR/SystemUI nav bar消失
点击Settings app 的 display 选项, 里面有个功能可以修改字体大小和屏幕密度.
发现修改屏幕密度为最小值时,Launcher3 停止运行,触发ANR和SystemUI nav bar消失.
下面是日志:
10-15 03:36:35.078 5241 5241 E AndroidRuntime: FATAL EXCEPTION: main
10-15 03:36:35.078 5241 5241 E AndroidRuntime: Process: com.android.launcher3, PID: 5241
...
10-15 03:36:35.078 5241 5241 E AndroidRuntime: Caused by: java.lang.ArithmeticException: divide by zero
10-15 03:36:35.078 5241 5241 E AndroidRuntime: at com.android.launcher3.DeviceProfile.calculateHotseatBorderSpace(DeviceProfile.java:1009)
10-15 03:36:35.078 5241 5241 E AndroidRuntime: at com.android.launcher3.DeviceProfile.recalculateHotseatWidthAndBorderSpace(DeviceProfile.java:670)
10-15 03:36:35.078 5241 5241 E AndroidRuntime: at com.android.launcher3.InvariantDeviceProfile.lambda$initGrid$7(InvariantDeviceProfile.java:444)
10-15 03:36:35.078 5241 5241 E AndroidRuntime: at com.android.launcher3.InvariantDeviceProfile$$ExternalSyntheticLambda5.accept(Unknown Source:4)
10-15 03:36:35.078 5241 5241 E AndroidRuntime: at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
10-15 03:36:35.078 5241 5241 E AndroidRuntime: at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:185)
10-15 03:36:35.078 5241 5241 E AndroidRuntime: at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1390)
10-15 03:36:35.078 5241 5241 E AndroidRuntime: at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:485)
10-15 03:36:35.078 5241 5241 E AndroidRuntime: at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:475)
10-15 03:36:35.078 5241 5241 E AndroidRuntime: at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
10-15 03:36:35.078 5241 5241 E AndroidRuntime: at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:133)
10-15 03:36:35.078 5241 5241 E AndroidRuntime: at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:236)
10-15 03:36:35.078 5241 5241 E AndroidRuntime: at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:435)
10-15 03:36:35.078 5241 5241 E AndroidRuntime: at com.android.launcher3.InvariantDeviceProfile.initGrid(InvariantDeviceProfile.java:442)
10-15 03:36:35.078 5241 5241 E AndroidRuntime: at com.android.launcher3.InvariantDeviceProfile.initGrid(InvariantDeviceProfile.java:329)
10-15 03:36:35.078 5241 5241 E AndroidRuntime: at com.android.launcher3.InvariantDeviceProfile.<init>(InvariantDeviceProfile.java:205)
10-15 03:36:35.078 5241 5241 E AndroidRuntime: at com.android.launcher3.InvariantDeviceProfile.$r8$lambda$DNcXzmawjoq65q3wgQi9M48DryY(Unknown Source:2)
10-15 03:36:35.078 5241 5241 E AndroidRuntime: at com.android.launcher3.InvariantDeviceProfile$$ExternalSyntheticLambda0.get(Unknown Source:0)
10-15 03:36:35.078 5241 5241 E AndroidRuntime: at com.android.launcher3.util.MainThreadInitializedObject.lambda$get$0$com-android-launcher3-util-MainThreadInitializedObject(MainThreadInitializedObject.java:58)
10-15 03:36:35.078 5241 5241 E AndroidRuntime: at com.android.launcher3.util.MainThreadInitializedObject$$ExternalSyntheticLambda0.get(Unknown Source:4)
10-15 03:36:35.078 5241 5241 E AndroidRuntime: at com.android.launcher3.util.TraceHelper.allowIpcs(TraceHelper.java:84)
10-15 03:36:35.078 5241 5241 E AndroidRuntime: at com.android.launcher3.util.MainThreadInitializedObject.get(MainThreadInitializedObject.java:57)
10-15 03:36:35.078 5241 5241 E AndroidRuntime: at com.android.launcher3.LauncherAppState.<init>(LauncherAppState.java:154)
10-15 03:36:35.078 5241 5241 E AndroidRuntime: at com.android.launcher3.LauncherAppState.<init>(LauncherAppState.java:96)
10-15 03:36:35.078 5241 5241 E AndroidRuntime: at com.android.launcher3.LauncherAppState$$ExternalSyntheticLambda8.get(Unknown Source:2)
10-15 03:36:35.078 5241 5241 E AndroidRuntime: at com.android.launcher3.util.MainThreadInitializedObject.lambda$get$0$com-android-launcher3-util-MainThreadInitializedObject(MainThreadInitializedObject.java:58)
10-15 03:36:35.078 5241 5241 E AndroidRuntime: at com.android.launcher3.util.MainThreadInitializedObject$$ExternalSyntheticLambda0.get(Unknown Source:4)
10-15 03:36:35.078 5241 5241 E AndroidRuntime: at com.android.launcher3.util.TraceHelper.allowIpcs(TraceHelper.java:84)
10-15 03:36:35.078 5241 5241 E AndroidRuntime: at com.android.launcher3.util.MainThreadInitializedObject.get(MainThreadInitializedObject.java:57)
10-15 03:36:35.079 5241 5241 E AndroidRuntime: at com.android.launcher3.LauncherAppState.getInstance(LauncherAppState.java:84)
10-15 03:36:35.079 5241 5241 E AndroidRuntime: at com.android.launcher3.Launcher.onCreate(Launcher.java:482)
10-15 03:36:35.079 5241 5241 E AndroidRuntime: at com.android.launcher3.uioverrides.QuickstepLauncher.onCreate(QuickstepLauncher.java:585)
10-15 03:36:35.079 5241 5241 E AndroidRuntime: at android.app.Activity.performCreate(Activity.java:8357)
10-15 03:36:35.079 5241 5241 E AndroidRuntime: at android.app.Activity.performCreate(Activity.java:8336)
10-15 03:36:35.079 5241 5241 E AndroidRuntime: at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1417)
10-15 03:36:35.079 5241 5241 E AndroidRuntime: at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3626)
10-15 03:36:35.079 5241 5241 E AndroidRuntime: ... 12 more
根据日志跟踪,再对应代码处添加日志
+++ b/packages/apps/Launcher3/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -436,11 +436,13 @@ public class InvariantDeviceProfile {
.min()
.orElse(0);
supportedProfiles
.stream()
.filter(deviceProfile -> deviceProfile.isTablet)
.forEach(deviceProfile -> {
deviceProfile.numShownHotseatIcons = numMinShownHotseatIconsForTablet;
+ android.util.Log.d("tag","numMinShownHotseatIconsForTablet:"+numMinShownHotseatIconsForTablet);
+ android.util.Log.d("tag","deviceProfile.isTablet:"+deviceProfile.isTablet);
deviceProfile.recalculateHotseatWidthAndBorderSpace();
});
根据打印,发现只有修改屏幕密度为最小值(114)时,才会打印tag numMinShownHotseatIconsForTablet 和 deviceProfile.isTablet 为 true.
但是当设备调节为其他屏幕密度时,代码不走这里,不打印.
bingo,问题点已经被发现了,可能是isTablet影响的一切,后面就是修改验证了...
ps:验证之后,确认是isTablet的问题.
/packages/apps/Launcher3/src/com/android/launcher3/util/window/WindowManagerProxy.java
boolean isTablet = config.smallestScreenWidthDp > MIN_TABLET_WIDTH;
当前设备config.smallestScreenWidthDp 打印为 674 > MIN_TABLET_WIDTH(600),isTablet 为true,其他屏幕密度为false,
Launcher3判断当前设备是tablet,导致了问题.
如何修改:
找到Launcher3 / SystemUI 判断isTablet ,返回false即可.
具体请点击下方链接.
isTablet影响
禁用上拉显示所有APP功能
./packages/apps/Launcher3/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
/**
* Touch controller for handling various state transitions in portrait UI.
触摸控制器,用于处理纵向UI中的各种状态转换。
*/
public class PortraitStatesTouchController extends AbstractStateChangeTouchController {
//返回为NORMAL即可禁用
@Override
protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
if (fromState == ALL_APPS && !isDragTowardPositive) {
return NORMAL;
} else if (fromState == OVERVIEW) {
return isDragTowardPositive ? OVERVIEW : NORMAL;
} else if (fromState == NORMAL && isDragTowardPositive) {
return ALL_APPS;
}
return fromState;
}
}
launcher3 的hotseat 和nav bar的距离
packages/apps/Launcher3/src/com/android/launcher3/InsettableFrameLayout.java
public void setFrameLayoutChildInsets(View child, Rect newInsets, Rect oldInsets) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (child instanceof Insettable) {
((Insettable) child).setInsets(newInsets);
} else if (!lp.ignoreInsets) {
lp.topMargin += (newInsets.top - oldInsets.top);
lp.leftMargin += (newInsets.left - oldInsets.left);
lp.rightMargin += (newInsets.right - oldInsets.right);
lp.bottomMargin += (newInsets.bottom - oldInsets.bottom);
}
lp.bottomMargin -= 15;// add text
child.setLayoutParams(lp);
}
Launcher3 pageView 的滑动速度阈值
bug:桌面短距离滑动,概率无法翻页
Launcher3/src/com/android/launcher3/PagedView.java
public abstract class PagedView<T extends View & PageIndicator> extends ViewGroup {
private static final String TAG = "PagedView";
private static final boolean DEBUG = false;
public static final boolean DEBUG_FAILED_QUICKSWITCH = false;
public static final int PAGE_SNAP_ANIMATION_DURATION = 750;
......
protected static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f;
// The page is moved more than halfway, automatically move to the next page on touch up.
protected static final float SIGNIFICANT_MOVE_THRESHOLD = 0.35f;//add launcher move threshold text
private static final float MAX_SCROLL_PROGRESS = 1.0f;
private static final int VELOCITY_UNITS = android.os.SystemProperties.getInt("ro.xxx.launcher_velocity_units", 1000);//add launcher text
@Override
public boolean onTouchEvent(MotionEvent ev) {
acquireVelocityTrackerAndAddMovement(ev);
final int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
updateIsBeingDraggedOnTouchDown(ev);
.....
case ACTION_MOVE_ALLOW_EASY_FLING:
// Start scrolling immediately
determineScrollingStart(ev);
mAllowEasyFling = true;
break;
case MotionEvent.ACTION_UP:
if (mIsBeingDragged) {
final int activePointerId = mActivePointerId;
action_up(ev,activePointerId);
final int pointerIndex = ev.findPointerIndex(activePointerId);
if (pointerIndex == -1) return true;
final float primaryDirection = mOrientationHandler.getPrimaryDirection(ev,
pointerIndex);
final VelocityTracker velocityTracker = mVelocityTracker;
//add text start
Log.v("TAG" + TAG, "VELOCITY_UNITS=" + VELOCITY_UNITS);
velocityTracker.computeCurrentVelocity(VELOCITY_UNITS, mMaximumVelocity);
//add text start
int velocity = (int) mOrientationHandler.getPrimaryVelocity(velocityTracker,
mActivePointerId);
float delta = primaryDirection - mDownMotionPrimary;
int pageOrientedSize = (int) (mOrientationHandler.getMeasuredSize(
getPageAt(mCurrentPage))
* mOrientationHandler.getPrimaryScale(this));
boolean isSignificantMove = isSignificantMove(Math.abs(delta), pageOrientedSize);
mTotalMotion += Math.abs(mLastMotion + mLastMotionRemainder - primaryDirection);
boolean passedSlop = mAllowEasyFling || mTotalMotion > mPageSlop;
boolean isFling = passedSlop && shouldFlingForVelocity(velocity);
//add debug fling params for text start
if(DEBUG){
Log.d(TAG, "UP vel=" + velocity + " delta=" + (int) delta
+ " pageSize=" + pageOrientedSize
+ " isFling=" + isFling + " significantMove=" + isSignificantMove
+ " threshold=" + (mAllowEasyFling ? mEasyFlingThresholdVelocity : mFlingThresholdVelocity)
+ " page=" + mCurrentPage + " childCount=" + getChildCount());
}
//add debug fling params for text end
protected boolean isSignificantMove(float absoluteDelta, int pageOrientedSize) {
return absoluteDelta > pageOrientedSize * SIGNIFICANT_MOVE_THRESHOLD;
}
ViewConfiguration 类的整体作用
frameworks/base/core/java/android/view/ViewConfiguration.java 是 Android框架层的一个静态配置常量集合。
它集中存放 UI交互相关的超时、尺寸、距离等标准值,供整个系统(View、GestureDetector、ScrollView、RecyclerView、gesture判定等)统一引用,避免各处各写一套魔法数字。
关键设计点:
- 分两类取值:一类是纯静态常量(如 TOUCH_SLOP = 8),直接从 config.xml 或代码里取;另一类是需要 Context
才能拿到的缩放后像素值(如 getScaledTouchSlop()),因为真实设备上 8dp 要乘以 density 才是像素。
- 通过 ViewConfiguration.get(Context) 获取,内部按 density 缓存到 sConfigurations(SparseArray)。
- 用 getScaledXXX() 拿像素值,旧式 getXXX() 是 deprecate 的 dip 值接口。
private static final int TOUCH_SLOP = 15; // 设 slop 为 15(减小触摸容差,提高灵敏度)
private static final int MINIMUM_FLING_VELOCITY = 60; // 设为 60(降低最小fling速度阈值)
TOUCH_SLOP(触摸滑动阈值)
定义(ViewConfiguration.java:182):private static final int TOUCH_SLOP = 8;(单位 dip)
作用:手指按下后,如果移动距离小于这个值,系统认为你只是"轻微抖动/按住";一旦超过这个值,才判定为**滚动(scrol
l)**而不是点击。
- 它决定了"点击 vs 拖动"的分界。手指往下滑但没超过 8dp,会当成 click;超过 8dp 触发 move/scroll。
- 默认 8dp,实际像素 = 8 * density。在 3x 屏上是 24px。
- 派生值:
- DOUBLE_TAP_TOUCH_SLOP = TOUCH_SLOP(双击时第一次触摸允许的偏移)
- PAGING_TOUCH_SLOP = TOUCH_SLOP * 2(翻页式滚动阈值,约 16dp)
- 源码注释里明确:真实取值走 config_viewConfigurationTouchSlop 这个 dimen 资源。你这款机型上 config.xml 是默认
8dp(ViewConfiguration.java:480 用 res.getDimensionPixelSize 取)。
MINIMUM_FLING_VELOCITY(最小甩动速度)
定义(ViewConfiguration.java:223):private static final int MINIMUM_FLING_VELOCITY = 50;(单位 dip/秒)
作用:手指在屏幕上快速划动(fling,甩动)结束抬起时,系统根据最后时刻的速度计算。如果速度 ≥
这个门槛,就把这次手势判定为一次 fling,从而触发惯性滚动(列表继续滑动、越来越慢直到停下);如果低于这个值,就
当作普通的拖动结束,不触发惯性滚动。
- 默认 50 dp/s。像素速度 = 50 * density。
- 对应有 MAXIMUM_FLING_VELOCITY = 8000,是速度上限,防止极端甩动滚得太快。
- 源码里这个值同样来自资源 config_viewMinFlingVelocity(ViewConfiguration.java:489)。
你这款机型上的实际值
我查了你的 config.xml,没有厂商/设备 overlay 覆盖(只有 cuttlefish 模拟器和手表 values-watch
有改,跟你无关),所以实际生效的就是 AOSP 默认:
┌────────────────────────┬────────┬─────────────────────────────────────────┬────────────────────────┐
│ 常量 │ 定义值 │ 实际取到 │ 注释中单位 │
├────────────────────────┼────────┼─────────────────────────────────────────┼────────────────────────┤
│ TOUCH_SLOP │ 8 │ config_viewConfigurationTouchSlop = 8dp │ 触发滚动的移动距离阈值 │
├────────────────────────┼────────┼─────────────────────────────────────────┼────────────────────────┤
│ MINIMUM_FLING_VELOCITY │ 50 │ config_viewMinFlingVelocity = 50dp │ 触发惯性滑动的速度门槛 │
├────────────────────────┼────────┼─────────────────────────────────────────┼────────────────────────┤
│ MAXIMUM_FLING_VELOCITY │ 8000 │ config_viewMaxFlingVelocity = 8000dp │ fling 速度上限 │
└────────────────────────┴────────┴─────────────────────────────────────────┴────────────────────────┘
一句话概括:TOUCH_SLOP 管"手要移多远才算滚动",MINIMUM_FLING_VELOCITY 管"甩多快才触发惯性滑动"。

浙公网安备 33010602011771号