update android16-s2-release

This commit is contained in:
MoXiang committed 2025-06-21 11:12:14 +08:00
1 parent b725301b43
commit 1889ce3062
1653 files changed
+113254 -46331

No files matched your search

Vendored
BIN
View File
Binary file not shown.
+291 -16
View File
@@ -17,7 +17,19 @@ package {
default_applicable_licenses: ["Android-Apache-2.0"],
}
min_launcher3_sdk_version = "30"
min_launcher3_sdk_version = "31"
// Targets that don't inherit framework aconfig libs (i.e., those that don't set
// `platform_apis: true`) must manually link them.
java_defaults {
name: "launcher-non-platform-apis-defaults",
static_libs: [
"android.os.flags-aconfig-java",
"android.multiuser.flags-aconfig-java",
"android.appwidget.flags-aconfig-java",
"com.android.window.flags.window-aconfig-java",
],
}
// Common source files used to build launcher (java and kotlin)
// All sources are split so they can be reused in many other libraries/apps in other folders
@@ -31,12 +43,122 @@ filegroup {
],
}
// Main Launcher source for compose, excluding the build config
filegroup {
name: "launcher-compose-enabled-src",
srcs: [
"compose/facade/enabled/*.kt",
"compose/facade/core/*.kt",
"compose/features/**/*.kt",
],
}
filegroup {
name: "launcher-compose-disabled-src",
srcs: [
"compose/facade/core/*.kt",
"compose/facade/disabled/*.kt",
],
}
// Source code for quickstep build, on top of launcher-src
filegroup {
name: "launcher-quickstep-src",
srcs: [
"quickstep/src/**/*.java",
"quickstep/src/**/*.kt",
"quickstep/src/**/*.java",
],
device_common_srcs: [
":launcher-quickstep-processed-protolog-src",
],
}
// Launcher ProtoLog support
filegroup {
name: "launcher-quickstep-unprocessed-protolog-src",
srcs: [
"quickstep/src_protolog/**/*.java",
],
}
java_library {
name: "launcher-quickstep_protolog-groups",
srcs: [
"quickstep/src_protolog/**/*.java",
],
static_libs: [
"protolog-group",
"androidx.annotation_annotation",
"com_android_launcher3_flags_lib",
],
}
java_genrule {
name: "launcher-quickstep-processed-protolog-src",
srcs: [
":protolog-impl",
":launcher-quickstep-unprocessed-protolog-src",
":launcher-quickstep_protolog-groups",
],
tools: ["protologtool"],
cmd: "$(location protologtool) transform-protolog-calls " +
"--protolog-class com.android.internal.protolog.common.ProtoLog " +
"--loggroups-class com.android.quickstep.util.QuickstepProtoLogGroup " +
"--loggroups-jar $(location :launcher-quickstep_protolog-groups) " +
"--viewer-config-file-path /system_ext/etc/launcher.quickstep.protolog.pb " +
"--output-srcjar $(out) " +
"$(locations :launcher-quickstep-unprocessed-protolog-src)",
out: ["launcher.quickstep.protolog.srcjar"],
}
java_genrule {
name: "gen-launcher.quickstep.protolog.pb",
srcs: [
":launcher-quickstep-unprocessed-protolog-src",
":launcher-quickstep_protolog-groups",
],
tools: ["protologtool"],
cmd: "$(location protologtool) generate-viewer-config " +
"--protolog-class com.android.internal.protolog.common.ProtoLog " +
"--loggroups-class com.android.quickstep.util.QuickstepProtoLogGroup " +
"--loggroups-jar $(location :launcher-quickstep_protolog-groups) " +
"--viewer-config-type proto " +
"--viewer-config $(out) " +
"$(locations :launcher-quickstep-unprocessed-protolog-src)",
out: ["launcher.quickstep.protolog.pb"],
}
prebuilt_etc {
name: "launcher.quickstep.protolog.pb",
system_ext_specific: true,
src: ":gen-launcher.quickstep.protolog.pb",
filename_from_src: true,
}
// Source code for quickstep dagger
filegroup {
name: "launcher-quickstep-dagger",
srcs: [
"quickstep/dagger/**/*.java",
"quickstep/dagger/**/*.kt",
],
}
// Source code for quickstep build with compose enabled, on top of launcher-src
filegroup {
name: "launcher-quickstep-compose-enabled-src",
srcs: [
"quickstep/compose/facade/core/*.kt",
"quickstep/compose/facade/enabled/*.kt",
"quickstep/compose/features/**/*.kt",
],
}
filegroup {
name: "launcher-quickstep-compose-disabled-src",
srcs: [
"quickstep/compose/facade/core/*.kt",
"quickstep/compose/facade/disabled/*.kt",
],
}
@@ -63,10 +185,118 @@ filegroup {
srcs: ["proguard.flags"],
}
// Opt-in configuration for Launcher3 code depending on Jetpack Compose.
soong_config_module_type {
name: "launcher_compose_java_defaults",
module_type: "java_defaults",
config_namespace: "ANDROID",
bool_variables: ["release_enable_compose_in_launcher"],
properties: [
"srcs",
"static_libs",
],
}
// Opt-in configuration for Launcher Quickstep code depending on Jetpack Compose.
soong_config_bool_variable {
name: "release_enable_compose_in_launcher",
}
soong_config_module_type {
name: "quickstep_compose_java_defaults",
module_type: "java_defaults",
config_namespace: "ANDROID",
bool_variables: ["release_enable_compose_in_launcher"],
properties: [
"srcs",
"static_libs",
],
}
soong_config_module_type {
name: "launcher_compose_tests_java_defaults",
module_type: "java_defaults",
config_namespace: "ANDROID",
bool_variables: ["release_enable_compose_in_launcher"],
properties: [
"static_libs",
],
}
launcher_compose_java_defaults {
name: "launcher_compose_defaults",
soong_config_variables: {
release_enable_compose_in_launcher: {
srcs: [
":launcher-compose-enabled-src",
],
// Compose dependencies
static_libs: [
"androidx.compose.runtime_runtime",
"androidx.compose.material3_material3",
],
// By default, Compose is disabled and we compile the ComposeFacade
// in compose/launcher3/facade/disabled/.
conditions_default: {
srcs: [
":launcher-compose-disabled-src",
],
static_libs: [],
},
},
},
}
quickstep_compose_java_defaults {
name: "quickstep_compose_defaults",
soong_config_variables: {
release_enable_compose_in_launcher: {
srcs: [
":launcher-quickstep-compose-enabled-src",
],
// Compose dependencies
static_libs: [
"androidx.compose.runtime_runtime",
"androidx.compose.material3_material3",
],
// By default, Compose is disabled and we compile the ComposeFacade
// in compose/quickstep/facade/disabled/.
conditions_default: {
srcs: [
":launcher-quickstep-compose-disabled-src",
],
static_libs: [],
},
},
},
}
launcher_compose_tests_java_defaults {
name: "launcher_compose_tests_defaults",
soong_config_variables: {
release_enable_compose_in_launcher: {
// Compose dependencies
static_libs: [
"androidx.compose.runtime_runtime",
"androidx.compose.ui_ui-test-junit4",
"androidx.compose.ui_ui-test-manifest",
],
conditions_default: {
static_libs: [],
},
},
},
}
android_library {
name: "launcher-aosp-tapl",
libs: [
"framework-statsd",
"framework-statsd.stubs.module_lib",
],
static_libs: [
"androidx.annotation_annotation",
@@ -76,6 +306,7 @@ android_library {
"androidx.preference_preference",
"SystemUISharedLib",
"//frameworks/libs/systemui:animationlib",
"//frameworks/libs/systemui:contextualeducationlib",
"launcher-testing-shared",
],
srcs: [
@@ -136,12 +367,14 @@ java_library {
// Library with all the dependencies for building Launcher3
android_library {
name: "Launcher3ResLib",
defaults: [
"launcher_compose_defaults",
],
srcs: [],
resource_dirs: ["res"],
static_libs: [
"LauncherPluginLib",
"launcher_quickstep_log_protos_lite",
"android.os.flags-aconfig-java",
"androidx-constraintlayout_constraintlayout",
"androidx.recyclerview_recyclerview",
"androidx.dynamicanimation_dynamicanimation",
@@ -154,7 +387,10 @@ android_library {
"//frameworks/libs/systemui:iconloader_base",
"//frameworks/libs/systemui:view_capture",
"//frameworks/libs/systemui:animationlib",
"//frameworks/libs/systemui:contextualeducationlib",
"//frameworks/libs/systemui:msdl",
"SystemUI-statsd",
"WindowManager-Shell-shared-AOSP",
"launcher-testing-shared",
"androidx.lifecycle_lifecycle-common-java8",
"androidx.lifecycle_lifecycle-extensions",
@@ -163,8 +399,9 @@ android_library {
"kotlinx_coroutines",
"com_android_launcher3_flags_lib",
"com_android_wm_shell_flags_lib",
"android.appwidget.flags-aconfig-java",
"com.android.window.flags.window-aconfig-java",
"dagger2",
"jsr330",
"com_android_systemui_shared_flags_lib",
],
manifest: "AndroidManifest-common.xml",
sdk_version: "current",
@@ -172,6 +409,9 @@ android_library {
lint: {
baseline_filename: "lint-baseline.xml",
},
flags_packages: [
"com_android_launcher3_flags",
],
}
//
@@ -179,6 +419,7 @@ android_library {
//
android_app {
name: "Launcher3",
defaults: ["launcher-non-platform-apis-defaults"],
static_libs: [
"Launcher3ResLib",
@@ -190,7 +431,7 @@ android_app {
],
optimize: {
proguard_flags_files: ["proguard.flags"],
proguard_flags_files: [":launcher-proguard-rules"],
// Proguard is disable for testing. Derivarive prjects to keep proguard enabled
enabled: false,
},
@@ -198,6 +439,7 @@ android_app {
sdk_version: "current",
min_sdk_version: min_launcher3_sdk_version,
target_sdk_version: "current",
plugins: ["dagger2-compiler"],
privileged: true,
system_ext_specific: true,
@@ -214,8 +456,12 @@ android_app {
"AndroidManifest-common.xml",
],
lint: {
extra_check_modules: ["Launcher3LintChecker"],
baseline_filename: "lint-baseline.xml",
},
kotlincflags: [
"-Xjvm-default=all",
],
}
// Library with all the dependencies for building quickstep
@@ -226,24 +472,34 @@ android_library {
"quickstep/res",
],
libs: [
"framework-statsd",
"framework-statsd.stubs.module_lib",
],
static_libs: [
"Launcher3ResLib",
"lottie",
"SystemUISharedLib",
"SettingsLibSettingsTheme",
"dagger2",
"protolog-group",
],
manifest: "quickstep/AndroidManifest.xml",
min_sdk_version: "current",
lint: {
disabled_checks: ["MissingClass"],
},
}
// Library with all the source code and dependencies for building Launcher Go
android_library {
name: "Launcher3GoLib",
defaults: [
"launcher_compose_defaults",
"quickstep_compose_defaults",
],
srcs: [
":launcher-src",
":launcher-quickstep-src",
":launcher-quickstep-dagger",
"go/quickstep/src/**/*.java",
"go/quickstep/src/**/*.kt",
],
@@ -258,7 +514,10 @@ android_library {
"QuickstepResLib",
"androidx.room_room-runtime",
],
plugins: ["androidx.room_room-compiler-plugin"],
plugins: [
"androidx.room_room-compiler-plugin",
"dagger2-compiler",
],
manifest: "quickstep/AndroidManifest.xml",
additional_manifests: [
"go/AndroidManifest.xml",
@@ -267,19 +526,27 @@ android_library {
min_sdk_version: "current",
// TODO(b/319712088): re-enable use_resource_processor
use_resource_processor: false,
kotlincflags: [
"-Xjvm-default=all",
],
}
// Library with all the source code and dependencies for building Quickstep
android_library {
name: "Launcher3QuickStepLib",
defaults: [
"launcher_compose_defaults",
"quickstep_compose_defaults",
],
srcs: [
":launcher-src",
":launcher-quickstep-src",
":launcher-quickstep-dagger",
":launcher-build-config",
],
resource_dirs: [],
libs: [
"framework-statsd",
"framework-statsd.stubs.module_lib",
],
// Note the ordering here is important when it comes to resource
// overriding. We want the most specific resource overrides defined
@@ -291,18 +558,23 @@ android_library {
],
manifest: "quickstep/AndroidManifest.xml",
platform_apis: true,
plugins: ["dagger2-compiler"],
min_sdk_version: "current",
// TODO(b/319712088): re-enable use_resource_processor
use_resource_processor: false,
kotlincflags: [
"-Xjvm-default=all",
],
}
// Build rule for Quickstep app.
android_app {
name: "Launcher3QuickStep",
static_libs: ["Launcher3QuickStepLib"],
optimize: {
enabled: false,
proguard_flags_files: [":launcher-proguard-rules"],
enabled: true,
shrink_resources: true,
},
platform_apis: true,
@@ -316,7 +588,10 @@ android_app {
"Launcher2",
"Launcher3",
],
required: ["privapp_whitelist_com.android.launcher3"],
required: [
"privapp_whitelist_com.android.launcher3",
"launcher.quickstep.protolog.pb",
],
resource_dirs: ["quickstep/res"],
@@ -332,13 +607,11 @@ android_app {
}
// Build rule for Launcher3 Go app with quickstep for Android Go devices.
// Note that the following two rules are exactly same, and should
// eventually be merged into a single target
android_app {
name: "Launcher3Go",
static_libs: ["Launcher3GoLib"],
resource_dirs: [],
@@ -349,6 +622,7 @@ android_app {
optimize: {
proguard_flags_files: ["proguard.flags"],
enabled: true,
shrink_resources: true,
},
privileged: true,
@@ -372,9 +646,9 @@ android_app {
include_filter: ["com.android.launcher3.*"],
},
}
android_app {
name: "Launcher3QuickStepGo",
static_libs: ["Launcher3GoLib"],
resource_dirs: [],
@@ -385,6 +659,7 @@ android_app {
optimize: {
proguard_flags_files: ["proguard.flags"],
enabled: true,
shrink_resources: true,
},
privileged: true,
+3 -5
View File
@@ -131,13 +131,11 @@
android:writePermission="${applicationId}.permission.WRITE_SETTINGS"
android:readPermission="${applicationId}.permission.READ_SETTINGS" />
<!--
The content provider for exposing various launcher grid options.
TODO: Add proper permissions
-->
<!-- The content provider for exposing various launcher grid options. -->
<provider
android:name="com.android.launcher3.graphics.GridCustomizationsProvider"
android:name="com.android.launcher3.graphics.LauncherCustomizationProvider"
android:authorities="${applicationId}.grid_control"
android:permission="android.permission.BIND_WALLPAPER"
android:exported="true" />
<!--
+1
View File
@@ -59,6 +59,7 @@
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.SHOW_WORK_APPS" />
<action android:name="android.intent.action.ALL_APPS" />
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.MONKEY"/>
@@ -1,29 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src_build_config" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src_no_quickstep" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src_plugins" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="quickstep" />
<orderEntry type="module" module-name="multivalentTestsForDevice1" />
<orderEntry type="module" module-name="go" />
<orderEntry type="module" module-name="multivalentTests" />
<orderEntry type="module" module-name="tests" />
<orderEntry type="module" module-name="quickstep" />
<orderEntry type="module" module-name="multivalentTestsForDevice1" />
<orderEntry type="module" module-name="go" />
<orderEntry type="module" module-name="tests" />
<orderEntry type="module" module-name="multivalentTests" />
<orderEntry type="module" module-name="quickstep" />
<orderEntry type="module" module-name="go" />
<orderEntry type="module" module-name="multivalentTests" />
<orderEntry type="module" module-name="multivalentTestsForDevice1" />
<orderEntry type="module" module-name="tests" />
</component>
</module>
+31 -7
View File
@@ -6,11 +6,8 @@
adamcohen@google.com
hyunyoungs@google.com
twickham@google.com
vadimt@google.com
winsonc@google.com
jonmiranda@google.com
awickham@google.com
agvard@google.com
# Launcher workspace eng team
@@ -23,19 +20,22 @@ fransebas@google.com
pinyaoting@google.com
andonian@google.com
sihua@google.com
abegovic@google.com
# Multitasking eng team
tracyzhou@google.com
peanutbutter@google.com
jeremysim@google.com
atsjenk@google.com
brianji@google.com
hwwang@google.com
# Overview eng team
alexchau@google.com
samcackett@google.com
silvajordan@google.com
uwaisashraf@google.com
vinayjoglekar@google.com
willosborn@google.com
# Physical Keyboard & Trackpad eng team
patmanning@google.com
@@ -45,11 +45,35 @@ helencheuk@google.com
shamalip@google.com
zakcohen@google.com
# System Navigation team
brianji@google.com
jonmiranda@google.com
jagrutdesai@google.com
randypfohl@google.com
saumyaprakash@google.com
sukeshram@google.com
twickham@google.com
victortulias@google.com
## Note: some of the below overlap and also work on other integrations like Circle to Search.
# All Apps / QSB team
awickham@google.com
brdayauon@google.com
ganjam@google.com
kylim@google.com
# Smartspace team
xilei@google.com
davidct@google.com
iamiam@google.com
jiuyu@google.com
per-file FeatureFlags.java, globs = set noparent
per-file FeatureFlags.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com, captaincole@google.com
per-file FeatureFlags.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com, captaincole@google.com, abegovic@google.com
per-file DeviceConfigWrapper.java, globs = set noparent
per-file DeviceConfigWrapper.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com
per-file DeviceConfigWrapper.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com, abegovic@google.com
# Predictive Back
per-file LauncherBackAnimationController.java = shanh@google.com, gallmann@google.com
per-file LauncherBackAnimationController.java = shanh@google.com, gallmann@google.com
+2 -1
View File
@@ -1,8 +1,10 @@
[Builtin Hooks]
ktfmt = true
bpfmt = true
[Builtin Hooks Options]
ktfmt = --kotlinlang-style
bpfmt = -d
[Tool Paths]
ktfmt = ${REPO_ROOT}/external/ktfmt/ktfmt.sh
@@ -10,4 +12,3 @@ ktfmt = ${REPO_ROOT}/external/ktfmt/ktfmt.sh
[Hook Scripts]
checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --config_xml tools/checkstyle.xml --sha ${PREUPLOAD_COMMIT}
flag_hook = ${REPO_ROOT}/frameworks/base/packages/SystemUI/flag_check.py --msg=${PREUPLOAD_COMMIT_MESSAGE} --files=${PREUPLOAD_FILES} --project=${REPO_PATH}
+5 -4
View File
@@ -1,15 +1,16 @@
# Launcher3
## 更新:20240926
* android15-s1-release
* https://android.googlesource.com/platform/packages/apps/Launcher3/+/refs/heads/android15-s1-release
## 更新:20250619
* android16-s2-release
* https://android.googlesource.com/platform/packages/apps/Launcher3/+/refs/heads/android16-s2-release
## 说明
目前只支持withoutQuickstep编译
## 主要分支
* main (android15-s1-release)--Android-15
* main (android16-s2-release)--Android-16
* Launcher3-15-s1-release (android15-s1-release)
* Launcher3-14-s2-release (android14-s2-release)
* Launcher3-13 (Launcher3-13.0.0_r83(7e9e70085176ea46bf773019a5054942d2fc3811))--Android-13
* Launcher3-13-Qc(高通版本,可以编译Quickstep版本)
+410 -22
View File
@@ -22,13 +22,6 @@ flag {
bug: "316027081"
}
flag {
name: "enable_grid_only_overview"
namespace: "launcher"
description: "Enable a grid-only overview without a focused task."
bug: "257950105"
}
flag {
name: "enable_cursor_hover_states"
namespace: "launcher"
@@ -43,13 +36,6 @@ flag {
bug: "302189128"
}
flag {
name: "enable_overview_icon_menu"
namespace: "launcher"
description: "Enable updated overview icon and menu within task."
bug: "257950105"
}
flag {
name: "enable_focus_outline"
namespace: "launcher"
@@ -237,13 +223,6 @@ flag {
bug: "323886237"
}
flag {
name: "enable_refactor_task_thumbnail"
namespace: "launcher"
description: "Enables rewritten version of TaskThumbnailViews in Overview"
bug: "331753115"
}
flag {
name: "enable_handle_delayed_gesture_callbacks"
namespace: "launcher"
@@ -256,7 +235,7 @@ flag {
flag {
name: "enable_fallback_overview_in_window"
namespace: "launcher"
namespace: "lse_desktop_experience"
description: "Enables fallback recents opening inside of a window instead of an activity."
bug: "292269949"
}
@@ -310,9 +289,418 @@ flag {
}
}
flag {
name: "enable_container_return_animations"
namespace: "launcher"
description: "Enables the container return animation mirroring launches."
bug: "341017746"
}
flag {
name: "floating_search_bar"
namespace: "launcher"
description: "Search bar persists at the bottom of the screen across Launcher states"
bug: "346408388"
}
flag {
name: "all_apps_sheet_for_handheld"
namespace: "launcher"
description: "All Apps will be presented on a bottom sheet in handheld mode"
bug: "374186088"
}
flag {
name: "all_apps_blur"
namespace: "launcher"
description: "Content behind the all apps panel in Launcher will be blurred."
bug: "400827727"
}
flag {
name: "multiline_search_bar"
namespace: "launcher"
description: "Search bar can wrap to multi-line"
bug: "341795751"
}
flag {
name: "enable_multi_instance_menu_taskbar"
namespace: "launcher"
description: "Menu in Taskbar with options to launch and manage multiple instances of the same app"
bug: "355237285"
}
flag {
name: "navigate_to_child_preference"
namespace: "launcher"
description: "Settings screen supports navigating to child preference if the key is not on the screen"
bug: "293390881"
}
flag {
name: "use_new_icon_for_archived_apps"
namespace: "launcher"
description: "Archived apps will use new cloud icon in app title instead of overlay"
bug: "350758155"
metadata {
purpose: PURPOSE_BUGFIX
}
}
flag {
name: "letter_fast_scroller"
namespace: "launcher"
description: "Change fast scroller to a lettered list"
bug: "358673724"
}
flag {
name: "enable_desktop_task_alpha_animation"
namespace: "launcher"
description: "Enables the animation of the desktop task's background view"
bug: "320307666"
metadata {
purpose: PURPOSE_BUGFIX
}
}
flag {
name: "ignore_three_finger_trackpad_for_nav_handle_long_press"
namespace: "launcher"
description: "Ignore three finger trackpad event for nav handle long press"
bug: "342143522"
metadata {
purpose: PURPOSE_BUGFIX
}
}
flag {
name: "work_scheduler_in_work_profile"
namespace: "launcher"
description: "Enables work scheduler view above the work pause button in work profile."
bug: "361589193"
}
flag {
name: "one_grid_specs"
namespace: "launcher"
description: "Defines the new specs for grids based on OneGrid"
bug: "364711064"
}
flag {
name: "one_grid_mounted_mode"
namespace: "launcher"
description: "Support a fixed landscape mode for handheld devices"
bug: "364711735"
}
flag {
name: "one_grid_rotation_handling"
namespace: "launcher"
description: "New landscape approach for the workspace using different rows and columns in landscape and portrait"
bug: "364711814"
}
flag {
name: "grid_migration_refactor"
namespace: "launcher"
description: "Refactor grid migration such that the code is simpler to understand and update"
bug: "358399271"
}
flag {
name: "accessibility_scroll_on_allapps"
namespace: "launcher"
description: "Scroll to item position if accessibility focused"
bug: "265392261"
metadata {
purpose: PURPOSE_BUGFIX
}
}
flag {
name: "enable_dismiss_prediction_undo"
namespace: "launcher"
description: "Show an 'Undo' snackbar when users dismiss a predicted hotseat item"
bug: "270394476"
}
flag {
name: "enable_all_apps_button_in_hotseat"
namespace: "launcher"
description: "Enables displaying the all apps button in the hotseat."
bug: "270393897"
}
flag {
name: "taskbar_quiet_mode_change_support"
namespace: "launcher"
description: "Support changing quiet mode for user profiles in taskbar."
bug: "345760034"
}
flag {
name: "taskbar_overflow"
namespace: "launcher"
description: "Show recent apps in the taskbar overflow."
bug: "368119679"
}
flag {
name: "enable_active_gesture_proto_log"
namespace: "launcher"
description: "Enables tracking active gesture logs in ProtoLog"
bug: "293182501"
}
flag {
name: "enable_recents_window_proto_log"
namespace: "lse_desktop_experience"
description: "Enables tracking recents window logs in ProtoLog"
bug: "292269949"
}
flag {
name: "enable_state_manager_proto_log"
namespace: "lse_desktop_experience"
description: "Enables tracking state manager logs in ProtoLog"
bug: "292269949"
}
flag {
name: "coordinate_workspace_scale"
namespace: "launcher"
description: "Ensure that the workspace and hotseat scale doesn't conflict and transitions smoothly between launching and closing apps"
bug: "366403487"
metadata {
purpose: PURPOSE_BUGFIX
}
}
flag {
name: "enable_tiered_widgets_by_default_in_picker"
namespace: "launcher"
description: "Shows filtered set of widgets by default and an option to show all widgets in the widget picker"
bug: "356127021"
}
flag {
name: "show_taskbar_pinning_popup_from_anywhere"
namespace: "launcher"
description: "Shows the pinning popup view after long-pressing or right-clicking anywhere on the pinned taskbar"
bug: "297325541"
}
flag {
name: "enable_launcher_overview_in_window"
namespace: "lse_desktop_experience"
description: "Enables launcher recents opening inside of a window instead of being hosted in launcher activity."
bug: "292269949"
}
flag {
name: "use_system_radius_for_app_widgets"
namespace: "launcher"
description: "Use system radius for enforced widget corners instead of a separate 16.dp value"
bug: "373351337"
metadata {
purpose: PURPOSE_BUGFIX
}
}
flag {
name: "enable_contrast_tiles"
namespace: "launcher"
description: "Enable launcher app contrast tiles."
bug: "341217082"
}
flag {
name: "msdl_feedback"
namespace: "launcher"
description: "Enable MSDL feedback for Launcher interactions"
bug: "377496684"
}
flag {
name: "enable_pinning_app_with_context_menu"
namespace: "launcher"
description: "Add options to pin/unpin to taskbar to app context menus."
bug: "375648361"
}
flag {
name: "enable_launcher_icon_shapes"
namespace: "launcher"
description: "Enable launcher icon shape customizations"
bug: "348708061"
}
flag {
name: "predictive_back_to_home_polish"
namespace: "launcher"
description: "Enables workspace reveal animation for predictive back-to-home"
bug: "382453424"
metadata {
purpose: PURPOSE_BUGFIX
}
}
flag {
name: "predictive_back_to_home_blur"
namespace: "launcher"
description: "Adds blur for predictive back-to-home"
bug: "342178850"
metadata {
purpose: PURPOSE_BUGFIX
}
}
flag {
name: "enable_launcher_visual_refresh"
namespace: "launcher"
description: "Adds refresh for font family, app longpress menu icons, and pagination dots"
bug: "395145453"
}
flag {
name: "gsf_res"
namespace: "launcher"
description: "Adds refresh for font family. Needs to be fixed to be used in resources."
bug: "395145453"
is_fixed_read_only: true
metadata {
purpose: PURPOSE_BUGFIX
}
}
flag {
name: "restore_archived_shortcuts"
namespace: "launcher"
description: "Makes sure pre-archived pinned shortcuts also get restored"
bug: "375414891"
metadata {
purpose: PURPOSE_BUGFIX
}
}
flag {
name: "restore_archived_app_icons_from_db"
namespace: "launcher"
description: "Restores pre-archived icons from db when available, mimicing promise icons"
bug: "391913214"
metadata {
purpose: PURPOSE_BUGFIX
}
}
flag {
name: "enable_mouse_interaction_changes"
namespace: "launcher"
description: "Changes mouse interaction behavior"
bug: "388897603"
}
flag {
name: "enable_alt_tab_kqs_on_connected_displays"
namespace: "lse_desktop_experience"
description: "Enable Alt + Tab KQS support on connected displays"
bug: "394007677"
}
flag {
name: "expressive_theme_in_taskbar_and_navigation"
namespace: "launcher"
description: "Enables the expressive theme and GSF font styles for Taskbar and Gesture Navigation"
bug: "394613212"
metadata {
purpose: PURPOSE_BUGFIX
}
}
flag {
name: "enable_strict_mode"
namespace: "launcher"
description: "Enable Strict Mode for the Launcher app"
bug: "394651876"
}
flag {
name: "extendible_theme_manager"
namespace: "launcher"
description: "Enables custom theme manager in Launcher"
bug: "381897614"
}
flag {
name: "enable_alt_tab_kqs_flatenning"
namespace: "lse_desktop_experience"
description: "Enable Alt + Tab KQS view to show apps in flattened structure"
bug: "382769617"
}
flag {
name: "enable_gesture_nav_on_connected_displays"
namespace: "lse_desktop_experience"
description: "Enables gesture navigation handling on connected displays"
bug: "382130680"
}
flag {
name: "enable_taskbar_behind_shade"
namespace: "lse_desktop_experience"
description: "Keeps taskbar behind notification shade when its pulled down"
bug: "343194358"
metadata {
purpose: PURPOSE_BUGFIX
}
}
flag {
name: "enable_scalability_for_desktop_experience"
namespace: "launcher"
description: "Enable more grid scale options on the launcher for desktop experience"
bug: "375491272"
}
flag {
name: "enable_gesture_nav_horizontal_touch_slop"
namespace: "launcher"
description: "Enables horizontal touch slop checking in non-vertical fling navigation gestures"
bug: "394364217"
metadata {
purpose: PURPOSE_BUGFIX
}
}
flag {
name: "sync_app_launch_with_taskbar_stash"
namespace: "launcher"
description: "Syncs the two animations (app launch, taskbar stash) so they play at the same time."
bug: "319162553"
metadata {
purpose: PURPOSE_BUGFIX
}
}
flag {
name: "remove_apps_refresh_on_right_click"
namespace: "launcher"
description: "Remove predicted apps refresh on right click"
bug: "343650193"
metadata {
purpose: PURPOSE_BUGFIX
}
}
flag {
name: "enable_taskbar_for_direct_boot"
namespace: "launcher"
description: "Initializes parts of Taskbar before onUserUnlocked"
bug: "324485921"
metadata {
purpose: PURPOSE_BUGFIX
}
}
+12
View File
@@ -0,0 +1,12 @@
package: "com.android.launcher3"
container: "system_ext"
flag {
name: "remove_exclude_from_screen_magnification_flag_usage"
namespace: "accessibility"
description: "Remove the WindowManager PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION flag usage in Launcher"
bug: "369019568"
metadata {
purpose: PURPOSE_BUGFIX
}
}
+9
View File
@@ -0,0 +1,9 @@
package: "com.android.launcher3"
container: "system_ext"
flag {
name: "enable_growth_nudge"
namespace: "desktop_oobe"
description: "Add growth nudge in launcher"
bug: "396165728"
}
+122
View File
@@ -0,0 +1,122 @@
package: "com.android.launcher3"
container: "system_ext"
flag {
name: "enable_grid_only_overview"
namespace: "launcher_overview"
description: "Enable a grid-only overview without a focused task."
bug: "360204325"
}
flag {
name: "enable_overview_icon_menu"
namespace: "launcher_overview"
description: "Enable updated overview icon and menu within task."
bug: "360205084"
}
flag {
name: "enable_refactor_task_thumbnail"
namespace: "launcher_overview"
description: "Enables rewritten version of TaskThumbnailViews in Overview"
bug: "331753115"
}
flag {
name: "enable_hover_of_child_elements_in_taskview"
namespace: "launcher_overview"
description: "Enables child elements to receive hover events in TaskView and respond visually to those hover events."
bug: "342594235"
metadata {
purpose: PURPOSE_BUGFIX
}
}
flag {
name: "enable_large_desktop_windowing_tile"
namespace: "launcher_overview"
description: "Makes the desktop tiles larger and moves them to the front of the list in Overview."
bug: "357860832"
}
flag {
name: "enable_overview_command_helper_timeout"
namespace: "launcher_overview"
description: "Enables OverviewCommandHelper new version with a timeout to prevent the queue to be unresponsive."
bug: "351122926"
metadata {
purpose: PURPOSE_BUGFIX
}
}
flag {
name: "enable_desktop_windowing_carousel_detach"
namespace: "launcher_overview"
description: "Makes the desktop windowing task carousel detaches from fullscreen task carousel during quickswitch."
bug: "353947917"
}
flag {
name: "enable_desktop_exploded_view"
namespace: "launcher_overview"
description: "Enables the non-overlapping layout for desktop windows in Overview mode."
bug: "378011776"
}
flag {
name: "enable_use_top_visible_activity_for_exclude_from_recent_task"
namespace: "launcher_overview"
description: "Enables using the top visible activity for exclude from recent task instead of the activity indicies."
bug: "342627272"
metadata {
purpose: PURPOSE_BUGFIX
}
}
flag {
name: "enable_expressive_dismiss_task_motion"
namespace: "launcher_overview"
description: "Enables expressive motion and animations for dismissing a task in Overview."
bug: "381239462"
}
flag {
name: "enable_separate_external_display_tasks"
namespace: "launcher_overview"
description: "Enables separating external display tasks in Overview."
bug: "391311008"
metadata {
purpose: PURPOSE_BUGFIX
}
}
flag {
name: "enable_overview_on_connected_displays"
namespace: "launcher_overview"
description: "Enable overview on connected displays."
bug: "363251602"
}
flag {
name: "enable_overview_background_wallpaper_blur"
namespace: "launcher_overview"
description: "Enable wallpaper blur in overview."
bug: "369975912"
}
flag {
name: "enable_overview_desktop_tile_wallpaper_background"
namespace: "launcher_overview"
description: "Enable wallpaper background for desktop tasks in overview."
bug: "363257721"
}
flag {
name: "enable_show_enabled_shortcuts_in_accessibility_menu"
namespace: "launcher_overview"
description: "Enables showing the same shortcuts in the Task menu as well as the accessibility actions menu"
bug: "383662632"
metadata {
purpose: PURPOSE_BUGFIX
}
}
BIN
View File
Binary file not shown.
+1 -2
View File
@@ -40,7 +40,7 @@ android_library {
android_library {
name: "animationlib-tests-base",
libs: [
"android.test.base",
"android.test.base.stubs.system",
"androidx.test.core",
],
static_libs: [
@@ -68,7 +68,6 @@ android_robolectric_test {
],
java_resource_dirs: ["tests/robolectric/config"],
instrumentation_for: "TestAnimationLibApp",
upstream: true,
strict_mode: false,
}
+9 -13
View File
@@ -2,14 +2,14 @@ apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
compileSdkVersion COMPILE_SDK
namespace = "com.android.app.animation"
testNamespace = "com.android.app.animation.tests"
compileSdk 36
defaultConfig {
minSdkVersion 26
targetSdkVersion 34
targetSdkVersion 36
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@@ -36,17 +36,13 @@ android {
jvmTarget = '1.8'
freeCompilerArgs = ["-Xjvm-default=all"]
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.1.10"
implementation "androidx.core:core-animation:1.0.0"
implementation "androidx.core:core-ktx:1.13.1"
// androidTestImplementation libs.robolectric
androidTestImplementation "androidx.test.ext:junit:1.2.1"
androidTestImplementation "androidx.test:rules:1.5.0"
implementation "androidx.core:core-ktx:1.9.0"
androidTestImplementation "org.robolectric:robolectric:4.14.1"
androidTestImplementation "androidx.test.ext:junit:1.1.3"
androidTestImplementation "androidx.test:rules:1.4.0"
}
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2022 The Android Open Source Project
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (C) 2024 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -14,6 +13,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@android:color/system_neutral1_500" android:lStar="24" />
</selector>
<resources>
<!-- Animations -->
<item type="id" name="ongoing_animation"/>
</resources>
@@ -0,0 +1,42 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.app.animation
import android.animation.Animator
import android.view.View
/** A static class for general animation-related utilities. */
class Animations {
companion object {
/** Stores a [view]'s ongoing [animation] so it can be cancelled if needed. */
@JvmStatic
fun setOngoingAnimation(view: View, animation: Animator?) {
cancelOngoingAnimation(view)
view.setTag(R.id.ongoing_animation, animation)
}
/**
* Cancels the ongoing animation affecting a [view], if any was previously stored using
* [setOngoingAnimation].
*/
@JvmStatic
fun cancelOngoingAnimation(view: View) {
(view.getTag(R.id.ongoing_animation) as? Animator)?.cancel()
view.setTag(R.id.ongoing_animation, null)
}
}
}
@@ -1,2 +1 @@
sdk=NEWEST_SDK
shadows=com.android.app.animation.robolectric.ShadowAnimationUtils2
@@ -1,12 +0,0 @@
package com.android.app.animation.robolectric
import android.view.animation.AnimationUtils
import org.robolectric.annotation.Implements
import org.robolectric.shadows.ShadowAnimationUtils
/**
* This shadow overwrites [ShadowAnimationUtils] and ensures that the real implementation of
* [AnimationUtils] is used in tests.
*/
@Implements(AnimationUtils::class)
class ShadowAnimationUtils2
@@ -0,0 +1,77 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.app.animation
import android.animation.ValueAnimator
import android.content.Context
import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
class AnimationsTest {
companion object {
const val TEST_DURATION = 1000L
}
private val context: Context = InstrumentationRegistry.getInstrumentation().context
@Test
fun ongoingAnimationsAreStoredAndCancelledCorrectly() {
val view = View(context)
val oldAnimation = FakeAnimator()
Animations.setOngoingAnimation(view, oldAnimation)
oldAnimation.start()
assertEquals(oldAnimation, view.getTag(R.id.ongoing_animation))
assertTrue(oldAnimation.started)
val newAnimation = FakeAnimator()
Animations.setOngoingAnimation(view, newAnimation)
newAnimation.start()
assertEquals(newAnimation, view.getTag(R.id.ongoing_animation))
assertTrue(oldAnimation.cancelled)
assertTrue(newAnimation.started)
Animations.cancelOngoingAnimation(view)
assertNull(view.getTag(R.id.ongoing_animation))
assertTrue(newAnimation.cancelled)
}
}
/** Test animator for tracking start and cancel signals. */
private class FakeAnimator : ValueAnimator() {
var started = false
var cancelled = false
override fun start() {
started = true
cancelled = false
}
override fun cancel() {
started = false
cancelled = true
}
}
-42
View File
@@ -1,42 +0,0 @@
171450807
170675311
170338029
170338170
160544577
171171594
170488559
171131394
171131394
171026321
170648272
170752716
170611866
170702596
170487752
170665892
168608912
170636685
169771796
141126144
166614700
168805872
170263425
169221288
143965596
169221287
167259591
156044202
169438169
164926736
168653219
169963211
170121063
169988381
169980192
169221288
169385783
168167693
169796517
169330678
168818961
168608912
-39
View File
@@ -1,39 +0,0 @@
141126144
143965596
156044202
160544577
164926736
166614700
167259591
168167693
168608912
168653219
168805872
168818961
169221287
169221288
169330678
169385783
169438169
169771796
169796517
169963211
169980192
169988381
170121063
170263425
170338029
170338170
170487752
170488559
170611866
170636685
170648272
170665892
170675311
170702596
170752716
171026321
171131394
171171594
171450807
+11 -7
View File
@@ -28,7 +28,7 @@ android {
defaultConfig {
minSdkVersion 26
targetSdkVersion 35
targetSdkVersion 36
versionCode 1
versionName "1.0"
@@ -49,8 +49,8 @@ android {
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
sourceCompatibility JavaVersion.VERSION_21
targetCompatibility JavaVersion.VERSION_21
}
// The flavor dimensions for build variants (e.g. aospWithQuickstep, aospWithoutQuickstep)
@@ -146,7 +146,7 @@ android {
}
}
kotlinOptions {
jvmTarget = '17'
jvmTarget = '21'
}
buildFeatures {
@@ -179,13 +179,14 @@ dependencies {
// implementation project(':UiTestsLibLauncher')
// withQuickstepImplementation project(':SharedLibWrapper')
implementation 'com.google.protobuf:protobuf-javalite:4.26.1'
implementation 'androidx.core:core-ktx:1.13.1'
implementation 'androidx.core:core-ktx:1.16.0'
// Recents lib dependency
withQuickstepImplementation fileTree(dir: "${FRAMEWORK_PREBUILTS_DIR}", include: 'sysui_shared.jar')
// Required for AOSP to compile. This is already included in the sysui_shared.jar
withoutQuickstepImplementation fileTree(dir: "${FRAMEWORK_PREBUILTS_DIR}", include: 'plugin_core.jar')
withoutQuickstepImplementation fileTree(dir: "${FRAMEWORK_PREBUILTS_DIR}", include: 'WindowManager-Shell-15.jar')
testImplementation 'junit:junit:4.13.2'
androidTestImplementation "org.mockito:mockito-core:1.9.5"
@@ -196,10 +197,13 @@ dependencies {
androidTestImplementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.3'
androidTestImplementation "androidx.annotation:annotation:${ANDROID_X_VERSION}"
api 'com.airbnb.android:lottie:3.3.0'
api 'com.airbnb.android:lottie:6.6.6'
implementation 'com.google.dagger:dagger:2.56.2'
annotationProcessor 'com.google.dagger:dagger-compiler:2.56.2'
implementation 'com.google.android.material:material:1.10.0'
implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.slice:slice-builders:1.0.0'
implementation 'androidx.graphics:graphics-shapes:1.1.0-alpha01'
implementation "androidx.window:window:1.3.0"
}
BIN
View File
Binary file not shown.
+46
View File
@@ -0,0 +1,46 @@
// Copyright (C) 2025 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package {
default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
default_applicable_licenses: ["Android-Apache-2.0"],
}
java_library_host {
name: "Launcher3LintChecker",
srcs: ["src/**/*.kt"],
plugins: ["auto_service_plugin"],
libs: [
"auto_service_annotations",
"lint_api",
],
kotlincflags: ["-Xjvm-default=all"],
}
java_test_host {
name: "Launcher3LintCheckerTest",
defaults: ["AndroidLintCheckerTestDefaults"],
srcs: ["tests/**/*.kt"],
data: [
":androidx.annotation_annotation",
":dagger2",
":kotlinx-coroutines-core",
],
device_common_data: [
":framework",
],
static_libs: [
"Launcher3LintChecker",
],
}
@@ -0,0 +1,68 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.Scope
import com.android.tools.lint.detector.api.Severity
import com.android.tools.lint.detector.api.SourceCodeScanner
import org.jetbrains.uast.UClass
/** Detector to identify custom usage of Android's Dialog within the Launcher3 codebase. */
class CustomDialogDetector : Detector(), SourceCodeScanner {
override fun applicableSuperClasses(): List<String> {
return listOf(DIALOG_CLASS_NAME)
}
override fun visitClass(context: JavaContext, declaration: UClass) {
val superTypeClassNames = declaration.superTypes.mapNotNull { it.resolve()?.qualifiedName }
if (superTypeClassNames.contains(DIALOG_CLASS_NAME)) {
context.report(
ISSUE,
declaration,
context.getNameLocation(declaration),
"Class implements Dialog",
)
}
}
companion object {
private const val DIALOG_CLASS_NAME = "android.app.Dialog"
@JvmField
val ISSUE =
Issue.create(
id = "IllegalUseOfCustomDialog",
briefDescription = "dialogs should not be used in Launcher",
explanation =
"""
Don't use custom Dialogs within the launcher code base, instead consider utilizing
AbstractFloatingView to display content that should float above the launcher where
it can be correctly managed for dismissal.
"""
.trimIndent(),
category = Category.CORRECTNESS,
priority = 10,
severity = Severity.ERROR,
implementation =
Implementation(CustomDialogDetector::class.java, Scope.JAVA_FILE_SCOPE),
)
}
}
@@ -0,0 +1,44 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.launcher3.lint
import CustomDialogDetector
import com.android.tools.lint.client.api.IssueRegistry
import com.android.tools.lint.client.api.Vendor
import com.android.tools.lint.detector.api.CURRENT_API
import com.android.tools.lint.detector.api.Issue
import com.google.auto.service.AutoService
@AutoService(IssueRegistry::class)
@Suppress("UnstableApiUsage")
class Launcher3IssueRegistry : IssueRegistry() {
override val issues: List<Issue>
get() = listOf(CustomDialogDetector.ISSUE)
override val api: Int
get() = CURRENT_API
override val minApi: Int
get() = 8
override val vendor: Vendor =
Vendor(
vendorName = "Android",
feedbackUrl = "http://b/issues/new?component=78010",
contact = "abegovic@google.com",
)
}
@@ -0,0 +1,78 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.launcher3.lint
import CustomDialogDetector
import com.android.tools.lint.checks.infrastructure.TestFiles
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
/** Test for [CustomDialogDetector]. */
class CustomDialogDetectorTest : Launcher3LintDetectorTest() {
override fun getDetector(): Detector = CustomDialogDetector()
override fun getIssues(): List<Issue> = listOf(CustomDialogDetector.ISSUE)
@Test
fun classDoesNotExtendDialog_noViolation() {
lint()
.files(
TestFiles.kotlin(
"""
package test.pkg
class SomeClass
"""
.trimIndent()
),
*androidStubs,
)
.issues(CustomDialogDetector.ISSUE)
.run()
.expectClean()
}
@Test
fun classDoesExtendDialog_violation() {
lint()
.files(
TestFiles.kotlin(
"""
package test.pkg
import android.app.Dialog
class SomeClass(context: Context) : Dialog(context)
"""
.trimIndent()
),
*androidStubs,
)
.issues(CustomDialogDetector.ISSUE)
.run()
.expect(
("""
src/test/pkg/SomeClass.kt:5: Error: Class implements Dialog [IllegalUseOfCustomDialog]
class SomeClass(context: Context) : Dialog(context)
~~~~~~~~~
1 errors, 0 warnings
""")
.trimIndent()
)
}
}
@@ -0,0 +1,60 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.launcher3.lint
import com.android.tools.lint.checks.infrastructure.LintDetectorTest
import com.android.tools.lint.checks.infrastructure.TestFiles
import com.android.tools.lint.checks.infrastructure.TestLintTask
import java.io.File
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
/**
* Abstract class that should be used by any test for launcher 3 lint detectors.
*
* When you write your test, ensure that you pass [androidStubs] as part of your [TestFiles]
* definition.
*/
@RunWith(JUnit4::class)
abstract class Launcher3LintDetectorTest : LintDetectorTest() {
/**
* Customize the lint task to disable SDK usage completely. This ensures that running the tests
* in Android Studio has the same result as running the tests in atest
*/
override fun lint(): TestLintTask =
super.lint().allowMissingSdk(true).sdkHome(File("/dev/null"))
companion object {
private val libraryNames =
arrayOf(
"androidx.annotation_annotation.jar",
"dagger2.jar",
"framework.jar",
"kotlinx-coroutines-core.jar",
)
/**
* This file contains stubs of framework APIs and System UI classes for testing purposes
* only. The stubs are not used in the lint detectors themselves.
*/
val androidStubs =
libraryNames
.map { TestFiles.LibraryReferenceTestFile(File(it).canonicalFile) }
.toTypedArray()
}
}
-934
View File
@@ -1,934 +0,0 @@
COMMAND>> git log f99351888c3e5a128559678304fefd647472bc7f..4c3952dc60fc78d3816012a86d7e71747ef34c74
commit 4c3952dc60fc78d3816012a86d7e71747ef34c74
Merge: cb403d9e5 70e8b1572
Author: TreeHugger Robot <treehugger-gerrit@google.com>
Date: Fri Oct 23 00:27:39 2020 +0000
Merge "Minor stylistic changes in Workspace.java." into ub-launcher3-master
commit cb403d9e5235c7323bc2fdffe6a264d17bb6d0a6
Author: Pinyao Ting <pinyaoting@google.com>
Date: Thu Oct 22 16:07:08 2020 -0700
flip default value of minimal device feature flag
Test: manual
Change-Id: Iaf46dffb935bdf4b46e7c57d547bdc697250ec56
commit a97557a15eb111616d868120a9f4659f1b451fa2
Merge: f5ce80b8a 932a327eb
Author: Tracy Zhou <tracyzhou@google.com>
Date: Thu Oct 22 18:22:56 2020 +0000
Merge "Consider overscroll adjustment of RecentsView for live tile" into ub-launcher3-master
commit 932a327ebf0587b8324b9fea7d31328b2f6719a8
Author: Tracy Zhou <tracyzhou@google.com>
Date: Wed Oct 21 23:29:00 2020 -0700
Consider overscroll adjustment of RecentsView for live tile
Fixes: 171450807
Test: manual
Change-Id: I83eebf1f6b61c67f289db51aabe5a971815d0df1
commit f5ce80b8a0a1636fc8159475177a07b281492c88
Author: Hilary Huo <hhuo@google.com>
Date: Wed Oct 14 16:35:55 2020 -0700
[pixel-search] Latency analysis, add logging statement in launcher
Bug: b/170675311
Change-Id: I229ace399085bea1c3f9535eb713edd329dff8bd
commit 31b03941ef3aa17edc08c1b509d4fa23766f2d2c
Merge: e0a50c9e3 0731273d5
Author: Tracy Zhou <tracyzhou@google.com>
Date: Wed Oct 21 20:03:57 2020 +0000
Merge "Track live tile better by considering resistance animation" into ub-launcher3-master
commit 0731273d5409149fca32dfb2ad76eab45f6ea79a
Author: Tracy Zhou <tracyzhou@google.com>
Date: Wed Oct 21 12:03:40 2020 -0700
Track live tile better by considering resistance animation
Fixes: 170338029
Test: Manual
Change-Id: I66536bae567aa94385d5e0352cec9d46d512927a
commit e0a50c9e3f1d4b9f113d6afae01ff2c4ed452fba
Merge: d2c27a595 acfac6187
Author: Alex Chau <alexchau@google.com>
Date: Wed Oct 21 17:02:57 2020 +0000
Merge "Use Diplay.getMetrics in DisplayController" into ub-launcher3-master
commit d2c27a595065d43bbea37dd2a512d37080f5233e
Merge: ff8febabb 8b488ccc2
Author: Tracy Zhou <tracyzhou@google.com>
Date: Wed Oct 21 07:19:48 2020 +0000
Merge "[Live Tile] Support launching running task animation" into ub-launcher3-master
commit 8b488ccc2e433a708c8b06f0b6866f2a305e4b0a
Author: Tracy Zhou <tracyzhou@google.com>
Date: Wed Oct 14 12:13:04 2020 -0700
[Live Tile] Support launching running task animation
Fixes: 170338170
Test: manual
Change-Id: I2526b7cfbacaea7899b8e2ed233f913630071d36
commit 70e8b157219e9090ba5e47fdfa51b2b92e98449d
Author: Andy Wickham <awickham@google.com>
Date: Wed Oct 7 23:00:06 2020 -0700
Minor stylistic changes in Workspace.java.
Change-Id: Ib07611f27cbc427d11abccd8b74ea144485752f7
commit acfac6187dd9d13d55b566a77a5da867a1813573
Author: Alex Chau <alexchau@google.com>
Date: Mon Oct 19 18:00:39 2020 +0100
Use Diplay.getMetrics in DisplayController
- This is a workaround of b/163815566, where DisplayMetrics is stale
when onDisplayChanged is called.
- Instead of relying on stale DisplayConext, get the DisplayMetrics
from the Display directly.
- Also optimized how DisplayController.Info is created by passing in
Display only
- Use mDisplayContext.getDisplay directly if availalbe
Bug: 163815566, 160544577
Test: DPI looks correct on device boot
Change-Id: I2a7454bb8cf2073ce592e8662781b87fc998444f
(cherry picked from commit 177c38243dc3bf245d1f7db3c265dfb56522f441)
commit ff8febabb039a3c27ee068f85119860a048b917c
Merge: b03d2b416 102746823
Author: TreeHugger Robot <treehugger-gerrit@google.com>
Date: Tue Oct 20 17:46:09 2020 +0000
Merge "Makes Plugin Settings gear adjust to dark mode." into ub-launcher3-master
commit b03d2b41616d479ba360fa4f97e57722c7f57b8e
Merge: fb79f5541 caa1e9c39
Author: Hyunyoung Song <hyunyoungs@google.com>
Date: Tue Oct 20 15:25:56 2020 +0000
Merge "Search query method should support multiple consumers" into ub-launcher3-master
commit fb79f5541dcbe587002756bb40a3c632d38cc25a
Merge: f6b05068d cf0b275a4
Author: Schneider Victor-tulias <victortulias@google.com>
Date: Tue Oct 20 13:51:06 2020 +0000
Merge "Add the ability to specify a list of tutorial steps in the gesture sandbox tutorial intent." into ub-launcher3-master
commit f6b05068d901d4e989b2e107c06f9c7a6e7b113f
Author: Hyunyoung Song <hyunyoungs@google.com>
Date: Tue Oct 20 00:19:29 2020 -0700
Invert the badging
Bug: 171171594
Change-Id: If84fdc03254105c843e16f39f479505b16e1cd5f
commit caa1e9c39978cb3b467b5ac441eb39b5e883fa2e
Author: Hyunyoung Song <hyunyoungs@google.com>
Date: Mon Oct 12 13:56:02 2020 -0700
Search query method should support multiple consumers
Bug: 170488559
Change-Id: I64bef9523d3c3950c4ca3a4b9ce1d506d1672200
commit 10274682339bb60cb24c50536b4f48f921970f3c
Author: Andy Wickham <awickham@google.com>
Date: Mon Oct 19 19:06:52 2020 -0700
Makes Plugin Settings gear adjust to dark mode.
It wasn't visible in dark mode before because it was
black on black. This makes it adjust automatically.
Change-Id: I5176cffc01842509ddafc4f30ff5029a0c4b8050
commit 744a0fbeae8efaa942d21c61e25012d86f5ff81e
Merge: 29c79947e 71f24588c
Author: TreeHugger Robot <treehugger-gerrit@google.com>
Date: Mon Oct 19 22:17:26 2020 +0000
Merge "Call click event on IME quick select for SearchResultIcon" into ub-launcher3-master
commit 29c79947ecf82f662d02004ba9a7289017fc0783
Merge: 13a2a010d a68ac3e5d
Author: TreeHugger Robot <treehugger-gerrit@google.com>
Date: Mon Oct 19 18:42:05 2020 +0000
Merge "Removing condition for CUJ tracing/metrics" into ub-launcher3-master
commit 71f24588c0a66449a0c68bcb360a8c671914ce75
Author: Samuel Fufa <sfufa@google.com>
Date: Mon Oct 19 10:19:31 2020 -0700
Call click event on IME quick select for SearchResultIcon
Bug: 171131394
Change-Id: I8a703e8d0ca10570e3f774510610d3fb4c0eaab8
commit 13a2a010decd87eeaf8932430c692f587d2de165
Author: Samuel Fufa <sfufa@google.com>
Date: Sun Oct 18 21:19:57 2020 -0700
Handle IME event for SearchResultIcon
Bug: 171131394
Test: Manual
Change-Id: I2ed1c61053c78aaecc3324418229d69634a72ae4
commit 1f79eeda76246534697e92740defc7f73c3c8d14
Author: Samuel Fufa <sfufa@google.com>
Date: Fri Oct 16 02:01:31 2020 -0700
Remove hardcoded itemTypes from SearchTarget
- Introduces componentName and userHandle members to SearchTarget
- SearchTargetEvent now has searchTarget member
- Builder pattern for SearchTarget and SearchTargetEvent
- Search backend should add headers manually instead of launcher inferring sections
Bug: 171026321
Test: Manual
Change-Id: I28e0455e82b925277a17703b9aa061c8f9f15262
commit a68ac3e5dd23095cea7c872c0ff1c5042d1695ba
Author: vadimt <vadimt@google.com>
Date: Fri Oct 16 10:48:28 2020 -0700
Removing condition for CUJ tracing/metrics
Is doesn't reflect whether jank monitors is collecting metrics,
which will eventually be always true anyways.
Change-Id: Iaebdc838ed2b2cebd32c8c48d7e45bdd93f76fb4
commit 9228ff53c2fb26850b7bd92d86214a6aaebb11d3
Author: Sunny Goyal <sunnygoyal@google.com>
Date: Mon Oct 12 13:43:51 2020 -0700
Trimming activity and task label
Bug: 170648272
Change-Id: Icd099acee65305e0aa0f98a2a301a0df8a27cf07
commit 7a09177e500a53205f9969bb6cbd4251d54e8fde
Merge: 37ed5ead3 314761a80
Author: TreeHugger Robot <treehugger-gerrit@google.com>
Date: Thu Oct 15 22:36:14 2020 +0000
Merge "Setup SearchResultIcon for single cell results" into ub-launcher3-master
commit 37ed5ead391df5747003b2d3a345be0347362f19
Merge: d5bbe6809 702ed2788
Author: TreeHugger Robot <treehugger-gerrit@google.com>
Date: Thu Oct 15 22:06:12 2020 +0000
Merge "Fix the issue where shortcuts are removed in minimal device mode" into ub-launcher3-master
commit 314761a80819a6e64a136161f51eebb0f0528c4d
Author: Samuel Fufa <sfufa@google.com>
Date: Wed Oct 14 10:15:07 2020 -0700
Setup SearchResultIcon for single cell results
SearchResultIcon will be able to render apps, shortcuts and remote actions. It can also handle its own focused state drawing.
Screenshot: https://screenshot.googleplex.com/C3KgjJtLQTBPgaf
Bug: 170752716
Test: Manual
Change-Id: I460a9c128ea3f5814784e342c5d5fa5b7e310882
commit 702ed2788678ac744c768aad6a6302e7cf91a26b
Author: Pinyao Ting <pinyaoting@google.com>
Date: Wed Oct 14 11:17:04 2020 -0700
Fix the issue where shortcuts are removed in minimal device mode
When loading the workspace, Launcher pins/unpins shortcuts in comply
with the loaded workspace. Since minimal device mode creates a mostly
empty workspace, existing shortcuts are getting unpinned as a result.
To mitigate the issue this CL compares the db name and only invoke
sanitizeData when it matches the one defined in InvariantDeviceProfile.
Bug: 170611866
Test: manual
1. add some deep shortcut in workspace (e.g. long tap on chrome, drag
"incognito tab" to workspace)
2. opt-in to sunshine fishfood (g/sunshine-teamfood)
3. enable bedtime mode with minimal device in Settings -> Digital
Wellbeing -> Show Your Data -> Bedtime mode -> Customize -> minimal
device
4. toggle bedtime mode, wait for apps in minimal device to show, then
toggle off bedtime mode
5. verify the deep shortcut still exist
Change-Id: Ie18216ecb288e7481aa2404c4cb3ea418aee85cb
commit cf0b275a48d3c9f91a346f7fc24b9604f6dde25a
Author: Schneider Victor-tulias <victortulias@google.com>
Date: Tue Oct 6 09:33:40 2020 -0400
Add the ability to specify a list of tutorial steps in the gesture sandbox tutorial intent.
Added tutorial_steps string array in the intent to allow specifying an ordered list of tutorial steps.
Change-Id: Ic42a65598a74a64f8441a22f58c6cd988a5762e3
commit d5bbe6809dcc056fbfc307909b171651f0fb3044
Author: Samuel Fufa <sfufa@google.com>
Date: Wed Oct 14 15:39:38 2020 -0700
Rename shrotcut container to deep-shrotcuts
Change-Id: If94f0dfa447235f3b1a652f7b6c749695b42d97c
commit 26c1105fa04c2bcc156051e51df90a6a253349bb
Author: Samuel Fufa <sfufa@google.com>
Date: Tue Oct 13 01:12:03 2020 -0700
[search api part 1] Setup centralized SearchEventTracker
- Rename AdapterItemWIthPayload to SearchAdapterItem, PayloadResultHandler to SearchTargetHandler
- Setup SliceViewWrapper for self contained slices
Bug: 170702596
Change-Id: I0baf984ec8123c95011abcc17372f8d055e98ad7
commit 057f2d0d7df67e3680e479ac76b48b30d8bcf884
Merge: 4bb65ff51 9a6145efb
Author: TreeHugger Robot <treehugger-gerrit@google.com>
Date: Tue Oct 13 01:57:47 2020 +0000
Merge "Introduce shortcut container for hotseat event reporting" into ub-launcher3-master
commit 4bb65ff516c6d9a429971ab7e04780792d5cb751
Merge: 69740e62b 2afcab804
Author: TreeHugger Robot <treehugger-gerrit@google.com>
Date: Tue Oct 13 00:07:36 2020 +0000
Merge "Search UI clean up" into ub-launcher3-master
commit 2afcab804b638ff3b9da5bad40c8f70bdcaae78d
Author: Samuel Fufa <sfufa@google.com>
Date: Mon Oct 12 15:38:14 2020 -0700
Search UI clean up
- Resolve spacing issue when work profile is installed
- Cache play icons and use icon shape
- Only draw focus indicator for the first result
Bug: 170487752
Bug: 170665892
Change-Id: I864d2e796786637132e127ef9b418c0a76c74d6e
commit 69740e62be3800fc918648009645f7a8e52cb73d
Merge: 2d7bfc878 979da64d8
Author: TreeHugger Robot <treehugger-gerrit@google.com>
Date: Mon Oct 12 20:53:29 2020 +0000
Merge "Add app start source info of apps launched from launcher" into ub-launcher3-master
commit 2d7bfc8782e9ed01178672aeb09ba2a6a07f4f4c
Author: Jon Miranda <jonmiranda@google.com>
Date: Mon Oct 12 12:09:22 2020 -0700
Fix shadowRadius not being used in swipe up animation.
Bug: 168608912
Change-Id: I08f7bb057237e5061d5f1fc29afb488b204ee385
commit a433fe1fb34715efb38ed094f39da49fce8cd51e
Merge: 2de606fe7 0471b9836
Author: Sunny Goyal <sunnygoyal@google.com>
Date: Mon Oct 12 18:08:35 2020 +0000
Merge "Using FrameCallbacks instead of windowCallbacks for surface removal" into ub-launcher3-master
commit 9a6145efb85f2bbdaccc07166a55e22c15fe27db
Author: Samuel Fufa <sfufa@google.com>
Date: Mon Oct 12 09:33:00 2020 -0700
Introduce shortcut container for hotseat event reporting
Bug: 170636685
Test: Manual
Change-Id: I5abeb17976bbafdc8cc74fb8b9a586d544c682fc
commit 2de606fe731573c081fd2d6ba166e21ea6aa2e9c
Author: Yogisha Dixit <ydixit@google.com>
Date: Mon Oct 12 15:36:07 2020 +0100
Delete the minimal database to force refresh.
Bug: 169771796
Test: manual
Change-Id: Ic2188bb162f295c208346861fddc137ace19ddcb
commit 0471b9836c9e382dc14bdc3abdf8502fb2b9f266
Author: Sunny Goyal <sunnygoyal@google.com>
Date: Wed Sep 23 13:54:37 2020 -0700
Using FrameCallbacks instead of windowCallbacks for surface removal
WindowCallbacks is called during the draw pass, before the frame has
been sent to the surfaceFlinger. Frame callback will provide a closer
approximation for when the frame is actually rendered on screen.
Bug: 141126144
Change-Id: I62aab526c2ca24b00b5e7b312b36080f26c7b439
commit 2727434c44d06882925369bf4b43687a06be4a3f
Merge: 59f532fe9 1b9e199b3
Author: Schneider Victor-tulias <victortulias@google.com>
Date: Fri Oct 9 20:09:08 2020 +0000
Merge "Fix hotseat and prediction row to allow updates when empty." into ub-launcher3-master
commit 59f532fe9e2b1817c094641f3c7c517f42e4faf0
Merge: d2bfce71f b5334e3f0
Author: TreeHugger Robot <treehugger-gerrit@google.com>
Date: Fri Oct 9 19:52:54 2020 +0000
Merge "Improve search section header" into ub-launcher3-master
commit 979da64d8254599c332d83bf94f3f1fc3fe45fef
Author: Riddle Hsu <riddlehsu@google.com>
Date: Tue Sep 22 21:52:40 2020 +0800
Add app start source info of apps launched from launcher
Bug: 166614700
Test: Enable statsd log: "adb shell cmd stats print-logs"
adb logcat | grep statsd | grep "(48)"
The line may contain 0x100000->1[I] 0x110000->10[I]
that means 1=from launcher and 10=latency 10ms.
Change-Id: Iddaff7066b66e241ba58ec87129ddbe2c531dc7e
(cherry picked from commit 7bdf3574a3bff06a377b4364877687bfa7619d06)
commit d2bfce71f776fd05633dfd915dfc664309274677
Merge: ed4530fed 222afb970
Author: Winson Chung <winsonc@google.com>
Date: Fri Oct 9 16:39:06 2020 +0000
Merge "Comply with the ISystemUiProxy.aidl change" into ub-launcher3-master
commit ed4530fedda0bf876f91d0745fc70d0f30d42991
Merge: 692d2109a 9d4a96ed0
Author: Winson Chung <winsonc@google.com>
Date: Fri Oct 9 16:39:06 2020 +0000
Merge "Add latency metrics for recents gesture" into ub-launcher3-master
commit 1b9e199b3d9c81c793758d96bb03e0c51c1b3fb1
Author: Schneider Victor-tulias <victortulias@google.com>
Date: Thu Oct 8 15:50:22 2020 -0400
Fix hotseat and prediction row to allow updates when empty.
Rotating the screen in the homescreen empties the hotseat, however it does not get populated while it is visible to the user. The user should not be able to see an empty hotseat or prediction row if predictions are available. It should therefore be possible to populate these when they are empty even if they are visible to the user.
Change-Id: I8e5252bd29050c2cd9d443aedcb3f3e305c0e2d7
commit b5334e3f07f0561808a2d6e9bba55f1e3a89191e
Author: Hyunyoung Song <hyunyoungs@google.com>
Date: Fri Oct 9 00:50:48 2020 -0700
Improve search section header
Change-Id: I47cf207f0d0ab792c0e7a47c9d1185eec087ec88
commit 692d2109a6702706d24b3b819d115882f7362509
Author: Samuel Fufa <sfufa@google.com>
Date: Thu Oct 8 18:42:48 2020 -0700
invalidate itemDecoration on predictedRow focus draw
Change-Id: I66c731f00ae1c1292c51ff281957f05fd2d70dfa
commit 8d5b118060bff7f7518a9a14c0be5d265621f14c
Author: Samuel Fufa <sfufa@google.com>
Date: Thu Oct 8 13:11:25 2020 -0700
Revert PredictionRow shuoldDraw check
+ Show Rounded play result icons
Bug: 168805872
Test: Manual
Change-Id: I663c7f7ca1f1ac072e5e9c441deabef7c3fbd97b
commit 86f8df6cf954ac27ab092b9ef8a4db3c9979c4cb
Merge: 4d19854b2 16045060c
Author: Hilary Huo <hhuo@google.com>
Date: Thu Oct 8 18:43:51 2020 +0000
Merge "[pixel-search] add escape hatch" into ub-launcher3-master
commit 4d19854b25a54599fe9b0ac8be9d60cf6c21d7ba
Merge: 0827e1e32 ab9ad20be
Author: Samuel Fufa <sfufa@google.com>
Date: Thu Oct 8 18:40:56 2020 +0000
Merge "Search UI cleanup" into ub-launcher3-master
commit 16045060c35639aea85afc572bea768d16e6c9f9
Author: Hilary Huo <hhuo@google.com>
Date: Thu Oct 8 10:18:41 2020 -0700
[pixel-search] add escape hatch
Change-Id: I33ffea1fc0859564955380d7d1db317293d1a2cb
commit 0827e1e32a5f99fa02418dae37270c6db8c989d2
Merge: 3463f0a87 68d7a6e5b
Author: Andy Wickham <awickham@google.com>
Date: Thu Oct 8 16:53:29 2020 +0000
Merge "Adds feature flag for BC Smartspace." into ub-launcher3-master
commit ab9ad20be600d1cbdc6b54a491d5fbb4c2cf9c16
Author: Samuel Fufa <sfufa@google.com>
Date: Wed Oct 7 15:18:24 2020 -0700
Search UI cleanup
- offset all apps header padding with search input margin
- avoid check shouldDraw check on HeaderRow. (race condition)
Bug: 170263425
Change-Id: I11a1fbb448aa6afd18ec0984af9bb8b1d7600f69
commit 68d7a6e5b28af8cc55bdae7efc24cc7ebee81257
Author: Andy Wickham <awickham@google.com>
Date: Wed Oct 7 14:27:17 2020 -0700
Adds feature flag for BC Smartspace.
Change-Id: Iaf9fb7507d0ccd004a4e00188c75dadd6a059246
commit 3463f0a876ff486ce03e160134e0504158271a92
Merge: 2470d812a 4b7f38b8f
Author: TreeHugger Robot <treehugger-gerrit@google.com>
Date: Wed Oct 7 20:09:04 2020 +0000
Merge "Align fallback result query with result text" into ub-launcher3-master
commit 2470d812a1ae989e67781e5056b534ad9a960819
Merge: cae7d74d8 7a6e4c931
Author: Vadim Tryshev <vadimt@google.com>
Date: Wed Oct 7 20:04:09 2020 +0000
Merge "Annotating Quick Switch CUJ for 3-button mode" into ub-launcher3-master
commit cae7d74d898769727105850ea5473c2c0ae25fdb
Merge: e9bf2bd14 1fddddb4f
Author: Tony Wickham <twickham@google.com>
Date: Wed Oct 7 18:32:48 2020 +0000
Merge "Update launcher_trace.proto for quick switch" into ub-launcher3-master
commit 7a6e4c931f13b369bfa4328196b4632d6d848a19
Author: vadimt <vadimt@google.com>
Date: Tue Oct 6 14:09:16 2020 -0700
Annotating Quick Switch CUJ for 3-button mode
Bug: 169221288
Change-Id: Ief62345fe6004dde699f44aa0c90329b7cd84e8b
commit 4b7f38b8fa004b514244304fcc07ff514a2fa46b
Author: Samuel Fufa <sfufa@google.com>
Date: Tue Oct 6 18:37:46 2020 -0700
Align fallback result query with result text
screenshot: https://screenshot.googleplex.com/6Daj5vdmz2jmznX
bug: 169438169
test: Manual
Change-Id: Ie621ed3c834aec5e9467607da4f685d05d152183
commit 222afb970434c7972589adfc509bd2c256ca6556
Author: Hongwei Wang <hwwang@google.com>
Date: Fri Oct 2 13:51:36 2020 -0700
Comply with the ISystemUiProxy.aidl change
Two methods are added to support communications between Launcher and
SysUI when user swipes an auto PiP-able Activity to home.
Bug: 143965596
Test: N/A
Change-Id: I2c73a287a094e882bde3cd71c27f9f66ae20e64a
(cherry picked from commit 88ddae38db924f700082a113670ce5a719116a95)
commit 9d4a96ed029fdad1e369d5eedd082938f0dc9e01
Author: Riddle Hsu <riddlehsu@google.com>
Date: Wed Sep 30 00:32:04 2020 +0800
Add latency metrics for recents gesture
Pass the touch down time to RecentsAnimation#startRecentsActivity.
Bug: 169221287
Test: Enable statsd log: "adb shell cmd stats print-logs"
Touch gesture navigation bar.
adb logcat | grep statsd | grep "(48)"
The line may contain 0x100000->4[I] 0x110000->20[I]
that means 4=by recents and 20=latency 20ms.
Change-Id: I81ee804895b7712f4d925736f5b4694c11a12cbe
(cherry picked from commit 63623967b83edad56db58173ebb6687c685b9177)
commit e9bf2bd14c9a7a48f8f93687932d41b1418cf4e4
Merge: 73ae75474 d028937e7
Author: Tracy Zhou <tracyzhou@google.com>
Date: Wed Oct 7 02:19:04 2020 +0000
Merge "[Live tile] Finish recents animation when the phone goes to sleep in live tile mode" into ub-launcher3-master
commit 1fddddb4f30505e0fc9bb2e7c0d88b38ad900e54
Author: Tony Wickham <twickham@google.com>
Date: Tue Sep 29 17:29:06 2020 -0700
Update launcher_trace.proto for quick switch
Sample output from one entry:
entry {
elapsed_realtime_nanos: 440461382888540
launcher {
touch_interaction_service {
service_connected: true
overview_component_obvserver {
overview_activity_started: true
overview_activity_resumed: false
}
input_consumer {
name: "TYPE_OTHER_ACTIVITY:TYPE_ONE_HANDED"
swipe_handler {
gesture_state {
endTarget: NEW_TASK
}
is_recents_attached_to_app_window: true
scroll_offset: 846
app_to_overview_progress: 0
}
}
}
}
}
Bug: 167259591
Change-Id: I7f199d88f1d736efcea6b9165b8c4b77a5d27c58
commit 73ae75474ec1dd8807d814ea6c22323905d2070c
Merge: 8a6f3e40d 0ebbc1880
Author: TreeHugger Robot <treehugger-gerrit@google.com>
Date: Tue Oct 6 23:18:44 2020 +0000
Merge "Removing tracing for a gone flake" into ub-launcher3-master
commit 8a6f3e40d0321217c624055db7929c397e455e0c
Merge: e29a9f796 565ed4ff6
Author: TreeHugger Robot <treehugger-gerrit@google.com>
Date: Tue Oct 6 22:49:40 2020 +0000
Merge "Update Search UI" into ub-launcher3-master
commit 0ebbc18803aaf8ef2f6db7d628d7ae1ce322e842
Author: vadimt <vadimt@google.com>
Date: Tue Oct 6 14:52:27 2020 -0700
Removing tracing for a gone flake
Bug: 156044202
Change-Id: Ice142bb941fee7b731f46c2073fab17d83bbc871
commit 565ed4ff69b534812818a2b9aa8789a1aea210eb
Author: Samuel Fufa <sfufa@google.com>
Date: Wed Sep 30 10:42:07 2020 -0700
Update Search UI
[preview attached to bug]
Bug: 169438169
Test: Manual
Change-Id: I085f3dd38ac373c1afab82a637ec08715a6e0cc5
commit e29a9f7961e6db0915bc028ef7e871dcb2c8bde0
Merge: 2c5ed10ff be17bdcd2
Author: Jayaprakash Sundararaj <jayaprakashs@google.com>
Date: Tue Oct 6 21:00:20 2020 +0000
Merge "[Search] Add logging to People and badding as to icons." into ub-launcher3-master
commit be17bdcd221f501c45876abe2249c1007858d0c0
Author: jayaprakashs <jayaprakashs@google.com>
Date: Mon Oct 5 09:01:52 2020 -0700
[Search] Add logging to People and badding as to icons.
Change-Id: I65948a2faca436216a94aa46139d425b8eade827
commit 2c5ed10ffa1a870de35f9b3c0c558270aff498dd
Merge: b2b65a1ef 8ed9707cf
Author: Tracy Zhou <tracyzhou@google.com>
Date: Tue Oct 6 18:40:57 2020 +0000
Merge "[Live Tile] Support launching another task (other than the current running task) in Overview" into ub-launcher3-master
commit b2b65a1ef58b020923d112051535b6eb83b582df
Merge: 3cf264f49 4c14f4b9e
Author: Samuel Fufa <sfufa@google.com>
Date: Tue Oct 6 17:45:35 2020 +0000
Merge "Avoid double search item highlight" into ub-launcher3-master
commit 8ed9707cf3a4300cb61942f08f0752c80eed086b
Author: Tracy Zhou <tracyzhou@google.com>
Date: Mon Sep 14 23:25:37 2020 -0700
[Live Tile] Support launching another task (other than the current running task) in Overview
- Get rid of the defer cancelation logic
- Render animation on the task view of the task being launched upon task view appeared callback
- Finish the recents animation upon the end of the recents window animation
Fixes: 164926736
Test: manual
Change-Id: Ibffb6a9c74c235efc8615a22b0306551532c7b61
commit 3cf264f498e37c482fa4c559bf48ffa791279585
Author: Schneider Victor-tulias <victortulias@google.com>
Date: Tue Sep 22 12:58:38 2020 -0700
Prevent hotseat updates if it is visible to the user.
Test: manual
Fixes: 168653219
Changing app icons under the user's finger could be disruptive. Added a checks for whether the hotseatand all apps predictions are visible and callbacks to update them when they become hidden.
Change-Id: Ib9e6e904e9f662ecfaeea6a2fe21d1d81ba39b96
commit b6aff1f56d55a36256446ec3970d92e9da39b98c
Author: Hyunyoung Song <hyunyoungs@google.com>
Date: Mon Oct 5 16:08:35 2020 -0700
Fix NPE inside RecentsOrientedState
Bug: 169963211
Change-Id: I86dd337dc1b862f3fa99b91b47fa250076233f96
commit eab40983b9a48b933bde5ca95a82ebd4d83b233d
Merge: 83ce7c0b5 020e628f2
Author: Jonathan Miranda <jonmiranda@google.com>
Date: Mon Oct 5 22:20:27 2020 +0000
Merge "Add shadow radius to windows during app launch / close animations." into ub-launcher3-master
commit 83ce7c0b5e461386bb92883a8d6cefe8365cd9ae
Merge: 679d920bf d6b1f3c08
Author: TreeHugger Robot <treehugger-gerrit@google.com>
Date: Mon Oct 5 19:18:39 2020 +0000
Merge "Action icon should be used as a badge instead of main icon" into ub-launcher3-master
commit 679d920bf5151cffed4e8186c12c25d8d7907af9
Merge: e108cc609 0c943966d
Author: TreeHugger Robot <treehugger-gerrit@google.com>
Date: Mon Oct 5 19:13:50 2020 +0000
Merge "Add null check for input receiver before updating batching" into ub-launcher3-master
commit e108cc609d0a7fd58f0c7e16ce45fa79be6dd272
Merge: 470403eb5 f622e42bf
Author: TreeHugger Robot <treehugger-gerrit@google.com>
Date: Mon Oct 5 18:39:58 2020 +0000
Merge "Removing unused proto extensions" into ub-launcher3-master
commit 470403eb58879380e2edac2262dc7f40327b2a15
Merge: a5130482a 1d7ed30db
Author: TreeHugger Robot <treehugger-gerrit@google.com>
Date: Mon Oct 5 18:29:54 2020 +0000
Merge "Remove widgets that no longer fit the workspace in their current spans." into ub-launcher3-master
commit 4c14f4b9eda8332347c81e0cf51c5de4dbc06399
Author: Samuel Fufa <sfufa@google.com>
Date: Mon Oct 5 10:50:00 2020 -0700
Avoid double search item highlight
Change-Id: Ic2e28b18f6d5e3ed32cd5646bc3bb4789c378e57
commit 0c943966d373d8ae7eef2b08e88ac44bf57d8a8d
Author: Winson Chung <winsonc@google.com>
Date: Mon Oct 5 10:23:27 2020 -0700
Add null check for input receiver before updating batching
- A change in the system (ie. sysui crash or nav mode change) could
cause the input monitor to be disposed before the swipe animation
settles
Bug: 170121063
Test: Kill sysui while swiping up
Change-Id: I1417b109fecdb98fae6197c7038dbe9307470853
commit a5130482aee1b0592661bc1c6e178a0de7a163da
Merge: b21819e18 7fcd74abb
Author: TreeHugger Robot <treehugger-gerrit@google.com>
Date: Mon Oct 5 17:14:21 2020 +0000
Merge "Suggest result should launch Bug: 169980192" into ub-launcher3-master
commit d028937e74a9ea6d36e463de4c87ed37283bbdf6
Author: Tracy Zhou <tracyzhou@google.com>
Date: Sat Oct 3 00:36:53 2020 -0700
[Live tile] Finish recents animation when the phone goes to sleep in live tile mode
Fixes: 169988381
Test: manual
Change-Id: Ic71d3e6767eadb6854dbd46581bf9d3242c161a4
commit 7fcd74abb399100ac8243be6ca28c09cc8adc8c8
Author: Hyunyoung Song <hyunyoungs@google.com>
Date: Fri Oct 2 19:20:11 2020 -0700
Suggest result should launch
Bug: 169980192
Change-Id: I762245a5cc4740d093c9cb3b44a508e9e3f2b763
commit b21819e181e99504c22c6ca028261a1f2665c6f9
Merge: 931bce369 a762b0241
Author: TreeHugger Robot <treehugger-gerrit@google.com>
Date: Fri Oct 2 22:07:12 2020 +0000
Merge "Annotating Quick Switch CUJ for non-3-button modes" into ub-launcher3-master
commit a762b02418695f5a1ff2f96586660de8c3610280
Author: vadimt <vadimt@google.com>
Date: Fri Oct 2 13:56:28 2020 -0700
Annotating Quick Switch CUJ for non-3-button modes
Bug: 169221288
Change-Id: I7145a9e28a2f0a789d19d2a0e3d15630c6e50f6a
commit 931bce3697595a214023bc72923dad47a61d5711
Merge: c935ba6b8 733e3c609
Author: TreeHugger Robot <treehugger-gerrit@google.com>
Date: Fri Oct 2 19:19:50 2020 +0000
Merge "Moving some initializations to the background thread" into ub-launcher3-master
commit c935ba6b8a2ec163533c0b19309dacb6199e6552
Merge: a4111f250 58804ac52
Author: Sunny Goyal <sunnygoyal@google.com>
Date: Fri Oct 2 18:26:06 2020 +0000
Merge "Adding stats log for add item flow" into ub-launcher3-master
commit 733e3c609b7653a36e58747c881458ec00d98df8
Author: Sunny Goyal <sunnygoyal@google.com>
Date: Tue Sep 29 10:32:32 2020 -0700
Moving some initializations to the background thread
HandlerThread.getLooper blocks until the thread is ready. Instead
moving all looper dependency to the new thread itself.
Change-Id: I240e8c56b855a991433a7fe93875059e6dab146b
commit 58804ac5257f45dddbf7a6db35cf8f369ee1e88e
Author: Sunny Goyal <sunnygoyal@google.com>
Date: Wed Sep 16 16:27:40 2020 -0700
Adding stats log for add item flow
Bug: 169385783
Bug: 168167693
Change-Id: I37395f1b118727f67e0f14c02f945b8213b165c8
commit a4111f250003328d1aef8bbaab59512208ec46cb
Merge: 8d14dbe04 f6b72c4ad
Author: Hilary Huo <hhuo@google.com>
Date: Fri Oct 2 17:41:22 2020 +0000
Merge "[pixel-search] Bug fix: automatically launch screenshot + center&crop remoteaction icon" into ub-launcher3-master
commit f622e42bf6983d3adb95386bfd6375d281f1d4f2
Author: Sunny Goyal <sunnygoyal@google.com>
Date: Fri Oct 2 10:35:56 2020 -0700
Removing unused proto extensions
Change-Id: I6d0319c99934dad5176b6f70b895a4ca772ec45f
commit d6b1f3c086f9ac097cd03e1ee898b153478ec11a
Author: Hyunyoung Song <hyunyoungs@google.com>
Date: Fri Oct 2 00:26:35 2020 -0700
Action icon should be used as a badge instead of main icon
Bug: 169796517
Change-Id: I3f07fdc2ae6e1af463701f942c26c3ca5d836ee2
commit f6b72c4ad1d2e082441a64c4d6a5a02ee8a251ca
Author: Hilary Huo <hhuo@google.com>
Date: Thu Oct 1 12:26:48 2020 -0700
[pixel-search] Bug fix: automatically launch screenshot + center&crop remoteaction icon
Bug: b/169330678
Change-Id: Id5f8a0ce6d68f7ed9e4d1ff258ee3772229eb63b
commit 1d7ed30dba4b2c71fc7b0981532a872a13e5aedb
Author: Jon Miranda <jonmiranda@google.com>
Date: Wed Sep 23 12:15:43 2020 -0700
Remove widgets that no longer fit the workspace in their current spans.
This can happen when display size changes.
We compare span sizes of widget in the db to the min sizes of the widget
in the current display size. If the widget can no longer fit in its existing
spans, we remove it.
Also update test widgets to have minWidth/minHeight of 1dp. This ensures that
the spanX, spanY, min* values remain consistent between different test devices.
Bug: 168818961
Change-Id: I723372e4582658f78b2f23ced9073cb77977a6b8
commit 020e628f22cc7975beab439c6da26af2f9ebc15b
Author: Jon Miranda <jonmiranda@google.com>
Date: Mon Sep 28 17:01:42 2020 -0700
Add shadow radius to windows during app launch / close animations.
Bug: 168608912
Change-Id: I2ec50b0b3711c0861659f9c641bbc05fcdeaab45
@@ -14,11 +14,13 @@
* limitations under the License.
*/
package com.android.quickstep.task.viewmodel
package com.android.launcher3.compose.core
import kotlinx.coroutines.flow.MutableStateFlow
import android.content.Context
import android.view.View
class TaskViewData {
// This is typically a View concern but it is used to invalidate rendering in other Views
val scale = MutableStateFlow(1f)
interface BaseComposeFacade {
fun isComposeAvailable(): Boolean
fun initComposeView(appContext: Context): View
}
+32
View File
@@ -0,0 +1,32 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.compose
import android.content.Context
import android.view.View
import com.android.launcher3.compose.core.BaseComposeFacade
object ComposeFacade : BaseComposeFacade {
override fun isComposeAvailable(): Boolean = false
override fun initComposeView(appContext: Context): View {
error(
"Compose is not available. Make sure to check isComposeAvailable() before calling any" +
" other function on ComposeFacade."
)
}
}
+28
View File
@@ -0,0 +1,28 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.compose
import android.content.Context
import android.view.View
import androidx.compose.ui.platform.ComposeView
import com.android.launcher3.compose.core.BaseComposeFacade
object ComposeFacade : BaseComposeFacade {
override fun isComposeAvailable(): Boolean = true
override fun initComposeView(appContext: Context): View = ComposeView(appContext)
}
+1
View File
@@ -57,6 +57,7 @@
android:enabled="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.ALL_APPS" />
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.MONKEY"/>
-35
View File
@@ -1,35 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/quickstep/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="multivalentTestsForDevice" />
<orderEntry type="module" module-name="quickstep" />
<orderEntry type="module" module-name="tests1" />
<orderEntry type="module" module-name="Launcher3-refs_heads_android15-s1-release" />
<orderEntry type="module" module-name="multivalentTestsForDevice1" />
<orderEntry type="module" module-name="multivalentTests" />
<orderEntry type="module" module-name="multivalentTests1" />
<orderEntry type="module" module-name="tests" />
<orderEntry type="module" module-name="quickstep" />
<orderEntry type="module" module-name="multivalentTestsForDevice" />
<orderEntry type="module" module-name="multivalentTestsForDevice1" />
<orderEntry type="module" module-name="tests1" />
<orderEntry type="module" module-name="tests" />
<orderEntry type="module" module-name="multivalentTests" />
<orderEntry type="module" module-name="multivalentTests1" />
<orderEntry type="module" module-name="Launcher3-refs_heads_android15-s1-release" />
<orderEntry type="module" module-name="quickstep" />
<orderEntry type="module" module-name="multivalentTestsForDevice" />
<orderEntry type="module" module-name="multivalentTests1" />
<orderEntry type="module" module-name="multivalentTests" />
<orderEntry type="module" module-name="multivalentTestsForDevice1" />
<orderEntry type="module" module-name="Launcher3-refs_heads_android15-s1-release" />
<orderEntry type="module" module-name="tests" />
<orderEntry type="module" module-name="tests1" />
</component>
</module>
+3 -3
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_share_drop_target_label" msgid="5804774105974539508">"Deel program"</string>
<string name="app_share_drop_target_label" msgid="5804774105974539508">"Deel app"</string>
<string name="action_listen" msgid="2370304050784689486">"Luister"</string>
<string name="action_translate" msgid="8028378961867277746">"Vertaal"</string>
<string name="action_search" msgid="6269564710943755464">"Lens"</string>
@@ -9,12 +9,12 @@
<string name="dialog_cancel" msgid="6464336969134856366">"KANSELLEER"</string>
<string name="dialog_settings" msgid="6564397136021186148">"INSTELLINGS"</string>
<string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Vertaal of luister na teks op skerm"</string>
<string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Inligting soos teks op jou skerm, webadresse en skermskote kan met Google gedeel word.\n\nGaan na "<b>"Instellings &gt; Programme &gt; Verstekprogramme &gt; Digitale Assistent-program"</b>" om te verander watter inligting jy deel."</string>
<string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Inligting soos teks op jou skerm, webadresse en skermskote kan met Google gedeel word.\n\nGaan na "<b>"Instellings &gt; Apps &gt; Verstekapps &gt; Digitale Assistent-app"</b>" om te verander watter inligting jy deel."</string>
<string name="assistant_not_selected_title" msgid="5017072974603345228">"Kies \'n assistent om hierdie kenmerk te gebruik"</string>
<string name="assistant_not_selected_text" msgid="3244613673884359276">"Kies \'n digitalebystandprogram in Instellings om na teks op jou skerm te luister of dit te vertaal"</string>
<string name="assistant_not_supported_title" msgid="1675788067597484142">"Verander jou assistent om hierdie kenmerk te gebruik"</string>
<string name="assistant_not_supported_text" msgid="1708031078549268884">"Verander jou digitalebystandprogram in Instellings om na teks op jou skerm te luister of dit te vertaal"</string>
<string name="tooltip_listen" msgid="7634466447860989102">"Tik hier om na teks op hierdie skerm te luister"</string>
<string name="tooltip_translate" msgid="4184845868901542567">"Tik hier om teks op hierdie skerm te vertaal"</string>
<string name="toast_p2p_app_not_shareable" msgid="7229739094132131536">"Hierdie program kan nie gedeel word nie"</string>
<string name="toast_p2p_app_not_shareable" msgid="7229739094132131536">"Hierdie app kan nie gedeel word nie"</string>
</resources>
+1 -1
View File
@@ -4,7 +4,7 @@
<string name="app_share_drop_target_label" msgid="5804774105974539508">"Абагуліць праграму"</string>
<string name="action_listen" msgid="2370304050784689486">"Праслухаць"</string>
<string name="action_translate" msgid="8028378961867277746">"Перакласці"</string>
<string name="action_search" msgid="6269564710943755464">"Аб\'ектыў"</string>
<string name="action_search" msgid="6269564710943755464">"Аб’ектыў"</string>
<string name="dialog_acknowledge" msgid="2804025517675853172">"ЗРАЗУМЕЛА"</string>
<string name="dialog_cancel" msgid="6464336969134856366">"СКАСАВАЦЬ"</string>
<string name="dialog_settings" msgid="6564397136021186148">"НАЛАДЫ"</string>
+1 -1
View File
@@ -5,7 +5,7 @@
<string name="action_listen" msgid="2370304050784689486">"گوش دادن"</string>
<string name="action_translate" msgid="8028378961867277746">"ترجمه"</string>
<string name="action_search" msgid="6269564710943755464">"لنز"</string>
<string name="dialog_acknowledge" msgid="2804025517675853172">"متوجه‌ام"</string>
<string name="dialog_acknowledge" msgid="2804025517675853172">"متوجهم"</string>
<string name="dialog_cancel" msgid="6464336969134856366">"لغو"</string>
<string name="dialog_settings" msgid="6564397136021186148">"تنظیمات"</string>
<string name="niu_actions_confirmation_title" msgid="3863451714863526143">"ترجمه نوشتار روی صفحه‌نمایش یا گوش دادن به آن"</string>
+2 -2
View File
@@ -14,7 +14,7 @@
<string name="assistant_not_selected_text" msgid="3244613673884359276">"כדי להאזין לטקסט שבמסך או לתרגם אותו, צריך לבחור אפליקציית עוזר דיגיטלי ב\'הגדרות\'"</string>
<string name="assistant_not_supported_title" msgid="1675788067597484142">"צריך לשנות את העוזר הדיגיטלי כדי להשתמש בתכונה הזו"</string>
<string name="assistant_not_supported_text" msgid="1708031078549268884">"כדי להאזין לטקסט שבמסך או לתרגם אותו, צריך לשנות את אפליקציית העוזר הדיגיטלי ב\'הגדרות\'"</string>
<string name="tooltip_listen" msgid="7634466447860989102">"צריך להקיש כאן כדי להאזין לטקסט שבמסך הזה"</string>
<string name="tooltip_translate" msgid="4184845868901542567">"צריך להקיש כאן כדי לתרגם את הטקסט שבמסך הזה"</string>
<string name="tooltip_listen" msgid="7634466447860989102">"צריך ללחוץ כאן כדי להאזין לטקסט שבמסך הזה"</string>
<string name="tooltip_translate" msgid="4184845868901542567">"צריך ללחוץ כאן כדי לתרגם את הטקסט שבמסך הזה"</string>
<string name="toast_p2p_app_not_shareable" msgid="7229739094132131536">"אי אפשר לשתף את האפליקציה הזו"</string>
</resources>
+3 -3
View File
@@ -9,11 +9,11 @@
<string name="dialog_cancel" msgid="6464336969134856366">"रद्द गर्नुहोस्"</string>
<string name="dialog_settings" msgid="6564397136021186148">"सेटिङ"</string>
<string name="niu_actions_confirmation_title" msgid="3863451714863526143">"स्क्रिनमा देखिने पाठ अनुवाद गर्नुहोस् वा पढेर सुनाउनुहोस्"</string>
<string name="niu_actions_confirmation_text" msgid="2105271481950866089">"तपाईंको स्क्रिनमा देखिने पाठ, वेब ठेगाना र स्क्रिनसटलगायतका जानकारी Google सँग सेयर गर्न सकिन्छ।\n\nकुन कुन जानकारी सेयर गर्न दिने भन्ने सेटिङ बदल्न "<b>"सेटिङ &gt; एप &gt; डिफल्ट एप &gt; डिजिटल सहायक एप"</b>" मा जानुहोस्।"</string>
<string name="niu_actions_confirmation_text" msgid="2105271481950866089">"तपाईंको स्क्रिनमा देखिने पाठ, वेब ठेगाना र स्क्रिनसटलगायतका जानकारी Google सँग सेयर गर्न सकिन्छ।\n\nकुन कुन जानकारी सेयर गर्न दिने भन्ने सेटिङ बदल्न "<b>"सेटिङ &gt; एप &gt; डिफल्ट एप &gt; डिजिटल एसिस्टेन्ट एप"</b>" मा जानुहोस्।"</string>
<string name="assistant_not_selected_title" msgid="5017072974603345228">"तपाईं यो सुविधा चलाउन चाहनुहुन्छ भने कुनै सहायक छनौट गर्नुहोस्"</string>
<string name="assistant_not_selected_text" msgid="3244613673884359276">"तपाईं आफ्नो स्क्रिनमा देखिने पाठ सुन्न वा अनुवाद गर्न चाहनुहुन्छ भने सेटिङमा गई कुनै डिजिटल सहायक एप छनौट गर्नुहोस्"</string>
<string name="assistant_not_selected_text" msgid="3244613673884359276">"तपाईं आफ्नो स्क्रिनमा देखिने पाठ सुन्न वा अनुवाद गर्न चाहनुहुन्छ भने सेटिङमा गई कुनै डिजिटल एसिस्टेन्ट एप छनौट गर्नुहोस्"</string>
<string name="assistant_not_supported_title" msgid="1675788067597484142">"तपाईं यो सुविधा चलाउन चाहनुहुन्छ भने आफ्नो सहायक परिवर्तन गर्नुहोस्"</string>
<string name="assistant_not_supported_text" msgid="1708031078549268884">"तपाईं आफ्नो स्क्रिनमा देखिने पाठ सुन्न वा अनुवाद गर्न चाहनुहुन्छ भने सेटिङमा गई कुनै डिजिटल सहायक एप परिर्वर्तन गर्नुहोस्"</string>
<string name="assistant_not_supported_text" msgid="1708031078549268884">"तपाईं आफ्नो स्क्रिनमा देखिने पाठ सुन्न वा अनुवाद गर्न चाहनुहुन्छ भने सेटिङमा गई कुनै डिजिटल एसिस्टेन्ट एप परिर्वर्तन गर्नुहोस्"</string>
<string name="tooltip_listen" msgid="7634466447860989102">"तपाईं यो स्क्रिनमा देखिने पाठ सुन्न चाहनुहुन्छ यहाँ ट्याप गर्नुहोस्"</string>
<string name="tooltip_translate" msgid="4184845868901542567">"तपाईं यो स्क्रिनमा देखिने पाठ अनुवाद गर्न चाहनुहुन्छ यहाँ ट्याप गर्नुहोस्"</string>
<string name="toast_p2p_app_not_shareable" msgid="7229739094132131536">"यो एप अरूलाई चलाउन दिन मिल्दैन"</string>
+1 -1
View File
@@ -16,7 +16,7 @@
-->
<resources>
<!-- App themes -->
<style name="LauncherTheme" parent="@style/BaseLauncherTheme">
<style name="LauncherTheme" parent="@style/DynamicColorsBaseLauncherTheme">
<item name="overviewButtonTextColor">@color/go_overview_text_color</item>
<item name="overviewButtonIconColor">@color/go_overview_text_color</item>
<item name="overviewButtonBackgroundColor">@color/go_overview_button_color</item>
@@ -37,12 +37,13 @@ import android.widget.Toast;
import androidx.core.content.FileProvider;
import com.android.internal.annotations.VisibleForTesting;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.AppShareabilityChecker;
import com.android.launcher3.model.AppShareabilityJobService;
import com.android.launcher3.model.AppShareabilityManager;
import com.android.launcher3.model.AppShareabilityManager.ShareabilityStatus;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.views.ActivityContext;
@@ -114,17 +115,17 @@ public final class AppSharing {
* The Share App system shortcut, used to initiate p2p sharing of a given app
*/
public final class Share extends SystemShortcut<Launcher> {
private final PopupDataProvider mPopupDataProvider;
private final boolean mSharingEnabledForUser;
private final Set<View> mBoundViews = Collections.newSetFromMap(new WeakHashMap<>());
private boolean mIsEnabled = true;
private StatsLogManager mStatsLogManager;
public Share(Launcher target, ItemInfo itemInfo, View originalView) {
super(R.drawable.ic_share, R.string.app_share_drop_target_label, target, itemInfo,
originalView);
mPopupDataProvider = target.getPopupDataProvider();
mStatsLogManager = ActivityContext.lookupContext(originalView.getContext())
.getStatsLogManager();
mSharingEnabledForUser = bluetoothSharingEnabled(target);
if (!mSharingEnabledForUser) {
setEnabled(false);
@@ -150,8 +151,7 @@ public final class AppSharing {
@Override
public void onClick(View view) {
ActivityContext.lookupContext(view.getContext())
.getStatsLogManager().logger().log(LAUNCHER_SYSTEM_SHORTCUT_APP_SHARE_TAP);
mStatsLogManager.logger().log(LAUNCHER_SYSTEM_SHORTCUT_APP_SHARE_TAP);
if (!mIsEnabled) {
showCannotShareToast(view.getContext());
return;
@@ -240,6 +240,11 @@ public final class AppSharing {
public boolean isEnabled() {
return mIsEnabled;
}
@VisibleForTesting
void setStatsLogManager(StatsLogManager statsLogManager) {
mStatsLogManager = statsLogManager;
}
}
/**
@@ -0,0 +1,71 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.util;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.content.Context;
import android.os.Looper;
import java.util.concurrent.ExecutionException;
/**
* Utility class for defining singletons which are initiated on main thread.
*/
public class MainThreadInitializedObject<T extends SafeCloseable> {
private final ObjectProvider<T> mProvider;
private T mValue;
public MainThreadInitializedObject(ObjectProvider<T> provider) {
mProvider = provider;
}
public T get(Context context) {
Context app = context.getApplicationContext();
if (app instanceof ObjectSandbox sc) {
return sc.getObject(this);
}
if (mValue == null) {
if (Looper.myLooper() == Looper.getMainLooper()) {
mValue = TraceHelper.allowIpcs("main.thread.object", () -> mProvider.get(app));
} else {
try {
return MAIN_EXECUTOR.submit(() -> get(context)).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
}
return mValue;
}
public interface ObjectProvider<T> {
T get(Context context);
}
/** Sandbox for isolating {@link MainThreadInitializedObject} instances from Launcher. */
public interface ObjectSandbox {
/**
* Find a cached object from mObjectMap if we have already created one. If not, generate
* an object using the provider.
*/
<T extends SafeCloseable> T getObject(MainThreadInitializedObject<T> object);
}
}
@@ -31,6 +31,7 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.drawable.ColorDrawable;
@@ -47,6 +48,7 @@ import android.widget.Button;
import android.widget.TextView;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.BaseActivity;
@@ -56,9 +58,8 @@ import com.android.launcher3.views.ArrowTipView;
import com.android.quickstep.util.AssistContentRequester;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.views.GoOverviewActionsView;
import com.android.quickstep.views.TaskView.TaskContainer;
import com.android.quickstep.views.TaskContainer;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
import java.lang.annotation.Retention;
@@ -131,7 +132,7 @@ public final class TaskOverlayFactoryGo extends TaskOverlayFactory {
* Called when the current task is interactive for the user
*/
@Override
public void initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix,
public void initOverlay(Task task, @Nullable Bitmap thumbnail, Matrix matrix,
boolean rotated) {
if (mDialog != null && mDialog.isShowing()) {
// Redraw the dialog in case the layout changed
@@ -148,8 +149,7 @@ public final class TaskOverlayFactoryGo extends TaskOverlayFactory {
// Disable Overview Actions for Work Profile apps
boolean isManagedProfileTask =
UserManager.get(mApplicationContext).isManagedProfile(task.key.userId);
boolean isAllowedByPolicy = mTaskContainer.getThumbnailViewDeprecated().isRealSnapshot()
&& !isManagedProfileTask;
boolean isAllowedByPolicy = isRealSnapshot() && !isManagedProfileTask;
getActionsView().setCallbacks(new OverlayUICallbacksGoImpl(isAllowedByPolicy, task));
mTaskPackageName = task.key.getPackageName();
mSharedPreferences = LauncherPrefs.getPrefs(mApplicationContext);
+5 -3
View File
@@ -5,9 +5,11 @@ org.gradle.parallel=true
ANDROID_X_VERSION=1+
# https://developer.android.google.cn/build/gradle-build-overview?hl=zh-cn
#GRADLE_CLASS_PATH=com.android.tools.build:gradle:3.5.1
#GRADLE_CLASS_PATH=com.android.tools.build:gradle:7.4.1
GRADLE_CLASS_PATH=com.android.tools.build:gradle:8.4.1
#GRADLE_CLASS_PATH=com.android.tools.build:gradle:8.4.1
GRADLE_CLASS_PATH=com.android.tools.build:gradle:8.10.1
#PROTOBUF_CLASS_PATH=com.google.protobuf:protobuf-gradle-plugin:0.8.8
#PROTOBUF_CLASS_PATH=com.google.protobuf:protobuf-gradle-plugin:0.8.18
@@ -16,6 +18,6 @@ PROTOBUF_CLASS_PATH=com.google.protobuf:protobuf-gradle-plugin:0.9.4
PROTOBUF_DEPENDENCY=com.google.protobuf.nano:protobuf-javanano:3.1.0
BUILD_TOOLS_VERSION=28.0.3
COMPILE_SDK=android-35
COMPILE_SDK=android-36
#KOTLIN=org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10
KOTLIN=org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.0
KOTLIN=org.jetbrains.kotlin:kotlin-gradle-plugin:2.1.20
+1 -1
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+7 -2
View File
@@ -19,31 +19,36 @@ package {
android_library {
name: "iconloader_base",
sdk_version: "current",
min_sdk_version: "26",
min_sdk_version: "31",
static_libs: [
"androidx.core_core",
"com_android_launcher3_flags_lib",
],
resource_dirs: [
"res",
],
srcs: [
"src/**/*.java",
"src/**/*.kt",
],
}
android_library {
name: "iconloader",
sdk_version: "system_current",
min_sdk_version: "26",
min_sdk_version: "31",
static_libs: [
"androidx.core_core",
"com_android_launcher3_flags_lib",
],
resource_dirs: [
"res",
],
srcs: [
"src/**/*.java",
"src/**/*.kt",
"src_full_lib/**/*.java",
"src_full_lib/**/*.kt",
],
apex_available: [
"//apex_available:platform",
-36
View File
@@ -1,36 +0,0 @@
plugins {
id 'com.android.library'
}
android {
namespace = "com.android.launcher3.icons"
compileSdkVersion COMPILE_SDK
defaultConfig {
minSdkVersion 26
targetSdkVersion 35
}
sourceSets {
main {
java.srcDirs = ['src', 'src_full_lib']
manifest.srcFile 'AndroidManifest.xml'
res.srcDirs = ['res']
}
}
lint {
abortOnError false
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
tasks.withType(JavaCompile) {
options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
}
}
dependencies {
implementation "androidx.core:core:1.13.1"
}
+34
View File
@@ -0,0 +1,34 @@
plugins {
id("com.android.library")
id("kotlin-android")
}
android {
namespace = "com.android.launcher3.icons"
compileSdk = 36
defaultConfig {
minSdk = 26
targetSdk = 36
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
}
sourceSets {
named("main") {
java.setSrcDirs(listOf("src", "src_full_lib"))
manifest.srcFile("AndroidManifest.xml")
res.setSrcDirs(listOf("res"))
}
}
kotlin {
jvmToolchain(21)//1.8(8)
}
}
dependencies {
implementation("androidx.core:core:1.15.0")
implementation("androidx.annotation:annotation:1.9.1")
// api(project(":NexusLauncher:Flags"))
}
+3
View File
@@ -27,4 +27,7 @@
<string name="calendar_component_name" translatable="false"></string>
<string name="clock_component_name" translatable="false"></string>
<!-- Configures whether to enable forced theme icon, disabled by default -->
<bool name="enable_forced_themed_icon">false</bool>
</resources>
@@ -0,0 +1,59 @@
package com.android.launcher3
object Flags {
@JvmStatic
fun restoreArchivedAppIconsFromDb(): Boolean {
return true
}
@JvmStatic
fun forceMonochromeAppIcons(): Boolean {
return false
}
@JvmStatic
fun useNewIconForArchivedApps(): Boolean {
return true
}
@JvmStatic
fun enableLauncherIconShapes(): Boolean {
return true
}
@JvmStatic
fun enableSmartspaceRemovalToggle(): Boolean {
return true
}
@JvmStatic
fun enableWorkspaceInflation(): Boolean {
return true
}
@JvmStatic
fun oneGridSpecs(): Boolean {
return true
}
@JvmStatic
fun enableSupportForArchiving(): Boolean {
return true
}
@JvmStatic
fun restoreArchivedShortcuts(): Boolean {
return true
}
@JvmStatic
fun newCustomizationPickerUi(): Boolean {
return true
}
@JvmStatic
fun enableScalingRevealHomeAnimation(): Boolean {
return true
}
}
@@ -1,5 +1,6 @@
package com.android.launcher3.icons;
import static android.graphics.Color.BLACK;
import static android.graphics.Paint.ANTI_ALIAS_FLAG;
import static android.graphics.Paint.DITHER_FLAG;
import static android.graphics.Paint.FILTER_BITMAP_FLAG;
@@ -7,6 +8,7 @@ import static android.graphics.drawable.AdaptiveIconDrawable.getExtraInsetFracti
import static com.android.launcher3.icons.BitmapInfo.FLAG_INSTANT;
import static com.android.launcher3.icons.ShadowGenerator.BLUR_FACTOR;
import static com.android.launcher3.icons.ShadowGenerator.ICON_SCALE_FOR_SHADOWS;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -21,8 +23,8 @@ import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
@@ -38,12 +40,12 @@ import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.Flags;
import com.android.launcher3.icons.BitmapInfo.Extender;
import com.android.launcher3.util.FlagOp;
import com.android.launcher3.util.UserIconInfo;
import java.lang.annotation.Retention;
import java.util.Objects;
/**
* This class will be moved to androidx library. There shouldn't be any dependency outside
@@ -82,47 +84,41 @@ public class BaseIconFactory implements AutoCloseable {
@NonNull
private final PackageManager mPm;
@NonNull
private final ColorExtractor mColorExtractor;
protected final int mFillResIconDpi;
protected final int mFullResIconDpi;
protected final int mIconBitmapSize;
protected boolean mMonoIconEnabled;
@Nullable
private IconNormalizer mNormalizer;
protected IconThemeController mThemeController;
@Nullable
private ShadowGenerator mShadowGenerator;
private final boolean mShapeDetection;
// Shadow bitmap used as background for theme icons
private Bitmap mWhiteShadowLayer;
private Drawable mWrapperIcon;
private int mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
private static int PLACEHOLDER_BACKGROUND_COLOR = Color.rgb(245, 245, 245);
protected BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize,
boolean shapeDetection) {
private final boolean mShouldForceThemeIcon;
protected BaseIconFactory(Context context, int fullResIconDpi, int iconBitmapSize,
boolean unused) {
this(context, fullResIconDpi, iconBitmapSize);
}
public BaseIconFactory(Context context, int fullResIconDpi, int iconBitmapSize) {
mContext = context.getApplicationContext();
mShapeDetection = shapeDetection;
mFillResIconDpi = fillResIconDpi;
mFullResIconDpi = fullResIconDpi;
mIconBitmapSize = iconBitmapSize;
mPm = mContext.getPackageManager();
mColorExtractor = new ColorExtractor();
mCanvas = new Canvas();
mCanvas.setDrawFilter(new PaintFlagsDrawFilter(DITHER_FLAG, FILTER_BITMAP_FLAG));
clear();
}
public BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize) {
this(context, fillResIconDpi, iconBitmapSize, false);
mShouldForceThemeIcon = mContext.getResources().getBoolean(
R.bool.enable_forced_themed_icon);
}
protected void clear() {
@@ -137,12 +133,17 @@ public class BaseIconFactory implements AutoCloseable {
return mShadowGenerator;
}
@NonNull
public IconNormalizer getNormalizer() {
if (mNormalizer == null) {
mNormalizer = new IconNormalizer(mContext, mIconBitmapSize, mShapeDetection);
}
return mNormalizer;
@Nullable
public IconThemeController getThemeController() {
return mThemeController;
}
public int getFullResIconDpi() {
return mFullResIconDpi;
}
public int getIconBitmapSize() {
return mIconBitmapSize;
}
@SuppressWarnings("deprecation")
@@ -152,7 +153,7 @@ public class BaseIconFactory implements AutoCloseable {
if (resources != null) {
final int id = resources.getIdentifier(iconRes.resourceName, null, null);
// do not stamp old legacy shortcuts as the app may have already forgotten about it
return createBadgedIconBitmap(resources.getDrawableForDensity(id, mFillResIconDpi));
return createBadgedIconBitmap(resources.getDrawableForDensity(id, mFullResIconDpi));
}
} catch (Exception e) {
// Icon not found.
@@ -179,20 +180,19 @@ public class BaseIconFactory implements AutoCloseable {
icon = createIconBitmap(new BitmapDrawable(mContext.getResources(), icon), 1f);
}
return BitmapInfo.of(icon, mColorExtractor.findDominantColorByHue(icon));
return BitmapInfo.of(icon, ColorExtractor.findDominantColorByHue(icon));
}
/**
* Creates an icon from the bitmap cropped to the current device icon shape
*/
@NonNull
public BitmapInfo createShapedIconBitmap(Bitmap icon, IconOptions options) {
Drawable d = new FixedSizeBitmapDrawable(icon);
public AdaptiveIconDrawable createShapedAdaptiveIcon(Bitmap iconBitmap) {
Drawable drawable = new FixedSizeBitmapDrawable(iconBitmap);
float inset = getExtraInsetFraction();
inset = inset / (1 + 2 * inset);
d = new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK),
new InsetDrawable(d, inset, inset, inset, inset));
return createBadgedIconBitmap(d, options);
return new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK),
new InsetDrawable(drawable, inset, inset, inset, inset));
}
@NonNull
@@ -205,47 +205,46 @@ public class BaseIconFactory implements AutoCloseable {
* The bitmap is visually normalized with other icons and has enough spacing to add shadow.
*
* @param icon source of the icon
* @return a bitmap suitable for disaplaying as an icon at various system UIs.
* @return a bitmap suitable for displaying as an icon at various system UIs.
*/
@TargetApi(Build.VERSION_CODES.TIRAMISU)
@NonNull
public BitmapInfo createBadgedIconBitmap(@NonNull Drawable icon,
@Nullable IconOptions options) {
float[] scale = new float[1];
AdaptiveIconDrawable adaptiveIcon = normalizeAndWrapToAdaptiveIcon(icon, null, scale);
Drawable tempIcon = icon;
if (options != null
&& options.mIsArchived
&& icon instanceof BitmapDrawable bitmapDrawable) {
// b/358123888
// Pre-archived apps can have BitmapDrawables without insets.
// Need to convert to Adaptive Icon with insets to avoid cropping.
tempIcon = createShapedAdaptiveIcon(bitmapDrawable.getBitmap());
}
AdaptiveIconDrawable adaptiveIcon = normalizeAndWrapToAdaptiveIcon(tempIcon, scale);
Bitmap bitmap = createIconBitmap(adaptiveIcon, scale[0],
options == null ? MODE_WITH_SHADOW : options.mGenerationMode);
int color = (options != null && options.mExtractedColor != null)
? options.mExtractedColor : mColorExtractor.findDominantColorByHue(bitmap);
? options.mExtractedColor : ColorExtractor.findDominantColorByHue(bitmap);
BitmapInfo info = BitmapInfo.of(bitmap, color);
if (adaptiveIcon instanceof BitmapInfo.Extender extender) {
if (adaptiveIcon instanceof Extender extender) {
info = extender.getExtendedInfo(bitmap, color, this, scale[0]);
} else if (IconProvider.ATLEAST_T && mMonoIconEnabled) {
Drawable mono = getMonochromeDrawable(adaptiveIcon);
if (mono != null) {
info.setMonoIcon(createIconBitmap(mono, scale[0], MODE_ALPHA), this);
}
} else if (IconProvider.ATLEAST_T && mThemeController != null && adaptiveIcon != null) {
info.setThemedBitmap(
mThemeController.createThemedBitmap(
adaptiveIcon,
info,
this,
options == null ? null : options.mSourceHint
)
);
}
info = info.withFlags(getBitmapFlagOp(options));
return info;
}
/**
* Returns a monochromatic version of the given drawable or null, if it is not supported
*
* @param base the original icon
*/
@TargetApi(Build.VERSION_CODES.TIRAMISU)
protected Drawable getMonochromeDrawable(AdaptiveIconDrawable base) {
Drawable mono = base.getMonochrome();
if (mono != null) {
return new ClippedMonoDrawable(mono);
}
return null;
}
@NonNull
public FlagOp getBitmapFlagOp(@Nullable IconOptions options) {
FlagOp op = FlagOp.NO_OP;
@@ -265,6 +264,13 @@ public class BaseIconFactory implements AutoCloseable {
return op;
}
/**
* @return True if forced theme icon is enabled
*/
public boolean shouldForceThemeIcon() {
return mShouldForceThemeIcon;
}
@NonNull
protected UserIconInfo getUserInfo(@NonNull UserHandle user) {
int key = user.hashCode();
@@ -283,6 +289,15 @@ public class BaseIconFactory implements AutoCloseable {
return info;
}
@NonNull
public Path getShapePath(AdaptiveIconDrawable drawable, Rect iconBounds) {
return drawable.getIconMask();
}
public float getIconScale() {
return 1f;
}
@NonNull
public Bitmap getWhiteShadowLayer() {
if (mWhiteShadowLayer == null) {
@@ -295,11 +310,9 @@ public class BaseIconFactory implements AutoCloseable {
@NonNull
public Bitmap createScaledBitmap(@NonNull Drawable icon, @BitmapGenerationMode int mode) {
RectF iconBounds = new RectF();
float[] scale = new float[1];
icon = normalizeAndWrapToAdaptiveIcon(icon, iconBounds, scale);
return createIconBitmap(icon,
Math.min(scale[0], ShadowGenerator.getScaleForBounds(iconBounds)), mode);
icon = normalizeAndWrapToAdaptiveIcon(icon, scale);
return createIconBitmap(icon, Math.min(scale[0], ICON_SCALE_FOR_SHADOWS), mode);
}
/**
@@ -310,18 +323,14 @@ public class BaseIconFactory implements AutoCloseable {
}
@Nullable
protected AdaptiveIconDrawable normalizeAndWrapToAdaptiveIcon(@Nullable Drawable icon,
@Nullable final RectF outIconBounds, @NonNull final float[] outScale) {
protected AdaptiveIconDrawable normalizeAndWrapToAdaptiveIcon(
@Nullable Drawable icon, @NonNull final float[] outScale) {
if (icon == null) {
return null;
}
AdaptiveIconDrawable adaptiveIcon;
float scale;
adaptiveIcon = wrapToAdaptiveIcon(icon, outIconBounds);
scale = getNormalizer().getScale(adaptiveIcon, outIconBounds, null, null);
outScale[0] = scale;
return adaptiveIcon;
outScale[0] = IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
return wrapToAdaptiveIcon(icon);
}
/**
@@ -345,8 +354,7 @@ public class BaseIconFactory implements AutoCloseable {
/**
* Wraps the provided icon in an adaptive icon drawable
*/
public AdaptiveIconDrawable wrapToAdaptiveIcon(@NonNull Drawable icon,
@Nullable final RectF outIconBounds) {
public AdaptiveIconDrawable wrapToAdaptiveIcon(@NonNull Drawable icon) {
if (icon instanceof AdaptiveIconDrawable aid) {
return aid;
} else {
@@ -354,24 +362,19 @@ public class BaseIconFactory implements AutoCloseable {
AdaptiveIconDrawable dr = new AdaptiveIconDrawable(
new ColorDrawable(mWrapperBackgroundColor), foreground);
dr.setBounds(0, 0, 1, 1);
boolean[] outShape = new boolean[1];
float scale = getNormalizer().getScale(icon, outIconBounds, dr.getIconMask(), outShape);
if (!outShape[0]) {
foreground.setDrawable(createScaledDrawable(icon, scale * LEGACY_ICON_SCALE));
} else {
foreground.setDrawable(createScaledDrawable(icon, 1 - getExtraInsetFraction()));
}
float scale = new IconNormalizer(mIconBitmapSize).getScale(icon);
foreground.setDrawable(createScaledDrawable(icon, scale * LEGACY_ICON_SCALE));
return dr;
}
}
@NonNull
protected Bitmap createIconBitmap(@Nullable final Drawable icon, final float scale) {
public Bitmap createIconBitmap(@Nullable final Drawable icon, final float scale) {
return createIconBitmap(icon, scale, MODE_DEFAULT);
}
@NonNull
protected Bitmap createIconBitmap(@Nullable final Drawable icon, final float scale,
public Bitmap createIconBitmap(@Nullable final Drawable icon, final float scale,
@BitmapGenerationMode int bitmapGenerationMode) {
final int size = mIconBitmapSize;
final Bitmap bitmap;
@@ -398,31 +401,31 @@ public class BaseIconFactory implements AutoCloseable {
return bitmap;
}
private void drawIconBitmap(@NonNull Canvas canvas, @Nullable final Drawable icon,
private void drawIconBitmap(@NonNull Canvas canvas, @Nullable Drawable icon,
final float scale, @BitmapGenerationMode int bitmapGenerationMode,
@Nullable Bitmap targetBitmap) {
final int size = mIconBitmapSize;
mOldBounds.set(icon.getBounds());
if (icon instanceof AdaptiveIconDrawable) {
if (icon instanceof AdaptiveIconDrawable aid) {
// We are ignoring KEY_SHADOW_DISTANCE because regular icons ignore this at the
// moment b/298203449
int offset = Math.max((int) Math.ceil(BLUR_FACTOR * size),
Math.round(size * (1 - scale) / 2));
// b/211896569: AdaptiveIconDrawable do not work properly for non top-left bounds
icon.setBounds(0, 0, size - offset - offset, size - offset - offset);
int newBounds = size - offset * 2;
icon.setBounds(0, 0, newBounds, newBounds);
Path shapePath = getShapePath(aid, icon.getBounds());
int count = canvas.save();
canvas.translate(offset, offset);
if (bitmapGenerationMode == MODE_WITH_SHADOW
|| bitmapGenerationMode == MODE_HARDWARE_WITH_SHADOW) {
getShadowGenerator().addPathShadow(
((AdaptiveIconDrawable) icon).getIconMask(), canvas);
getShadowGenerator().addPathShadow(shapePath, canvas);
}
if (icon instanceof BitmapInfo.Extender) {
if (icon instanceof Extender) {
((Extender) icon).drawForPersistence(canvas);
} else {
icon.draw(canvas);
drawAdaptiveIcon(canvas, aid, shapePath);
}
canvas.restoreToCount(count);
} else {
@@ -470,20 +473,39 @@ public class BaseIconFactory implements AutoCloseable {
icon.setBounds(mOldBounds);
}
/**
* Draws AdaptiveIconDrawable onto canvas.
* @param canvas canvas to draw on
* @param drawable AdaptiveIconDrawable to draw
* @param overridePath path to clip icon with for shapes
*/
protected void drawAdaptiveIcon(
@NonNull Canvas canvas,
@NonNull AdaptiveIconDrawable drawable,
@NonNull Path overridePath
) {
if (!Flags.enableLauncherIconShapes()) {
drawable.draw(canvas);
return;
}
canvas.clipPath(overridePath);
canvas.drawColor(BLACK);
if (drawable.getBackground() != null) {
drawable.getBackground().draw(canvas);
}
if (drawable.getForeground() != null) {
drawable.getForeground().draw(canvas);
}
}
@Override
public void close() {
clear();
}
@NonNull
public BitmapInfo makeDefaultIcon() {
return createBadgedIconBitmap(getFullResDefaultActivityIcon(mFillResIconDpi));
}
@NonNull
public static Drawable getFullResDefaultActivityIcon(final int iconDpi) {
return Objects.requireNonNull(Resources.getSystem().getDrawableForDensity(
android.R.drawable.sym_def_app_icon, iconDpi));
public BitmapInfo makeDefaultIcon(IconProvider iconProvider) {
return createBadgedIconBitmap(iconProvider.getFullResDefaultActivityIcon(mFullResIconDpi));
}
/**
@@ -497,6 +519,8 @@ public class BaseIconFactory implements AutoCloseable {
boolean mIsInstantApp;
boolean mIsArchived;
@BitmapGenerationMode
int mGenerationMode = MODE_WITH_SHADOW;
@@ -509,6 +533,8 @@ public class BaseIconFactory implements AutoCloseable {
@Nullable
Integer mExtractedColor;
@Nullable
SourceHint mSourceHint;
/**
* User for this icon, in case of badging
@@ -537,6 +563,14 @@ public class BaseIconFactory implements AutoCloseable {
return this;
}
/**
* If the icon represents an archived app
*/
public IconOptions setIsArchived(boolean isArchived) {
mIsArchived = isArchived;
return this;
}
/**
* Disables auto color extraction and overrides the color to the provided value
*/
@@ -555,6 +589,15 @@ public class BaseIconFactory implements AutoCloseable {
mGenerationMode = generationMode;
return this;
}
/**
* User for this icon, in case of badging
*/
@NonNull
public IconOptions setSourceHint(@Nullable SourceHint sourceHint) {
mSourceHint = sourceHint;
return this;
}
}
/**
@@ -591,26 +634,6 @@ public class BaseIconFactory implements AutoCloseable {
}
}
protected static class ClippedMonoDrawable extends InsetDrawable {
@NonNull
private final AdaptiveIconDrawable mCrop;
public ClippedMonoDrawable(@Nullable final Drawable base) {
super(base, -getExtraInsetFraction());
mCrop = new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK), null);
}
@Override
public void draw(Canvas canvas) {
mCrop.setBounds(getBounds());
int saveCount = canvas.save();
canvas.clipPath(mCrop.getIconMask());
super.draw(canvas);
canvas.restoreToCount(saveCount);
}
}
private static class CenterTextDrawable extends ColorDrawable {
@NonNull
@@ -15,16 +15,20 @@
*/
package com.android.launcher3.icons;
import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.drawable.Drawable;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.icons.cache.CacheLookupFlag;
import com.android.launcher3.util.FlagOp;
public class BitmapInfo {
@@ -56,17 +60,21 @@ public class BitmapInfo {
public static final String TAG = "BitmapInfo";
@NonNull
public final Bitmap icon;
public final int color;
@Nullable
protected Bitmap mMono;
protected Bitmap mWhiteShadowLayer;
private ThemedBitmap mThemedBitmap;
public @BitmapInfoFlags int flags;
// b/377618519: These are saved to debug why work badges sometimes don't show up on work apps
public @DrawableCreationFlags int creationFlags;
private BitmapInfo badgeInfo;
public BitmapInfo(Bitmap icon, int color) {
public BitmapInfo(@NonNull Bitmap icon, int color) {
this.icon = icon;
this.color = color;
}
@@ -90,8 +98,7 @@ public class BitmapInfo {
}
protected BitmapInfo copyInternalsTo(BitmapInfo target) {
target.mMono = mMono;
target.mWhiteShadowLayer = mWhiteShadowLayer;
target.mThemedBitmap = mThemedBitmap;
target.flags = flags;
target.badgeInfo = badgeInfo;
return target;
@@ -102,9 +109,13 @@ public class BitmapInfo {
return copyInternalsTo(new BitmapInfo(icon, color));
}
public void setMonoIcon(Bitmap mono, BaseIconFactory iconFactory) {
mMono = mono;
mWhiteShadowLayer = iconFactory.getWhiteShadowLayer();
public void setThemedBitmap(@Nullable ThemedBitmap themedBitmap) {
mThemedBitmap = themedBitmap;
}
@Nullable
public ThemedBitmap getThemedBitmap() {
return mThemedBitmap;
}
/**
@@ -118,6 +129,13 @@ public class BitmapInfo {
return LOW_RES_ICON == icon;
}
/**
* Returns the lookup flag to match this current state of this info
*/
public CacheLookupFlag getMatchingLookupFlag() {
return DEFAULT_LOOKUP_FLAG.withUseLowRes(isLowRes());
}
/**
* BitmapInfo can be stored on disk or other persistent storage
*/
@@ -125,10 +143,6 @@ public class BitmapInfo {
return !isNullOrLowRes();
}
public Bitmap getMono() {
return mMono;
}
/**
* Creates a drawable for the provided BitmapInfo
*/
@@ -140,61 +154,89 @@ public class BitmapInfo {
* Creates a drawable for the provided BitmapInfo
*/
public FastBitmapDrawable newIcon(Context context, @DrawableCreationFlags int creationFlags) {
return newIcon(context, creationFlags, null);
}
/**
* Creates a drawable for the provided BitmapInfo
*
* @param context Context
* @param creationFlags Flags for creating the FastBitmapDrawable
* @param badgeShape Optional Path for masking icon badges to a shape. Should be 100x100.
* @return FastBitmapDrawable
*/
public FastBitmapDrawable newIcon(Context context, @DrawableCreationFlags int creationFlags,
@Nullable Path badgeShape) {
FastBitmapDrawable drawable;
if (isLowRes()) {
drawable = new PlaceHolderIconDrawable(this, context);
} else if ((creationFlags & FLAG_THEMED) != 0 && mMono != null) {
drawable = ThemedIconDrawable.newDrawable(this, context);
} else if ((creationFlags & FLAG_THEMED) != 0 && mThemedBitmap != null) {
drawable = mThemedBitmap.newDrawable(this, context);
} else {
drawable = new FastBitmapDrawable(this);
}
applyFlags(context, drawable, creationFlags);
applyFlags(context, drawable, creationFlags, badgeShape);
return drawable;
}
protected void applyFlags(Context context, FastBitmapDrawable drawable,
@DrawableCreationFlags int creationFlags) {
@DrawableCreationFlags int creationFlags, @Nullable Path badgeShape) {
this.creationFlags = creationFlags;
drawable.mDisabledAlpha = GraphicsUtils.getFloat(context, R.attr.disabledIconAlpha, 1f);
drawable.mCreationFlags = creationFlags;
if ((creationFlags & FLAG_NO_BADGE) == 0) {
Drawable badge = getBadgeDrawable(context, (creationFlags & FLAG_THEMED) != 0,
(creationFlags & FLAG_SKIP_USER_BADGE) != 0);
(creationFlags & FLAG_SKIP_USER_BADGE) != 0, badgeShape);
if (badge != null) {
drawable.setBadge(badge);
}
}
}
public Drawable getBadgeDrawable(Context context, boolean isThemed) {
return getBadgeDrawable(context, isThemed, false);
/**
* Gets Badge drawable based on current flags
* @param context Context
* @param isThemed If Drawable is themed.
* @param badgeShape Optional Path to mask badges to a shape. Should be 100x100.
* @return Drawable for the badge.
*/
public Drawable getBadgeDrawable(Context context, boolean isThemed, @Nullable Path badgeShape) {
return getBadgeDrawable(context, isThemed, false, badgeShape);
}
/**
* Returns a drawable representing the badge for this info
* Creates a Drawable for an icon badge for this BitmapInfo
* @param context Context
* @param isThemed If the drawable is themed.
* @param skipUserBadge If should skip User Profile badging.
* @param badgeShape Optional Path to mask badge Drawable to a shape. Should be 100x100.
* @return Drawable for an icon Badge.
*/
@Nullable
private Drawable getBadgeDrawable(Context context, boolean isThemed, boolean skipUserBadge) {
private Drawable getBadgeDrawable(Context context, boolean isThemed, boolean skipUserBadge,
@Nullable Path badgeShape) {
if (badgeInfo != null) {
int creationFlag = isThemed ? FLAG_THEMED : 0;
if (skipUserBadge) {
creationFlag |= FLAG_SKIP_USER_BADGE;
}
return badgeInfo.newIcon(context, creationFlag);
return badgeInfo.newIcon(context, creationFlag, badgeShape);
}
if (skipUserBadge) {
return null;
} else if ((flags & FLAG_INSTANT) != 0) {
return new UserBadgeDrawable(context, R.drawable.ic_instant_app_badge,
R.color.badge_tint_instant, isThemed);
R.color.badge_tint_instant, isThemed, badgeShape);
} else if ((flags & FLAG_WORK) != 0) {
return new UserBadgeDrawable(context, R.drawable.ic_work_app_badge,
R.color.badge_tint_work, isThemed);
R.color.badge_tint_work, isThemed, badgeShape);
} else if ((flags & FLAG_CLONE) != 0) {
return new UserBadgeDrawable(context, R.drawable.ic_clone_app_badge,
R.color.badge_tint_clone, isThemed);
R.color.badge_tint_clone, isThemed, badgeShape);
} else if ((flags & FLAG_PRIVATE) != 0) {
return new UserBadgeDrawable(context, R.drawable.ic_private_profile_app_badge,
R.color.badge_tint_private, isThemed);
R.color.badge_tint_private, isThemed, badgeShape);
}
return null;
}
@@ -77,9 +77,7 @@ public class BubbleIconFactory extends BaseIconFactory {
if (outScale == null) {
outScale = new float[1];
}
icon = normalizeAndWrapToAdaptiveIcon(icon,
null /* outscale */,
outScale);
icon = normalizeAndWrapToAdaptiveIcon(icon, outScale);
return createIconBitmap(icon, outScale[0], MODE_WITH_SHADOW);
}
@@ -22,7 +22,6 @@ import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BlendMode;
import android.graphics.BlendModeColorFilter;
@@ -30,6 +29,7 @@ import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.ColorDrawable;
@@ -39,11 +39,8 @@ import android.os.Build;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;
import android.util.TypedValue;
import androidx.annotation.Nullable;
import com.android.launcher3.icons.IconProvider.ThemeData;
import com.android.launcher3.icons.mono.ThemedIconDrawable;
import java.util.Calendar;
import java.util.concurrent.TimeUnit;
@@ -53,7 +50,6 @@ import java.util.function.IntFunction;
* Wrapper over {@link AdaptiveIconDrawable} to intercept icon flattening logic for dynamic
* clock icons
*/
@TargetApi(Build.VERSION_CODES.O)
public class ClockDrawableWrapper extends AdaptiveIconDrawable implements BitmapInfo.Extender {
public static boolean sRunningInTest = false;
@@ -94,34 +90,6 @@ public class ClockDrawableWrapper extends AdaptiveIconDrawable implements Bitmap
super(base.getBackground(), base.getForeground());
}
private void applyThemeData(ThemeData themeData) {
if (!IconProvider.ATLEAST_T || mThemeInfo != null) {
return;
}
try {
TypedArray ta = themeData.mResources.obtainTypedArray(themeData.mResID);
int count = ta.length();
Bundle extras = new Bundle();
for (int i = 0; i < count; i += 2) {
TypedValue v = ta.peekValue(i + 1);
extras.putInt(ta.getString(i), v.type >= TypedValue.TYPE_FIRST_INT
&& v.type <= TypedValue.TYPE_LAST_INT
? v.data : v.resourceId);
}
ta.recycle();
ClockDrawableWrapper drawable = ClockDrawableWrapper.forExtras(extras, resId -> {
Drawable bg = new ColorDrawable(Color.WHITE);
Drawable fg = themeData.mResources.getDrawable(resId).mutate();
return new AdaptiveIconDrawable(bg, fg);
});
if (drawable != null) {
mThemeInfo = drawable.mAnimationInfo;
}
} catch (Exception e) {
Log.e(TAG, "Error loading themed clock", e);
}
}
@Override
public Drawable getMonochrome() {
if (mThemeInfo == null) {
@@ -140,26 +108,19 @@ public class ClockDrawableWrapper extends AdaptiveIconDrawable implements Bitmap
* Loads and returns the wrapper from the provided package, or returns null
* if it is unable to load.
*/
public static ClockDrawableWrapper forPackage(Context context, String pkg, int iconDpi,
@Nullable ThemeData themeData) {
public static ClockDrawableWrapper forPackage(Context context, String pkg, int iconDpi) {
try {
PackageManager pm = context.getPackageManager();
ApplicationInfo appInfo = pm.getApplicationInfo(pkg,
PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA);
Resources res = pm.getResourcesForApplication(appInfo);
ClockDrawableWrapper wrapper = forExtras(appInfo.metaData,
resId -> res.getDrawableForDensity(resId, iconDpi));
if (wrapper != null && themeData != null) {
wrapper.applyThemeData(themeData);
}
return wrapper;
return forExtras(appInfo.metaData, resId -> res.getDrawableForDensity(resId, iconDpi));
} catch (Exception e) {
Log.d(TAG, "Unable to load clock drawable info", e);
}
return null;
}
@TargetApi(Build.VERSION_CODES.TIRAMISU)
private static ClockDrawableWrapper forExtras(
Bundle metadata, IntFunction<Drawable> drawableProvider) {
if (metadata == null) {
@@ -220,7 +181,7 @@ public class ClockDrawableWrapper extends AdaptiveIconDrawable implements Bitmap
BaseIconFactory.MODE_HARDWARE_WITH_SHADOW);
// Only pass theme info if mono-icon is enabled
AnimationInfo themeInfo = iconFactory.mMonoIconEnabled ? mThemeInfo : null;
AnimationInfo themeInfo = iconFactory.getThemeController() != null ? mThemeInfo : null;
Bitmap themeBG = themeInfo == null ? null : iconFactory.getWhiteShadowLayer();
return new ClockBitmapInfo(bitmap, color, normalizationScale,
mAnimationInfo, flattenBG, themeInfo, themeBG);
@@ -323,7 +284,7 @@ public class ClockDrawableWrapper extends AdaptiveIconDrawable implements Bitmap
@Override
@TargetApi(Build.VERSION_CODES.TIRAMISU)
public FastBitmapDrawable newIcon(Context context,
@DrawableCreationFlags int creationFlags) {
@DrawableCreationFlags int creationFlags, Path badgeShape) {
AnimationInfo info;
Bitmap bg;
int themedFgColor;
@@ -346,9 +307,9 @@ public class ClockDrawableWrapper extends AdaptiveIconDrawable implements Bitmap
return super.newIcon(context, creationFlags);
}
ClockIconDrawable.ClockConstantState cs = new ClockIconDrawable.ClockConstantState(
icon, color, themedFgColor, boundsOffset, info, bg, bgFilter);
this, themedFgColor, boundsOffset, info, bg, bgFilter);
FastBitmapDrawable d = cs.newDrawable();
applyFlags(context, d, creationFlags);
applyFlags(context, d, creationFlags, null);
return d;
}
@@ -381,7 +342,7 @@ public class ClockDrawableWrapper extends AdaptiveIconDrawable implements Bitmap
private final float mCanvasScale;
ClockIconDrawable(ClockConstantState cs) {
super(cs.mBitmap, cs.mIconColor);
super(cs.mBitmapInfo);
mBoundsOffset = cs.mBoundsOffset;
mAnimInfo = cs.mAnimInfo;
@@ -487,7 +448,7 @@ public class ClockDrawableWrapper extends AdaptiveIconDrawable implements Bitmap
@Override
public FastBitmapConstantState newConstantState() {
return new ClockConstantState(mBitmap, mIconColor, mThemedFgColor, mBoundsOffset,
return new ClockConstantState(mBitmapInfo, mThemedFgColor, mBoundsOffset,
mAnimInfo, mBG, mBgPaint.getColorFilter());
}
@@ -499,9 +460,9 @@ public class ClockDrawableWrapper extends AdaptiveIconDrawable implements Bitmap
private final ColorFilter mBgFilter;
private final int mThemedFgColor;
ClockConstantState(Bitmap bitmap, int color, int themedFgColor,
ClockConstantState(BitmapInfo info, int themedFgColor,
float boundsOffset, AnimationInfo animInfo, Bitmap bg, ColorFilter bgFilter) {
super(bitmap, color);
super(info);
mBoundsOffset = boundsOffset;
mAnimInfo = animInfo;
mBG = bg;
@@ -1,138 +0,0 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.icons;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.util.SparseArray;
import androidx.annotation.NonNull;
import java.util.Arrays;
/**
* Utility class for extracting colors from a bitmap.
*/
public class ColorExtractor {
private final int NUM_SAMPLES = 20;
@NonNull
private final float[] mTmpHsv = new float[3];
@NonNull
private final float[] mTmpHueScoreHistogram = new float[360];
@NonNull
private final int[] mTmpPixels = new int[NUM_SAMPLES];
@NonNull
private final SparseArray<Float> mTmpRgbScores = new SparseArray<>();
/**
* This picks a dominant color, looking for high-saturation, high-value, repeated hues.
* @param bitmap The bitmap to scan
*/
public int findDominantColorByHue(@NonNull final Bitmap bitmap) {
return findDominantColorByHue(bitmap, NUM_SAMPLES);
}
/**
* This picks a dominant color, looking for high-saturation, high-value, repeated hues.
* @param bitmap The bitmap to scan
*/
protected int findDominantColorByHue(@NonNull final Bitmap bitmap, final int samples) {
final int height = bitmap.getHeight();
final int width = bitmap.getWidth();
int sampleStride = (int) Math.sqrt((height * width) / samples);
if (sampleStride < 1) {
sampleStride = 1;
}
// This is an out-param, for getting the hsv values for an rgb
float[] hsv = mTmpHsv;
Arrays.fill(hsv, 0);
// First get the best hue, by creating a histogram over 360 hue buckets,
// where each pixel contributes a score weighted by saturation, value, and alpha.
float[] hueScoreHistogram = mTmpHueScoreHistogram;
Arrays.fill(hueScoreHistogram, 0);
float highScore = -1;
int bestHue = -1;
int[] pixels = mTmpPixels;
Arrays.fill(pixels, 0);
int pixelCount = 0;
for (int y = 0; y < height; y += sampleStride) {
for (int x = 0; x < width; x += sampleStride) {
int argb = bitmap.getPixel(x, y);
int alpha = 0xFF & (argb >> 24);
if (alpha < 0x80) {
// Drop mostly-transparent pixels.
continue;
}
// Remove the alpha channel.
int rgb = argb | 0xFF000000;
Color.colorToHSV(rgb, hsv);
// Bucket colors by the 360 integer hues.
int hue = (int) hsv[0];
if (hue < 0 || hue >= hueScoreHistogram.length) {
// Defensively avoid array bounds violations.
continue;
}
if (pixelCount < samples) {
pixels[pixelCount++] = rgb;
}
float score = hsv[1] * hsv[2];
hueScoreHistogram[hue] += score;
if (hueScoreHistogram[hue] > highScore) {
highScore = hueScoreHistogram[hue];
bestHue = hue;
}
}
}
SparseArray<Float> rgbScores = mTmpRgbScores;
rgbScores.clear();
int bestColor = 0xff000000;
highScore = -1;
// Go back over the RGB colors that match the winning hue,
// creating a histogram of weighted s*v scores, for up to 100*100 [s,v] buckets.
// The highest-scoring RGB color wins.
for (int i = 0; i < pixelCount; i++) {
int rgb = pixels[i];
Color.colorToHSV(rgb, hsv);
int hue = (int) hsv[0];
if (hue == bestHue) {
float s = hsv[1];
float v = hsv[2];
int bucket = (int) (s * 100) + (int) (v * 10000);
// Score by cumulative saturation * value.
float score = s * v;
Float oldTotal = rgbScores.get(bucket);
float newTotal = oldTotal == null ? score : oldTotal + score;
rgbScores.put(bucket, newTotal);
if (newTotal > highScore) {
highScore = newTotal;
// All the colors in the winning bucket are very similar. Last in wins.
bestColor = rgb;
}
}
}
return bestColor;
}
}
@@ -0,0 +1,107 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.icons
import android.graphics.Bitmap
import android.graphics.Color
import android.util.SparseArray
import kotlin.math.sqrt
/** Utility class for extracting colors from a bitmap. */
object ColorExtractor {
private const val NUM_SAMPLES = 20
/**
* This picks a dominant color, looking for high-saturation, high-value, repeated hues.
*
* @param bitmap The bitmap to scan
*/
@JvmStatic
fun findDominantColorByHue(bitmap: Bitmap): Int {
val height = bitmap.height
val width = bitmap.width
val sampleStride = sqrt((height * width) / NUM_SAMPLES.toDouble()).toInt().coerceAtLeast(1)
// This is an out-param, for getting the hsv values for an rgb
val hsv = FloatArray(3)
// First get the best hue, by creating a histogram over 360 hue buckets,
// where each pixel contributes a score weighted by saturation, value, and alpha.
val hueScoreHistogram = FloatArray(360)
var highScore = -1f
var bestHue = -1
val pixels = IntArray(NUM_SAMPLES)
var pixelCount = 0
for (y in 0..<height step sampleStride) {
for (x in 0..<width step sampleStride) {
val argb = bitmap.getPixel(x, y)
val alpha = 0xFF and (argb shr 24)
if (alpha < 0x80) {
// Drop mostly-transparent pixels.
continue
}
// Remove the alpha channel.
val rgb = argb or -0x1000000
Color.colorToHSV(rgb, hsv)
// Bucket colors by the 360 integer hues.
val hue = hsv[0].toInt()
if (hue < 0 || hue >= hueScoreHistogram.size) {
// Defensively avoid array bounds violations.
continue
}
if (pixelCount < NUM_SAMPLES) {
pixels[pixelCount++] = rgb
}
val score = hsv[1] * hsv[2]
hueScoreHistogram[hue] += score
if (hueScoreHistogram[hue] > highScore) {
highScore = hueScoreHistogram[hue]
bestHue = hue
}
}
}
val rgbScores = SparseArray<Float>()
var bestColor = -0x1000000
highScore = -1f
// Go back over the RGB colors that match the winning hue,
// creating a histogram of weighted s*v scores, for up to 100*100 [s,v] buckets.
// The highest-scoring RGB color wins.
for (i in 0..<pixelCount) {
val rgb = pixels[i]
Color.colorToHSV(rgb, hsv)
val hue = hsv[0].toInt()
if (hue == bestHue) {
val s = hsv[1]
val v = hsv[2]
val bucket = (s * 100).toInt() + (v * 10000).toInt()
// Score by cumulative saturation * value.
val score = s * v
val oldTotal = rgbScores[bucket]
val newTotal = if (oldTotal == null) score else oldTotal + score
rgbScores.put(bucket, newTotal)
if (newTotal > highScore) {
highScore = newTotal
// All the colors in the winning bucket are very similar. Last in wins.
bestColor = rgb
}
}
}
return bestColor
}
}
@@ -65,15 +65,14 @@ public class FastBitmapDrawable extends Drawable implements Drawable.Callback {
private static boolean sFlagHoverEnabled = false;
protected final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
protected final Bitmap mBitmap;
protected final int mIconColor;
public final BitmapInfo mBitmapInfo;
@Nullable private ColorFilter mColorFilter;
@VisibleForTesting protected boolean mIsPressed;
@VisibleForTesting protected boolean mIsHovered;
protected boolean mIsDisabled;
float mDisabledAlpha = 1f;
protected float mDisabledAlpha = 1f;
@DrawableCreationFlags int mCreationFlags = 0;
@@ -97,18 +96,26 @@ public class FastBitmapDrawable extends Drawable implements Drawable.Callback {
private Drawable mBadge;
private boolean mHoverScaleEnabledForDisplay = true;
protected FastBitmapDrawable(Bitmap b, int iconColor) {
this(BitmapInfo.of(b, iconColor));
}
public FastBitmapDrawable(Bitmap b) {
this(b, Color.TRANSPARENT);
this(BitmapInfo.fromBitmap(b));
}
public FastBitmapDrawable(BitmapInfo info) {
this(info.icon, info.color);
mBitmapInfo = info;
setFilterBitmap(true);
}
protected FastBitmapDrawable(Bitmap b, int iconColor) {
mBitmap = b;
mIconColor = iconColor;
setFilterBitmap(true);
/**
* Returns true if the drawable points to the same bitmap icon object
*/
public boolean isSameInfo(BitmapInfo info) {
return mBitmapInfo == info;
}
@Override
@@ -143,7 +150,7 @@ public class FastBitmapDrawable extends Drawable implements Drawable.Callback {
}
protected void drawInternal(Canvas canvas, Rect bounds) {
canvas.drawBitmap(mBitmap, null, bounds, mPaint);
canvas.drawBitmap(mBitmapInfo.icon, null, bounds, mPaint);
}
/**
@@ -151,7 +158,7 @@ public class FastBitmapDrawable extends Drawable implements Drawable.Callback {
*/
public int getIconColor() {
int whiteScrim = setColorAlphaBound(Color.WHITE, WHITE_SCRIM_ALPHA);
return ColorUtils.compositeColors(whiteScrim, mIconColor);
return ColorUtils.compositeColors(whiteScrim, mBitmapInfo.color);
}
/**
@@ -218,12 +225,12 @@ public class FastBitmapDrawable extends Drawable implements Drawable.Callback {
@Override
public int getIntrinsicWidth() {
return mBitmap.getWidth();
return mBitmapInfo.icon.getWidth();
}
@Override
public int getIntrinsicHeight() {
return mBitmap.getHeight();
return mBitmapInfo.icon.getHeight();
}
@Override
@@ -254,7 +261,9 @@ public class FastBitmapDrawable extends Drawable implements Drawable.Callback {
if (s == android.R.attr.state_pressed) {
isPressed = true;
break;
} else if (sFlagHoverEnabled && s == android.R.attr.state_hovered) {
} else if (sFlagHoverEnabled
&& s == android.R.attr.state_hovered
&& mHoverScaleEnabledForDisplay) {
isHovered = true;
// Do not break on hovered state, as pressed state should take precedence.
}
@@ -292,6 +301,9 @@ public class FastBitmapDrawable extends Drawable implements Drawable.Callback {
public void setIsDisabled(boolean isDisabled) {
if (mIsDisabled != isDisabled) {
mIsDisabled = isDisabled;
if (mBadge instanceof FastBitmapDrawable fbd) {
fbd.setIsDisabled(isDisabled);
}
updateFilter();
}
}
@@ -329,7 +341,7 @@ public class FastBitmapDrawable extends Drawable implements Drawable.Callback {
}
protected FastBitmapConstantState newConstantState() {
return new FastBitmapConstantState(mBitmap, mIconColor);
return new FastBitmapConstantState(mBitmapInfo);
}
@Override
@@ -414,9 +426,12 @@ public class FastBitmapDrawable extends Drawable implements Drawable.Callback {
sFlagHoverEnabled = isFlagHoverEnabled;
}
protected static class FastBitmapConstantState extends ConstantState {
protected final Bitmap mBitmap;
protected final int mIconColor;
public void setHoverScaleEnabledForDisplay(boolean hoverScaleEnabledForDisplay) {
mHoverScaleEnabledForDisplay = hoverScaleEnabledForDisplay;
}
public static class FastBitmapConstantState extends ConstantState {
protected final BitmapInfo mBitmapInfo;
// These are initialized later so that subclasses don't need to
// pass everything in constructor
@@ -426,12 +441,15 @@ public class FastBitmapDrawable extends Drawable implements Drawable.Callback {
@DrawableCreationFlags int mCreationFlags = 0;
public FastBitmapConstantState(Bitmap bitmap, int color) {
mBitmap = bitmap;
mIconColor = color;
this(BitmapInfo.of(bitmap, color));
}
public FastBitmapConstantState(BitmapInfo info) {
mBitmapInfo = info;
}
protected FastBitmapDrawable createDrawable() {
return new FastBitmapDrawable(mBitmap, mIconColor);
return new FastBitmapDrawable(mBitmapInfo);
}
@Override
@@ -16,22 +16,14 @@
package com.android.launcher3.icons;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.RegionIterator;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.ColorDrawable;
import android.util.Log;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.core.graphics.PathParser;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -39,7 +31,6 @@ import java.io.IOException;
public class GraphicsUtils {
private static final String TAG = "GraphicsUtils";
private static final float MASK_SIZE = 100f;
public static Runnable sOnNewBitmapRunnable = () -> { };
@@ -99,30 +90,6 @@ public class GraphicsUtils {
sOnNewBitmapRunnable.run();
}
/**
* Returns the default path to be used by an icon
*/
public static Path getShapePath(@NonNull Context context, int size) {
if (IconProvider.CONFIG_ICON_MASK_RES_ID != Resources.ID_NULL) {
Path path = PathParser.createPathFromPathData(
context.getString(IconProvider.CONFIG_ICON_MASK_RES_ID));
if (path != null) {
if (size != MASK_SIZE) {
Matrix m = new Matrix();
float scale = ((float) size) / MASK_SIZE;
m.setScale(scale, scale);
path.transform(m);
}
return path;
}
}
AdaptiveIconDrawable drawable = new AdaptiveIconDrawable(
new ColorDrawable(Color.BLACK), new ColorDrawable(Color.BLACK));
drawable.setBounds(0, 0, size, size);
return new Path(drawable.getIconMask());
}
/**
* Returns the color associated with the attribute
*/
@@ -16,34 +16,19 @@
package com.android.launcher3.icons;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.Log;
import androidx.annotation.NonNull;
import java.nio.ByteBuffer;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class IconNormalizer {
private static final String TAG = "IconNormalizer";
private static final boolean DEBUG = false;
// Ratio of icon visible area to full icon size for a square shaped icon
private static final float MAX_SQUARE_AREA_FACTOR = 375.0f / 576;
// Ratio of icon visible area to full icon size for a circular shaped icon
@@ -57,35 +42,21 @@ public class IconNormalizer {
private static final int MIN_VISIBLE_ALPHA = 40;
// Shape detection related constants
private static final float BOUND_RATIO_MARGIN = .05f;
private static final float PIXEL_DIFF_PERCENTAGE_THRESHOLD = 0.005f;
private static final float SCALE_NOT_INITIALIZED = 0;
// Ratio of the diameter of an normalized circular icon to the actual icon size.
public static final float ICON_VISIBLE_AREA_FACTOR = 0.92f;
private final int mMaxSize;
private final Bitmap mBitmap;
private final Canvas mCanvas;
private final Paint mPaintMaskShape;
private final Paint mPaintMaskShapeOutline;
private final byte[] mPixels;
private final RectF mAdaptiveIconBounds;
private float mAdaptiveIconScale;
private boolean mEnableShapeDetection;
// for each y, stores the position of the leftmost x and the rightmost x
private final float[] mLeftBorder;
private final float[] mRightBorder;
private final Rect mBounds;
private final Path mShapePath;
private final Matrix mMatrix;
/** package private **/
IconNormalizer(Context context, int iconBitmapSize, boolean shapeDetection) {
public IconNormalizer(int iconBitmapSize) {
// Use twice the icon size as maximum size to avoid scaling down twice.
mMaxSize = iconBitmapSize * 2;
mBitmap = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ALPHA_8);
@@ -94,24 +65,6 @@ public class IconNormalizer {
mLeftBorder = new float[mMaxSize];
mRightBorder = new float[mMaxSize];
mBounds = new Rect();
mAdaptiveIconBounds = new RectF();
mPaintMaskShape = new Paint();
mPaintMaskShape.setColor(Color.RED);
mPaintMaskShape.setStyle(Paint.Style.FILL);
mPaintMaskShape.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR));
mPaintMaskShapeOutline = new Paint();
mPaintMaskShapeOutline.setStrokeWidth(
2 * context.getResources().getDisplayMetrics().density);
mPaintMaskShapeOutline.setStyle(Paint.Style.STROKE);
mPaintMaskShapeOutline.setColor(Color.BLACK);
mPaintMaskShapeOutline.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
mShapePath = new Path();
mMatrix = new Matrix();
mAdaptiveIconScale = SCALE_NOT_INITIALIZED;
mEnableShapeDetection = shapeDetection;
}
private static float getScale(float hullArea, float boundingArea, float fullArea) {
@@ -128,100 +81,6 @@ public class IconNormalizer {
return areaScale > scaleRequired ? (float) Math.sqrt(scaleRequired / areaScale) : 1;
}
/**
* @param d Should be AdaptiveIconDrawable
* @param size Canvas size to use
*/
@TargetApi(Build.VERSION_CODES.O)
public static float normalizeAdaptiveIcon(Drawable d, int size, @Nullable RectF outBounds) {
Rect tmpBounds = new Rect(d.getBounds());
d.setBounds(0, 0, size, size);
Path path = ((AdaptiveIconDrawable) d).getIconMask();
Region region = new Region();
region.setPath(path, new Region(0, 0, size, size));
Rect hullBounds = region.getBounds();
int hullArea = GraphicsUtils.getArea(region);
if (outBounds != null) {
float sizeF = size;
outBounds.set(
hullBounds.left / sizeF,
hullBounds.top / sizeF,
1 - (hullBounds.right / sizeF),
1 - (hullBounds.bottom / sizeF));
}
d.setBounds(tmpBounds);
return getScale(hullArea, hullArea, size * size);
}
/**
* Returns if the shape of the icon is same as the path.
* For this method to work, the shape path bounds should be in [0,1]x[0,1] bounds.
*/
private boolean isShape(Path maskPath) {
// Condition1:
// If width and height of the path not close to a square, then the icon shape is
// not same as the mask shape.
float iconRatio = ((float) mBounds.width()) / mBounds.height();
if (Math.abs(iconRatio - 1) > BOUND_RATIO_MARGIN) {
if (DEBUG) {
Log.d(TAG, "Not same as mask shape because width != height. " + iconRatio);
}
return false;
}
// Condition 2:
// Actual icon (white) and the fitted shape (e.g., circle)(red) XOR operation
// should generate transparent image, if the actual icon is equivalent to the shape.
// Fit the shape within the icon's bounding box
mMatrix.reset();
mMatrix.setScale(mBounds.width(), mBounds.height());
mMatrix.postTranslate(mBounds.left, mBounds.top);
maskPath.transform(mMatrix, mShapePath);
// XOR operation
mCanvas.drawPath(mShapePath, mPaintMaskShape);
// DST_OUT operation around the mask path outline
mCanvas.drawPath(mShapePath, mPaintMaskShapeOutline);
// Check if the result is almost transparent
return isTransparentBitmap();
}
/**
* Used to determine if certain the bitmap is transparent.
*/
private boolean isTransparentBitmap() {
ByteBuffer buffer = ByteBuffer.wrap(mPixels);
buffer.rewind();
mBitmap.copyPixelsToBuffer(buffer);
int y = mBounds.top;
// buffer position
int index = y * mMaxSize;
// buffer shift after every row, width of buffer = mMaxSize
int rowSizeDiff = mMaxSize - mBounds.right;
int sum = 0;
for (; y < mBounds.bottom; y++) {
index += mBounds.left;
for (int x = mBounds.left; x < mBounds.right; x++) {
if ((mPixels[index] & 0xFF) > MIN_VISIBLE_ALPHA) {
sum++;
}
index++;
}
index += rowSizeDiff;
}
float percentageDiffPixels = ((float) sum) / (mBounds.width() * mBounds.height());
return percentageDiffPixels < PIXEL_DIFF_PERCENTAGE_THRESHOLD;
}
/**
* Returns the amount by which the {@param d} should be scaled (in both dimensions) so that it
* matches the design guidelines for a launcher icon.
@@ -233,19 +92,10 @@ public class IconNormalizer {
*
* This closeness is used to determine the ratio of hull area to the full icon size.
* Refer {@link #MAX_CIRCLE_AREA_FACTOR} and {@link #MAX_SQUARE_AREA_FACTOR}
*
* @param outBounds optional rect to receive the fraction distance from each edge.
*/
public synchronized float getScale(@NonNull Drawable d, @Nullable RectF outBounds,
@Nullable Path path, @Nullable boolean[] outMaskShape) {
public synchronized float getScale(@NonNull Drawable d) {
if (d instanceof AdaptiveIconDrawable) {
if (mAdaptiveIconScale == SCALE_NOT_INITIALIZED) {
mAdaptiveIconScale = normalizeAdaptiveIcon(d, mMaxSize, mAdaptiveIconBounds);
}
if (outBounds != null) {
outBounds.set(mAdaptiveIconBounds);
}
return mAdaptiveIconScale;
return ICON_VISIBLE_AREA_FACTOR;
}
int width = d.getIntrinsicWidth();
int height = d.getIntrinsicHeight();
@@ -334,14 +184,6 @@ public class IconNormalizer {
mBounds.top = topY;
mBounds.bottom = bottomY;
if (outBounds != null) {
outBounds.set(((float) mBounds.left) / width, ((float) mBounds.top) / height,
1 - ((float) mBounds.right) / width,
1 - ((float) mBounds.bottom) / height);
}
if (outMaskShape != null && mEnableShapeDetection && outMaskShape.length > 0) {
outMaskShape[0] = isShape(path);
}
// Area of the rectangle required to fit the convex hull
float rectArea = (bottomY + 1 - topY) * (rightX + 1 - leftX);
return getScale(area, rectArea, width * height);
@@ -400,12 +242,4 @@ public class IconNormalizer {
last = i;
}
}
/**
* @return The diameter of the normalized circle that fits inside of the square (size x size).
*/
public static int getNormalizedCircleSize(int size) {
float area = size * size * MAX_CIRCLE_AREA_FACTOR;
return (int) Math.round(Math.sqrt((4 * area) / Math.PI));
}
}
@@ -28,8 +28,9 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ComponentInfo;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
@@ -40,30 +41,26 @@ import android.graphics.drawable.InsetDrawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.PatternMatcher;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.os.BuildCompat;
import com.android.launcher3.util.SafeCloseable;
import java.util.Calendar;
import java.util.function.Supplier;
import java.util.Objects;
/**
* Class to handle icon loading from different packages
*/
public class IconProvider {
private final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
static final int CONFIG_ICON_MASK_RES_ID = Resources.getSystem().getIdentifier(
"config_icon_mask", "string", "android");
private static final String TAG = "IconProvider";
private static final boolean DEBUG = false;
public static final boolean ATLEAST_T = BuildCompat.isAtLeastT();
@@ -76,6 +73,9 @@ public class IconProvider {
private final ComponentName mCalendar;
private final ComponentName mClock;
@NonNull
protected String mSystemState = "";
public IconProvider(Context context) {
mContext = context;
mCalendar = parseComponentOrNull(context, R.string.calendar_component_name);
@@ -83,53 +83,73 @@ public class IconProvider {
}
/**
* Adds any modification to the provided systemState for dynamic icons. This system state
* is used by caches to check for icon invalidation.
* Returns a string representing the current state of the app icon. It can be used as a
* identifier to invalidate any resources loaded from the app.
* It also incorporated ay system state, that can affect the loaded resource
*
* @see #updateSystemState()
*/
public String getSystemStateForPackage(String systemState, String packageName) {
if (mCalendar != null && mCalendar.getPackageName().equals(packageName)) {
return systemState + SYSTEM_STATE_SEPARATOR + getDay();
public String getStateForApp(@Nullable ApplicationInfo appInfo) {
if (appInfo == null) {
return mSystemState;
}
if (mCalendar != null && mCalendar.getPackageName().equals(appInfo.packageName)) {
return mSystemState + SYSTEM_STATE_SEPARATOR + getDay() + SYSTEM_STATE_SEPARATOR
+ getApplicationInfoHash(appInfo);
} else {
return systemState;
return mSystemState + SYSTEM_STATE_SEPARATOR + getApplicationInfoHash(appInfo);
}
}
/**
* Loads the icon for the provided LauncherActivityInfo
* Returns a hash to uniquely identify a particular version of appInfo
*/
public Drawable getIcon(LauncherActivityInfo info, int iconDpi) {
return getIconWithOverrides(info.getApplicationInfo().packageName, iconDpi,
() -> info.getIcon(iconDpi));
protected String getApplicationInfoHash(@NonNull ApplicationInfo appInfo) {
// The hashString in source dir changes with every install
return appInfo.sourceDir;
}
/**
* Loads the icon for the provided activity info
*/
public Drawable getIcon(ActivityInfo info) {
public Drawable getIcon(ComponentInfo info) {
return getIcon(info, mContext.getResources().getConfiguration().densityDpi);
}
/**
* Loads the icon for the provided activity info
* Loads the icon for the provided component info
*/
public Drawable getIcon(ActivityInfo info, int iconDpi) {
return getIconWithOverrides(info.applicationInfo.packageName, iconDpi,
() -> loadActivityInfoIcon(info, iconDpi));
public Drawable getIcon(ComponentInfo info, int iconDpi) {
return getIcon(info, info.applicationInfo, iconDpi);
}
@TargetApi(Build.VERSION_CODES.TIRAMISU)
private Drawable getIconWithOverrides(String packageName, int iconDpi,
Supplier<Drawable> fallback) {
/**
* Loads the icon for the provided application info
*/
public Drawable getIcon(ApplicationInfo info) {
return getIcon(info, mContext.getResources().getConfiguration().densityDpi);
}
/**
* Loads the icon for the provided application info
*/
public Drawable getIcon(ApplicationInfo info, int iconDpi) {
return getIcon(info, info, iconDpi);
}
private Drawable getIcon(PackageItemInfo info, ApplicationInfo appInfo, int iconDpi) {
String packageName = info.packageName;
ThemeData td = getThemeDataForPackage(packageName);
Drawable icon = null;
if (mCalendar != null && mCalendar.getPackageName().equals(packageName)) {
icon = loadCalendarDrawable(iconDpi, td);
} else if (mClock != null && mClock.getPackageName().equals(packageName)) {
icon = ClockDrawableWrapper.forPackage(mContext, mClock.getPackageName(), iconDpi, td);
icon = ClockDrawableWrapper.forPackage(mContext, mClock.getPackageName(), iconDpi);
}
if (icon == null) {
icon = fallback.get();
icon = loadPackageIcon(info, appInfo, iconDpi);
if (ATLEAST_T && icon instanceof AdaptiveIconDrawable && td != null) {
AdaptiveIconDrawable aid = (AdaptiveIconDrawable) icon;
if (aid.getMonochrome() == null) {
@@ -145,22 +165,37 @@ public class IconProvider {
return null;
}
private Drawable loadActivityInfoIcon(ActivityInfo ai, int density) {
final int iconRes = ai.getIconResource();
private Drawable loadPackageIcon(PackageItemInfo info, ApplicationInfo appInfo, int density) {
Drawable icon = null;
// Get the preferred density icon from the app's resources
if (density != 0 && iconRes != 0) {
if (BuildCompat.isAtLeastV() && info.isArchived) {
// Icons for archived apps com from system service, let the default impl handle that
icon = info.loadIcon(mContext.getPackageManager());
}
if (icon == null && density != 0 && (info.icon != 0 || appInfo.icon != 0)) {
try {
final Resources resources = mContext.getPackageManager()
.getResourcesForApplication(ai.applicationInfo);
icon = resources.getDrawableForDensity(iconRes, density);
.getResourcesForApplication(appInfo);
// Try to load the package item icon first
if (info != appInfo && info.icon != 0) {
try {
icon = resources.getDrawableForDensity(info.icon, density);
} catch (Resources.NotFoundException exc) { }
}
if (icon == null && appInfo.icon != 0) {
// Load the fallback app icon
icon = loadAppInfoIcon(appInfo, resources, density);
}
} catch (NameNotFoundException | Resources.NotFoundException exc) { }
}
// Get the default density icon
if (icon == null) {
icon = ai.loadIcon(mContext.getPackageManager());
}
return icon;
return icon != null ? icon : getFullResDefaultActivityIcon(density);
}
@Nullable
protected Drawable loadAppInfoIcon(ApplicationInfo info, Resources resources, int density) {
try {
return resources.getDrawableForDensity(info.icon, density);
} catch (Resources.NotFoundException exc) { }
return null;
}
@TargetApi(Build.VERSION_CODES.TIRAMISU)
@@ -201,6 +236,15 @@ public class IconProvider {
return null;
}
/**
* Returns the default activity icon
*/
@NonNull
public Drawable getFullResDefaultActivityIcon(final int iconDpi) {
return Objects.requireNonNull(Resources.getSystem().getDrawableForDensity(
android.R.drawable.sym_def_app_icon, iconDpi));
}
/**
* @param metadata metadata of the default activity of Calendar
* @param resources from the Calendar package
@@ -225,6 +269,16 @@ public class IconProvider {
}
}
/**
* Refreshes the system state definition used to check the validity of an app icon. It
* incorporates all the properties that can affect the app icon like the list of enabled locale
* and system-version.
*/
public void updateSystemState() {
mSystemState = mContext.getResources().getConfiguration().getLocales().toLanguageTags()
+ "," + Build.VERSION.SDK_INT;
}
/**
* @return Today's day of the month, zero-indexed.
*/
@@ -237,14 +291,6 @@ public class IconProvider {
return TextUtils.isEmpty(cn) ? null : ComponentName.unflattenFromString(cn);
}
/**
* Returns a string representation of the current system icon state
*/
public String getSystemIconState() {
return (CONFIG_ICON_MASK_RES_ID == ID_NULL
? "" : mContext.getResources().getString(CONFIG_ICON_MASK_RES_ID));
}
/**
* Registers a callback to listen for various system dependent icon changes.
*/
@@ -277,18 +323,9 @@ public class IconProvider {
private class IconChangeReceiver extends BroadcastReceiver implements SafeCloseable {
private final IconChangeListener mCallback;
private String mIconState;
IconChangeReceiver(IconChangeListener callback, Handler handler) {
mCallback = callback;
mIconState = getSystemIconState();
IntentFilter packageFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
packageFilter.addDataScheme("package");
packageFilter.addDataSchemeSpecificPart("android", PatternMatcher.PATTERN_LITERAL);
mContext.registerReceiver(this, packageFilter, null, handler);
if (mCalendar != null || mClock != null) {
final IntentFilter filter = new IntentFilter(ACTION_TIMEZONE_CHANGED);
if (mCalendar != null) {
@@ -316,20 +353,14 @@ public class IconProvider {
}
}
break;
case ACTION_OVERLAY_CHANGED: {
String newState = getSystemIconState();
if (!mIconState.equals(newState)) {
mIconState = newState;
mCallback.onSystemIconStateChanged(mIconState);
}
break;
}
}
}
@Override
public void close() {
mContext.unregisterReceiver(this);
try {
mContext.unregisterReceiver(this);
} catch (Exception ignored) { }
}
}
@@ -342,10 +373,5 @@ public class IconProvider {
* Called when the icon for a particular app changes
*/
void onAppIconChanged(String packageName, UserHandle user);
/**
* Called when the global icon state changed, which can typically affect all icons
*/
void onSystemIconStateChanged(String iconState);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2022 The Android Open Source Project
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@ import android.graphics.ColorFilter;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.AdaptiveIconDrawable;
@@ -35,7 +36,7 @@ import android.os.Build;
import androidx.annotation.WorkerThread;
import com.android.launcher3.icons.BaseIconFactory.ClippedMonoDrawable;
import com.android.launcher3.icons.mono.MonoIconThemeController.ClippedMonoDrawable;
import java.nio.ByteBuffer;
@@ -59,7 +60,7 @@ public class MonochromeIconFactory extends Drawable {
private final Paint mDrawPaint;
private final Rect mSrcRect;
MonochromeIconFactory(int iconBitmapSize) {
public MonochromeIconFactory(int iconBitmapSize) {
float extraFactor = AdaptiveIconDrawable.getExtraInsetFraction();
float viewPortScale = 1 / (1 + 2 * extraFactor);
mBitmapSize = Math.round(iconBitmapSize * 2 * viewPortScale);
@@ -82,7 +83,6 @@ public class MonochromeIconFactory extends Drawable {
// Crate a color matrix which converts the icon to grayscale and then uses the average
// of RGB components as the alpha component.
ColorMatrix satMatrix = new ColorMatrix();
satMatrix.setSaturation(0);
float[] vals = satMatrix.getArray();
vals[15] = vals[16] = vals[17] = .3333f;
vals[18] = vals[19] = 0;
@@ -100,12 +100,12 @@ public class MonochromeIconFactory extends Drawable {
* Creates a monochrome version of the provided drawable
*/
@WorkerThread
public Drawable wrap(AdaptiveIconDrawable icon) {
public Drawable wrap(AdaptiveIconDrawable icon, Path shapePath, Float iconScale) {
mFlatCanvas.drawColor(Color.BLACK);
drawDrawable(icon.getBackground());
drawDrawable(icon.getForeground());
generateMono();
return new ClippedMonoDrawable(this);
return new ClippedMonoDrawable(this, shapePath, iconScale);
}
@WorkerThread
@@ -145,6 +145,29 @@ public class MonochromeIconFactory extends Drawable {
int p2 = Math.round((p - min) * 0xFF / range);
mPixels[i] = flipColor ? (byte) (255 - p2) : (byte) (p2);
}
// Second phase of processing, aimed on increasing the contrast
for (int i = 0; i < mPixels.length; i++) {
int p = mPixels[i] & 0xFF;
int p2;
double coefficient;
if (p > 128) {
coefficient = (1 - (double) (p - 128) / 128);
p2 = 255 - (int) (coefficient * (255 - p));
} else {
coefficient = (1 - (double) (128 - p) / 128);
p2 = (int) (coefficient * p);
}
if (p2 > 255) {
p2 = 255;
} else if (p2 < 0) {
p2 = 0;
}
mPixels[i] = (byte) p2;
}
buffer.rewind();
mAlphaBitmap.copyPixelsFromBuffer(buffer);
}
@@ -25,6 +25,8 @@ import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import androidx.core.graphics.ColorUtils;
@@ -39,12 +41,22 @@ public class PlaceHolderIconDrawable extends FastBitmapDrawable {
public PlaceHolderIconDrawable(BitmapInfo info, Context context) {
super(info);
mProgressPath = GraphicsUtils.getShapePath(context, 100);
mProgressPath = getDefaultPath();
mPaint.setColor(ColorUtils.compositeColors(
GraphicsUtils.getAttrColor(context, R.attr.loadingIconColor), info.color));
}
/**
* Gets the current default icon mask {@link Path}.
* @return Shaped {@link Path} scaled to [0, 0, 100, 100] bounds
*/
private Path getDefaultPath() {
AdaptiveIconDrawable drawable = new AdaptiveIconDrawable(
new ColorDrawable(Color.BLACK), new ColorDrawable(Color.BLACK));
drawable.setBounds(0, 0, 100, 100);
return new Path(drawable.getIconMask());
}
@Override
protected void drawInternal(Canvas canvas, Rect bounds) {
int saveCount = canvas.save();
@@ -31,18 +31,27 @@ public class RoundDrawableWrapper extends DrawableWrapper {
private final RectF mTempRect = new RectF();
private final Path mClipPath = new Path();
private final float mRoundedCornersRadius;
private float mScaledCornerRadius;
public RoundDrawableWrapper(Drawable dr, float radius) {
super(dr);
mRoundedCornersRadius = radius;
mScaledCornerRadius = mRoundedCornersRadius;
}
/**
* Sets the scaling to be applied to the corner radius when it draws next time.
*/
public void setCornerRadiusScale(float scale) {
mScaledCornerRadius = Math.round(mRoundedCornersRadius * scale);
}
@Override
protected void onBoundsChange(Rect bounds) {
mTempRect.set(getBounds());
mClipPath.reset();
mClipPath.addRoundRect(mTempRect, mRoundedCornersRadius,
mRoundedCornersRadius, Path.Direction.CCW);
mClipPath.addRoundRect(mTempRect, mScaledCornerRadius, mScaledCornerRadius,
Path.Direction.CCW);
super.onBoundsChange(bounds);
}
@@ -45,6 +45,11 @@ public class ShadowGenerator {
private static final float HALF_DISTANCE = 0.5f;
private static final int AMBIENT_SHADOW_ALPHA = 25;
// Amount by which an icon should be scaled down to make room for shadows.
// We are ignoring KEY_SHADOW_DISTANCE because regular icons also ignore this: b/298203449
public static final float ICON_SCALE_FOR_SHADOWS =
(HALF_DISTANCE - BLUR_FACTOR) / HALF_DISTANCE;
private final int mIconSize;
private final Paint mBlurPaint;
@@ -95,30 +100,6 @@ public class ShadowGenerator {
}
}
/**
* Returns the minimum amount by which an icon with {@param bounds} should be scaled
* so that the shadows do not get clipped.
*/
public static float getScaleForBounds(RectF bounds) {
float scale = 1;
if (ENABLE_SHADOWS) {
// For top, left & right, we need same space.
float minSide = Math.min(Math.min(bounds.left, bounds.right), bounds.top);
if (minSide < BLUR_FACTOR) {
scale = (HALF_DISTANCE - BLUR_FACTOR) / (HALF_DISTANCE - minSide);
}
// We are ignoring KEY_SHADOW_DISTANCE because regular icons ignore this at the moment b/298203449
float bottomSpace = BLUR_FACTOR;
if (bounds.bottom < bottomSpace) {
scale = Math.min(scale,
(HALF_DISTANCE - bottomSpace) / (HALF_DISTANCE - bounds.bottom));
}
}
return scale;
}
public static class Builder {
public final RectF bounds = new RectF();
@@ -0,0 +1,63 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.icons
import android.content.Context
import android.graphics.drawable.AdaptiveIconDrawable
import com.android.launcher3.icons.cache.CachingLogic
import com.android.launcher3.util.ComponentKey
/** Represents a themed version of a BitmapInfo */
interface ThemedBitmap {
/** Creates a new Drawable */
fun newDrawable(info: BitmapInfo, context: Context): FastBitmapDrawable
fun serialize(): ByteArray
}
interface IconThemeController {
val themeID: String
fun createThemedBitmap(
icon: AdaptiveIconDrawable,
info: BitmapInfo,
factory: BaseIconFactory,
sourceHint: SourceHint? = null,
): ThemedBitmap?
fun decode(
data: ByteArray,
info: BitmapInfo,
factory: BaseIconFactory,
sourceHint: SourceHint,
): ThemedBitmap?
fun createThemedAdaptiveIcon(
context: Context,
originalIcon: AdaptiveIconDrawable,
info: BitmapInfo?,
): AdaptiveIconDrawable?
}
data class SourceHint(
val key: ComponentKey,
val logic: CachingLogic<*>,
val freshnessId: String? = null,
val isFileDrawable: Boolean = false,
)
@@ -1,130 +0,0 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.icons;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BlendMode;
import android.graphics.BlendModeColorFilter;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Rect;
/**
* Class to handle monochrome themed app icons
*/
@SuppressWarnings("NewApi")
public class ThemedIconDrawable extends FastBitmapDrawable {
public static final String TAG = "ThemedIconDrawable";
final BitmapInfo bitmapInfo;
final int colorFg, colorBg;
// The foreground/monochrome icon for the app
private final Bitmap mMonoIcon;
private final Paint mMonoPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
private final Bitmap mBgBitmap;
private final Paint mBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
private final ColorFilter mBgFilter, mMonoFilter;
protected ThemedIconDrawable(ThemedConstantState constantState) {
super(constantState.mBitmap, constantState.colorFg);
bitmapInfo = constantState.bitmapInfo;
colorBg = constantState.colorBg;
colorFg = constantState.colorFg;
mMonoIcon = bitmapInfo.mMono;
mMonoFilter = new BlendModeColorFilter(colorFg, BlendMode.SRC_IN);
mMonoPaint.setColorFilter(mMonoFilter);
mBgBitmap = bitmapInfo.mWhiteShadowLayer;
mBgFilter = new BlendModeColorFilter(colorBg, BlendMode.SRC_IN);
mBgPaint.setColorFilter(mBgFilter);
}
@Override
protected void drawInternal(Canvas canvas, Rect bounds) {
canvas.drawBitmap(mBgBitmap, null, bounds, mBgPaint);
canvas.drawBitmap(mMonoIcon, null, bounds, mMonoPaint);
}
@Override
protected void updateFilter() {
super.updateFilter();
int alpha = mIsDisabled ? (int) (mDisabledAlpha * FULLY_OPAQUE) : FULLY_OPAQUE;
mBgPaint.setAlpha(alpha);
mBgPaint.setColorFilter(mIsDisabled ? new BlendModeColorFilter(
getDisabledColor(colorBg), BlendMode.SRC_IN) : mBgFilter);
mMonoPaint.setAlpha(alpha);
mMonoPaint.setColorFilter(mIsDisabled ? new BlendModeColorFilter(
getDisabledColor(colorFg), BlendMode.SRC_IN) : mMonoFilter);
}
@Override
public boolean isThemed() {
return true;
}
@Override
public FastBitmapConstantState newConstantState() {
return new ThemedConstantState(bitmapInfo, colorBg, colorFg);
}
static class ThemedConstantState extends FastBitmapConstantState {
final BitmapInfo bitmapInfo;
final int colorFg, colorBg;
public ThemedConstantState(BitmapInfo bitmapInfo, int colorBg, int colorFg) {
super(bitmapInfo.icon, bitmapInfo.color);
this.bitmapInfo = bitmapInfo;
this.colorBg = colorBg;
this.colorFg = colorFg;
}
@Override
public FastBitmapDrawable createDrawable() {
return new ThemedIconDrawable(this);
}
}
public static FastBitmapDrawable newDrawable(BitmapInfo info, Context context) {
int[] colors = getColors(context);
return new ThemedConstantState(info, colors[0], colors[1]).newDrawable();
}
/**
* Get an int array representing background and foreground colors for themed icons
*/
public static int[] getColors(Context context) {
Resources res = context.getResources();
int[] colors = new int[2];
colors[0] = res.getColor(R.color.themed_icon_background_color);
colors[1] = res.getColor(R.color.themed_icon_color);
return colors;
}
@Override
public int getIconColor() {
return colorFg;
}
}
@@ -25,8 +25,11 @@ import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.DrawableWrapper;
@@ -57,13 +60,24 @@ public class UserBadgeDrawable extends DrawableWrapper {
private final int mBaseColor;
private final int mBgColor;
private boolean mShouldDrawBackground = true;
@Nullable private Path mShape;
private Matrix mShapeMatrix = new Matrix();
@VisibleForTesting
public final boolean mIsThemed;
public UserBadgeDrawable(Context context, int badgeRes, int colorRes, boolean isThemed) {
public UserBadgeDrawable(Context context, int badgeRes, int colorRes, boolean isThemed,
@Nullable Path shape) {
super(context.getDrawable(badgeRes));
mShape = shape;
mShapeMatrix = new Matrix();
if (mShape != null) {
mShapeMatrix.setRectToRect(new RectF(0f, 0f, 100f, 100f),
new RectF(0f, 0f, CENTER * 2, CENTER * 2),
Matrix.ScaleToFit.CENTER);
mShape.transform(mShapeMatrix);
}
mIsThemed = isThemed;
if (isThemed) {
mutate();
@@ -94,11 +108,17 @@ public class UserBadgeDrawable extends DrawableWrapper {
canvas.scale(b.width() / VIEWPORT_SIZE, b.height() / VIEWPORT_SIZE);
mPaint.setColor(blendDrawableAlpha(SHADOW_COLOR));
canvas.drawCircle(CENTER, CENTER + SHADOW_OFFSET_Y, SHADOW_RADIUS, mPaint);
if (mShape != null) {
canvas.drawPath(mShape, mPaint);
} else {
canvas.drawCircle(CENTER, CENTER + SHADOW_OFFSET_Y, SHADOW_RADIUS, mPaint);
}
mPaint.setColor(blendDrawableAlpha(mBgColor));
canvas.drawCircle(CENTER, CENTER, BG_RADIUS, mPaint);
if (mShape != null) {
canvas.drawPath(mShape, mPaint);
} else {
canvas.drawCircle(CENTER, CENTER, BG_RADIUS, mPaint);
}
canvas.restoreToCount(saveCount);
}
super.draw(canvas);
@@ -0,0 +1,73 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.icons.cache
import android.content.ComponentName
import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.os.UserHandle
import com.android.launcher3.icons.BaseIconFactory.IconOptions
import com.android.launcher3.icons.BitmapInfo
import com.android.launcher3.icons.IconProvider
import com.android.launcher3.icons.cache.BaseIconCache.Companion.EMPTY_CLASS_NAME
/** Caching logic for ApplicationInfo */
class AppInfoCachingLogic(
private val pm: PackageManager,
private val instantAppResolver: (ApplicationInfo) -> Boolean,
private val errorLogger: (String, Exception?) -> Unit = { _, _ -> },
) : CachingLogic<ApplicationInfo> {
override fun getComponent(info: ApplicationInfo) =
ComponentName(info.packageName, info.packageName + EMPTY_CLASS_NAME)
override fun getUser(info: ApplicationInfo) = UserHandle.getUserHandleForUid(info.uid)
override fun getLabel(info: ApplicationInfo) = info.loadLabel(pm)
override fun getApplicationInfo(info: ApplicationInfo) = info
override fun loadIcon(
context: Context,
cache: BaseIconCache,
info: ApplicationInfo,
): BitmapInfo {
// Load the full res icon for the application, but if useLowResIcon is set, then
// only keep the low resolution icon instead of the larger full-sized icon
val appIcon = cache.iconProvider.getIcon(info)
if (context.packageManager.isDefaultApplicationIcon(appIcon)) {
errorLogger.invoke(
String.format("Default icon returned for %s", info.packageName),
null,
)
}
return cache.iconFactory.use { li ->
li.createBadgedIconBitmap(
appIcon,
IconOptions()
.setUser(getUser(info))
.setInstantApp(instantAppResolver.invoke(info))
.setSourceHint(getSourceHint(info, cache)),
)
}
}
override fun getFreshnessIdentifier(item: ApplicationInfo, iconProvider: IconProvider) =
iconProvider.getStateForApp(item)
}
@@ -1,823 +0,0 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.icons.cache;
import static android.graphics.BitmapFactory.decodeByteArray;
import static com.android.launcher3.icons.BaseIconFactory.getFullResDefaultActivityIcon;
import static com.android.launcher3.icons.BitmapInfo.LOW_RES_ICON;
import static com.android.launcher3.icons.GraphicsUtils.flattenBitmap;
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
import static java.util.Objects.requireNonNull;
import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Handler;
import android.os.LocaleList;
import android.os.Looper;
import android.os.Process;
import android.os.Trace;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
import com.android.launcher3.icons.BaseIconFactory;
import com.android.launcher3.icons.BaseIconFactory.IconOptions;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.FlagOp;
import com.android.launcher3.util.SQLiteCacheHelper;
import java.nio.ByteBuffer;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
public abstract class BaseIconCache {
private static final String TAG = "BaseIconCache";
private static final boolean DEBUG = false;
private static final int INITIAL_ICON_CACHE_CAPACITY = 50;
// A format string which returns the original string as is.
private static final String IDENTITY_FORMAT_STRING = "%1$s";
// Empty class name is used for storing package default entry.
public static final String EMPTY_CLASS_NAME = ".";
public static class CacheEntry {
@NonNull
public BitmapInfo bitmap = BitmapInfo.LOW_RES_INFO;
@NonNull
public CharSequence title = "";
@NonNull
public CharSequence contentDescription = "";
}
@NonNull
protected final Context mContext;
@NonNull
protected final PackageManager mPackageManager;
@NonNull
private final Map<ComponentKey, CacheEntry> mCache;
@NonNull
protected final Handler mWorkerHandler;
protected int mIconDpi;
@NonNull
protected IconDB mIconDb;
@NonNull
protected LocaleList mLocaleList = LocaleList.getEmptyLocaleList();
@NonNull
protected String mSystemState = "";
@Nullable
private BitmapInfo mDefaultIcon;
@NonNull
private final SparseArray<FlagOp> mUserFlagOpMap = new SparseArray<>();
private final SparseArray<String> mUserFormatString = new SparseArray<>();
@Nullable
private final String mDbFileName;
@NonNull
private final Looper mBgLooper;
private volatile boolean mIconUpdateInProgress = false;
public BaseIconCache(@NonNull final Context context, @Nullable final String dbFileName,
@NonNull final Looper bgLooper, final int iconDpi, final int iconPixelSize,
final boolean inMemoryCache) {
mContext = context;
mDbFileName = dbFileName;
mPackageManager = context.getPackageManager();
mBgLooper = bgLooper;
mWorkerHandler = new Handler(mBgLooper);
if (inMemoryCache) {
mCache = new HashMap<>(INITIAL_ICON_CACHE_CAPACITY);
} else {
// Use a dummy cache
mCache = new AbstractMap<ComponentKey, CacheEntry>() {
@Override
public Set<Entry<ComponentKey, CacheEntry>> entrySet() {
return Collections.emptySet();
}
@Override
public CacheEntry put(ComponentKey key, CacheEntry value) {
return value;
}
};
}
updateSystemState();
mIconDpi = iconDpi;
mIconDb = new IconDB(context, dbFileName, iconPixelSize);
}
/**
* Returns the persistable serial number for {@param user}. Subclass should implement proper
* caching strategy to avoid making binder call every time.
*/
protected abstract long getSerialNumberForUser(@NonNull final UserHandle user);
/**
* Return true if the given app is an instant app and should be badged appropriately.
*/
protected abstract boolean isInstantApp(@NonNull final ApplicationInfo info);
/**
* Opens and returns an icon factory. The factory is recycled by the caller.
*/
@NonNull
public abstract BaseIconFactory getIconFactory();
public void updateIconParams(final int iconDpi, final int iconPixelSize) {
mWorkerHandler.post(() -> updateIconParamsBg(iconDpi, iconPixelSize));
}
private synchronized void updateIconParamsBg(final int iconDpi, final int iconPixelSize) {
mIconDpi = iconDpi;
mDefaultIcon = null;
mUserFlagOpMap.clear();
mIconDb.clear();
mIconDb.close();
mIconDb = new IconDB(mContext, mDbFileName, iconPixelSize);
mCache.clear();
}
@Nullable
private Drawable getFullResIcon(@Nullable final Resources resources, final int iconId) {
if (resources != null && iconId != 0) {
try {
return resources.getDrawableForDensity(iconId, mIconDpi);
} catch (Resources.NotFoundException e) {
}
}
return getFullResDefaultActivityIcon(mIconDpi);
}
@Nullable
public Drawable getFullResIcon(@NonNull final String packageName, final int iconId) {
try {
return getFullResIcon(mPackageManager.getResourcesForApplication(packageName), iconId);
} catch (PackageManager.NameNotFoundException e) {
}
return getFullResDefaultActivityIcon(mIconDpi);
}
@Nullable
public Drawable getFullResIcon(@NonNull final ActivityInfo info) {
try {
return getFullResIcon(mPackageManager.getResourcesForApplication(info.applicationInfo),
info.getIconResource());
} catch (PackageManager.NameNotFoundException e) {
}
return getFullResDefaultActivityIcon(mIconDpi);
}
public void setIconUpdateInProgress(boolean updating) {
mIconUpdateInProgress = updating;
}
public boolean isIconUpdateInProgress() {
return mIconUpdateInProgress;
}
/**
* Remove any records for the supplied ComponentName.
*/
public synchronized void remove(@NonNull final ComponentName componentName,
@NonNull final UserHandle user) {
mCache.remove(new ComponentKey(componentName, user));
}
/**
* Remove any records for the supplied package name from memory.
*/
private void removeFromMemCacheLocked(@Nullable final String packageName,
@Nullable final UserHandle user) {
HashSet<ComponentKey> forDeletion = new HashSet<>();
for (ComponentKey key : mCache.keySet()) {
if (key.componentName.getPackageName().equals(packageName)
&& key.user.equals(user)) {
forDeletion.add(key);
}
}
for (ComponentKey condemned : forDeletion) {
mCache.remove(condemned);
}
}
/**
* Removes the entries related to the given package in memory and persistent DB.
*/
public synchronized void removeIconsForPkg(@NonNull final String packageName,
@NonNull final UserHandle user) {
removeFromMemCacheLocked(packageName, user);
long userSerial = getSerialNumberForUser(user);
mIconDb.delete(
IconDB.COLUMN_COMPONENT + " LIKE ? AND " + IconDB.COLUMN_USER + " = ?",
new String[]{packageName + "/%", Long.toString(userSerial)});
}
@NonNull
public IconCacheUpdateHandler getUpdateHandler() {
updateSystemState();
return new IconCacheUpdateHandler(this);
}
/**
* Refreshes the system state definition used to check the validity of the cache. It
* incorporates all the properties that can affect the cache like the list of enabled locale
* and system-version.
*/
private void updateSystemState() {
mLocaleList = mContext.getResources().getConfiguration().getLocales();
mSystemState = mLocaleList.toLanguageTags() + "," + Build.VERSION.SDK_INT;
mUserFormatString.clear();
}
@NonNull
protected String getIconSystemState(@Nullable final String packageName) {
return mSystemState;
}
public CharSequence getUserBadgedLabel(CharSequence label, UserHandle user) {
int key = user.hashCode();
int index = mUserFormatString.indexOfKey(key);
String format;
if (index < 0) {
format = mPackageManager.getUserBadgedLabel(IDENTITY_FORMAT_STRING, user).toString();
if (TextUtils.equals(IDENTITY_FORMAT_STRING, format)) {
format = null;
}
mUserFormatString.put(key, format);
} else {
format = mUserFormatString.valueAt(index);
}
return format == null ? label : String.format(format, label);
}
/**
* Adds an entry into the DB and the in-memory cache.
*
* @param replaceExisting if true, it will recreate the bitmap even if it already exists in
* the memory. This is useful then the previous bitmap was created using
* old data.
*/
@VisibleForTesting
public synchronized <T> void addIconToDBAndMemCache(@NonNull final T object,
@NonNull final CachingLogic<T> cachingLogic, @NonNull final PackageInfo info,
final long userSerial, final boolean replaceExisting) {
UserHandle user = cachingLogic.getUser(object);
ComponentName componentName = cachingLogic.getComponent(object);
final ComponentKey key = new ComponentKey(componentName, user);
CacheEntry entry = null;
if (!replaceExisting) {
entry = mCache.get(key);
// We can't reuse the entry if the high-res icon is not present.
if (entry == null || entry.bitmap.isNullOrLowRes()) {
entry = null;
}
}
if (entry == null) {
entry = new CacheEntry();
entry.bitmap = cachingLogic.loadIcon(mContext, object);
}
// Icon can't be loaded from cachingLogic, which implies alternative icon was loaded
// (e.g. fallback icon, default icon). So we drop here since there's no point in caching
// an empty entry.
if (entry.bitmap.isNullOrLowRes()) return;
CharSequence entryTitle = cachingLogic.getLabel(object);
if (TextUtils.isEmpty(entryTitle)) {
if (entryTitle == null) {
Log.wtf(TAG, "No label returned from caching logic instance: " + cachingLogic);
}
entryTitle = componentName.getPackageName();
}
entry.title = entryTitle;
entry.contentDescription = getUserBadgedLabel(entry.title, user);
if (cachingLogic.addToMemCache()) mCache.put(key, entry);
ContentValues values = newContentValues(entry.bitmap, entry.title.toString(),
componentName.getPackageName(), cachingLogic.getKeywords(object, mLocaleList));
addIconToDB(values, componentName, info, userSerial,
cachingLogic.getLastUpdatedTime(object, info));
}
/**
* Updates {@param values} to contain versioning information and adds it to the DB.
*
* @param values {@link ContentValues} containing icon & title
*/
private void addIconToDB(@NonNull final ContentValues values, @NonNull final ComponentName key,
@NonNull final PackageInfo info, final long userSerial, final long lastUpdateTime) {
values.put(IconDB.COLUMN_COMPONENT, key.flattenToString());
values.put(IconDB.COLUMN_USER, userSerial);
values.put(IconDB.COLUMN_LAST_UPDATED, lastUpdateTime);
values.put(IconDB.COLUMN_VERSION, info.versionCode);
mIconDb.insertOrReplace(values);
}
@NonNull
public synchronized BitmapInfo getDefaultIcon(@NonNull final UserHandle user) {
if (mDefaultIcon == null) {
try (BaseIconFactory li = getIconFactory()) {
mDefaultIcon = li.makeDefaultIcon();
}
}
return mDefaultIcon.withFlags(getUserFlagOpLocked(user));
}
@NonNull
protected FlagOp getUserFlagOpLocked(@NonNull final UserHandle user) {
int key = user.hashCode();
int index;
if ((index = mUserFlagOpMap.indexOfKey(key)) >= 0) {
return mUserFlagOpMap.valueAt(index);
} else {
try (BaseIconFactory li = getIconFactory()) {
FlagOp op = li.getBitmapFlagOp(new IconOptions().setUser(user));
mUserFlagOpMap.put(key, op);
return op;
}
}
}
public boolean isDefaultIcon(@NonNull final BitmapInfo icon, @NonNull final UserHandle user) {
return getDefaultIcon(user).icon == icon.icon;
}
/**
* Retrieves the entry from the cache. If the entry is not present, it creates a new entry.
* This method is not thread safe, it must be called from a synchronized method.
*/
@NonNull
protected <T> CacheEntry cacheLocked(
@NonNull final ComponentName componentName, @NonNull final UserHandle user,
@NonNull final Supplier<T> infoProvider, @NonNull final CachingLogic<T> cachingLogic,
final boolean usePackageIcon, final boolean useLowResIcon) {
return cacheLocked(
componentName,
user,
infoProvider,
cachingLogic,
null,
usePackageIcon,
useLowResIcon);
}
@NonNull
protected <T> CacheEntry cacheLocked(
@NonNull final ComponentName componentName, @NonNull final UserHandle user,
@NonNull final Supplier<T> infoProvider, @NonNull final CachingLogic<T> cachingLogic,
@Nullable final Cursor cursor, final boolean usePackageIcon,
final boolean useLowResIcon) {
assertWorkerThread();
ComponentKey cacheKey = new ComponentKey(componentName, user);
CacheEntry entry = mCache.get(cacheKey);
if (entry == null || (entry.bitmap.isLowRes() && !useLowResIcon)) {
entry = new CacheEntry();
if (cachingLogic.addToMemCache()) {
mCache.put(cacheKey, entry);
}
// Check the DB first.
T object = null;
boolean providerFetchedOnce = false;
boolean cacheEntryUpdated = cursor == null
? getEntryFromDBLocked(cacheKey, entry, useLowResIcon)
: updateTitleAndIconLocked(cacheKey, entry, cursor, useLowResIcon);
if (!cacheEntryUpdated) {
object = infoProvider.get();
providerFetchedOnce = true;
loadFallbackIcon(
object,
entry,
cachingLogic,
usePackageIcon,
/* usePackageTitle= */ true,
componentName,
user);
}
if (TextUtils.isEmpty(entry.title)) {
if (object == null && !providerFetchedOnce) {
object = infoProvider.get();
providerFetchedOnce = true;
}
if (object != null) {
loadFallbackTitle(object, entry, cachingLogic, user);
}
}
}
return entry;
}
/**
* Fallback method for loading an icon bitmap.
*/
protected <T> void loadFallbackIcon(@Nullable final T object, @NonNull final CacheEntry entry,
@NonNull final CachingLogic<T> cachingLogic, final boolean usePackageIcon,
final boolean usePackageTitle, @NonNull final ComponentName componentName,
@NonNull final UserHandle user) {
if (object != null) {
entry.bitmap = cachingLogic.loadIcon(mContext, object);
} else {
if (usePackageIcon) {
CacheEntry packageEntry = getEntryForPackageLocked(
componentName.getPackageName(), user, false);
if (DEBUG) {
Log.d(TAG, "using package default icon for "
+ componentName.toShortString());
}
entry.bitmap = packageEntry.bitmap;
entry.contentDescription = packageEntry.contentDescription;
if (usePackageTitle) {
entry.title = packageEntry.title;
}
}
if (entry.bitmap == null) {
// TODO: entry.bitmap can never be null, so this should not happen at all.
Log.wtf(TAG, "using default icon for " + componentName.toShortString());
entry.bitmap = getDefaultIcon(user);
}
}
}
/**
* Fallback method for loading an app title.
*/
protected <T> void loadFallbackTitle(
@NonNull final T object, @NonNull final CacheEntry entry,
@NonNull final CachingLogic<T> cachingLogic, @NonNull final UserHandle user) {
entry.title = cachingLogic.getLabel(object);
if (TextUtils.isEmpty(entry.title)) {
entry.title = cachingLogic.getComponent(object).getPackageName();
}
entry.contentDescription = getUserBadgedLabel(
cachingLogic.getDescription(object, entry.title), user);
}
public synchronized void clearMemoryCache() {
assertWorkerThread();
mCache.clear();
}
/**
* Adds a default package entry in the cache. This entry is not persisted and will be removed
* when the cache is flushed.
*/
protected synchronized void cachePackageInstallInfo(@NonNull final String packageName,
@NonNull final UserHandle user, @Nullable final Bitmap icon,
@Nullable final CharSequence title) {
removeFromMemCacheLocked(packageName, user);
ComponentKey cacheKey = getPackageKey(packageName, user);
CacheEntry entry = mCache.get(cacheKey);
// For icon caching, do not go through DB. Just update the in-memory entry.
if (entry == null) {
entry = new CacheEntry();
}
if (!TextUtils.isEmpty(title)) {
entry.title = title;
}
if (icon != null) {
BaseIconFactory li = getIconFactory();
entry.bitmap = li.createShapedIconBitmap(icon, new IconOptions().setUser(user));
li.close();
}
if (!TextUtils.isEmpty(title) && entry.bitmap.icon != null) {
mCache.put(cacheKey, entry);
}
}
@NonNull
private static ComponentKey getPackageKey(@NonNull final String packageName,
@NonNull final UserHandle user) {
ComponentName cn = new ComponentName(packageName, packageName + EMPTY_CLASS_NAME);
return new ComponentKey(cn, user);
}
/**
* Gets an entry for the package, which can be used as a fallback entry for various components.
* This method is not thread safe, it must be called from a synchronized method.
*/
@WorkerThread
@NonNull
@SuppressWarnings("NewApi")
protected CacheEntry getEntryForPackageLocked(@NonNull final String packageName,
@NonNull final UserHandle user, final boolean useLowResIcon) {
assertWorkerThread();
ComponentKey cacheKey = getPackageKey(packageName, user);
CacheEntry entry = mCache.get(cacheKey);
if (entry == null || (entry.bitmap.isLowRes() && !useLowResIcon)) {
entry = new CacheEntry();
boolean entryUpdated = true;
// Check the DB first.
if (!getEntryFromDBLocked(cacheKey, entry, useLowResIcon)) {
try {
long flags = Process.myUserHandle().equals(user) ? 0 :
PackageManager.GET_UNINSTALLED_PACKAGES;
flags |= PackageManager.MATCH_ARCHIVED_PACKAGES;
PackageInfo info = mPackageManager.getPackageInfo(packageName,
PackageManager.PackageInfoFlags.of(flags));
ApplicationInfo appInfo = info.applicationInfo;
if (appInfo == null) {
NameNotFoundException e = new NameNotFoundException(
"ApplicationInfo is null");
logdPersistently(TAG,
String.format("ApplicationInfo is null for %s", packageName),
e);
throw e;
}
BaseIconFactory li = getIconFactory();
// Load the full res icon for the application, but if useLowResIcon is set, then
// only keep the low resolution icon instead of the larger full-sized icon
Drawable appIcon = appInfo.loadIcon(mPackageManager);
if (mPackageManager.isDefaultApplicationIcon(appIcon)) {
logdPersistently(TAG,
String.format("Default icon returned for %s", packageName),
null);
}
BitmapInfo iconInfo = li.createBadgedIconBitmap(appIcon,
new IconOptions().setUser(user).setInstantApp(isInstantApp(appInfo)));
li.close();
entry.title = appInfo.loadLabel(mPackageManager);
entry.contentDescription = getUserBadgedLabel(entry.title, user);
entry.bitmap = useLowResIcon
? BitmapInfo.of(LOW_RES_ICON, iconInfo.color)
: iconInfo;
// Add the icon in the DB here, since these do not get written during
// package updates.
ContentValues values = newContentValues(
iconInfo, entry.title.toString(), packageName, null);
addIconToDB(values, cacheKey.componentName, info, getSerialNumberForUser(user),
info.lastUpdateTime);
} catch (NameNotFoundException e) {
if (DEBUG) Log.d(TAG, "Application not installed " + packageName);
entryUpdated = false;
}
}
// Only add a filled-out entry to the cache
if (entryUpdated) {
mCache.put(cacheKey, entry);
}
}
return entry;
}
protected boolean getEntryFromDBLocked(@NonNull final ComponentKey cacheKey,
@NonNull final CacheEntry entry, final boolean lowRes) {
Cursor c = null;
Trace.beginSection("loadIconIndividually");
try {
c = mIconDb.query(
lowRes ? IconDB.COLUMNS_LOW_RES : IconDB.COLUMNS_HIGH_RES,
IconDB.COLUMN_COMPONENT + " = ? AND " + IconDB.COLUMN_USER + " = ?",
new String[]{
cacheKey.componentName.flattenToString(),
Long.toString(getSerialNumberForUser(cacheKey.user))});
if (c.moveToNext()) {
return updateTitleAndIconLocked(cacheKey, entry, c, lowRes);
}
} catch (SQLiteException e) {
Log.d(TAG, "Error reading icon cache", e);
} finally {
if (c != null) {
c.close();
}
Trace.endSection();
}
return false;
}
private boolean updateTitleAndIconLocked(
@NonNull final ComponentKey cacheKey, @NonNull final CacheEntry entry,
@NonNull final Cursor c, final boolean lowRes) {
// Set the alpha to be 255, so that we never have a wrong color
entry.bitmap = BitmapInfo.of(LOW_RES_ICON,
setColorAlphaBound(c.getInt(IconDB.INDEX_COLOR), 255));
entry.title = c.getString(IconDB.INDEX_TITLE);
if (entry.title == null) {
entry.title = "";
entry.contentDescription = "";
} else {
entry.contentDescription = getUserBadgedLabel(entry.title, cacheKey.user);
}
if (!lowRes) {
byte[] data = c.getBlob(IconDB.INDEX_ICON);
if (data == null) {
return false;
}
try {
BitmapFactory.Options decodeOptions = new BitmapFactory.Options();
decodeOptions.inPreferredConfig = Config.HARDWARE;
entry.bitmap = BitmapInfo.of(
requireNonNull(decodeByteArray(data, 0, data.length, decodeOptions)),
entry.bitmap.color);
} catch (Exception e) {
return false;
}
// Decode mono bitmap
data = c.getBlob(IconDB.INDEX_MONO_ICON);
Bitmap icon = entry.bitmap.icon;
if (data != null && data.length == icon.getHeight() * icon.getWidth()) {
Bitmap monoBitmap = Bitmap.createBitmap(
icon.getWidth(), icon.getHeight(), Config.ALPHA_8);
monoBitmap.copyPixelsFromBuffer(ByteBuffer.wrap(data));
Bitmap hwMonoBitmap = monoBitmap.copy(Config.HARDWARE, false /*isMutable*/);
if (hwMonoBitmap != null) {
monoBitmap.recycle();
monoBitmap = hwMonoBitmap;
}
try (BaseIconFactory factory = getIconFactory()) {
entry.bitmap.setMonoIcon(monoBitmap, factory);
}
}
}
entry.bitmap.flags = c.getInt(IconDB.INDEX_FLAGS);
entry.bitmap = entry.bitmap.withFlags(getUserFlagOpLocked(cacheKey.user));
return entry.bitmap != null;
}
/**
* Returns a cursor for an arbitrary query to the cache db
*/
public synchronized Cursor queryCacheDb(String[] columns, String selection,
String[] selectionArgs) {
return mIconDb.query(columns, selection, selectionArgs);
}
/**
* Cache class to store the actual entries on disk
*/
public static final class IconDB extends SQLiteCacheHelper {
private static final int RELEASE_VERSION = 34;
public static final String TABLE_NAME = "icons";
public static final String COLUMN_ROWID = "rowid";
public static final String COLUMN_COMPONENT = "componentName";
public static final String COLUMN_USER = "profileId";
public static final String COLUMN_LAST_UPDATED = "lastUpdated";
public static final String COLUMN_VERSION = "version";
public static final String COLUMN_ICON = "icon";
public static final String COLUMN_ICON_COLOR = "icon_color";
public static final String COLUMN_MONO_ICON = "mono_icon";
public static final String COLUMN_FLAGS = "flags";
public static final String COLUMN_LABEL = "label";
public static final String COLUMN_SYSTEM_STATE = "system_state";
public static final String COLUMN_KEYWORDS = "keywords";
public static final String[] COLUMNS_LOW_RES = new String[]{
COLUMN_COMPONENT,
COLUMN_LABEL,
COLUMN_ICON_COLOR,
COLUMN_FLAGS};
public static final String[] COLUMNS_HIGH_RES = Arrays.copyOf(COLUMNS_LOW_RES,
COLUMNS_LOW_RES.length + 2, String[].class);
static {
COLUMNS_HIGH_RES[COLUMNS_LOW_RES.length] = COLUMN_ICON;
COLUMNS_HIGH_RES[COLUMNS_LOW_RES.length + 1] = COLUMN_MONO_ICON;
}
private static final int INDEX_TITLE = Arrays.asList(COLUMNS_LOW_RES).indexOf(COLUMN_LABEL);
private static final int INDEX_COLOR = Arrays.asList(COLUMNS_LOW_RES)
.indexOf(COLUMN_ICON_COLOR);
private static final int INDEX_FLAGS = Arrays.asList(COLUMNS_LOW_RES).indexOf(COLUMN_FLAGS);
private static final int INDEX_ICON = COLUMNS_LOW_RES.length;
private static final int INDEX_MONO_ICON = INDEX_ICON + 1;
public IconDB(Context context, String dbFileName, int iconPixelSize) {
super(context, dbFileName, (RELEASE_VERSION << 16) + iconPixelSize, TABLE_NAME);
}
@Override
protected void onCreateTable(SQLiteDatabase db) {
db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " ("
+ COLUMN_COMPONENT + " TEXT NOT NULL, "
+ COLUMN_USER + " INTEGER NOT NULL, "
+ COLUMN_LAST_UPDATED + " INTEGER NOT NULL DEFAULT 0, "
+ COLUMN_VERSION + " INTEGER NOT NULL DEFAULT 0, "
+ COLUMN_ICON + " BLOB, "
+ COLUMN_MONO_ICON + " BLOB, "
+ COLUMN_ICON_COLOR + " INTEGER NOT NULL DEFAULT 0, "
+ COLUMN_FLAGS + " INTEGER NOT NULL DEFAULT 0, "
+ COLUMN_LABEL + " TEXT, "
+ COLUMN_SYSTEM_STATE + " TEXT, "
+ COLUMN_KEYWORDS + " TEXT, "
+ "PRIMARY KEY (" + COLUMN_COMPONENT + ", " + COLUMN_USER + ") "
+ ");");
}
}
@NonNull
private ContentValues newContentValues(@NonNull final BitmapInfo bitmapInfo,
@NonNull final String label, @NonNull final String packageName,
@Nullable final String keywords) {
ContentValues values = new ContentValues();
if (bitmapInfo.canPersist()) {
values.put(IconDB.COLUMN_ICON, flattenBitmap(bitmapInfo.icon));
// Persist mono bitmap as alpha channel
Bitmap mono = bitmapInfo.getMono();
if (mono != null && mono.getHeight() == bitmapInfo.icon.getHeight()
&& mono.getWidth() == bitmapInfo.icon.getWidth()
&& mono.getConfig() == Config.ALPHA_8) {
byte[] pixels = new byte[mono.getWidth() * mono.getHeight()];
mono.copyPixelsToBuffer(ByteBuffer.wrap(pixels));
values.put(IconDB.COLUMN_MONO_ICON, pixels);
} else {
values.put(IconDB.COLUMN_MONO_ICON, (byte[]) null);
}
} else {
values.put(IconDB.COLUMN_ICON, (byte[]) null);
values.put(IconDB.COLUMN_MONO_ICON, (byte[]) null);
}
values.put(IconDB.COLUMN_ICON_COLOR, bitmapInfo.color);
values.put(IconDB.COLUMN_FLAGS, bitmapInfo.flags);
values.put(IconDB.COLUMN_LABEL, label);
values.put(IconDB.COLUMN_SYSTEM_STATE, getIconSystemState(packageName));
values.put(IconDB.COLUMN_KEYWORDS, keywords);
return values;
}
private void assertWorkerThread() {
if (Looper.myLooper() != mBgLooper) {
throw new IllegalStateException("Cache accessed on wrong thread " + Looper.myLooper());
}
}
/** Log to Log.d. Subclasses can override this method to log persistently for debugging. */
protected void logdPersistently(String tag, String message, @Nullable Exception e) {
Log.d(tag, message, e);
}
}
@@ -0,0 +1,682 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.icons.cache
import android.content.ComponentName
import android.content.ContentValues
import android.content.Context
import android.content.pm.ActivityInfo
import android.content.pm.ApplicationInfo
import android.content.pm.LauncherApps
import android.content.pm.PackageManager
import android.content.pm.PackageManager.NameNotFoundException
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteException
import android.database.sqlite.SQLiteReadOnlyDatabaseException
import android.graphics.Bitmap
import android.graphics.Bitmap.Config.HARDWARE
import android.graphics.BitmapFactory
import android.graphics.BitmapFactory.Options
import android.graphics.drawable.Drawable
import android.os.Handler
import android.os.Looper
import android.os.Trace
import android.os.UserHandle
import android.text.TextUtils
import android.util.Log
import android.util.SparseArray
import androidx.annotation.VisibleForTesting
import androidx.annotation.WorkerThread
import com.android.launcher3.Flags
import com.android.launcher3.icons.BaseIconFactory
import com.android.launcher3.icons.BaseIconFactory.IconOptions
import com.android.launcher3.icons.BitmapInfo
import com.android.launcher3.icons.GraphicsUtils
import com.android.launcher3.icons.IconProvider
import com.android.launcher3.icons.SourceHint
import com.android.launcher3.icons.cache.CacheLookupFlag.Companion.DEFAULT_LOOKUP_FLAG
import com.android.launcher3.util.ComponentKey
import com.android.launcher3.util.FlagOp
import com.android.launcher3.util.SQLiteCacheHelper
import java.util.function.Supplier
import kotlin.collections.MutableMap.MutableEntry
abstract class BaseIconCache
@JvmOverloads
constructor(
@JvmField protected val context: Context,
private val dbFileName: String?,
private val bgLooper: Looper,
private var iconDpi: Int,
iconPixelSize: Int,
inMemoryCache: Boolean,
val iconProvider: IconProvider = IconProvider(context),
) {
class CacheEntry {
@JvmField var bitmap: BitmapInfo = BitmapInfo.LOW_RES_INFO
@JvmField var title: CharSequence = ""
@JvmField var contentDescription: CharSequence = ""
}
private val packageManager: PackageManager = context.packageManager
private val cache: MutableMap<ComponentKey, CacheEntry?> =
if (inMemoryCache) {
HashMap(INITIAL_ICON_CACHE_CAPACITY)
} else {
object : AbstractMutableMap<ComponentKey, CacheEntry?>() {
override fun put(key: ComponentKey, value: CacheEntry?): CacheEntry? = value
override val entries: MutableSet<MutableEntry<ComponentKey, CacheEntry?>> =
mutableSetOf()
}
}
val iconUpdateToken = Any()
@JvmField val workerHandler = Handler(bgLooper)
@JvmField protected var iconDb = IconDB(context, dbFileName, iconPixelSize)
private var defaultIcon: BitmapInfo? = null
private val userFlagOpMap = SparseArray<FlagOp>()
private val userFormatString = SparseArray<String?>()
private val appInfoCachingLogic =
AppInfoCachingLogic(
pm = context.packageManager,
instantAppResolver = this::isInstantApp,
errorLogger = this::logPersistently,
)
init {
updateSystemState()
}
/**
* Returns the persistable serial number for {@param user}. Subclass should implement proper
* caching strategy to avoid making binder call every time.
*/
abstract fun getSerialNumberForUser(user: UserHandle): Long
/** Return true if the given app is an instant app and should be badged appropriately. */
protected abstract fun isInstantApp(info: ApplicationInfo): Boolean
/** Opens and returns an icon factory. The factory is recycled by the caller. */
abstract val iconFactory: BaseIconFactory
fun updateIconParams(iconDpi: Int, iconPixelSize: Int) =
workerHandler.post { updateIconParamsBg(iconDpi, iconPixelSize) }
@Synchronized
private fun updateIconParamsBg(iconDpi: Int, iconPixelSize: Int) {
try {
this.iconDpi = iconDpi
defaultIcon = null
userFlagOpMap.clear()
iconDb.clear()
iconDb.close()
iconDb = IconDB(context, dbFileName, iconPixelSize)
cache.clear()
} catch (e: SQLiteReadOnlyDatabaseException) {
// This is known to happen during repeated backup and restores, if the Launcher is in
// restricted mode. When the launcher is loading and the backup restore is being cleared
// there can be a conflict where one DB is trying to delete the DB file, and the other
// is attempting to write to it. The effect is that launcher crashes, then the backup /
// restore process fails, then the user's home screen icons fail to restore. Adding this
// try / catch will stop the crash, and LoaderTask will sanitize any residual icon data,
// leading to a completed backup / restore and a better experience for our customers.
Log.e(TAG, "failed to clear the launcher's icon db or cache.", e)
}
}
fun getFullResIcon(info: ActivityInfo): Drawable? = iconProvider.getIcon(info, iconDpi)
/** Remove any records for the supplied ComponentName. */
@Synchronized
fun remove(componentName: ComponentName, user: UserHandle) =
cache.remove(ComponentKey(componentName, user))
/** Remove any records for the supplied package name from memory. */
private fun removeFromMemCacheLocked(packageName: String, user: UserHandle) =
cache.keys.removeIf { it.componentName.packageName == packageName && it.user == user }
/** Removes the entries related to the given package in memory and persistent DB. */
@Synchronized
fun removeIconsForPkg(packageName: String, user: UserHandle) {
removeFromMemCacheLocked(packageName, user)
iconDb.delete(
"$COLUMN_COMPONENT LIKE ? AND $COLUMN_USER = ?",
arrayOf("$packageName/%", getSerialNumberForUser(user).toString()),
)
}
fun getUpdateHandler(): IconCacheUpdateHandler {
updateSystemState()
// Remove all active icon update tasks.
workerHandler.removeCallbacksAndMessages(iconUpdateToken)
return IconCacheUpdateHandler(this, iconDb, workerHandler)
}
/**
* Refreshes the system state definition used to check the validity of the cache. It
* incorporates all the properties that can affect the cache like the list of enabled locale and
* system-version.
*/
private fun updateSystemState() {
iconProvider.updateSystemState()
userFormatString.clear()
}
fun getUserBadgedLabel(label: CharSequence, user: UserHandle): CharSequence {
val key = user.hashCode()
val index = userFormatString.indexOfKey(key)
var format: String?
if (index < 0) {
format = packageManager.getUserBadgedLabel(IDENTITY_FORMAT_STRING, user).toString()
if (TextUtils.equals(IDENTITY_FORMAT_STRING, format)) {
format = null
}
userFormatString.put(key, format)
} else {
format = userFormatString.valueAt(index)
}
return if (format == null) label else String.format(format, label)
}
/**
* Adds/updates an entry into the DB and the in-memory cache. The update is skipped if the entry
* fails to load
*/
@Synchronized
fun <T : Any> addIconToDBAndMemCache(obj: T, cachingLogic: CachingLogic<T>, userSerial: Long) {
val user = cachingLogic.getUser(obj)
val componentName = cachingLogic.getComponent(obj)
val key = ComponentKey(componentName, user)
val bitmapInfo = cachingLogic.loadIcon(context, this, obj)
// Icon can't be loaded from cachingLogic, which implies alternative icon was loaded
// (e.g. fallback icon, default icon). So we drop here since there's no point in caching
// an empty entry.
if (bitmapInfo.isNullOrLowRes || isDefaultIcon(bitmapInfo, user)) {
return
}
val entryTitle =
cachingLogic.getLabel(obj).let {
if (it.isNullOrEmpty()) componentName.packageName else it
}
// Only add an entry in memory, if there was already something previously
if (cache[key] != null) {
val entry = CacheEntry()
entry.bitmap = bitmapInfo
entry.title = entryTitle
entry.contentDescription = getUserBadgedLabel(entryTitle, user)
cache[key] = entry
}
val freshnessId = cachingLogic.getFreshnessIdentifier(obj, iconProvider)
if (freshnessId != null) {
addOrUpdateCacheDbEntry(bitmapInfo, entryTitle, componentName, userSerial, freshnessId)
}
}
@Synchronized
fun getDefaultIcon(user: UserHandle): BitmapInfo {
if (defaultIcon == null) {
iconFactory.use { li -> defaultIcon = li.makeDefaultIcon(iconProvider) }
}
return defaultIcon!!.withFlags(getUserFlagOpLocked(user))
}
protected fun getUserFlagOpLocked(user: UserHandle): FlagOp {
val key = user.hashCode()
val index = userFlagOpMap.indexOfKey(key)
if (index >= 0) {
return userFlagOpMap.valueAt(index)
} else {
iconFactory.use { li ->
val op = li.getBitmapFlagOp(IconOptions().setUser(user))
userFlagOpMap.put(key, op)
return op
}
}
}
fun isDefaultIcon(icon: BitmapInfo, user: UserHandle) = getDefaultIcon(user).icon == icon.icon
/**
* Retrieves the entry from the cache. If the entry is not present, it creates a new entry. This
* method is not thread safe, it must be called from a synchronized method.
*/
@JvmOverloads
protected fun <T : Any> cacheLocked(
componentName: ComponentName,
user: UserHandle,
infoProvider: Supplier<T?>,
cachingLogic: CachingLogic<T>,
lookupFlags: CacheLookupFlag,
cursor: Cursor? = null,
): CacheEntry {
assertWorkerThread()
val cacheKey = ComponentKey(componentName, user)
var entry = cache[cacheKey]
if (entry == null || entry.bitmap.matchingLookupFlag.isVisuallyLessThan(lookupFlags)) {
val addToMemCache = entry != null || !lookupFlags.skipAddToMemCache()
entry = CacheEntry()
if (addToMemCache) cache[cacheKey] = entry
// Check the DB first.
val cacheEntryUpdated =
if (cursor == null) getEntryFromDBLocked(cacheKey, entry, lookupFlags, cachingLogic)
else updateTitleAndIconLocked(cacheKey, entry, cursor, lookupFlags, cachingLogic)
val obj: T? by lazy { infoProvider.get() }
if (!cacheEntryUpdated) {
loadFallbackIcon(
obj,
entry,
cachingLogic,
lookupFlags.usePackageIcon(),
/* usePackageTitle= */ true,
componentName,
user,
)
}
if (TextUtils.isEmpty(entry.title)) {
obj?.let { loadFallbackTitle(it, entry, cachingLogic, user) }
}
}
return entry
}
/** Fallback method for loading an icon bitmap. */
protected fun <T : Any> loadFallbackIcon(
obj: T?,
entry: CacheEntry,
cachingLogic: CachingLogic<T>,
usePackageIcon: Boolean,
usePackageTitle: Boolean,
componentName: ComponentName,
user: UserHandle,
) {
if (obj != null) {
entry.bitmap = cachingLogic.loadIcon(context, this, obj)
} else {
if (usePackageIcon) {
val packageEntry = getEntryForPackageLocked(componentName.packageName, user)
if (DEBUG) {
Log.d(TAG, "using package default icon for " + componentName.toShortString())
}
entry.bitmap = packageEntry.bitmap
entry.contentDescription = packageEntry.contentDescription
if (usePackageTitle) {
entry.title = packageEntry.title
}
}
}
}
/** Fallback method for loading an app title. */
protected fun <T : Any> loadFallbackTitle(
obj: T,
entry: CacheEntry,
cachingLogic: CachingLogic<T>,
user: UserHandle,
) {
entry.title =
cachingLogic.getLabel(obj).let {
if (it.isNullOrEmpty()) cachingLogic.getComponent(obj).packageName else it
}
entry.contentDescription = getUserBadgedLabel(entry.title, user)
}
@Synchronized
fun clearMemoryCache() {
assertWorkerThread()
cache.clear()
}
/**
* Adds a default package entry in the cache. This entry is not persisted and will be removed
* when the cache is flushed.
*/
@Synchronized
protected fun cachePackageInstallInfo(
packageName: String,
user: UserHandle,
icon: Bitmap?,
title: CharSequence?,
) {
removeFromMemCacheLocked(packageName, user)
val cacheKey = getPackageKey(packageName, user)
// For icon caching, do not go through DB. Just update the in-memory entry.
val entry = cache[cacheKey] ?: CacheEntry()
if (!title.isNullOrEmpty()) {
entry.title = title
}
if (icon != null) {
iconFactory.use { li ->
entry.bitmap =
li.createBadgedIconBitmap(
li.createShapedAdaptiveIcon(icon),
IconOptions().setUser(user),
)
}
}
if (!TextUtils.isEmpty(title) && entry.bitmap.icon != null) {
cache[cacheKey] = entry
}
}
/** Returns the package entry if it has already been cached in memory, null otherwise */
protected fun getInMemoryPackageEntryLocked(
packageName: String,
user: UserHandle,
): CacheEntry? = getInMemoryEntryLocked(getPackageKey(packageName, user))
@VisibleForTesting
fun getInMemoryEntryLocked(key: ComponentKey): CacheEntry? {
assertWorkerThread()
return cache[key]
}
/**
* Gets an entry for the package, which can be used as a fallback entry for various components.
* This method is not thread safe, it must be called from a synchronized method.
*/
@WorkerThread
protected fun getEntryForPackageLocked(
packageName: String,
user: UserHandle,
lookupFlags: CacheLookupFlag = DEFAULT_LOOKUP_FLAG,
): CacheEntry {
assertWorkerThread()
val cacheKey = getPackageKey(packageName, user)
var entry = cache[cacheKey]
if (entry == null || entry.bitmap.matchingLookupFlag.isVisuallyLessThan(lookupFlags)) {
entry = CacheEntry()
var entryUpdated = true
// Check the DB first.
if (!getEntryFromDBLocked(cacheKey, entry, lookupFlags, appInfoCachingLogic)) {
try {
val appInfo =
context
.getSystemService(LauncherApps::class.java)!!
.getApplicationInfo(
packageName,
PackageManager.MATCH_UNINSTALLED_PACKAGES,
user,
)
if (appInfo == null) {
throw NameNotFoundException("ApplicationInfo is null").also {
logPersistently(
String.format("ApplicationInfo is null for %s", packageName),
it,
)
}
}
// Load the full res icon for the application, but if useLowResIcon is set, then
// only keep the low resolution icon instead of the larger full-sized icon
val iconInfo = appInfoCachingLogic.loadIcon(context, this, appInfo)
entry.bitmap =
if (lookupFlags.useLowRes())
BitmapInfo.of(BitmapInfo.LOW_RES_ICON, iconInfo.color)
else iconInfo
loadFallbackTitle(appInfo, entry, appInfoCachingLogic, user)
// Add the icon in the DB here, since these do not get written during
// package updates.
appInfoCachingLogic.getFreshnessIdentifier(appInfo, iconProvider)?.let {
freshnessId ->
addOrUpdateCacheDbEntry(
iconInfo,
entry.title,
cacheKey.componentName,
getSerialNumberForUser(user),
freshnessId,
)
}
} catch (e: NameNotFoundException) {
if (DEBUG) Log.d(TAG, "Application not installed $packageName")
entryUpdated = false
}
}
val shouldAddToCache =
!(lookupFlags.skipAddToMemCache() && Flags.restoreArchivedAppIconsFromDb())
// Only add a filled-out entry to the cache
if (entryUpdated && shouldAddToCache) {
cache[cacheKey] = entry
}
}
return entry
}
protected fun getEntryFromDBLocked(
cacheKey: ComponentKey,
entry: CacheEntry,
lookupFlags: CacheLookupFlag,
cachingLogic: CachingLogic<*>,
): Boolean {
var c: Cursor? = null
Trace.beginSection("loadIconIndividually")
try {
c =
iconDb.query(
lookupFlags.toLookupColumns(),
"$COLUMN_COMPONENT = ? AND $COLUMN_USER = ?",
arrayOf(
cacheKey.componentName.flattenToString(),
getSerialNumberForUser(cacheKey.user).toString(),
),
)
if (c.moveToNext()) {
return updateTitleAndIconLocked(cacheKey, entry, c, lookupFlags, cachingLogic)
}
} catch (e: SQLiteException) {
Log.d(TAG, "Error reading icon cache", e)
} finally {
c?.close()
Trace.endSection()
}
return false
}
private fun updateTitleAndIconLocked(
cacheKey: ComponentKey,
entry: CacheEntry,
c: Cursor,
lookupFlags: CacheLookupFlag,
logic: CachingLogic<*>,
): Boolean {
// Set the alpha to be 255, so that we never have a wrong color
entry.bitmap =
BitmapInfo.of(
BitmapInfo.LOW_RES_ICON,
GraphicsUtils.setColorAlphaBound(c.getInt(INDEX_COLOR), 255),
)
c.getString(INDEX_TITLE).let {
if (it.isNullOrEmpty()) {
entry.title = ""
entry.contentDescription = ""
} else {
entry.title = it
entry.contentDescription = getUserBadgedLabel(it, cacheKey.user)
}
}
if (!lookupFlags.useLowRes()) {
try {
val data: ByteArray = c.getBlob(INDEX_ICON) ?: return false
entry.bitmap =
BitmapInfo.of(
BitmapFactory.decodeByteArray(
data,
0,
data.size,
Options().apply { inPreferredConfig = HARDWARE },
)!!,
entry.bitmap.color,
)
} catch (e: Exception) {
return false
}
iconFactory.use { factory ->
val themeController = factory.themeController
val monoIconData = c.getBlob(INDEX_MONO_ICON)
if (themeController != null && monoIconData != null) {
entry.bitmap.themedBitmap =
themeController.decode(
data = monoIconData,
info = entry.bitmap,
factory = factory,
sourceHint =
SourceHint(cacheKey, logic, c.getString(INDEX_FRESHNESS_ID)),
)
}
}
}
entry.bitmap.flags = c.getInt(INDEX_FLAGS)
entry.bitmap = entry.bitmap.withFlags(getUserFlagOpLocked(cacheKey.user))
return true
}
private fun addOrUpdateCacheDbEntry(
bitmapInfo: BitmapInfo,
label: CharSequence,
key: ComponentName,
userSerial: Long,
freshnessId: String,
) {
val values = ContentValues()
if (bitmapInfo.canPersist()) {
values.put(COLUMN_ICON, GraphicsUtils.flattenBitmap(bitmapInfo.icon))
values.put(COLUMN_MONO_ICON, bitmapInfo.themedBitmap?.serialize())
} else {
values.put(COLUMN_ICON, null as ByteArray?)
values.put(COLUMN_MONO_ICON, null as ByteArray?)
}
values.put(COLUMN_ICON_COLOR, bitmapInfo.color)
values.put(COLUMN_FLAGS, bitmapInfo.flags)
values.put(COLUMN_LABEL, label.toString())
values.put(COLUMN_COMPONENT, key.flattenToString())
values.put(COLUMN_USER, userSerial)
values.put(COLUMN_FRESHNESS_ID, freshnessId)
iconDb.insertOrReplace(values)
}
private fun assertWorkerThread() {
check(Looper.myLooper() == bgLooper) {
"Cache accessed on wrong thread " + Looper.myLooper()
}
}
/** Log to Log.d. Subclasses can override this method to log persistently for debugging. */
protected open fun logPersistently(message: String, e: Exception?) {
Log.d(TAG, message, e)
}
/** Cache class to store the actual entries on disk */
class IconDB(context: Context, dbFileName: String?, iconPixelSize: Int) :
SQLiteCacheHelper(
context,
dbFileName,
(RELEASE_VERSION shl 16) + iconPixelSize,
TABLE_NAME,
) {
override fun onCreateTable(db: SQLiteDatabase) {
db.execSQL(
("CREATE TABLE IF NOT EXISTS $TABLE_NAME (" +
"$COLUMN_COMPONENT TEXT NOT NULL, " +
"$COLUMN_USER INTEGER NOT NULL, " +
"$COLUMN_FRESHNESS_ID TEXT, " +
"$COLUMN_ICON BLOB, " +
"$COLUMN_MONO_ICON BLOB, " +
"$COLUMN_ICON_COLOR INTEGER NOT NULL DEFAULT 0, " +
"$COLUMN_FLAGS INTEGER NOT NULL DEFAULT 0, " +
"$COLUMN_LABEL TEXT, " +
"PRIMARY KEY ($COLUMN_COMPONENT, $COLUMN_USER) " +
");")
)
}
}
companion object {
protected const val TAG = "BaseIconCache"
private const val DEBUG = false
private const val INITIAL_ICON_CACHE_CAPACITY = 50
// A format string which returns the original string as is.
private const val IDENTITY_FORMAT_STRING = "%1\$s"
// Empty class name is used for storing package default entry.
const val EMPTY_CLASS_NAME: String = "."
fun getPackageKey(packageName: String, user: UserHandle) =
ComponentKey(ComponentName(packageName, packageName + EMPTY_CLASS_NAME), user)
// Ensures themed bitmaps in the icon cache are invalidated
@JvmField val RELEASE_VERSION = if (Flags.forceMonochromeAppIcons()) 10 else 9
@JvmField val TABLE_NAME = "icons"
@JvmField val COLUMN_ROWID = "rowid"
@JvmField val COLUMN_COMPONENT = "componentName"
@JvmField val COLUMN_USER = "profileId"
@JvmField val COLUMN_FRESHNESS_ID = "freshnessId"
@JvmField val COLUMN_ICON = "icon"
@JvmField val COLUMN_ICON_COLOR = "icon_color"
@JvmField val COLUMN_MONO_ICON = "mono_icon"
@JvmField val COLUMN_FLAGS = "flags"
@JvmField val COLUMN_LABEL = "label"
@JvmField
val COLUMNS_LOW_RES =
arrayOf(COLUMN_COMPONENT, COLUMN_LABEL, COLUMN_ICON_COLOR, COLUMN_FLAGS)
@JvmField
val COLUMNS_HIGH_RES =
COLUMNS_LOW_RES.copyOf(COLUMNS_LOW_RES.size + 3).apply {
this[size - 3] = COLUMN_ICON
this[size - 2] = COLUMN_MONO_ICON
this[size - 1] = COLUMN_FRESHNESS_ID
}
@JvmField val INDEX_TITLE = COLUMNS_HIGH_RES.indexOf(COLUMN_LABEL)
@JvmField val INDEX_COLOR = COLUMNS_HIGH_RES.indexOf(COLUMN_ICON_COLOR)
@JvmField val INDEX_FLAGS = COLUMNS_HIGH_RES.indexOf(COLUMN_FLAGS)
@JvmField val INDEX_ICON = COLUMNS_HIGH_RES.indexOf(COLUMN_ICON)
@JvmField val INDEX_MONO_ICON = COLUMNS_HIGH_RES.indexOf(COLUMN_MONO_ICON)
@JvmField val INDEX_FRESHNESS_ID = COLUMNS_HIGH_RES.indexOf(COLUMN_FRESHNESS_ID)
@JvmStatic
fun CacheLookupFlag.toLookupColumns() =
if (useLowRes()) COLUMNS_LOW_RES else COLUMNS_HIGH_RES
}
}
@@ -0,0 +1,72 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.icons.cache
import androidx.annotation.IntDef
import kotlin.annotation.AnnotationRetention.SOURCE
/** Flags to control cache lookup behavior */
data class CacheLookupFlag private constructor(@LookupFlag private val flag: Int) {
/**
* Cache will try to load the low res version of the entry unless a high-res is already in
* memory
*/
fun useLowRes() = hasFlag(USE_LOW_RES)
@JvmOverloads fun withUseLowRes(useLowRes: Boolean = true) = updateMask(USE_LOW_RES, useLowRes)
/** Cache will try to lookup the package entry for the item, if the object entry fails */
fun usePackageIcon() = hasFlag(USE_PACKAGE_ICON)
@JvmOverloads
fun withUsePackageIcon(usePackageIcon: Boolean = true) =
updateMask(USE_PACKAGE_ICON, usePackageIcon)
/**
* Entry will not be added to the memory cache if it was not already added by a previous lookup
*/
fun skipAddToMemCache() = hasFlag(SKIP_ADD_TO_MEM_CACHE)
@JvmOverloads
fun withSkipAddToMemCache(skipAddToMemCache: Boolean = true) =
updateMask(SKIP_ADD_TO_MEM_CACHE, skipAddToMemCache)
private fun hasFlag(@LookupFlag mask: Int) = flag.and(mask) != 0
private fun updateMask(@LookupFlag mask: Int, addMask: Boolean) =
if (addMask) flagCache[flag.or(mask)] else flagCache[flag.and(mask.inv())]
/** Returns `true` if this flag has less UI information then [other] */
fun isVisuallyLessThan(other: CacheLookupFlag): Boolean {
return useLowRes() && !other.useLowRes()
}
@Retention(SOURCE)
@IntDef(value = [USE_LOW_RES, USE_PACKAGE_ICON, SKIP_ADD_TO_MEM_CACHE], flag = true)
/** Various options to control cache lookup */
private annotation class LookupFlag
companion object {
private const val USE_LOW_RES: Int = 1 shl 0
private const val USE_PACKAGE_ICON: Int = 1 shl 1
private const val SKIP_ADD_TO_MEM_CACHE: Int = 1 shl 2
private val flagCache = Array(8) { CacheLookupFlag(it) }
@JvmField val DEFAULT_LOOKUP_FLAG = CacheLookupFlag(0)
}
}
@@ -0,0 +1,71 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.icons.cache;
import android.content.ComponentName;
import android.content.pm.ApplicationInfo;
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.icons.IconProvider;
/**
* A simple interface to represent an object which can be added to icon cache
*/
public interface CachedObject {
/**
* Returns the component name for the underlying object
*/
@NonNull ComponentName getComponent();
/**
* Returns the user for the underlying object
*/
@NonNull UserHandle getUser();
/**
* Loads the user visible label for the provided object
*/
@Nullable CharSequence getLabel();
/**
* Loads the user visible icon for the provided object
*/
@Nullable
default Drawable getFullResIcon(@NonNull BaseIconCache cache) {
return null;
}
/**
* @see CachingLogic#getApplicationInfo
*/
@Nullable
ApplicationInfo getApplicationInfo();
/**
* Returns a persistable string that can be used to indicate indicate the correctness of the
* cache for the provided item
*/
@Nullable
default String getFreshnessIdentifier(@NonNull IconProvider iconProvider) {
return iconProvider.getStateForApp(getApplicationInfo());
}
}
@@ -0,0 +1,49 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.icons.cache
import android.content.ComponentName
import android.content.Context
import android.os.UserHandle
import com.android.launcher3.icons.BaseIconFactory.IconOptions
import com.android.launcher3.icons.BitmapInfo
import com.android.launcher3.icons.IconProvider
/** Caching logic for ComponentWithLabelAndIcon */
object CachedObjectCachingLogic : CachingLogic<CachedObject> {
override fun getComponent(info: CachedObject): ComponentName = info.component
override fun getUser(info: CachedObject): UserHandle = info.user
override fun getLabel(info: CachedObject): CharSequence? = info.label
override fun loadIcon(context: Context, cache: BaseIconCache, info: CachedObject): BitmapInfo {
val d = info.getFullResIcon(cache) ?: return BitmapInfo.LOW_RES_INFO
cache.iconFactory.use { li ->
return li.createBadgedIconBitmap(
d,
IconOptions().setUser(info.user).setSourceHint(getSourceHint(info, cache)),
)
}
}
override fun getApplicationInfo(info: CachedObject) = info.applicationInfo
override fun getFreshnessIdentifier(item: CachedObject, provider: IconProvider): String? =
item.getFreshnessIdentifier(provider)
}
@@ -1,70 +0,0 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.icons.cache;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.os.LocaleList;
import android.os.UserHandle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.icons.BitmapInfo;
public interface CachingLogic<T> {
@NonNull
ComponentName getComponent(@NonNull final T object);
@NonNull
UserHandle getUser(@NonNull final T object);
@NonNull
CharSequence getLabel(@NonNull final T object);
@NonNull
default CharSequence getDescription(@NonNull final T object,
@NonNull final CharSequence fallback) {
return fallback;
}
@NonNull
BitmapInfo loadIcon(@NonNull final Context context, @NonNull final T object);
/**
* Provides a option list of keywords to associate with this object
*/
@Nullable
default String getKeywords(@NonNull final T object, @NonNull final LocaleList localeList) {
return null;
}
/**
* Returns the timestamp the entry was last updated in cache.
*/
default long getLastUpdatedTime(@Nullable final T object, @NonNull final PackageInfo info) {
return info.lastUpdateTime;
}
/**
* Returns true the object should be added to mem cache; otherwise returns false.
*/
default boolean addToMemCache() {
return true;
}
}
@@ -0,0 +1,57 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.icons.cache
import android.content.ComponentName
import android.content.Context
import android.content.pm.ApplicationInfo
import android.os.UserHandle
import com.android.launcher3.icons.BitmapInfo
import com.android.launcher3.icons.IconProvider
import com.android.launcher3.icons.SourceHint
import com.android.launcher3.util.ComponentKey
interface CachingLogic<T> {
/** Returns the source hint for this object that can be sued by theme controllers */
fun getSourceHint(item: T, cache: BaseIconCache): SourceHint {
return SourceHint(
key = ComponentKey(getComponent(item), getUser(item)),
logic = this,
freshnessId = getFreshnessIdentifier(item, cache.iconProvider),
)
}
fun getComponent(item: T): ComponentName
fun getUser(item: T): UserHandle
/** Loads the user visible label for the object */
fun getLabel(item: T): CharSequence?
/**
* Returns the application info associated with the object. This is used to maintain the
* "freshness" of the disk cache. If null, the item will not be persisted to the disk
*/
fun getApplicationInfo(item: T): ApplicationInfo?
fun loadIcon(context: Context, cache: BaseIconCache, item: T): BitmapInfo
/**
* Returns a persistable string that can be used to indicate indicate the correctness of the
* cache for the provided item
*/
fun getFreshnessIdentifier(item: T, iconProvider: IconProvider): String?
}
@@ -1,322 +0,0 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.icons.cache;
import android.content.ComponentName;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import android.util.SparseBooleanArray;
import com.android.launcher3.icons.cache.BaseIconCache.IconDB;
import java.util.ArrayDeque;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
/**
* Utility class to handle updating the Icon cache
*/
public class IconCacheUpdateHandler {
private static final String TAG = "IconCacheUpdateHandler";
/**
* In this mode, all invalid icons are marked as to-be-deleted in {@link #mItemsToDelete}.
* This mode is used for the first run.
*/
private static final boolean MODE_SET_INVALID_ITEMS = true;
/**
* In this mode, any valid icon is removed from {@link #mItemsToDelete}. This is used for all
* subsequent runs, which essentially acts as set-union of all valid items.
*/
private static final boolean MODE_CLEAR_VALID_ITEMS = false;
static final Object ICON_UPDATE_TOKEN = new Object();
private final HashMap<String, PackageInfo> mPkgInfoMap;
private final BaseIconCache mIconCache;
private final ArrayMap<UserHandle, Set<String>> mPackagesToIgnore = new ArrayMap<>();
private final SparseBooleanArray mItemsToDelete = new SparseBooleanArray();
private boolean mFilterMode = MODE_SET_INVALID_ITEMS;
IconCacheUpdateHandler(BaseIconCache cache) {
mIconCache = cache;
mPkgInfoMap = new HashMap<>();
// Remove all active icon update tasks.
mIconCache.mWorkerHandler.removeCallbacksAndMessages(ICON_UPDATE_TOKEN);
createPackageInfoMap();
}
/**
* Sets a package to ignore for processing
*/
public void addPackagesToIgnore(UserHandle userHandle, String packageName) {
Set<String> packages = mPackagesToIgnore.get(userHandle);
if (packages == null) {
packages = new HashSet<>();
mPackagesToIgnore.put(userHandle, packages);
}
packages.add(packageName);
}
private void createPackageInfoMap() {
PackageManager pm = mIconCache.mPackageManager;
for (PackageInfo info :
pm.getInstalledPackages(PackageManager.MATCH_UNINSTALLED_PACKAGES)) {
mPkgInfoMap.put(info.packageName, info);
}
}
/**
* Updates the persistent DB, such that only entries corresponding to {@param apps} remain in
* the DB and are updated.
*
* @return The set of packages for which icons have updated.
*/
public <T> void updateIcons(List<T> apps, CachingLogic<T> cachingLogic,
OnUpdateCallback onUpdateCallback) {
// Filter the list per user
HashMap<UserHandle, HashMap<ComponentName, T>> userComponentMap = new HashMap<>();
int count = apps.size();
for (int i = 0; i < count; i++) {
T app = apps.get(i);
UserHandle userHandle = cachingLogic.getUser(app);
HashMap<ComponentName, T> componentMap = userComponentMap.get(userHandle);
if (componentMap == null) {
componentMap = new HashMap<>();
userComponentMap.put(userHandle, componentMap);
}
componentMap.put(cachingLogic.getComponent(app), app);
}
for (Entry<UserHandle, HashMap<ComponentName, T>> entry : userComponentMap.entrySet()) {
updateIconsPerUser(entry.getKey(), entry.getValue(), cachingLogic, onUpdateCallback);
}
// From now on, clear every valid item from the global valid map.
mFilterMode = MODE_CLEAR_VALID_ITEMS;
}
/**
* Updates the persistent DB, such that only entries corresponding to {@param apps} remain in
* the DB and are updated.
*
* @return The set of packages for which icons have updated.
*/
@SuppressWarnings("unchecked")
private <T> void updateIconsPerUser(UserHandle user, HashMap<ComponentName, T> componentMap,
CachingLogic<T> cachingLogic, OnUpdateCallback onUpdateCallback) {
Set<String> ignorePackages = mPackagesToIgnore.get(user);
if (ignorePackages == null) {
ignorePackages = Collections.emptySet();
}
long userSerial = mIconCache.getSerialNumberForUser(user);
ArrayDeque<T> appsToUpdate = new ArrayDeque<>();
try (Cursor c = mIconCache.mIconDb.query(
new String[]{IconDB.COLUMN_ROWID, IconDB.COLUMN_COMPONENT,
IconDB.COLUMN_LAST_UPDATED, IconDB.COLUMN_VERSION,
IconDB.COLUMN_SYSTEM_STATE},
IconDB.COLUMN_USER + " = ? ",
new String[]{Long.toString(userSerial)})) {
final int indexComponent = c.getColumnIndex(IconDB.COLUMN_COMPONENT);
final int indexLastUpdate = c.getColumnIndex(IconDB.COLUMN_LAST_UPDATED);
final int indexVersion = c.getColumnIndex(IconDB.COLUMN_VERSION);
final int rowIndex = c.getColumnIndex(IconDB.COLUMN_ROWID);
final int systemStateIndex = c.getColumnIndex(IconDB.COLUMN_SYSTEM_STATE);
while (c.moveToNext()) {
String cn = c.getString(indexComponent);
ComponentName component = ComponentName.unflattenFromString(cn);
PackageInfo info = mPkgInfoMap.get(component.getPackageName());
int rowId = c.getInt(rowIndex);
if (info == null) {
if (!ignorePackages.contains(component.getPackageName())) {
if (mFilterMode == MODE_SET_INVALID_ITEMS) {
mIconCache.remove(component, user);
mItemsToDelete.put(rowId, true);
}
}
continue;
}
if ((info.applicationInfo.flags & ApplicationInfo.FLAG_IS_DATA_ONLY) != 0) {
// Application is not present
continue;
}
long updateTime = c.getLong(indexLastUpdate);
int version = c.getInt(indexVersion);
T app = componentMap.remove(component);
if (version == info.versionCode
&& updateTime == cachingLogic.getLastUpdatedTime(app, info)
&& TextUtils.equals(c.getString(systemStateIndex),
mIconCache.getIconSystemState(info.packageName))) {
if (mFilterMode == MODE_CLEAR_VALID_ITEMS) {
mItemsToDelete.put(rowId, false);
}
continue;
}
if (app == null) {
if (mFilterMode == MODE_SET_INVALID_ITEMS) {
mIconCache.remove(component, user);
mItemsToDelete.put(rowId, true);
}
} else {
appsToUpdate.add(app);
}
}
} catch (SQLiteException e) {
Log.d(TAG, "Error reading icon cache", e);
// Continue updating whatever we have read so far
}
// Insert remaining apps.
if (!componentMap.isEmpty() || !appsToUpdate.isEmpty()) {
ArrayDeque<T> appsToAdd = new ArrayDeque<>();
appsToAdd.addAll(componentMap.values());
mIconCache.setIconUpdateInProgress(true);
new SerializedIconUpdateTask(userSerial, user, appsToAdd, appsToUpdate, cachingLogic,
onUpdateCallback).scheduleNext();
}
}
/**
* Commits all updates as part of the update handler to disk. Not more calls should be made
* to this class after this.
*/
public void finish() {
// Commit all deletes
int deleteCount = 0;
StringBuilder queryBuilder = new StringBuilder()
.append(IconDB.COLUMN_ROWID)
.append(" IN (");
int count = mItemsToDelete.size();
for (int i = 0; i < count; i++) {
if (mItemsToDelete.valueAt(i)) {
if (deleteCount > 0) {
queryBuilder.append(", ");
}
queryBuilder.append(mItemsToDelete.keyAt(i));
deleteCount++;
}
}
queryBuilder.append(')');
if (deleteCount > 0) {
mIconCache.mIconDb.delete(queryBuilder.toString(), null);
}
}
/**
* A runnable that updates invalid icons and adds missing icons in the DB for the provided
* LauncherActivityInfo list. Items are updated/added one at a time, so that the
* worker thread doesn't get blocked.
*/
private class SerializedIconUpdateTask<T> implements Runnable {
private final long mUserSerial;
private final UserHandle mUserHandle;
private final ArrayDeque<T> mAppsToAdd;
private final ArrayDeque<T> mAppsToUpdate;
private final CachingLogic<T> mCachingLogic;
private final HashSet<String> mUpdatedPackages = new HashSet<>();
private final OnUpdateCallback mOnUpdateCallback;
SerializedIconUpdateTask(long userSerial, UserHandle userHandle,
ArrayDeque<T> appsToAdd, ArrayDeque<T> appsToUpdate, CachingLogic<T> cachingLogic,
OnUpdateCallback onUpdateCallback) {
mUserHandle = userHandle;
mUserSerial = userSerial;
mAppsToAdd = appsToAdd;
mAppsToUpdate = appsToUpdate;
mCachingLogic = cachingLogic;
mOnUpdateCallback = onUpdateCallback;
}
@Override
public void run() {
if (!mAppsToUpdate.isEmpty()) {
T app = mAppsToUpdate.removeLast();
String pkg = mCachingLogic.getComponent(app).getPackageName();
PackageInfo info = mPkgInfoMap.get(pkg);
mIconCache.addIconToDBAndMemCache(
app, mCachingLogic, info, mUserSerial, true /*replace existing*/);
mUpdatedPackages.add(pkg);
if (mAppsToUpdate.isEmpty() && !mUpdatedPackages.isEmpty()) {
// No more app to update. Notify callback.
mOnUpdateCallback.onPackageIconsUpdated(mUpdatedPackages, mUserHandle);
}
// Let it run one more time.
scheduleNext();
} else if (!mAppsToAdd.isEmpty()) {
T app = mAppsToAdd.removeLast();
PackageInfo info = mPkgInfoMap.get(
mCachingLogic.getComponent(app).getPackageName());
// We do not check the mPkgInfoMap when generating the mAppsToAdd. Although every
// app should have package info, this is not guaranteed by the api
if (info != null) {
mIconCache.addIconToDBAndMemCache(app, mCachingLogic, info,
mUserSerial, false /*replace existing*/);
}
if (!mAppsToAdd.isEmpty()) {
scheduleNext();
} else if (!mIconCache.mWorkerHandler.hasMessages(0, ICON_UPDATE_TOKEN)) {
// This checks if there is a second icon update process happening
// before notifying BaseIconCache that the updates are over
mIconCache.setIconUpdateInProgress(false);
}
}
}
public void scheduleNext() {
mIconCache.mWorkerHandler.postAtTime(this, ICON_UPDATE_TOKEN,
SystemClock.uptimeMillis() + 1);
}
}
public interface OnUpdateCallback {
void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandle user);
}
}
@@ -0,0 +1,300 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.icons.cache
import android.content.ComponentName
import android.content.pm.ApplicationInfo
import android.database.sqlite.SQLiteException
import android.os.Handler
import android.os.SystemClock
import android.os.UserHandle
import android.util.ArrayMap
import android.util.Log
import com.android.launcher3.util.ComponentKey
import com.android.launcher3.util.SQLiteCacheHelper
import java.util.ArrayDeque
/** Utility class to handle updating the Icon cache */
class IconCacheUpdateHandler(
private val iconCache: BaseIconCache,
private val cacheDb: SQLiteCacheHelper,
private val workerHandler: Handler,
) {
private val packagesToIgnore = ArrayMap<UserHandle, MutableSet<String>>()
// Map of packageKey to ApplicationInfo, dynamically created based on all incoming data
private val packageAppInfoMap = HashMap<ComponentKey, ApplicationInfo?>()
private val itemsToDelete = HashSet<UpdateRow>()
// During the first pass, we load all the items from DB and add all invalid items to
// mItemsToDelete. In follow up passes, we go through the items in mItemsToDelete, and if the
// item is valid, removes it from the list, or leave it there.
private var firstPass = true
/** Sets a package to ignore for processing */
fun addPackagesToIgnore(userHandle: UserHandle, packageName: String) {
packagesToIgnore.getOrPut(userHandle) { HashSet() }.add(packageName)
}
/**
* Updates the persistent DB, such that only entries corresponding to {@param apps} remain in
* the DB and are updated.
*
* @return The set of packages for which icons have updated.
*/
fun <T : Any> updateIcons(
apps: List<T>,
cachingLogic: CachingLogic<T>,
onUpdateCallback: OnUpdateCallback,
) {
// Filter the list per user
val userComponentMap = HashMap<UserHandle, HashMap<ComponentName, T>>()
for (app in apps) {
val userHandle = cachingLogic.getUser(app)
val cn = cachingLogic.getComponent(app)
userComponentMap.getOrPut(userHandle) { HashMap() }[cn] = app
// Populate application info map
val packageKey = BaseIconCache.getPackageKey(cn.packageName, userHandle)
packageAppInfoMap.getOrPut(packageKey) { cachingLogic.getApplicationInfo(app) }
}
if (firstPass) {
userComponentMap.forEach { (user, componentMap) ->
updateIconsPerUserForFirstPass(user, componentMap, cachingLogic, onUpdateCallback)
}
} else {
userComponentMap.forEach { (user, componentMap) ->
updateIconsPerUserForSecondPass(user, componentMap, cachingLogic, onUpdateCallback)
}
}
// From now on, clear every valid item from the global valid map.
firstPass = false
}
/**
* During the first pass, all the items from the cache are verified one-by-one and any entry
* with no corresponding entry in {@code componentMap} is added to {@code itemsToDelete}
*
* Also starts a SerializedIconUpdateTask for all updated entries
*/
private fun <T : Any> updateIconsPerUserForFirstPass(
user: UserHandle,
componentMap: MutableMap<ComponentName, T>,
cachingLogic: CachingLogic<T>,
onUpdateCallback: OnUpdateCallback,
) {
val appsToUpdate = ArrayDeque<T>()
val userSerial = iconCache.getSerialNumberForUser(user)
try {
cacheDb
.query(
arrayOf(
BaseIconCache.COLUMN_ROWID,
BaseIconCache.COLUMN_COMPONENT,
BaseIconCache.COLUMN_FRESHNESS_ID,
),
"${BaseIconCache.COLUMN_USER} = ? ",
arrayOf(userSerial.toString()),
)
.use { c ->
var ignorePackages = packagesToIgnore[user] ?: emptySet()
val indexComponent = c.getColumnIndex(BaseIconCache.COLUMN_COMPONENT)
val indexFreshnessId = c.getColumnIndex(BaseIconCache.COLUMN_FRESHNESS_ID)
val rowIndex = c.getColumnIndex(BaseIconCache.COLUMN_ROWID)
while (c.moveToNext()) {
val rowId = c.getInt(rowIndex)
val cn = c.getString(indexComponent)
val freshnessId = c.getString(indexFreshnessId) ?: ""
val component = ComponentName.unflattenFromString(cn)
if (component == null) {
// b/357725795
Log.e(TAG, "Invalid component name while updating icon cache: $cn")
itemsToDelete.add(
UpdateRow(rowId, ComponentName("", ""), user, freshnessId)
)
continue
}
val app = componentMap.remove(component)
if (app == null) {
if (!ignorePackages.contains(component.packageName)) {
iconCache.remove(component, user)
itemsToDelete.add(UpdateRow(rowId, component, user, freshnessId))
}
continue
}
if (
freshnessId ==
cachingLogic.getFreshnessIdentifier(app, iconCache.iconProvider)
) {
// Item is up-to-date
continue
}
appsToUpdate.add(app)
}
}
} catch (e: SQLiteException) {
Log.d(TAG, "Error reading icon cache", e)
// Continue updating whatever we have read so far
}
// Insert remaining apps.
if (componentMap.isNotEmpty() || appsToUpdate.isNotEmpty()) {
val appsToAdd = ArrayDeque(componentMap.values)
SerializedIconUpdateTask(
userSerial,
user,
appsToAdd,
appsToUpdate,
cachingLogic,
onUpdateCallback,
)
.scheduleNext()
}
}
/**
* During the second pass, we go through the items in {@code itemsToDelete}, and remove any item
* with corresponding entry in {@code componentMap}.
*/
private fun <T : Any> updateIconsPerUserForSecondPass(
user: UserHandle,
componentMap: MutableMap<ComponentName, T>,
cachingLogic: CachingLogic<T>,
onUpdateCallback: OnUpdateCallback,
) {
val userSerial = iconCache.getSerialNumberForUser(user)
val appsToUpdate = ArrayDeque<T>()
val itr = itemsToDelete.iterator()
while (itr.hasNext()) {
val row = itr.next()
if (user != row.user) continue
val app = componentMap.remove(row.componentName) ?: continue
itr.remove()
if (
row.freshnessId != cachingLogic.getFreshnessIdentifier(app, iconCache.iconProvider)
) {
appsToUpdate.add(app)
}
}
// Insert remaining apps.
if (componentMap.isNotEmpty() || appsToUpdate.isNotEmpty()) {
val appsToAdd = ArrayDeque<T>()
appsToAdd.addAll(componentMap.values)
SerializedIconUpdateTask(
userSerial,
user,
appsToAdd,
appsToUpdate,
cachingLogic,
onUpdateCallback,
)
.scheduleNext()
}
}
/**
* Commits all updates as part of the update handler to disk. Not more calls should be made to
* this class after this.
*/
fun finish() {
// Ignore any application info entries which are already correct
itemsToDelete.removeIf { row ->
val info = packageAppInfoMap[ComponentKey(row.componentName, row.user)]
info != null && row.freshnessId == iconCache.iconProvider.getStateForApp(info)
}
// Commit all deletes
if (itemsToDelete.isNotEmpty()) {
val r = itemsToDelete.joinToString { it.rowId.toString() }
cacheDb.delete("${BaseIconCache.COLUMN_ROWID} IN ($r)", null)
Log.d(TAG, "Deleting obsolete entries, count=" + itemsToDelete.size)
}
}
data class UpdateRow(
val rowId: Int,
val componentName: ComponentName,
val user: UserHandle,
val freshnessId: String,
)
/**
* A runnable that updates invalid icons and adds missing icons in the DB for the provided
* LauncherActivityInfo list. Items are updated/added one at a time, so that the worker thread
* doesn't get blocked.
*/
private inner class SerializedIconUpdateTask<T : Any>(
private val userSerial: Long,
private val userHandle: UserHandle,
private val appsToAdd: ArrayDeque<T>,
private val appsToUpdate: ArrayDeque<T>,
private val cachingLogic: CachingLogic<T>,
private val onUpdateCallback: OnUpdateCallback,
) : Runnable {
private val updatedPackages = HashSet<String>()
override fun run() {
if (appsToUpdate.isNotEmpty()) {
val app = appsToUpdate.removeLast()
val pkg = cachingLogic.getComponent(app).packageName
iconCache.addIconToDBAndMemCache(app, cachingLogic, userSerial)
updatedPackages.add(pkg)
if (appsToUpdate.isEmpty() && updatedPackages.isNotEmpty()) {
// No more app to update. Notify callback.
onUpdateCallback.onPackageIconsUpdated(updatedPackages, userHandle)
}
// Let it run one more time.
scheduleNext()
} else if (appsToAdd.isNotEmpty()) {
iconCache.addIconToDBAndMemCache(appsToAdd.removeLast(), cachingLogic, userSerial)
// Let it run one more time.
scheduleNext()
}
}
fun scheduleNext() {
workerHandler.postAtTime(
this,
iconCache.iconUpdateToken,
SystemClock.uptimeMillis() + 1,
)
}
}
fun interface OnUpdateCallback {
fun onPackageIconsUpdated(updatedPackages: HashSet<String>, user: UserHandle)
}
companion object {
private const val TAG = "IconCacheUpdateHandler"
}
}
@@ -0,0 +1,77 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.icons.cache
import android.content.ComponentName
import android.content.Context
import android.content.pm.LauncherActivityInfo
import android.os.Build
import android.os.Build.VERSION
import android.os.UserHandle
import android.util.Log
import androidx.annotation.RequiresApi
import com.android.launcher3.Flags.useNewIconForArchivedApps
import com.android.launcher3.icons.BaseIconFactory.IconOptions
import com.android.launcher3.icons.BitmapInfo
import com.android.launcher3.icons.IconProvider
object LauncherActivityCachingLogic : CachingLogic<LauncherActivityInfo> {
const val TAG = "LauncherActivityCachingLogic"
override fun getComponent(info: LauncherActivityInfo): ComponentName = info.componentName
override fun getUser(info: LauncherActivityInfo): UserHandle = info.user
override fun getLabel(info: LauncherActivityInfo): CharSequence? = info.label
override fun getApplicationInfo(info: LauncherActivityInfo) = info.applicationInfo
@RequiresApi(Build.VERSION_CODES.S)
override fun loadIcon(
context: Context,
cache: BaseIconCache,
info: LauncherActivityInfo,
): BitmapInfo {
cache.iconFactory.use { li ->
val iconOptions: IconOptions = IconOptions().setUser(info.user)
iconOptions
.setIsArchived(
useNewIconForArchivedApps() &&
VERSION.SDK_INT >= 35 &&
info.activityInfo.isArchived
)
.setSourceHint(getSourceHint(info, cache))
val iconDrawable = cache.iconProvider.getIcon(info.activityInfo, li.fullResIconDpi)
if (context.packageManager.isDefaultApplicationIcon(iconDrawable)) {
Log.w(
TAG,
"loadIcon: Default app icon returned from PackageManager." +
" component=${info.componentName}, user=${info.user}",
Exception(),
)
// Make sure this default icon always matches BaseIconCache#getDefaultIcon
return cache.getDefaultIcon(info.user)
}
return li.createBadgedIconBitmap(iconDrawable, iconOptions)
}
}
override fun getFreshnessIdentifier(
item: LauncherActivityInfo,
provider: IconProvider,
): String? = provider.getStateForApp(getApplicationInfo(item))
}
@@ -0,0 +1,173 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.icons.mono
import android.annotation.TargetApi
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Bitmap.Config.ALPHA_8
import android.graphics.Bitmap.Config.HARDWARE
import android.graphics.BlendMode.SRC_IN
import android.graphics.BlendModeColorFilter
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Path
import android.graphics.Rect
import android.graphics.drawable.AdaptiveIconDrawable
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.InsetDrawable
import android.os.Build
import com.android.launcher3.Flags
import com.android.launcher3.icons.BaseIconFactory
import com.android.launcher3.icons.BaseIconFactory.MODE_ALPHA
import com.android.launcher3.icons.BitmapInfo
import com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR
import com.android.launcher3.icons.IconThemeController
import com.android.launcher3.icons.MonochromeIconFactory
import com.android.launcher3.icons.SourceHint
import com.android.launcher3.icons.ThemedBitmap
import java.nio.ByteBuffer
@TargetApi(Build.VERSION_CODES.TIRAMISU)
class MonoIconThemeController(
private val colorProvider: (Context) -> IntArray = ThemedIconDrawable.Companion::getColors
) : IconThemeController {
override val themeID = "with-theme"
override fun createThemedBitmap(
icon: AdaptiveIconDrawable,
info: BitmapInfo,
factory: BaseIconFactory,
sourceHint: SourceHint?,
): ThemedBitmap? {
val mono =
getMonochromeDrawable(
icon,
info,
factory.getShapePath(icon, Rect(0, 0, info.icon.width, info.icon.height)),
factory.iconScale,
sourceHint?.isFileDrawable ?: false,
factory.shouldForceThemeIcon(),
)
if (mono != null) {
return MonoThemedBitmap(
factory.createIconBitmap(mono, ICON_VISIBLE_AREA_FACTOR, MODE_ALPHA),
factory.whiteShadowLayer,
colorProvider,
)
}
return null
}
/**
* Returns a monochromatic version of the given drawable or null, if it is not supported
*
* @param base the original icon
*/
private fun getMonochromeDrawable(
base: AdaptiveIconDrawable,
info: BitmapInfo,
shapePath: Path,
iconScale: Float,
isFileDrawable: Boolean,
shouldForceThemeIcon: Boolean,
): Drawable? {
val mono = base.monochrome
if (mono != null) {
return ClippedMonoDrawable(mono, shapePath, iconScale)
}
if (Flags.forceMonochromeAppIcons() && shouldForceThemeIcon && !isFileDrawable) {
return MonochromeIconFactory(info.icon.width).wrap(base, shapePath, iconScale)
}
return null
}
override fun decode(
data: ByteArray,
info: BitmapInfo,
factory: BaseIconFactory,
sourceHint: SourceHint,
): ThemedBitmap? {
val icon = info.icon
if (data.size != icon.height * icon.width) return null
var monoBitmap = Bitmap.createBitmap(icon.width, icon.height, ALPHA_8)
monoBitmap.copyPixelsFromBuffer(ByteBuffer.wrap(data))
val hwMonoBitmap = monoBitmap.copy(HARDWARE, false /*isMutable*/)
if (hwMonoBitmap != null) {
monoBitmap.recycle()
monoBitmap = hwMonoBitmap
}
return MonoThemedBitmap(monoBitmap, factory.whiteShadowLayer, colorProvider)
}
override fun createThemedAdaptiveIcon(
context: Context,
originalIcon: AdaptiveIconDrawable,
info: BitmapInfo?,
): AdaptiveIconDrawable? {
val colors = colorProvider(context)
originalIcon.mutate()
var monoDrawable = originalIcon.monochrome?.apply { setTint(colors[1]) }
if (monoDrawable == null) {
info?.themedBitmap?.let { themedBitmap ->
if (themedBitmap is MonoThemedBitmap) {
// Inject a previously generated monochrome icon
// Use BitmapDrawable instead of FastBitmapDrawable so that the colorState is
// preserved in constantState
// Inset the drawable according to the AdaptiveIconDrawable layers
monoDrawable =
InsetDrawable(
BitmapDrawable(themedBitmap.mono).apply {
colorFilter = BlendModeColorFilter(colors[1], SRC_IN)
},
AdaptiveIconDrawable.getExtraInsetFraction() / 2,
)
}
}
}
return monoDrawable?.let { AdaptiveIconDrawable(ColorDrawable(colors[0]), it) }
}
class ClippedMonoDrawable(
base: Drawable?,
private val shapePath: Path,
private val iconScale: Float,
) : InsetDrawable(base, -AdaptiveIconDrawable.getExtraInsetFraction()) {
// TODO(b/399666950): remove this after launcher icon shapes is fully enabled
private val mCrop = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null)
override fun draw(canvas: Canvas) {
mCrop.bounds = bounds
val saveCount = canvas.save()
if (Flags.enableLauncherIconShapes()) {
canvas.clipPath(shapePath)
canvas.scale(iconScale, iconScale, bounds.width() / 2f, bounds.height() / 2f)
} else {
canvas.clipPath(mCrop.iconMask)
}
super.draw(canvas)
canvas.restoreToCount(saveCount)
}
}
}
@@ -0,0 +1,40 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.icons.mono
import android.content.Context
import android.graphics.Bitmap
import com.android.launcher3.icons.BitmapInfo
import com.android.launcher3.icons.FastBitmapDrawable
import com.android.launcher3.icons.ThemedBitmap
import com.android.launcher3.icons.mono.ThemedIconDrawable.ThemedConstantState
import java.nio.ByteBuffer
class MonoThemedBitmap(
val mono: Bitmap,
private val whiteShadowLayer: Bitmap,
private val colorProvider: (Context) -> IntArray = ThemedIconDrawable.Companion::getColors,
) : ThemedBitmap {
override fun newDrawable(info: BitmapInfo, context: Context): FastBitmapDrawable {
val colors = colorProvider(context)
return ThemedConstantState(info, mono, whiteShadowLayer, colors[0], colors[1]).newDrawable()
}
override fun serialize() =
ByteArray(mono.width * mono.height).apply { mono.copyPixelsToBuffer(ByteBuffer.wrap(this)) }
}
@@ -0,0 +1,98 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.icons.mono
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BlendMode.SRC_IN
import android.graphics.BlendModeColorFilter
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Rect
import com.android.launcher3.icons.BitmapInfo
import com.android.launcher3.icons.FastBitmapDrawable
import com.android.launcher3.icons.R
/** Class to handle monochrome themed app icons */
class ThemedIconDrawable(constantState: ThemedConstantState) :
FastBitmapDrawable(constantState.getBitmapInfo()) {
private val colorFg = constantState.colorFg
private val colorBg = constantState.colorBg
// The foreground/monochrome icon for the app
private val monoIcon = constantState.mono
private val monoFilter = BlendModeColorFilter(colorFg, SRC_IN)
private val monoPaint =
Paint(Paint.ANTI_ALIAS_FLAG or Paint.FILTER_BITMAP_FLAG).apply { colorFilter = monoFilter }
private val bgBitmap = constantState.whiteShadowLayer
private val bgFilter = BlendModeColorFilter(colorBg, SRC_IN)
private val mBgPaint =
Paint(Paint.ANTI_ALIAS_FLAG or Paint.FILTER_BITMAP_FLAG).apply { colorFilter = bgFilter }
override fun drawInternal(canvas: Canvas, bounds: Rect) {
canvas.drawBitmap(bgBitmap, null, bounds, mBgPaint)
canvas.drawBitmap(monoIcon, null, bounds, monoPaint)
}
override fun updateFilter() {
super.updateFilter()
val alpha = if (mIsDisabled) (mDisabledAlpha * FULLY_OPAQUE).toInt() else FULLY_OPAQUE
mBgPaint.alpha = alpha
mBgPaint.setColorFilter(
if (mIsDisabled) BlendModeColorFilter(getDisabledColor(colorBg), SRC_IN) else bgFilter
)
monoPaint.alpha = alpha
monoPaint.setColorFilter(
if (mIsDisabled) BlendModeColorFilter(getDisabledColor(colorFg), SRC_IN) else monoFilter
)
}
override fun isThemed() = true
override fun newConstantState() =
ThemedConstantState(mBitmapInfo, monoIcon, bgBitmap, colorBg, colorFg)
override fun getIconColor() = colorFg
class ThemedConstantState(
bitmapInfo: BitmapInfo,
val mono: Bitmap,
val whiteShadowLayer: Bitmap,
val colorBg: Int,
val colorFg: Int,
) : FastBitmapConstantState(bitmapInfo) {
public override fun createDrawable() = ThemedIconDrawable(this)
fun getBitmapInfo(): BitmapInfo = mBitmapInfo
}
companion object {
const val TAG: String = "ThemedIconDrawable"
/** Get an int array representing background and foreground colors for themed icons */
@JvmStatic
fun getColors(context: Context): IntArray {
val res = context.resources
return intArrayOf(
res.getColor(R.color.themed_icon_background_color),
res.getColor(R.color.themed_icon_color),
)
}
}
}
@@ -48,8 +48,8 @@ public class ComponentKey {
@Override
public boolean equals(Object o) {
ComponentKey other = (ComponentKey) o;
return other.componentName.equals(componentName) && other.user.equals(user);
return (o instanceof ComponentKey other)
&& other.componentName.equals(componentName) && other.user.equals(user);
}
/**
@@ -66,16 +66,16 @@ public class SimpleIconCache extends BaseIconCache {
}
@Override
protected long getSerialNumberForUser(@NonNull UserHandle user) {
public long getSerialNumberForUser(@NonNull UserHandle user) {
synchronized (mUserSerialMap) {
int index = mUserSerialMap.indexOfKey(user.hashCode());
// int index = mUserSerialMap.indexOfKey(user.getIdentifier());
// int userId = user.getIdentifier();
int userId = 0;
int index = mUserSerialMap.indexOfKey(userId);
if (index >= 0) {
return mUserSerialMap.valueAt(index);
}
long serial = mUserManager.getSerialNumberForUser(user);
mUserSerialMap.put(user.hashCode(), serial);
// mUserSerialMap.put(user.getIdentifier(), serial);
mUserSerialMap.put(userId, serial);
return serial;
}
}
@@ -88,14 +88,14 @@ public class SimpleIconCache extends BaseIconCache {
@Override
protected boolean isInstantApp(@NonNull ApplicationInfo info) {
return false;
// return info.isInstantApp();
return false;
}
@NonNull
@Override
public BaseIconFactory getIconFactory() {
return IconFactory.obtain(mContext);
return IconFactory.obtain(context);
}
public static SimpleIconCache getIconCache(Context context) {
-48
View File
@@ -1,48 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
<issue
id="NewApi"
message="`?android:attr/dialogCornerRadius` requires API level 28 (current min is 26)"
errorLine1=" android:topLeftRadius=&quot;?android:attr/dialogCornerRadius&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/res/drawable/add_item_dialog_background.xml"
line="6"
column="9"/>
</issue>
<issue
id="NewApi"
message="`?android:attr/dialogCornerRadius` requires API level 28 (current min is 26)"
errorLine1=" android:topRightRadius=&quot;?android:attr/dialogCornerRadius&quot; />"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/res/drawable/add_item_dialog_background.xml"
line="7"
column="9"/>
</issue>
<issue
id="NewApi"
message="`@android:style/Widget.DeviceDefault.Button.Colored` requires API level 28 (current min is 26)"
errorLine1=" &lt;style name=&quot;Widget.DeviceDefault.Button.Rounded.Colored&quot; parent=&quot;@android:style/Widget.DeviceDefault.Button.Colored&quot;>"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/res/values/styles.xml"
line="287"
column="63"/>
</issue>
<issue
id="NewApi"
message="`@android:dimen/system_app_widget_background_radius` requires API level 31 (current min is 26)"
errorLine1=" &lt;corners android:radius=&quot;@android:dimen/system_app_widget_background_radius&quot; />"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/res/drawable/widget_resize_frame.xml"
line="20"
column="14"/>
</issue>
</issues>
-576
View File
@@ -1,576 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
<issue
id="NewApi"
message="Call requires API level R (current min is 29): `android.view.View#getWindowInsetsController`"
errorLine1=" getWindowInsetsController().hide(WindowInsets.Type.ime());"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/allapps/AllAppsContainerView.java"
line="203"
column="9"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 29): `android.view.WindowInsets.Type#ime`"
errorLine1=" getWindowInsetsController().hide(WindowInsets.Type.ime());"
errorLine2=" ~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/allapps/AllAppsContainerView.java"
line="203"
column="60"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 29): `android.view.WindowInsetsController#hide`"
errorLine1=" getWindowInsetsController().hide(WindowInsets.Type.ime());"
errorLine2=" ~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/allapps/AllAppsContainerView.java"
line="203"
column="37"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 29): `android.view.View#getWindowInsetsController`"
errorLine1=" getWindowInsetsController().hide(WindowInsets.Type.ime());"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/allapps/AllAppsRecyclerView.java"
line="193"
column="17"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 29): `android.view.WindowInsets.Type#ime`"
errorLine1=" getWindowInsetsController().hide(WindowInsets.Type.ime());"
errorLine2=" ~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/allapps/AllAppsRecyclerView.java"
line="193"
column="68"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 29): `android.view.WindowInsetsController#hide`"
errorLine1=" getWindowInsetsController().hide(WindowInsets.Type.ime());"
errorLine2=" ~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/allapps/AllAppsRecyclerView.java"
line="193"
column="45"/>
</issue>
<issue
id="NewApi"
message="Call requires API level 31 (current min is 29): `android.appwidget.AppWidgetHostView#updateAppWidgetSize`"
errorLine1=" widgetView.updateAppWidgetSize(new Bundle(), sizes);"
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/AppWidgetResizeFrame.java"
line="399"
column="24"/>
</issue>
<issue
id="NewApi"
message="Call requires API level 31 (current min is 29): `android.graphics.Rect#inset`"
errorLine1=" potentialTaskRect.inset(insets.left, insets.top, insets.right, insets.bottom);"
errorLine2=" ~~~~~">
<location
file="packages/apps/Launcher3/quickstep/src/com/android/quickstep/BaseActivityInterface.java"
line="248"
column="27"/>
</issue>
<issue
id="NewApi"
message="Call requires API level 31 (current min is 29): `android.graphics.Rect#inset`"
errorLine1=" potentialTaskRect.inset("
errorLine2=" ~~~~~">
<location
file="packages/apps/Launcher3/quickstep/src/com/android/quickstep/BaseActivityInterface.java"
line="249"
column="27"/>
</issue>
<issue
id="NewApi"
message="Call requires API level 31 (current min is 29): `android.graphics.Rect#inset`"
errorLine1=" outRect.inset(Math.max(insets.left, sideMargin), Math.max(insets.top, topMargin),"
errorLine2=" ~~~~~">
<location
file="packages/apps/Launcher3/quickstep/src/com/android/quickstep/BaseActivityInterface.java"
line="291"
column="17"/>
</issue>
<issue
id="NewApi"
message="Call requires API level 31 (current min is 29): `android.graphics.Rect#inset`"
errorLine1=" gridRect.inset(0, dp.overviewTaskThumbnailTopMarginPx, 0, 0);"
errorLine2=" ~~~~~">
<location
file="packages/apps/Launcher3/quickstep/src/com/android/quickstep/BaseActivityInterface.java"
line="315"
column="18"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 29): `android.view.WindowManager#getCurrentWindowMetrics`"
errorLine1=" .getCurrentWindowMetrics().getWindowInsets();"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/DeviceProfile.java"
line="236"
column="22"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 29): `android.view.WindowMetrics#getWindowInsets`"
errorLine1=" .getCurrentWindowMetrics().getWindowInsets();"
errorLine2=" ~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/DeviceProfile.java"
line="236"
column="48"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 29): `android.content.Context#getDisplay`"
errorLine1=" if (mContext.getDisplay() != null) {"
errorLine2=" ~~~~~~~~~~">
<location
file="packages/apps/Launcher3/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java"
line="115"
column="26"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 29): `android.content.Context#getDisplay`"
errorLine1=" mContext.getDisplay().getRealSize(mDisplaySize);"
errorLine2=" ~~~~~~~~~~">
<location
file="packages/apps/Launcher3/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java"
line="116"
column="26"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 29): `android.content.ContextWrapper#getDisplay`"
errorLine1=" Display display = getDisplay();"
errorLine2=" ~~~~~~~~~~">
<location
file="packages/apps/Launcher3/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java"
line="153"
column="27"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 29): `java.util.List#of`"
errorLine1=" List.of(new Rect(0, 0, metrics.widthPixels, metrics.heightPixels)));"
errorLine2=" ~~">
<location
file="packages/apps/Launcher3/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java"
line="158"
column="26"/>
</issue>
<issue
id="NewApi"
message="Call requires API level 31 (current min is 29): `android.appwidget.AppWidgetHostView#resetColorResources`"
errorLine1=" resetColorResources();"
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java"
line="137"
column="13"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 29): `java.util.List#of`"
errorLine1=" mColorExtractor.addLocation(List.of(mLastLocationRegistered));"
errorLine2=" ~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java"
line="367"
column="46"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 29): `java.util.List#of`"
errorLine1=" mColorExtractor.addLocation(List.of(mLastLocationRegistered));"
errorLine2=" ~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java"
line="390"
column="50"/>
</issue>
<issue
id="NewApi"
message="Field requires API level 31 (current min is 29): `android.appwidget.AppWidgetProviderInfo#maxResizeWidth`"
errorLine1=" (ATLEAST_S &amp;&amp; maxResizeWidth > 0)"
errorLine2=" ~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
line="91"
column="31"/>
</issue>
<issue
id="NewApi"
message="Field requires API level 31 (current min is 29): `android.appwidget.AppWidgetProviderInfo#maxResizeWidth`"
errorLine1=" ? getSpanX(widgetPadding, maxResizeWidth, smallestCellWidth)"
errorLine2=" ~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
line="92"
column="51"/>
</issue>
<issue
id="NewApi"
message="Field requires API level 31 (current min is 29): `android.appwidget.AppWidgetProviderInfo#maxResizeHeight`"
errorLine1=" (ATLEAST_S &amp;&amp; maxResizeHeight > 0)"
errorLine2=" ~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
line="95"
column="31"/>
</issue>
<issue
id="NewApi"
message="Field requires API level 31 (current min is 29): `android.appwidget.AppWidgetProviderInfo#maxResizeHeight`"
errorLine1=" ? getSpanY(widgetPadding, maxResizeHeight, smallestCellHeight)"
errorLine2=" ~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
line="96"
column="51"/>
</issue>
<issue
id="NewApi"
message="Field requires API level 31 (current min is 29): `android.appwidget.AppWidgetProviderInfo#targetCellWidth`"
errorLine1=" if (ATLEAST_S &amp;&amp; targetCellWidth >= minSpanX &amp;&amp; targetCellWidth &lt;= maxSpanX"
errorLine2=" ~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
line="101"
column="26"/>
</issue>
<issue
id="NewApi"
message="Field requires API level 31 (current min is 29): `android.appwidget.AppWidgetProviderInfo#targetCellWidth`"
errorLine1=" if (ATLEAST_S &amp;&amp; targetCellWidth >= minSpanX &amp;&amp; targetCellWidth &lt;= maxSpanX"
errorLine2=" ~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
line="101"
column="57"/>
</issue>
<issue
id="NewApi"
message="Field requires API level 31 (current min is 29): `android.appwidget.AppWidgetProviderInfo#targetCellHeight`"
errorLine1=" &amp;&amp; targetCellHeight >= minSpanY &amp;&amp; targetCellHeight &lt;= maxSpanY) {"
errorLine2=" ~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
line="102"
column="20"/>
</issue>
<issue
id="NewApi"
message="Field requires API level 31 (current min is 29): `android.appwidget.AppWidgetProviderInfo#targetCellHeight`"
errorLine1=" &amp;&amp; targetCellHeight >= minSpanY &amp;&amp; targetCellHeight &lt;= maxSpanY) {"
errorLine2=" ~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
line="102"
column="52"/>
</issue>
<issue
id="NewApi"
message="Field requires API level 31 (current min is 29): `android.appwidget.AppWidgetProviderInfo#targetCellWidth`"
errorLine1=" spanX = targetCellWidth;"
errorLine2=" ~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
line="103"
column="21"/>
</issue>
<issue
id="NewApi"
message="Field requires API level 31 (current min is 29): `android.appwidget.AppWidgetProviderInfo#targetCellHeight`"
errorLine1=" spanY = targetCellHeight;"
errorLine2=" ~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
line="104"
column="21"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 29): `android.content.Context#getDisplay`"
errorLine1=" final Display display = mContext.getDisplay();"
errorLine2=" ~~~~~~~~~~">
<location
file="packages/apps/Launcher3/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java"
line="94"
column="42"/>
</issue>
<issue
id="NewApi"
message="Call requires API level 31 (current min is 29): `android.content.pm.LauncherActivityInfo#getLoadingProgress`"
errorLine1=" return (int) (100 * info.getLoadingProgress());"
errorLine2=" ~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/util/PackageManagerHelper.java"
line="338"
column="38"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 29): `java.util.List#of`"
errorLine1=" private List&lt;WidgetsListBaseEntry> mAllWidgets = List.of();"
errorLine2=" ~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/popup/PopupDataProvider.java"
line="64"
column="59"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 29): `java.util.List#of`"
errorLine1=" private List&lt;ItemInfo> mRecommendedWidgets = List.of();"
errorLine2=" ~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/popup/PopupDataProvider.java"
line="66"
column="55"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 29): `new android.view.SurfaceControlViewHost`"
errorLine1=" .submit(() -> new SurfaceControlViewHost(mContext, mDisplay, mHostToken))"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java"
line="91"
column="35"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 29): `android.view.SurfaceControlViewHost#getSurfacePackage`"
errorLine1=" surfacePackage = mSurfaceControlViewHost.getSurfacePackage();"
errorLine2=" ~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java"
line="93"
column="54"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 29): `android.view.SurfaceControlViewHost#setView`"
errorLine1=" host.setView(view, view.getMeasuredWidth(), view.getMeasuredHeight());"
errorLine2=" ~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java"
line="127"
column="22"/>
</issue>
<issue
id="NewApi"
message="Cast from `SurfacePackage` to `Parcelable` requires API level 30 (current min is 29)"
errorLine1=" result.putParcelable(KEY_SURFACE_PACKAGE, surfacePackage);"
errorLine2=" ~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java"
line="132"
column="51"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 29): `android.view.SurfaceControlViewHost#release`"
errorLine1=" mSurfaceControlViewHost.release();"
errorLine2=" ~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java"
line="149"
column="41"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 29): `android.graphics.Outline#setPath`"
errorLine1=" outline.setPath(mPath);"
errorLine2=" ~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/popup/RoundedArrowDrawable.java"
line="88"
column="17"/>
</issue>
<issue
id="NewApi"
message="Call requires API level 31 (current min is 29): `new android.widget.EdgeEffect`"
errorLine1=" ? new EdgeEffect(context, attrs) : new EdgeEffect(context);"
errorLine2=" ~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/views/SpringRelativeLayout.java"
line="49"
column="19"/>
</issue>
<issue
id="NewApi"
message="Call requires API level 31 (current min is 29): `new android.widget.EdgeEffect`"
errorLine1=" ? new EdgeEffect(context, attrs) : new EdgeEffect(context);"
errorLine2=" ~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/views/SpringRelativeLayout.java"
line="51"
column="19"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 29): `android.view.WindowManager.LayoutParams#setFitInsetsTypes`"
errorLine1=" mWindowLayoutParams.setFitInsetsTypes(0);"
errorLine2=" ~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java"
line="316"
column="29"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 29): `android.view.WindowInsets#getInsets`"
errorLine1=" Insets systemInsets = insets.getInsets(WindowInsets.Type.systemBars());"
errorLine2=" ~~~~~~~~~">
<location
file="packages/apps/Launcher3/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java"
line="118"
column="42"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 29): `android.view.WindowInsets.Type#systemBars`"
errorLine1=" Insets systemInsets = insets.getInsets(WindowInsets.Type.systemBars());"
errorLine2=" ~~~~~~~~~~">
<location
file="packages/apps/Launcher3/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java"
line="118"
column="70"/>
</issue>
<issue
id="NewApi"
message="Call requires API level 31 (current min is 29): `android.appwidget.AppWidgetProviderInfo#loadDescription`"
errorLine1=" CharSequence description = mItem.widgetInfo.loadDescription(getContext());"
errorLine2=" ~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/WidgetCell.java"
line="193"
column="57"/>
</issue>
<issue
id="NewApi"
message="Field requires API level 31 (current min is 29): `android.appwidget.AppWidgetProviderInfo#previewLayout`"
errorLine1=" &amp;&amp; item.widgetInfo.previewLayout != Resources.ID_NULL) {"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/WidgetCell.java"
line="214"
column="20"/>
</issue>
<issue
id="NewApi"
message="Field requires API level 31 (current min is 29): `android.appwidget.AppWidgetProviderInfo#previewLayout`"
errorLine1=" launcherAppWidgetProviderInfo.initialLayout = item.widgetInfo.previewLayout;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/WidgetCell.java"
line="222"
column="59"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 29): `android.view.View#getWindowInsetsController`"
errorLine1=" getWindowInsetsController().hide(WindowInsets.Type.ime());"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java"
line="558"
column="9"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 29): `android.view.WindowInsets.Type#ime`"
errorLine1=" getWindowInsetsController().hide(WindowInsets.Type.ime());"
errorLine2=" ~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java"
line="558"
column="60"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 29): `android.view.WindowInsetsController#hide`"
errorLine1=" getWindowInsetsController().hide(WindowInsets.Type.ime());"
errorLine2=" ~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java"
line="558"
column="37"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 29): `java.util.List#of`"
errorLine1=" return new RecommendationTableData(List.of(), previewScale);"
errorLine2=" ~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java"
line="139"
column="53"/>
</issue>
</issues>
-609
View File
@@ -1,609 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
<issue
id="NewApi"
message="Call requires API level 28 (current min is 26): `android.os.UserManager#requestQuietModeEnabled`"
errorLine1=" showConfirm |= !userManager.requestQuietModeEnabled(!toState, userProfile);"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/allapps/WorkModeSwitch.java"
line="110"
column="41"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 26): `android.view.View#getWindowInsetsController`"
errorLine1=" getWindowInsetsController().hide(WindowInsets.Type.ime());"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/allapps/AllAppsContainerView.java"
line="203"
column="9"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 26): `android.view.WindowInsets.Type#ime`"
errorLine1=" getWindowInsetsController().hide(WindowInsets.Type.ime());"
errorLine2=" ~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/allapps/AllAppsContainerView.java"
line="203"
column="60"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 26): `android.view.WindowInsetsController#hide`"
errorLine1=" getWindowInsetsController().hide(WindowInsets.Type.ime());"
errorLine2=" ~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/allapps/AllAppsContainerView.java"
line="203"
column="37"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 26): `android.view.View#getWindowInsetsController`"
errorLine1=" getWindowInsetsController().hide(WindowInsets.Type.ime());"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/allapps/AllAppsRecyclerView.java"
line="193"
column="17"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 26): `android.view.WindowInsets.Type#ime`"
errorLine1=" getWindowInsetsController().hide(WindowInsets.Type.ime());"
errorLine2=" ~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/allapps/AllAppsRecyclerView.java"
line="193"
column="68"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 26): `android.view.WindowInsetsController#hide`"
errorLine1=" getWindowInsetsController().hide(WindowInsets.Type.ime());"
errorLine2=" ~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/allapps/AllAppsRecyclerView.java"
line="193"
column="45"/>
</issue>
<issue
id="NewApi"
message="Call requires API level 31 (current min is 26): `android.appwidget.AppWidgetHostView#updateAppWidgetSize`"
errorLine1=" widgetView.updateAppWidgetSize(new Bundle(), sizes);"
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/AppWidgetResizeFrame.java"
line="399"
column="24"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 26): `android.view.WindowManager#getCurrentWindowMetrics`"
errorLine1=" .getCurrentWindowMetrics().getWindowInsets();"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/DeviceProfile.java"
line="236"
column="22"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 26): `android.view.WindowMetrics#getWindowInsets`"
errorLine1=" .getCurrentWindowMetrics().getWindowInsets();"
errorLine2=" ~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/DeviceProfile.java"
line="236"
column="48"/>
</issue>
<issue
id="NewApi"
message="Call requires API level 29 (current min is 26): `android.content.res.Resources#getFloat`"
errorLine1=" folderLabelTextScale = res.getFloat(R.dimen.folder_label_text_scale);"
errorLine2=" ~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/DeviceProfile.java"
line="256"
column="36"/>
</issue>
<issue
id="NewApi"
message="Call requires API level 29 (current min is 26): `android.content.res.Resources#getFloat`"
errorLine1=" return mContext.getResources().getFloat(resId);"
errorLine2=" ~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/util/DynamicResource.java"
line="73"
column="40"/>
</issue>
<issue
id="NewApi"
message="Call requires API level 31 (current min is 26): `android.appwidget.AppWidgetHostView#resetColorResources`"
errorLine1=" resetColorResources();"
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java"
line="137"
column="13"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 26): `java.util.List#of`"
errorLine1=" mColorExtractor.addLocation(List.of(mLastLocationRegistered));"
errorLine2=" ~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java"
line="367"
column="46"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 26): `java.util.List#of`"
errorLine1=" mColorExtractor.addLocation(List.of(mLastLocationRegistered));"
errorLine2=" ~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java"
line="390"
column="50"/>
</issue>
<issue
id="NewApi"
message="Field requires API level 31 (current min is 26): `android.appwidget.AppWidgetProviderInfo#maxResizeWidth`"
errorLine1=" (ATLEAST_S &amp;&amp; maxResizeWidth > 0)"
errorLine2=" ~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
line="91"
column="31"/>
</issue>
<issue
id="NewApi"
message="Field requires API level 31 (current min is 26): `android.appwidget.AppWidgetProviderInfo#maxResizeWidth`"
errorLine1=" ? getSpanX(widgetPadding, maxResizeWidth, smallestCellWidth)"
errorLine2=" ~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
line="92"
column="51"/>
</issue>
<issue
id="NewApi"
message="Field requires API level 31 (current min is 26): `android.appwidget.AppWidgetProviderInfo#maxResizeHeight`"
errorLine1=" (ATLEAST_S &amp;&amp; maxResizeHeight > 0)"
errorLine2=" ~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
line="95"
column="31"/>
</issue>
<issue
id="NewApi"
message="Field requires API level 31 (current min is 26): `android.appwidget.AppWidgetProviderInfo#maxResizeHeight`"
errorLine1=" ? getSpanY(widgetPadding, maxResizeHeight, smallestCellHeight)"
errorLine2=" ~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
line="96"
column="51"/>
</issue>
<issue
id="NewApi"
message="Field requires API level 31 (current min is 26): `android.appwidget.AppWidgetProviderInfo#targetCellWidth`"
errorLine1=" if (ATLEAST_S &amp;&amp; targetCellWidth >= minSpanX &amp;&amp; targetCellWidth &lt;= maxSpanX"
errorLine2=" ~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
line="101"
column="26"/>
</issue>
<issue
id="NewApi"
message="Field requires API level 31 (current min is 26): `android.appwidget.AppWidgetProviderInfo#targetCellWidth`"
errorLine1=" if (ATLEAST_S &amp;&amp; targetCellWidth >= minSpanX &amp;&amp; targetCellWidth &lt;= maxSpanX"
errorLine2=" ~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
line="101"
column="57"/>
</issue>
<issue
id="NewApi"
message="Field requires API level 31 (current min is 26): `android.appwidget.AppWidgetProviderInfo#targetCellHeight`"
errorLine1=" &amp;&amp; targetCellHeight >= minSpanY &amp;&amp; targetCellHeight &lt;= maxSpanY) {"
errorLine2=" ~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
line="102"
column="20"/>
</issue>
<issue
id="NewApi"
message="Field requires API level 31 (current min is 26): `android.appwidget.AppWidgetProviderInfo#targetCellHeight`"
errorLine1=" &amp;&amp; targetCellHeight >= minSpanY &amp;&amp; targetCellHeight &lt;= maxSpanY) {"
errorLine2=" ~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
line="102"
column="52"/>
</issue>
<issue
id="NewApi"
message="Field requires API level 31 (current min is 26): `android.appwidget.AppWidgetProviderInfo#targetCellWidth`"
errorLine1=" spanX = targetCellWidth;"
errorLine2=" ~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
line="103"
column="21"/>
</issue>
<issue
id="NewApi"
message="Field requires API level 31 (current min is 26): `android.appwidget.AppWidgetProviderInfo#targetCellHeight`"
errorLine1=" spanY = targetCellHeight;"
errorLine2=" ~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java"
line="104"
column="21"/>
</issue>
<issue
id="NewApi"
message="Call requires API level 28 (current min is 26): `android.app.Person#getKey`"
errorLine1=" return people.stream().filter(person -> person.getKey() != null)"
errorLine2=" ~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/notification/NotificationKeyData.java"
line="72"
column="56"/>
</issue>
<issue
id="NewApi"
message="Method reference requires API level 28 (current min is 26): `Person::getKey`"
errorLine1=" .map(Person::getKey).sorted().toArray(String[]::new);"
errorLine2=" ~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/notification/NotificationKeyData.java"
line="73"
column="22"/>
</issue>
<issue
id="NewApi"
message="Call requires API level 31 (current min is 26): `android.content.pm.LauncherActivityInfo#getLoadingProgress`"
errorLine1=" return (int) (100 * info.getLoadingProgress());"
errorLine2=" ~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/util/PackageManagerHelper.java"
line="338"
column="38"/>
</issue>
<issue
id="NewApi"
message="Field requires API level 29 (current min is 26): `android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_PAGE_LEFT`"
errorLine1=" AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_LEFT"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/PagedView.java"
line="1752"
column="17"/>
</issue>
<issue
id="NewApi"
message="Field requires API level 29 (current min is 26): `android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_PAGE_RIGHT`"
errorLine1=" : AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_RIGHT);"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/PagedView.java"
line="1753"
column="19"/>
</issue>
<issue
id="NewApi"
message="Field requires API level 29 (current min is 26): `android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_PAGE_RIGHT`"
errorLine1=" AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_RIGHT"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/PagedView.java"
line="1760"
column="17"/>
</issue>
<issue
id="NewApi"
message="Field requires API level 29 (current min is 26): `android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_PAGE_LEFT`"
errorLine1=" : AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_LEFT);"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/PagedView.java"
line="1761"
column="19"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 26): `java.util.List#of`"
errorLine1=" private List&lt;WidgetsListBaseEntry> mAllWidgets = List.of();"
errorLine2=" ~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/popup/PopupDataProvider.java"
line="64"
column="59"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 26): `java.util.List#of`"
errorLine1=" private List&lt;ItemInfo> mRecommendedWidgets = List.of();"
errorLine2=" ~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/popup/PopupDataProvider.java"
line="66"
column="55"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 26): `new android.view.SurfaceControlViewHost`"
errorLine1=" .submit(() -> new SurfaceControlViewHost(mContext, mDisplay, mHostToken))"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java"
line="91"
column="35"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 26): `android.view.SurfaceControlViewHost#getSurfacePackage`"
errorLine1=" surfacePackage = mSurfaceControlViewHost.getSurfacePackage();"
errorLine2=" ~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java"
line="93"
column="54"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 26): `android.view.SurfaceControlViewHost#setView`"
errorLine1=" host.setView(view, view.getMeasuredWidth(), view.getMeasuredHeight());"
errorLine2=" ~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java"
line="127"
column="22"/>
</issue>
<issue
id="NewApi"
message="Cast from `SurfacePackage` to `Parcelable` requires API level 30 (current min is 26)"
errorLine1=" result.putParcelable(KEY_SURFACE_PACKAGE, surfacePackage);"
errorLine2=" ~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java"
line="132"
column="51"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 26): `android.view.SurfaceControlViewHost#release`"
errorLine1=" mSurfaceControlViewHost.release();"
errorLine2=" ~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java"
line="149"
column="41"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 26): `android.graphics.Outline#setPath`"
errorLine1=" outline.setPath(mPath);"
errorLine2=" ~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/popup/RoundedArrowDrawable.java"
line="88"
column="17"/>
</issue>
<issue
id="NewApi"
message="Call requires API level 31 (current min is 26): `new android.widget.EdgeEffect`"
errorLine1=" ? new EdgeEffect(context, attrs) : new EdgeEffect(context);"
errorLine2=" ~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/views/SpringRelativeLayout.java"
line="49"
column="19"/>
</issue>
<issue
id="NewApi"
message="Call requires API level 31 (current min is 26): `new android.widget.EdgeEffect`"
errorLine1=" ? new EdgeEffect(context, attrs) : new EdgeEffect(context);"
errorLine2=" ~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/views/SpringRelativeLayout.java"
line="51"
column="19"/>
</issue>
<issue
id="NewApi"
message="Call requires API level 29 (current min is 26): `android.view.WindowInsets#getTappableElementInsets`"
errorLine1=" return windowInsets.getTappableElementInsets().bottom > 0;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/graphics/SysUiScrim.java"
line="190"
column="33"/>
</issue>
<issue
id="NewApi"
message="Field requires API level 29 (current min is 26): `android.graphics.Insets#bottom`"
errorLine1=" return windowInsets.getTappableElementInsets().bottom > 0;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/graphics/SysUiScrim.java"
line="190"
column="20"/>
</issue>
<issue
id="NewApi"
message="Field requires API level 28 (current min is 26): `android.appwidget.AppWidgetProviderInfo#widgetFeatures`"
errorLine1=" int featureFlags = mProviderInfo.widgetFeatures;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/WidgetAddFlowHandler.java"
line="93"
column="28"/>
</issue>
<issue
id="NewApi"
message="Call requires API level 31 (current min is 26): `android.appwidget.AppWidgetProviderInfo#loadDescription`"
errorLine1=" CharSequence description = mItem.widgetInfo.loadDescription(getContext());"
errorLine2=" ~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/WidgetCell.java"
line="193"
column="57"/>
</issue>
<issue
id="NewApi"
message="Field requires API level 31 (current min is 26): `android.appwidget.AppWidgetProviderInfo#previewLayout`"
errorLine1=" &amp;&amp; item.widgetInfo.previewLayout != Resources.ID_NULL) {"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/WidgetCell.java"
line="214"
column="20"/>
</issue>
<issue
id="NewApi"
message="Field requires API level 31 (current min is 26): `android.appwidget.AppWidgetProviderInfo#previewLayout`"
errorLine1=" launcherAppWidgetProviderInfo.initialLayout = item.widgetInfo.previewLayout;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/WidgetCell.java"
line="222"
column="59"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 26): `android.view.View#getWindowInsetsController`"
errorLine1=" getWindowInsetsController().hide(WindowInsets.Type.ime());"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java"
line="558"
column="9"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 26): `android.view.WindowInsets.Type#ime`"
errorLine1=" getWindowInsetsController().hide(WindowInsets.Type.ime());"
errorLine2=" ~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java"
line="558"
column="60"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 26): `android.view.WindowInsetsController#hide`"
errorLine1=" getWindowInsetsController().hide(WindowInsets.Type.ime());"
errorLine2=" ~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java"
line="558"
column="37"/>
</issue>
<issue
id="NewApi"
message="Call requires API level R (current min is 26): `java.util.List#of`"
errorLine1=" return new RecommendationTableData(List.of(), previewScale);"
errorLine2=" ~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java"
line="139"
column="53"/>
</issue>
<issue
id="NewApi"
message="Method reference requires API level 28 (current min is 26): `Person::getKey`"
errorLine1=" : Arrays.stream(persons).map(Person::getKey).sorted().toArray(String[]::new);"
errorLine2=" ~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/model/data/WorkspaceItemInfo.java"
line="178"
column="42"/>
</issue>
<issue
id="NewApi"
message="Call requires API level 31 (current min is 26): `android.appwidget.AppWidgetHostView#setColorResources`"
errorLine1=" setColorResources(mWallpaperColorResources);"
errorLine2=" ~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java"
line="528"
column="17"/>
</issue>
<issue
id="NewApi"
message="Call requires API level 27 (current min is 26): `android.app.WallpaperManager#getWallpaperColors`"
errorLine1=" : WallpaperManager.getInstance(context).getWallpaperColors(FLAG_SYSTEM);"
errorLine2=" ~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java"
line="288"
column="61"/>
</issue>
</issues>
-48
View File
@@ -1,48 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
<issue
id="NewApi"
message="`?android:attr/dialogCornerRadius` requires API level 28 (current min is 26)"
errorLine1=" android:topLeftRadius=&quot;?android:attr/dialogCornerRadius&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/res/drawable/add_item_dialog_background.xml"
line="6"
column="9"/>
</issue>
<issue
id="NewApi"
message="`?android:attr/dialogCornerRadius` requires API level 28 (current min is 26)"
errorLine1=" android:topRightRadius=&quot;?android:attr/dialogCornerRadius&quot; />"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/res/drawable/add_item_dialog_background.xml"
line="7"
column="9"/>
</issue>
<issue
id="NewApi"
message="`@android:style/Widget.DeviceDefault.Button.Colored` requires API level 28 (current min is 26)"
errorLine1=" &lt;style name=&quot;Widget.DeviceDefault.Button.Rounded.Colored&quot; parent=&quot;@android:style/Widget.DeviceDefault.Button.Colored&quot;>"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/res/values/styles.xml"
line="287"
column="63"/>
</issue>
<issue
id="NewApi"
message="`@android:dimen/system_app_widget_background_radius` requires API level 31 (current min is 26)"
errorLine1=" &lt;corners android:radius=&quot;@android:dimen/system_app_widget_background_radius&quot; />"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/res/drawable/widget_resize_frame.xml"
line="20"
column="14"/>
</issue>
</issues>
+1 -1
View File
@@ -4,5 +4,5 @@
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
#Thu Jun 20 23:21:48 CST 2024
#Thu Jun 19 22:57:51 CST 2025
sdk.dir=/Users/yuchuan/Library/Android/sdk
BIN
View File
Binary file not shown.
Binary file not shown.
+10 -8
View File
@@ -1,6 +1,12 @@
-keep,allowshrinking,allowoptimization class com.android.launcher3.** {
*;
}
-keep,allowshrinking,allowoptimization class com.android.launcher3.** {*;}
-keepclasseswithmembernames class com.android.launcher3.** {*;}
-keep,allowshrinking,allowoptimization class com.android.systemui.shared.** {*;}
-keepclasseswithmembernames class com.android.systemui.shared.** {*;}
-keep,allowshrinking,allowoptimization class com.android.quickstep.** {*;}
-keepclasseswithmembernames class com.android.quickstep.** {*;}
# The support library contains references to newer platform versions.
# Don't warn about those in case this app is linking against an older
@@ -49,11 +55,7 @@
# Ignore warnings for hidden utility classes referenced from the shared lib
-dontwarn com.android.internal.util.**
################ Do not optimize recents lib #############
-keep class com.android.systemui.shared.** {
*;
}
-keep class com.android.quickstep.** {
-keep class com.android.internal.protolog.** {
*;
}
+16
View File
@@ -33,6 +33,7 @@ message ItemInfo {
FolderIcon folder_icon = 9;
Slice slice = 10;
SearchActionItem search_action_item = 11;
TaskView task_view = 15;
}
// When used for launch event, stores the global predictive rank
optional int32 rank = 5;
@@ -262,6 +263,21 @@ message Task {
optional int32 index = 3;
}
// TaskView in RecentsView.
message TaskView {
// TaskViewType.
optional int32 type = 1;
// Index of TaskView in RecentsView.
optional int32 index = 2;
// ComponentName of the Task.
optional string component_name = 3;
// Number of tasks in the TaskView.
optional int32 cardinality = 4;
}
// Represents folder in a closed state.
message FolderIcon {
// Number of items inside folder.
BIN
View File
Binary file not shown.
+18 -1
View File
@@ -52,6 +52,23 @@ filegroup {
"tests/src/com/android/quickstep/TaplOverviewIconTest.java",
"tests/src/com/android/quickstep/TaplTestsQuickstep.java",
"tests/src/com/android/quickstep/TaplTestsSplitscreen.java",
"tests/src/com/android/launcher3/testcomponent/ExcludeFromRecentsTestActivity.java"
"tests/src/com/android/quickstep/util/SplitScreenTestUtils.kt",
"tests/src/com/android/launcher3/testcomponent/ExcludeFromRecentsTestActivity.java",
],
}
filegroup {
name: "launcher3-quickstep-screenshot-tests-src",
path: "tests/multivalentScreenshotTests",
srcs: [
"tests/multivalentScreenshotTests/src/**/*.kt",
],
}
filegroup {
name: "launcher3-quickstep-testing",
path: "testing",
srcs: [
"testing/**/*.kt",
],
}
+2 -1
View File
@@ -48,7 +48,7 @@
android:stateNotNeeded="true"
android:windowSoftInputMode="adjustPan"
android:screenOrientation="unspecified"
android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize|uiMode"
android:resizeableActivity="true"
android:resumeWhilePausing="true"
android:taskAffinity=""
@@ -57,6 +57,7 @@
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.SHOW_WORK_APPS" />
<action android:name="android.intent.action.ALL_APPS" />
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.MONKEY"/>
+4 -2
View File
@@ -54,6 +54,7 @@
android:protectionLevel="signature|privileged" />
<application android:backupAgent="com.android.launcher3.LauncherBackupAgent"
android:enableOnBackInvokedCallback="true"
android:fullBackupOnly="true"
android:fullBackupContent="@xml/backupscheme"
android:hardwareAccelerated="true"
@@ -80,7 +81,7 @@
android:stateNotNeeded="true"
android:theme="@style/LauncherTheme"
android:screenOrientation="behind"
android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize|uiMode"
android:resizeableActivity="true"
android:resumeWhilePausing="true"
android:enableOnBackInvokedCallback="false"
@@ -118,6 +119,7 @@
android:autoRemoveFromRecents="true"
android:excludeFromRecents="true"
android:theme="@style/GestureTutorialActivity"
android:label="@string/gesture_tutorial_title"
android:exported="true"
android:configChanges="orientation">
<intent-filter>
@@ -151,7 +153,7 @@
android:showOnLockScreen="true"
android:launchMode="singleTop"
android:exported="true"
android:permission="android.permission.START_WIDGET_PICKER_ACTIVITY">
android:permission="${applicationId}.permission.START_WIDGET_PICKER_ACTIVITY">
<intent-filter>
<action android:name="android.intent.action.PICK" />
<category android:name="android.intent.category.DEFAULT" />
@@ -0,0 +1,19 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep.compose.core
interface QuickstepComposeFeatures
@@ -0,0 +1,29 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep.compose
import android.content.Context
import com.android.launcher3.compose.ComposeFacade
import com.android.launcher3.compose.core.BaseComposeFacade
import com.android.quickstep.compose.core.QuickstepComposeFeatures
object QuickstepComposeFacade : BaseComposeFacade, QuickstepComposeFeatures {
override fun isComposeAvailable() = ComposeFacade.isComposeAvailable()
override fun initComposeView(appContext: Context) = ComposeFacade.initComposeView(appContext)
}
@@ -0,0 +1,28 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep.compose
import android.content.Context
import com.android.launcher3.compose.ComposeFacade
import com.android.launcher3.compose.core.BaseComposeFacade
import com.android.quickstep.compose.core.QuickstepComposeFeatures
object QuickstepComposeFacade : BaseComposeFacade, QuickstepComposeFeatures {
override fun isComposeAvailable() = ComposeFacade.isComposeAvailable()
override fun initComposeView(appContext: Context) = ComposeFacade.initComposeView(appContext)
}
Loaded 100 of 1653 files, more files were not shown because too many files have changed in this diff. Show more