mirror of
https://github.com/alesimula/Murine-launcher.git
synced 2026-10-28 09:43:42 +00:00
update android16-s2-release
This commit is contained in:
1 parent
b725301b43
commit
1889ce3062
1653 files changed
+113254
-46331
No files matched your search
+291
-16
@@ -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,
|
||||
|
||||
@@ -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" />
|
||||
|
||||
<!--
|
||||
|
||||
@@ -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>
|
||||
@@ -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
@@ -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}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Vendored
BIN
Binary file not shown.
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
+6
-6
@@ -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
|
||||
-12
@@ -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
@@ -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
|
||||
@@ -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
@@ -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"
|
||||
}
|
||||
|
||||
Vendored
BIN
Binary file not shown.
@@ -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
@@ -1,934 +0,0 @@
|
||||
[34mCOMMAND>> git log f99351888c3e5a128559678304fefd647472bc7f..4c3952dc60fc78d3816012a86d7e71747ef34c74[m
|
||||
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
|
||||
+7
-5
@@ -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
|
||||
}
|
||||
@@ -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."
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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>
|
||||
@@ -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 > Programme > Verstekprogramme > 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 > Apps > Verstekapps > 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>"सेटिङ > एप > डिफल्ट एप > डिजिटल सहायक एप"</b>" मा जानुहोस्।"</string>
|
||||
<string name="niu_actions_confirmation_text" msgid="2105271481950866089">"तपाईंको स्क्रिनमा देखिने पाठ, वेब ठेगाना र स्क्रिनसटलगायतका जानकारी Google सँग सेयर गर्न सकिन्छ।\n\nकुन कुन जानकारी सेयर गर्न दिने भन्ने सेटिङ बदल्न "<b>"सेटिङ > एप > डिफल्ट एप > डिजिटल एसिस्टेन्ट एप"</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>
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"))
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
+29
-6
@@ -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());
|
||||
}
|
||||
}
|
||||
+49
@@ -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?
|
||||
}
|
||||
-322
@@ -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);
|
||||
}
|
||||
}
|
||||
+300
@@ -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"
|
||||
}
|
||||
}
|
||||
+77
@@ -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) {
|
||||
|
||||
@@ -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="?android:attr/dialogCornerRadius""
|
||||
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="?android:attr/dialogCornerRadius" />"
|
||||
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=" <style name="Widget.DeviceDefault.Button.Rounded.Colored" parent="@android:style/Widget.DeviceDefault.Button.Colored">"
|
||||
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=" <corners android:radius="@android:dimen/system_app_widget_background_radius" />"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="packages/apps/Launcher3/res/drawable/widget_resize_frame.xml"
|
||||
line="20"
|
||||
column="14"/>
|
||||
</issue>
|
||||
|
||||
</issues>
|
||||
@@ -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 && 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 && 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 && targetCellWidth >= minSpanX && targetCellWidth <= 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 && targetCellWidth >= minSpanX && targetCellWidth <= 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=" && targetCellHeight >= minSpanY && targetCellHeight <= 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=" && targetCellHeight >= minSpanY && targetCellHeight <= 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<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<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=" && 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>
|
||||
@@ -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 && 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 && 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 && targetCellWidth >= minSpanX && targetCellWidth <= 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 && targetCellWidth >= minSpanX && targetCellWidth <= 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=" && targetCellHeight >= minSpanY && targetCellHeight <= 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=" && targetCellHeight >= minSpanY && targetCellHeight <= 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<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<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=" && 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>
|
||||
@@ -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="?android:attr/dialogCornerRadius""
|
||||
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="?android:attr/dialogCornerRadius" />"
|
||||
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=" <style name="Widget.DeviceDefault.Button.Rounded.Colored" parent="@android:style/Widget.DeviceDefault.Button.Colored">"
|
||||
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=" <corners android:radius="@android:dimen/system_app_widget_background_radius" />"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="packages/apps/Launcher3/res/drawable/widget_resize_frame.xml"
|
||||
line="20"
|
||||
column="14"/>
|
||||
</issue>
|
||||
|
||||
</issues>
|
||||
+1
-1
@@ -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
|
||||
Vendored
BIN
Binary file not shown.
Binary file not shown.
+10
-8
@@ -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.** {
|
||||
*;
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+18
-1
@@ -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",
|
||||
],
|
||||
}
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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
Reference in new issue
Block a user