mirror of
https://github.com/alesimula/Murine-launcher.git
synced 2026-10-28 09:43:42 +00:00
更新为android-13.0.0_r83版本
This commit is contained in:
1 parent
aa953f3f4b
commit
8e63bd67f5
1130 files changed
+63395
-29939
No files matched your search
+195
-64
@@ -19,6 +19,54 @@ package {
|
||||
|
||||
min_launcher3_sdk_version = "26"
|
||||
|
||||
// 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
|
||||
filegroup {
|
||||
name: "launcher-src",
|
||||
srcs: [ "src/**/*.java", "src/**/*.kt" ],
|
||||
}
|
||||
|
||||
filegroup {
|
||||
name: "launcher-quickstep-src",
|
||||
srcs: [ "quickstep/src/**/*.java", "quickstep/src/**/*.kt" ],
|
||||
}
|
||||
|
||||
filegroup {
|
||||
name: "launcher-go-src",
|
||||
srcs: [ "go/src/**/*.java", "go/src/**/*.kt" ],
|
||||
}
|
||||
|
||||
filegroup {
|
||||
name: "launcher-go-quickstep-src",
|
||||
srcs: [ "go/quickstep/src/**/*.java", "go/quickstep/src/**/*.kt" ],
|
||||
}
|
||||
|
||||
filegroup {
|
||||
name: "launcher-src_shortcuts_overrides",
|
||||
srcs: [ "src_shortcuts_overrides/**/*.java", "src_shortcuts_overrides/**/*.kt" ],
|
||||
}
|
||||
|
||||
filegroup {
|
||||
name: "launcher-src_ui_overrides",
|
||||
srcs: [ "src_ui_overrides/**/*.java", "src_ui_overrides/**/*.kt" ],
|
||||
}
|
||||
|
||||
filegroup {
|
||||
name: "launcher-ext_tests",
|
||||
srcs: [ "ext_tests/**/*.java", "ext_tests/**/*.kt" ],
|
||||
}
|
||||
|
||||
filegroup {
|
||||
name: "launcher-quickstep-ext_tests",
|
||||
srcs: [ "quickstep/ext_tests/**/*.java", "quickstep/ext_tests/**/*.kt" ],
|
||||
}
|
||||
|
||||
// Proguard files for Launcher3
|
||||
filegroup {
|
||||
name: "launcher-proguard-rules",
|
||||
srcs: ["proguard.flags"],
|
||||
}
|
||||
|
||||
android_library {
|
||||
name: "launcher-aosp-tapl",
|
||||
libs: [
|
||||
@@ -32,12 +80,10 @@ android_library {
|
||||
"androidx.preference_preference",
|
||||
"SystemUISharedLib",
|
||||
"SystemUIAnimationLib",
|
||||
"launcher-testing-shared",
|
||||
],
|
||||
srcs: [
|
||||
"tests/tapl/**/*.java",
|
||||
"src/com/android/launcher3/ResourceUtils.java",
|
||||
"src/com/android/launcher3/testing/TestProtocol.java",
|
||||
"src/com/android/launcher3/testing/*Request.java",
|
||||
],
|
||||
resource_dirs: [ ],
|
||||
manifest: "tests/tapl/AndroidManifest.xml",
|
||||
@@ -107,6 +153,7 @@ android_library {
|
||||
"androidx.cardview_cardview",
|
||||
"com.google.android.material_material",
|
||||
"iconloader_base",
|
||||
"view_capture"
|
||||
],
|
||||
manifest: "AndroidManifest-common.xml",
|
||||
sdk_version: "current",
|
||||
@@ -122,7 +169,10 @@ android_library {
|
||||
android_library {
|
||||
name: "Launcher3CommonDepsLib",
|
||||
srcs: ["src_build_config/**/*.java"],
|
||||
static_libs: ["Launcher3ResLib"],
|
||||
static_libs: [
|
||||
"Launcher3ResLib",
|
||||
"launcher-testing-shared",
|
||||
],
|
||||
sdk_version: "current",
|
||||
min_sdk_version: min_launcher3_sdk_version,
|
||||
manifest: "AndroidManifest-common.xml",
|
||||
@@ -141,14 +191,10 @@ android_app {
|
||||
"Launcher3CommonDepsLib",
|
||||
],
|
||||
srcs: [
|
||||
"src/**/*.java",
|
||||
"src/**/*.kt",
|
||||
"src_shortcuts_overrides/**/*.java",
|
||||
"src_shortcuts_overrides/**/*.kt",
|
||||
"src_ui_overrides/**/*.java",
|
||||
"src_ui_overrides/**/*.kt",
|
||||
"ext_tests/src/**/*.java",
|
||||
"ext_tests/src/**/*.kt",
|
||||
":launcher-src",
|
||||
":launcher-src_shortcuts_overrides",
|
||||
":launcher-src_ui_overrides",
|
||||
":launcher-ext_tests",
|
||||
],
|
||||
resource_dirs: [
|
||||
"ext_tests/res",
|
||||
@@ -204,61 +250,14 @@ android_library {
|
||||
}
|
||||
|
||||
|
||||
// Source code used for test helpers
|
||||
filegroup {
|
||||
name: "launcher-src-ext-tests",
|
||||
srcs: [
|
||||
"ext_tests/src/**/*.java",
|
||||
"ext_tests/src/**/*.kt",
|
||||
"quickstep/ext_tests/src/**/*.java",
|
||||
"quickstep/ext_tests/src/**/*.kt",
|
||||
],
|
||||
}
|
||||
|
||||
// Common source files used to build launcher
|
||||
filegroup {
|
||||
name: "launcher-src-no-build-config",
|
||||
srcs: [
|
||||
"src/**/*.java",
|
||||
"src/**/*.kt",
|
||||
"src_shortcuts_overrides/**/*.java",
|
||||
"src_shortcuts_overrides/**/*.kt",
|
||||
"quickstep/src/**/*.java",
|
||||
"quickstep/src/**/*.kt",
|
||||
],
|
||||
}
|
||||
|
||||
// Common source files used to build go launcher except go/src files
|
||||
filegroup {
|
||||
name: "launcher-go-src-no-build-config",
|
||||
srcs: [
|
||||
"src/**/*.java",
|
||||
"src/**/*.kt",
|
||||
"quickstep/src/**/*.java",
|
||||
"quickstep/src/**/*.kt",
|
||||
"go/quickstep/src/**/*.java",
|
||||
"go/quickstep/src/**/*.kt",
|
||||
],
|
||||
}
|
||||
|
||||
// Proguard files for Launcher3
|
||||
filegroup {
|
||||
name: "launcher-proguard-rules",
|
||||
srcs: ["proguard.flags"],
|
||||
}
|
||||
|
||||
// Library with all the dependencies for building Launcher Go
|
||||
android_library {
|
||||
name: "LauncherGoResLib",
|
||||
srcs: [
|
||||
"src/**/*.java",
|
||||
"src/**/*.kt",
|
||||
"quickstep/src/**/*.java",
|
||||
"quickstep/src/**/*.kt",
|
||||
"go/src/**/*.java",
|
||||
"go/src/**/*.kt",
|
||||
"go/quickstep/src/**/*.java",
|
||||
"go/quickstep/src/**/*.kt",
|
||||
":launcher-src",
|
||||
":launcher-quickstep-src",
|
||||
":launcher-go-src",
|
||||
":launcher-go-quickstep-src",
|
||||
],
|
||||
resource_dirs: [
|
||||
"go/res",
|
||||
@@ -289,7 +288,9 @@ android_library {
|
||||
android_library {
|
||||
name: "Launcher3QuickStepLib",
|
||||
srcs: [
|
||||
":launcher-src-no-build-config",
|
||||
":launcher-src",
|
||||
":launcher-quickstep-src",
|
||||
":launcher-src_shortcuts_overrides",
|
||||
],
|
||||
resource_dirs: [],
|
||||
libs: [
|
||||
@@ -313,3 +314,133 @@ android_library {
|
||||
baseline_filename: "lint-baseline-launcher3.xml",
|
||||
},
|
||||
}
|
||||
|
||||
// Build rule for Launcher3 Go app for Android Go devices.
|
||||
android_app {
|
||||
name: "Launcher3Go",
|
||||
|
||||
static_libs: ["Launcher3CommonDepsLib"],
|
||||
|
||||
srcs: [
|
||||
":launcher-src",
|
||||
":launcher-go-src",
|
||||
":launcher-src_ui_overrides",
|
||||
],
|
||||
|
||||
resource_dirs: ["go/res"],
|
||||
|
||||
optimize: {
|
||||
proguard_flags_files: ["proguard.flags"],
|
||||
},
|
||||
|
||||
sdk_version: "current",
|
||||
min_sdk_version: "current",
|
||||
target_sdk_version: "current",
|
||||
privileged: true,
|
||||
system_ext_specific: true,
|
||||
overrides: [
|
||||
"Home",
|
||||
"Launcher2",
|
||||
"Launcher3",
|
||||
"Launcher3QuickStep",
|
||||
],
|
||||
required: ["privapp_whitelist_com.android.launcher3"],
|
||||
|
||||
additional_manifests: [
|
||||
"AndroidManifest.xml",
|
||||
"AndroidManifest-common.xml",
|
||||
],
|
||||
|
||||
manifest: "go/AndroidManifest.xml",
|
||||
jacoco: {
|
||||
include_filter: ["com.android.launcher3.*"],
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Build rule for Quickstep app.
|
||||
android_app {
|
||||
name: "Launcher3QuickStep",
|
||||
|
||||
static_libs: ["Launcher3QuickStepLib"],
|
||||
optimize: {
|
||||
enabled: false,
|
||||
},
|
||||
|
||||
platform_apis: true,
|
||||
min_sdk_version: "current",
|
||||
target_sdk_version: "current",
|
||||
|
||||
privileged: true,
|
||||
system_ext_specific: true,
|
||||
overrides: [
|
||||
"Home",
|
||||
"Launcher2",
|
||||
"Launcher3",
|
||||
],
|
||||
required: ["privapp_whitelist_com.android.launcher3"],
|
||||
|
||||
resource_dirs: ["quickstep/res"],
|
||||
|
||||
additional_manifests: [
|
||||
"quickstep/AndroidManifest-launcher.xml",
|
||||
"AndroidManifest-common.xml",
|
||||
],
|
||||
|
||||
manifest: "quickstep/AndroidManifest.xml",
|
||||
jacoco: {
|
||||
include_filter: ["com.android.launcher3.*"],
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Build rule for Launcher3 Go app with quickstep for Android Go devices.
|
||||
android_app {
|
||||
name: "Launcher3QuickStepGo",
|
||||
|
||||
static_libs: [
|
||||
"SystemUI-statsd",
|
||||
"SystemUISharedLib",
|
||||
"LauncherGoResLib",
|
||||
],
|
||||
|
||||
platform_apis: true,
|
||||
min_sdk_version: "current",
|
||||
target_sdk_version: "current",
|
||||
|
||||
srcs: [ ],
|
||||
|
||||
resource_dirs: [
|
||||
"go/quickstep/res",
|
||||
"go/res",
|
||||
"quickstep/res",
|
||||
],
|
||||
|
||||
optimize: {
|
||||
proguard_flags_files: ["proguard.flags"],
|
||||
enabled: true,
|
||||
},
|
||||
|
||||
privileged: true,
|
||||
system_ext_specific: true,
|
||||
overrides: [
|
||||
"Home",
|
||||
"Launcher2",
|
||||
"Launcher3",
|
||||
"Launcher3QuickStep",
|
||||
],
|
||||
required: ["privapp_whitelist_com.android.launcher3"],
|
||||
|
||||
additional_manifests: [
|
||||
"go/AndroidManifest.xml",
|
||||
"go/AndroidManifest-launcher.xml",
|
||||
"AndroidManifest-common.xml",
|
||||
],
|
||||
|
||||
manifest: "quickstep/AndroidManifest.xml",
|
||||
jacoco: {
|
||||
include_filter: ["com.android.launcher3.*"],
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -40,10 +40,11 @@
|
||||
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
|
||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
<!-- for rotating surface by arbitrary degree -->
|
||||
<uses-permission android:name="android.permission.ROTATE_SURFACE_FLINGER" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
|
||||
<!--
|
||||
Permissions required for read/write access to the workspace data. These permission name
|
||||
should not conflict with that defined in other apps, as such an app should embed its package
|
||||
|
||||
@@ -4,42 +4,37 @@
|
||||
# People who can approve changes for submission
|
||||
#
|
||||
|
||||
alexchau@google.com
|
||||
andraskloczl@google.com
|
||||
patmanning@google.com
|
||||
petrcermak@google.com
|
||||
pbdr@google.com
|
||||
kideckel@google.com
|
||||
stevenckng@google.com
|
||||
ydixit@google.com
|
||||
boadway@google.com
|
||||
alinazaidi@google.com
|
||||
adamcohen@google.com
|
||||
hyunyoungs@google.com
|
||||
mrcasey@google.com
|
||||
sunnygoyal@google.com
|
||||
alexchau@google.com
|
||||
andonian@google.com
|
||||
awickham@google.com
|
||||
twickham@google.com
|
||||
winsonc@google.com
|
||||
zakcohen@google.com
|
||||
santie@google.com
|
||||
vadimt@google.com
|
||||
mett@google.com
|
||||
jonmiranda@google.com
|
||||
pinyaoting@google.com
|
||||
sfufa@google.com
|
||||
gwasserman@google.com
|
||||
jamesoleary@google.com
|
||||
joshtrask@google.com
|
||||
mrenouf@google.com
|
||||
mkephart@google.com
|
||||
hwwang@google.com
|
||||
tracyzhou@google.com
|
||||
peanutbutter@google.com
|
||||
xuqiu@google.com
|
||||
sreyasr@google.com
|
||||
thiruram@google.com
|
||||
brdayauon@google.com
|
||||
brianji@google.com
|
||||
captaincole@google.com
|
||||
charlander@google.com
|
||||
fbaron@google.com
|
||||
ganjam@google.com
|
||||
hwwang@google.com
|
||||
hyunyoungs@google.com
|
||||
jagrutdesai@google.com
|
||||
jeremysim@google.com
|
||||
jiuyu@google.com
|
||||
jonmiranda@google.com
|
||||
kylim@google.com
|
||||
patmanning@google.com
|
||||
peanutbutter@google.com
|
||||
pinyaoting@google.com
|
||||
randypfohl@google.com
|
||||
saumyaprakash@google.com
|
||||
sihua@google.com
|
||||
sunnygoyal@google.com
|
||||
tracyzhou@google.com
|
||||
tsuharesu@google.com
|
||||
twickham@google.com
|
||||
vadimt@google.com
|
||||
victortulias@google.com
|
||||
winsonc@google.com
|
||||
xuqiu@google.com
|
||||
|
||||
per-file FeatureFlags.java, globs = set noparent
|
||||
per-file FeatureFlags.java = sunnygoyal@google.com, winsonc@google.com, zakcohen@google.com, mrcasey@google.com, adamcohen@google.com, hyunyoungs@google.com
|
||||
per-file FeatureFlags.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com, captaincole@google.com
|
||||
@@ -1,2 +1,4 @@
|
||||
[Hook Scripts]
|
||||
checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --config_xml tools/checkstyle.xml --sha ${PREUPLOAD_COMMIT}
|
||||
|
||||
ktfmt_hook = ${REPO_ROOT}/external/ktfmt/ktfmt.py --check ${PREUPLOAD_FILES}
|
||||
@@ -1,7 +1,8 @@
|
||||
# Launcher3
|
||||
|
||||
## 更新:20220821
|
||||
* Android-13 (Launcher-master-20220821(b62600b8c3de47a26386aa59f06959b2cf1af753)版本源码)
|
||||
## 更新:20231209
|
||||
* Android-13 (Launcher3-13.0.0_r83(7e9e70085176ea46bf773019a5054942d2fc3811)版本源码)
|
||||
* https://android.googlesource.com/platform/packages/apps/Launcher3/+/refs/tags/android-13.0.0_r83
|
||||
|
||||
## 说明
|
||||
目前只支持withoutQuickstep编译
|
||||
@@ -15,8 +16,9 @@
|
||||
* Launcher3-9.0
|
||||
* Launcher3-10.0 (android-10.0.0_r46)
|
||||
* Launcher3-11
|
||||
* Launcher3-12
|
||||
* master (Launcher-master-20220821(b62600b8c3de47a26386aa59f06959b2cf1af753))--Android-13
|
||||
* Launcher3-12(可以编译Quickstep版本)
|
||||
* Launcher3-13-Qc(高通版本,可以编译Quickstep版本)
|
||||
* master (Launcher3-13.0.0_r83(7e9e70085176ea46bf773019a5054942d2fc3811))--Android-13
|
||||
|
||||
## 源码参考
|
||||
https://cs.android.com/android/platform/superproject/+/master:packages/apps/Launcher3/
|
||||
|
||||
+47
-27
@@ -10,15 +10,16 @@ buildscript {
|
||||
dependencies {
|
||||
classpath GRADLE_CLASS_PATH
|
||||
classpath PROTOBUF_CLASS_PATH
|
||||
// classpath KOTLIN
|
||||
classpath KOTLIN
|
||||
}
|
||||
}
|
||||
|
||||
final String ANDROID_TOP = "${rootDir}"
|
||||
final String ANDROID_TOP = "${rootDir}/"
|
||||
final String FRAMEWORK_PREBUILTS_DIR = "${ANDROID_TOP}/prebuilts/framework_intermediates/"
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'com.google.protobuf'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
def protocVersion = '3.8.0'
|
||||
|
||||
@@ -39,7 +40,10 @@ android {
|
||||
minifyEnabled false
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
checkReleaseBuilds false
|
||||
abortOnError false
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
@@ -83,7 +87,7 @@ android {
|
||||
sourceSets {
|
||||
main {
|
||||
res.srcDirs = ['res']
|
||||
java.srcDirs = ['src', 'src_plugins']
|
||||
java.srcDirs = ['src', 'src_plugins', 'tests/shared']
|
||||
manifest.srcFile 'AndroidManifest-common.xml'
|
||||
proto {
|
||||
srcDirs = ['protos/', 'protos_overrides/']
|
||||
@@ -140,52 +144,68 @@ android {
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
//maven { url "../../../prebuilts/sdk/current/androidx/m2repository" }
|
||||
//maven { url "../../../prebuilts/fullsdk-darwin/extras/android/m2repository" }
|
||||
//maven { url "../../../prebuilts/fullsdk-linux/extras/android/m2repository" }
|
||||
mavenCentral()
|
||||
google()
|
||||
mavenLocal()
|
||||
gradlePluginPortal()
|
||||
maven { url 'https://maven.aliyun.com/repository/jcenter' }
|
||||
maven { url 'https://maven.aliyun.com/repository/google' }
|
||||
maven { url 'https://maven.aliyun.com/repository/central' }
|
||||
maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
|
||||
}
|
||||
// gradle.projectsEvaluated {
|
||||
// tasks.withType(JavaCompile) {
|
||||
// options.compilerArgs.add("-Xbootclasspath/p:${FRAMEWORK_PREBUILTS_DIR}/libs/framework.jar")
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.appcompat:appcompat:1.4.1'
|
||||
implementation "androidx.dynamicanimation:dynamicanimation:${ANDROID_X_VERSION}"
|
||||
implementation "androidx.recyclerview:recyclerview:${ANDROID_X_VERSION}"
|
||||
implementation "androidx.preference:preference:${ANDROID_X_VERSION}"
|
||||
implementation "androidx.dynamicanimation:dynamicanimation:1.0.0"
|
||||
implementation "androidx.recyclerview:recyclerview:1.3.1"
|
||||
implementation "androidx.preference:preference:1.2.1"
|
||||
implementation project(':IconLoader')
|
||||
implementation 'androidx.slice:slice-core:1.0.0'
|
||||
|
||||
implementation 'com.google.protobuf:protobuf-javalite:3.8.0'
|
||||
// withQuickstepImplementation project(':SharedLibWrapper')
|
||||
|
||||
// implementation project(':UiTestsLibLauncher')
|
||||
//withQuickstepImplementation project(':SharedLibWrapper')
|
||||
implementation "androidx.slice:slice-builders:1.0.0"
|
||||
implementation "com.google.protobuf:protobuf-javalite:3.8.0"
|
||||
// Recents lib dependency
|
||||
withQuickstepImplementation fileTree(dir: "${FRAMEWORK_PREBUILTS_DIR}/libs", include: 'sysui_shared.jar')
|
||||
|
||||
// Required for AOSP to compile. This is already included in the sysui_shared.jar
|
||||
withoutQuickstepImplementation fileTree(dir: "${FRAMEWORK_PREBUILTS_DIR}/libs", include: 'plugin_core.jar')
|
||||
// compileOnly files("${FRAMEWORK_PREBUILTS_DIR}/libs/framework.jar")
|
||||
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'org.mockito:mockito-core:4.7.0'
|
||||
androidTestImplementation 'com.google.dexmaker:dexmaker:1.2'
|
||||
androidTestImplementation 'com.google.dexmaker:dexmaker-mockito:1.2'
|
||||
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
||||
androidTestImplementation 'com.android.support.test:rules:1.0.2'
|
||||
androidTestImplementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.3'
|
||||
androidTestImplementation "androidx.annotation:annotation:${ANDROID_X_VERSION}"
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation "com.google.android.material:material:1.5.0"
|
||||
|
||||
api 'com.airbnb.android:lottie:5.2.0'
|
||||
|
||||
implementation 'com.google.android.material:material:1.4.0'
|
||||
implementation 'androidx.slice:slice-builders:1.0.0'
|
||||
//testImplementation 'junit:junit:4.12'
|
||||
//androidTestImplementation "org.mockito:mockito-core:1.9.5"
|
||||
//androidTestImplementation 'com.google.dexmaker:dexmaker:1.2'
|
||||
//androidTestImplementation 'com.google.dexmaker:dexmaker-mockito:1.2'
|
||||
//androidTestImplementation 'com.android.support.test:runner:1.0.0'
|
||||
//androidTestImplementation 'com.android.support.test:rules:1.0.0'
|
||||
//androidTestImplementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
|
||||
//androidTestImplementation "androidx.annotation:annotation:${ANDROID_X_VERSION}"
|
||||
//implementation 'com.google.dagger:dagger:2.11-rc2'
|
||||
//annotationProcessor 'com.google.dagger:dagger-compiler:2.11-rc2'
|
||||
//implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
api 'com.airbnb.android:lottie:3.3.0'
|
||||
}
|
||||
|
||||
protobuf {
|
||||
// Configure the protoc executable
|
||||
protoc {
|
||||
// artifact = "com.google.protobuf:protoc:${protocVersion}${PROTO_ARCH_SUFFIX}"
|
||||
artifact = "com.google.protobuf:protoc:${protocVersion}"
|
||||
if (osdetector.os == "osx") {
|
||||
artifact = "com.google.protobuf:protoc:${protocVersion}:osx-x86_64"
|
||||
} else {
|
||||
// artifact = "com.google.protobuf:protoc:${protocVersion}${PROTO_ARCH_SUFFIX}"
|
||||
artifact = "com.google.protobuf:protoc:${protocVersion}"
|
||||
}
|
||||
}
|
||||
generateProtoTasks {
|
||||
all().each { task ->
|
||||
|
||||
@@ -16,14 +16,18 @@
|
||||
|
||||
package com.android.launcher3.testing;
|
||||
|
||||
import static com.android.launcher3.testing.shared.TestProtocol.VIEW_AND_ACTIVITY_LEAKS;
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.os.Process;
|
||||
import android.system.Os;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
@@ -33,6 +37,7 @@ import com.android.launcher3.BubbleTextView;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.ShortcutAndWidgetContainer;
|
||||
import com.android.launcher3.testing.shared.TestProtocol;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -155,11 +160,17 @@ public class DebugTestInformationHandler extends TestInformationHandler {
|
||||
|
||||
case TestProtocol.REQUEST_VIEW_LEAK: {
|
||||
if (sLeaks == null) sLeaks = new LinkedList();
|
||||
Log.d(VIEW_AND_ACTIVITY_LEAKS, "forcefully leaking 2 views");
|
||||
sLeaks.add(new View(mContext));
|
||||
sLeaks.add(new View(mContext));
|
||||
return response;
|
||||
}
|
||||
|
||||
case TestProtocol.PRINT_VIEW_LEAK: {
|
||||
Log.d(VIEW_AND_ACTIVITY_LEAKS, "(pid=" + Process.myPid() + ") sLeaks=" + sLeaks);
|
||||
return response;
|
||||
}
|
||||
|
||||
case TestProtocol.REQUEST_START_EVENT_LOGGING: {
|
||||
sEvents = new ArrayList<>();
|
||||
TestLogging.setEventConsumer(
|
||||
@@ -208,12 +219,25 @@ public class DebugTestInformationHandler extends TestInformationHandler {
|
||||
}
|
||||
|
||||
case TestProtocol.REQUEST_USE_TEST_WORKSPACE_LAYOUT: {
|
||||
useTestWorkspaceLayout(true);
|
||||
useTestWorkspaceLayout(
|
||||
LauncherSettings.Settings.ARG_DEFAULT_WORKSPACE_LAYOUT_TEST);
|
||||
return response;
|
||||
}
|
||||
|
||||
case TestProtocol.REQUEST_USE_TEST2_WORKSPACE_LAYOUT: {
|
||||
useTestWorkspaceLayout(
|
||||
LauncherSettings.Settings.ARG_DEFAULT_WORKSPACE_LAYOUT_TEST2);
|
||||
return response;
|
||||
}
|
||||
|
||||
case TestProtocol.REQUEST_USE_TAPL_WORKSPACE_LAYOUT: {
|
||||
useTestWorkspaceLayout(
|
||||
LauncherSettings.Settings.ARG_DEFAULT_WORKSPACE_LAYOUT_TAPL);
|
||||
return response;
|
||||
}
|
||||
|
||||
case TestProtocol.REQUEST_USE_DEFAULT_WORKSPACE_LAYOUT: {
|
||||
useTestWorkspaceLayout(false);
|
||||
useTestWorkspaceLayout(null);
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -247,17 +271,25 @@ public class DebugTestInformationHandler extends TestInformationHandler {
|
||||
return response;
|
||||
}
|
||||
|
||||
case TestProtocol.REQUEST_MODEL_QUEUE_CLEARED:
|
||||
return getFromExecutorSync(MODEL_EXECUTOR, Bundle::new);
|
||||
|
||||
default:
|
||||
return super.call(method, arg, extras);
|
||||
}
|
||||
}
|
||||
|
||||
private void useTestWorkspaceLayout(boolean useTestWorkspaceLayout) {
|
||||
private void useTestWorkspaceLayout(String layout) {
|
||||
final long identity = Binder.clearCallingIdentity();
|
||||
try {
|
||||
LauncherSettings.Settings.call(mContext.getContentResolver(), useTestWorkspaceLayout
|
||||
? LauncherSettings.Settings.METHOD_SET_USE_TEST_WORKSPACE_LAYOUT_FLAG
|
||||
: LauncherSettings.Settings.METHOD_CLEAR_USE_TEST_WORKSPACE_LAYOUT_FLAG);
|
||||
if (layout != null) {
|
||||
LauncherSettings.Settings.call(mContext.getContentResolver(),
|
||||
LauncherSettings.Settings.METHOD_SET_USE_TEST_WORKSPACE_LAYOUT_FLAG,
|
||||
layout);
|
||||
} else {
|
||||
LauncherSettings.Settings.call(mContext.getContentResolver(),
|
||||
LauncherSettings.Settings.METHOD_CLEAR_USE_TEST_WORKSPACE_LAYOUT_FLAG);
|
||||
}
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(identity);
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
style="@style/GoOverviewActionButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawableStart="@drawable/ic_split_screen"
|
||||
android:drawableStart="@drawable/ic_split_vertical"
|
||||
android:text="@string/action_split"
|
||||
android:theme="@style/ThemeControlHighlightWorkspaceColor"
|
||||
android:visibility="gone" />
|
||||
|
||||
@@ -9,12 +9,12 @@
|
||||
<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_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>
|
||||
@@ -1,19 +1,19 @@
|
||||
<?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">"Share app"</string>
|
||||
<string name="app_share_drop_target_label" msgid="5804774105974539508">"Share App"</string>
|
||||
<string name="action_listen" msgid="2370304050784689486">"Listen"</string>
|
||||
<string name="action_translate" msgid="8028378961867277746">"Translate"</string>
|
||||
<string name="action_search" msgid="6269564710943755464">"Lens"</string>
|
||||
<string name="dialog_acknowledge" msgid="2804025517675853172">"OK"</string>
|
||||
<string name="dialog_acknowledge" msgid="2804025517675853172">"GOT IT"</string>
|
||||
<string name="dialog_cancel" msgid="6464336969134856366">"CANCEL"</string>
|
||||
<string name="dialog_settings" msgid="6564397136021186148">"SETTINGS"</string>
|
||||
<string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Translate or listen to text on screen"</string>
|
||||
<string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Information such as text on your screen, web addresses and screenshots may be shared with Google.\n\nTo change what information you share, go to "<b>"Settings > Apps > Default apps > Digital assistant app"</b>"."</string>
|
||||
<string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Information such as text on your screen, web addresses, and screenshots may be shared with Google.\n\nTo change what information you share, go to "<b>"Settings > Apps > Default apps > Digital assistant app"</b>"."</string>
|
||||
<string name="assistant_not_selected_title" msgid="5017072974603345228">"Choose an assistant to use this feature"</string>
|
||||
<string name="assistant_not_selected_text" msgid="3244613673884359276">"To listen to or translate text on your screen, choose a digital assistant app in settings"</string>
|
||||
<string name="assistant_not_selected_text" msgid="3244613673884359276">"To listen to or translate text on your screen, choose a digital assistant app in Settings"</string>
|
||||
<string name="assistant_not_supported_title" msgid="1675788067597484142">"Change your assistant to use this feature"</string>
|
||||
<string name="assistant_not_supported_text" msgid="1708031078549268884">"To listen to or translate text on your screen, change your digital assistant app in settings"</string>
|
||||
<string name="assistant_not_supported_text" msgid="1708031078549268884">"To listen to or translate text on your screen, change your digital assistant app in Settings"</string>
|
||||
<string name="tooltip_listen" msgid="7634466447860989102">"Tap here to listen to text on this screen"</string>
|
||||
<string name="tooltip_translate" msgid="4184845868901542567">"Tap here to translate text on this screen"</string>
|
||||
<string name="toast_p2p_app_not_shareable" msgid="7229739094132131536">"This app can’t be shared"</string>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<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_supported_title" msgid="1675788067597484142">"Бул функцияны колдонуу үчүн жардамчыңызды өзгөртүңүз"</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">"Lens"</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>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?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">"ଆପ୍ ସେୟାର୍ କରନ୍ତୁ"</string>
|
||||
<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">"Lens"</string>
|
||||
<string name="dialog_acknowledge" msgid="2804025517675853172">"ବୁଝିଗଲି"</string>
|
||||
<string name="dialog_cancel" msgid="6464336969134856366">"ବାତିଲ୍ କରନ୍ତୁ"</string>
|
||||
<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>"ସେଟିଂସ > ଆପ୍ସ > ଡିଫଲ୍ଟ ଆପ୍ସ > Digital assistant ଆପ"</b>"କୁ ଯାଆନ୍ତୁ।"</string>
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<?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">"Trimiteți aplicația"</string>
|
||||
<string name="action_listen" msgid="2370304050784689486">"Ascultați"</string>
|
||||
<string name="action_translate" msgid="8028378961867277746">"Traduceți"</string>
|
||||
<string name="app_share_drop_target_label" msgid="5804774105974539508">"Trimite aplicația"</string>
|
||||
<string name="action_listen" msgid="2370304050784689486">"Ascultă"</string>
|
||||
<string name="action_translate" msgid="8028378961867277746">"Tradu"</string>
|
||||
<string name="action_search" msgid="6269564710943755464">"Lens"</string>
|
||||
<string name="dialog_acknowledge" msgid="2804025517675853172">"OK"</string>
|
||||
<string name="dialog_cancel" msgid="6464336969134856366">"ANULAȚI"</string>
|
||||
<string name="dialog_cancel" msgid="6464336969134856366">"ANULEAZĂ"</string>
|
||||
<string name="dialog_settings" msgid="6564397136021186148">"SETĂRI"</string>
|
||||
<string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Traduceți sau ascultați textul de pe ecran"</string>
|
||||
<string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Informații precum textul de pe ecran, adresele web și capturile de ecran pot fi trimise la Google.\n\nCa să schimbați informațiile trimise, accesați "<b>"Setări > Aplicații > Aplicații prestabilite > Aplicația asistent digital"</b>"."</string>
|
||||
<string name="assistant_not_selected_title" msgid="5017072974603345228">"Alegeți un asistent pentru a folosi această funcție"</string>
|
||||
<string name="assistant_not_selected_text" msgid="3244613673884359276">"Pentru a asculta sau a traduce text de pe ecran, alegeți o aplicație asistent digital în Setări"</string>
|
||||
<string name="assistant_not_supported_title" msgid="1675788067597484142">"Schimbați asistentul pentru a folosi această funcție"</string>
|
||||
<string name="assistant_not_supported_text" msgid="1708031078549268884">"Pentru a asculta sau a traduce text de pe ecran, schimbați aplicația asistent digital în Setări"</string>
|
||||
<string name="tooltip_listen" msgid="7634466447860989102">"Atingeți aici pentru a asculta text de pe ecran"</string>
|
||||
<string name="tooltip_translate" msgid="4184845868901542567">"Atingeți aici pentru a traduce text de pe ecran"</string>
|
||||
<string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Tradu sau ascultă textul de pe ecran"</string>
|
||||
<string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Informații precum textul de pe ecran, adresele web și capturile de ecran pot fi trimise la Google.\n\nCa să schimbi informațiile trimise, accesează "<b>"Setări > Aplicații > Aplicații prestabilite > Aplicația asistent digital"</b>"."</string>
|
||||
<string name="assistant_not_selected_title" msgid="5017072974603345228">"Alege un asistent pentru a folosi această funcție"</string>
|
||||
<string name="assistant_not_selected_text" msgid="3244613673884359276">"Pentru a asculta sau a traduce text de pe ecran, alege o aplicație asistent digital în Setări"</string>
|
||||
<string name="assistant_not_supported_title" msgid="1675788067597484142">"Schimbă asistentul pentru a folosi această funcție"</string>
|
||||
<string name="assistant_not_supported_text" msgid="1708031078549268884">"Pentru a asculta sau a traduce text de pe ecran, schimbă aplicația asistent digital în Setări"</string>
|
||||
<string name="tooltip_listen" msgid="7634466447860989102">"Atinge aici pentru a asculta text de pe ecran"</string>
|
||||
<string name="tooltip_translate" msgid="4184845868901542567">"Atinge aici pentru a traduce text de pe ecran"</string>
|
||||
<string name="toast_p2p_app_not_shareable" msgid="7229739094132131536">"Aplicația nu poate fi distribuită"</string>
|
||||
</resources>
|
||||
@@ -50,8 +50,8 @@ import androidx.annotation.IntDef;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.launcher3.BaseActivity;
|
||||
import com.android.launcher3.LauncherPrefs;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.views.ArrowTipView;
|
||||
import com.android.quickstep.util.AssistContentRequester;
|
||||
import com.android.quickstep.util.RecentsOrientedState;
|
||||
@@ -124,7 +124,7 @@ public final class TaskOverlayFactoryGo extends TaskOverlayFactory {
|
||||
AssistContentRequester assistContentRequester) {
|
||||
super(taskThumbnailView);
|
||||
mFactoryContentRequester = assistContentRequester;
|
||||
mSharedPreferences = Utilities.getPrefs(mApplicationContext);
|
||||
mSharedPreferences = LauncherPrefs.getPrefs(mApplicationContext);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -151,7 +151,7 @@ public final class TaskOverlayFactoryGo extends TaskOverlayFactory {
|
||||
boolean isAllowedByPolicy = mThumbnailView.isRealSnapshot() && !isManagedProfileTask;
|
||||
getActionsView().setCallbacks(new OverlayUICallbacksGoImpl(isAllowedByPolicy, task));
|
||||
mTaskPackageName = task.key.getPackageName();
|
||||
mSharedPreferences = Utilities.getPrefs(mApplicationContext);
|
||||
mSharedPreferences = LauncherPrefs.getPrefs(mApplicationContext);
|
||||
checkSettings();
|
||||
|
||||
if (!mAssistStructurePermitted || !mAssistScreenshotPermitted
|
||||
|
||||
+3
-3
@@ -22,11 +22,11 @@ import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.model.BgDataModel.Callbacks;
|
||||
|
||||
/**
|
||||
* Helper class to handle results of {@link com.android.launcher3.model.LoaderTask}.
|
||||
* Binds the results of {@link com.android.launcher3.model.LoaderTask} to the Callbacks objects.
|
||||
*/
|
||||
public class LoaderResults extends BaseLoaderResults {
|
||||
public class LauncherBinder extends BaseLauncherBinder {
|
||||
|
||||
public LoaderResults(LauncherAppState app, BgDataModel dataModel,
|
||||
public LauncherBinder(LauncherAppState app, BgDataModel dataModel,
|
||||
AllAppsList allAppsList, Callbacks[] callbacks) {
|
||||
super(app, dataModel, allAppsList, callbacks, MAIN_EXECUTOR);
|
||||
}
|
||||
+52
-48
@@ -1,48 +1,52 @@
|
||||
// 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 {
|
||||
default_applicable_licenses: ["Android-Apache-2.0"],
|
||||
}
|
||||
|
||||
android_library {
|
||||
name: "iconloader_base",
|
||||
sdk_version: "current",
|
||||
min_sdk_version: "26",
|
||||
static_libs: [
|
||||
"androidx.core_core",
|
||||
],
|
||||
resource_dirs: [
|
||||
"res",
|
||||
],
|
||||
srcs: [
|
||||
"src/**/*.java",
|
||||
],
|
||||
}
|
||||
|
||||
android_library {
|
||||
name: "iconloader",
|
||||
sdk_version: "system_current",
|
||||
min_sdk_version: "26",
|
||||
static_libs: [
|
||||
"androidx.core_core",
|
||||
],
|
||||
resource_dirs: [
|
||||
"res",
|
||||
],
|
||||
srcs: [
|
||||
"src/**/*.java",
|
||||
"src_full_lib/**/*.java",
|
||||
],
|
||||
}
|
||||
// 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 {
|
||||
default_applicable_licenses: ["Android-Apache-2.0"],
|
||||
}
|
||||
|
||||
android_library {
|
||||
name: "iconloader_base",
|
||||
sdk_version: "current",
|
||||
min_sdk_version: "26",
|
||||
static_libs: [
|
||||
"androidx.core_core",
|
||||
],
|
||||
resource_dirs: [
|
||||
"res",
|
||||
],
|
||||
srcs: [
|
||||
"src/**/*.java",
|
||||
],
|
||||
}
|
||||
|
||||
android_library {
|
||||
name: "iconloader",
|
||||
sdk_version: "system_current",
|
||||
min_sdk_version: "26",
|
||||
static_libs: [
|
||||
"androidx.core_core",
|
||||
],
|
||||
resource_dirs: [
|
||||
"res",
|
||||
],
|
||||
srcs: [
|
||||
"src/**/*.java",
|
||||
"src_full_lib/**/*.java",
|
||||
],
|
||||
apex_available: [
|
||||
"//apex_available:platform",
|
||||
"com.android.permission",
|
||||
],
|
||||
}
|
||||
@@ -1,20 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.launcher3.icons">
|
||||
</manifest>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.launcher3.icons">
|
||||
</manifest>
|
||||
+38
-35
@@ -1,35 +1,38 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion COMPILE_SDK
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 26
|
||||
targetSdkVersion 33
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java.srcDirs = ['src', 'src_full_lib']
|
||||
manifest.srcFile 'AndroidManifest.xml'
|
||||
res.srcDirs = ['res']
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "androidx.core:core:${ANDROID_X_VERSION}"
|
||||
}
|
||||
plugins {
|
||||
id 'com.android.library'
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.android.launcher3.icons"
|
||||
|
||||
compileSdkVersion COMPILE_SDK
|
||||
defaultConfig {
|
||||
minSdkVersion 26
|
||||
targetSdkVersion 33
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
minifyEnabled false
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java.srcDirs = ['src', 'src_full_lib']
|
||||
manifest.srcFile 'AndroidManifest.xml'
|
||||
res.srcDirs = ['res']
|
||||
}
|
||||
}
|
||||
lint {
|
||||
abortOnError false
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "androidx.core:core:1.9.0"
|
||||
}
|
||||
@@ -1,22 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/legacy_icon_background"/>
|
||||
<foreground>
|
||||
<com.android.launcher3.icons.FixedScaleDrawable />
|
||||
</foreground>
|
||||
</adaptive-icon>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/legacy_icon_background"/>
|
||||
<foreground>
|
||||
<com.android.launcher3.icons.FixedScaleDrawable />
|
||||
</foreground>
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2023 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.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="@dimen/profile_badge_size"
|
||||
android:height="@dimen/profile_badge_size"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="#11000000"
|
||||
android:pathData="M.5,12.25
|
||||
A11.5,11.5 0 1,1 23.5,12.25
|
||||
A11.5,11.5 0 1,1 .5,12.25" />
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M1,12
|
||||
A11,11 0 1,1 23,12
|
||||
A11,11 0 1,1 1,12" />
|
||||
|
||||
<group android:scaleX=".6" android:scaleY=".6" android:pivotX="12" android:pivotY="12">
|
||||
<path
|
||||
android:pathData="M22,9.5C22,13.642 18.642,17 14.5,17C10.358,17 7,13.642 7,9.5C7,5.358 10.358,2 14.5,2C18.642,2 22,5.358 22,9.5Z"
|
||||
android:fillColor="#ff3C4043"/>
|
||||
<path
|
||||
android:pathData="M9.5,20.333C12.722,20.333 15.333,17.722 15.333,14.5C15.333,11.278 12.722,8.667 9.5,8.667C6.278,8.667 3.667,11.278 3.667,14.5C3.667,17.722 6.278,20.333 9.5,20.333ZM9.5,22C13.642,22 17,18.642 17,14.5C17,10.358 13.642,7 9.5,7C5.358,7 2,10.358 2,14.5C2,18.642 5.358,22 9.5,22Z"
|
||||
android:fillColor="#ff3C4043"
|
||||
android:fillType="evenOdd"/>
|
||||
</group>
|
||||
</vector>
|
||||
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2023 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.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="@dimen/profile_badge_size"
|
||||
android:height="@dimen/profile_badge_size"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="#11000000"
|
||||
android:pathData="M.5,12.25
|
||||
A11.5,11.5 0 1,1 23.5,12.25
|
||||
A11.5,11.5 0 1,1 .5,12.25" />
|
||||
|
||||
<path
|
||||
android:fillColor="@color/themed_icon_background_color"
|
||||
android:pathData="M1,12
|
||||
A11,11 0 1,1 23,12
|
||||
A11,11 0 1,1 1,12" />
|
||||
|
||||
<group android:scaleX=".6" android:scaleY=".6" android:pivotX="12" android:pivotY="12">
|
||||
<path
|
||||
android:pathData="M22,9.5C22,13.642 18.642,17 14.5,17C10.358,17 7,13.642 7,9.5C7,5.358 10.358,2 14.5,2C18.642,2 22,5.358 22,9.5Z"
|
||||
android:fillColor="@color/themed_badge_icon_color"/>
|
||||
<path
|
||||
android:pathData="M9.5,20.333C12.722,20.333 15.333,17.722 15.333,14.5C15.333,11.278 12.722,8.667 9.5,8.667C6.278,8.667 3.667,11.278 3.667,14.5C3.667,17.722 6.278,20.333 9.5,20.333ZM9.5,22C13.642,22 17,18.642 17,14.5C17,10.358 13.642,7 9.5,7C5.358,7 2,10.358 2,14.5C2,18.642 5.358,22 9.5,22Z"
|
||||
android:fillColor="@color/themed_badge_icon_color"
|
||||
android:fillType="evenOdd"/>
|
||||
</group>
|
||||
</vector>
|
||||
@@ -1,39 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="@dimen/profile_badge_size"
|
||||
android:height="@dimen/profile_badge_size"
|
||||
android:viewportWidth="18"
|
||||
android:viewportHeight="18">
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/black"
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M 9 0 C 13.9705627485 0 18 4.02943725152 18 9 C 18 13.9705627485 13.9705627485 18 9 18 C 4.02943725152 18 0 13.9705627485 0 9 C 0 4.02943725152 4.02943725152 0 9 0 Z" />
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M 9 0 C 13.9705627485 0 18 4.02943725152 18 9 C 18 13.9705627485 13.9705627485 18 9 18 C 4.02943725152 18 0 13.9705627485 0 9 C 0 4.02943725152 4.02943725152 0 9 0 Z" />
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M 9 0 C 13.9705627485 0 18 4.02943725152 18 9 C 18 13.9705627485 13.9705627485 18 9 18 C 4.02943725152 18 0 13.9705627485 0 9 C 0 4.02943725152 4.02943725152 0 9 0 Z" />
|
||||
<path
|
||||
android:fillColor="@android:color/black"
|
||||
android:fillAlpha="0.87"
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M 6 10.4123279 L 8.63934949 10.4123279 L 8.63934949 15.6 L 12.5577168 7.84517705 L 9.94547194 7.84517705 L 9.94547194 2 Z" />
|
||||
</vector>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="@dimen/profile_badge_size"
|
||||
android:height="@dimen/profile_badge_size"
|
||||
android:viewportWidth="18"
|
||||
android:viewportHeight="18">
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M 9 0 C 13.9705627485 0 18 4.02943725152 18 9 C 18 13.9705627485 13.9705627485 18 9 18 C 4.02943725152 18 0 13.9705627485 0 9 C 0 4.02943725152 4.02943725152 0 9 0 Z" />
|
||||
<path
|
||||
android:fillColor="@android:color/black"
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M 6 10.4123279 L 8.63934949 10.4123279 L 8.63934949 15.6 L 12.5577168 7.84517705 L 9.94547194 7.84517705 L 9.94547194 2 Z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="@dimen/profile_badge_size"
|
||||
android:height="@dimen/profile_badge_size"
|
||||
android:viewportWidth="18"
|
||||
android:viewportHeight="18">
|
||||
|
||||
<path
|
||||
android:fillColor="@color/themed_badge_icon_background_color"
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M 9 0 C 13.9705627485 0 18 4.02943725152 18 9 C 18 13.9705627485 13.9705627485 18 9 18 C 4.02943725152 18 0 13.9705627485 0 9 C 0 4.02943725152 4.02943725152 0 9 0 Z" />
|
||||
<path
|
||||
android:fillColor="@color/themed_badge_icon_color"
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M 6 10.4123279 L 8.63934949 10.4123279 L 8.63934949 15.6 L 12.5577168 7.84517705 L 9.94547194 7.84517705 L 9.94547194 2 Z" />
|
||||
</vector>
|
||||
@@ -1,39 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="@dimen/profile_badge_size"
|
||||
android:height="@dimen/profile_badge_size"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="#11000000"
|
||||
android:pathData="M.5,12.25
|
||||
A11.5,11.5 0 1,1 23.5,12.25
|
||||
A11.5,11.5 0 1,1 .5,12.25" />
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M1,12
|
||||
A11,11 0 1,1 23,12
|
||||
A11,11 0 1,1 1,12" />
|
||||
|
||||
<group android:scaleX=".6" android:scaleY=".6" android:pivotX="12" android:pivotY="12">
|
||||
<path
|
||||
android:fillColor="#1A73E8"
|
||||
android:pathData="M20,6h-4L16,4c0,-1.11 -0.89,-2 -2,-2h-4c-1.11,0 -2,0.89 -2,2v2L4,6c-1.11,0 -1.99,0.89 -1.99,2L2,19c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,8c0,-1.11 -0.89,-2 -2,-2zM14,6h-4L10,4h4v2z" />
|
||||
</group>
|
||||
</vector>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="@dimen/profile_badge_size"
|
||||
android:height="@dimen/profile_badge_size"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="#11000000"
|
||||
android:pathData="M.5,12.25
|
||||
A11.5,11.5 0 1,1 23.5,12.25
|
||||
A11.5,11.5 0 1,1 .5,12.25" />
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M1,12
|
||||
A11,11 0 1,1 23,12
|
||||
A11,11 0 1,1 1,12" />
|
||||
|
||||
<group android:scaleX=".6" android:scaleY=".6" android:pivotX="12" android:pivotY="12">
|
||||
<path
|
||||
android:fillColor="#1A73E8"
|
||||
android:pathData="M20,6h-4L16,4c0,-1.11 -0.89,-2 -2,-2h-4c-1.11,0 -2,0.89 -2,2v2L4,6c-1.11,0 -1.99,0.89 -1.99,2L2,19c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,8c0,-1.11 -0.89,-2 -2,-2zM14,6h-4L10,4h4v2z" />
|
||||
</group>
|
||||
</vector>
|
||||
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2023 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.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="@dimen/profile_badge_size"
|
||||
android:height="@dimen/profile_badge_size"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="#11000000"
|
||||
android:pathData="M.5,12.25
|
||||
A11.5,11.5 0 1,1 23.5,12.25
|
||||
A11.5,11.5 0 1,1 .5,12.25" />
|
||||
|
||||
<path
|
||||
android:fillColor="@color/themed_badge_icon_background_color"
|
||||
android:pathData="M1,12
|
||||
A11,11 0 1,1 23,12
|
||||
A11,11 0 1,1 1,12" />
|
||||
|
||||
<group android:scaleX=".6" android:scaleY=".6" android:pivotX="12" android:pivotY="12">
|
||||
<path
|
||||
android:fillColor="@color/themed_badge_icon_color"
|
||||
android:pathData="M20,6h-4L16,4c0,-1.11 -0.89,-2 -2,-2h-4c-1.11,0 -2,0.89 -2,2v2L4,6c-1.11,0 -1.99,0.89 -1.99,2L2,19c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,8c0,-1.11 -0.89,-2 -2,-2zM14,6h-4L10,4h4v2z" />
|
||||
</group>
|
||||
</vector>
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
**
|
||||
** Copyright 2023, 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.
|
||||
*/
|
||||
-->
|
||||
<resources>
|
||||
<color name="themed_icon_color">@android:color/system_accent1_200</color>
|
||||
<color name="themed_icon_background_color">@android:color/system_accent2_800</color>
|
||||
<color name="themed_badge_icon_color">@android:color/system_accent2_800</color>
|
||||
<color name="themed_badge_icon_background_color">@android:color/system_accent1_200</color>
|
||||
</resources>
|
||||
+7
-5
@@ -1,8 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/*
|
||||
**
|
||||
** Copyright 2021, The Android Open Source Project
|
||||
** Copyright 2023, 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.
|
||||
@@ -17,6 +16,9 @@
|
||||
** limitations under the License.
|
||||
*/
|
||||
-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@android:color/system_neutral1_900" android:alpha="0.8" />
|
||||
</selector>
|
||||
<resources>
|
||||
<color name="themed_icon_color">#A8C7FA</color>
|
||||
<color name="themed_icon_background_color">#003355</color>
|
||||
<color name="themed_badge_icon_color">#003355</color>
|
||||
<color name="themed_badge_icon_background_color">#A8C7FA</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
**
|
||||
** Copyright 2023, 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.
|
||||
*/
|
||||
-->
|
||||
<resources>
|
||||
<color name="themed_icon_color">@android:color/system_accent1_700</color>
|
||||
<color name="themed_icon_background_color">@android:color/system_accent1_100</color>
|
||||
<color name="themed_badge_icon_color">@android:color/system_accent1_700</color>
|
||||
<color name="themed_badge_icon_background_color">@android:color/system_accent1_100</color>
|
||||
</resources>
|
||||
@@ -1,23 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
**
|
||||
** Copyright 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.
|
||||
*/
|
||||
-->
|
||||
<resources>
|
||||
<attr name="disabledIconAlpha" format="float" />
|
||||
<attr name="loadingIconColor" format="color" />
|
||||
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
**
|
||||
** Copyright 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.
|
||||
*/
|
||||
-->
|
||||
<resources>
|
||||
<attr name="disabledIconAlpha" format="float" />
|
||||
<attr name="loadingIconColor" format="color" />
|
||||
|
||||
</resources>
|
||||
@@ -1,24 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
**
|
||||
** Copyright 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.
|
||||
*/
|
||||
-->
|
||||
<resources>
|
||||
<color name="legacy_icon_background">#FFFFFF</color>
|
||||
|
||||
<!-- Yellow 600, used for highlighting "important" conversations in settings & notifications -->
|
||||
<color name="important_conversation">#f9ab00</color>
|
||||
</resources>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
**
|
||||
** Copyright 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.
|
||||
*/
|
||||
-->
|
||||
<resources>
|
||||
<color name="themed_icon_color">#0842A0</color>
|
||||
<color name="themed_icon_background_color">#D3E3FD</color>
|
||||
<color name="themed_badge_icon_color">#0842A0</color>
|
||||
<color name="themed_badge_icon_background_color">#D3E3FD</color>
|
||||
<color name="legacy_icon_background">#FFFFFF</color>
|
||||
|
||||
<!-- Yellow 600, used for highlighting "important" conversations in settings & notifications -->
|
||||
<color name="important_conversation">#f9ab00</color>
|
||||
</resources>
|
||||
@@ -1,30 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
**
|
||||
** Copyright 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.
|
||||
*/
|
||||
-->
|
||||
<resources>
|
||||
|
||||
<!-- Various configurations to control the simple cache implementation -->
|
||||
|
||||
<dimen name="default_icon_bitmap_size">56dp</dimen>
|
||||
<bool name="simple_cache_enable_im_memory">false</bool>
|
||||
<string name="cache_db_name" translatable="false">app_icons.db</string>
|
||||
|
||||
<string name="calendar_component_name" translatable="false">com.google.android.calendar/com.android.calendar.AllInOneActivity</string>
|
||||
<string name="clock_component_name" translatable="false">com.android.deskclock/com.android.deskclock.DeskClock</string>
|
||||
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
**
|
||||
** Copyright 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.
|
||||
*/
|
||||
-->
|
||||
<resources>
|
||||
|
||||
<!-- Various configurations to control the simple cache implementation -->
|
||||
|
||||
<dimen name="default_icon_bitmap_size">56dp</dimen>
|
||||
<bool name="simple_cache_enable_im_memory">false</bool>
|
||||
<string name="cache_db_name" translatable="false">app_icons.db</string>
|
||||
|
||||
<string name="calendar_component_name" translatable="false"></string>
|
||||
<string name="clock_component_name" translatable="false"></string>
|
||||
|
||||
</resources>
|
||||
@@ -1,19 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<dimen name="profile_badge_size">24dp</dimen>
|
||||
</resources>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<dimen name="profile_badge_size">24dp</dimen>
|
||||
</resources>
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,186 +1,197 @@
|
||||
/*
|
||||
* 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.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.Config;
|
||||
import android.graphics.Canvas;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.util.FlagOp;
|
||||
|
||||
public class BitmapInfo {
|
||||
|
||||
static final int FLAG_WORK = 1 << 0;
|
||||
static final int FLAG_INSTANT = 1 << 1;
|
||||
@IntDef(flag = true, value = {
|
||||
FLAG_WORK,
|
||||
FLAG_INSTANT,
|
||||
})
|
||||
@interface BitmapInfoFlags {}
|
||||
|
||||
public static final int FLAG_THEMED = 1 << 0;
|
||||
public static final int FLAG_NO_BADGE = 1 << 1;
|
||||
@IntDef(flag = true, value = {
|
||||
FLAG_THEMED,
|
||||
FLAG_NO_BADGE,
|
||||
})
|
||||
public @interface DrawableCreationFlags {}
|
||||
|
||||
public static final Bitmap LOW_RES_ICON = Bitmap.createBitmap(1, 1, Config.ALPHA_8);
|
||||
public static final BitmapInfo LOW_RES_INFO = fromBitmap(LOW_RES_ICON);
|
||||
|
||||
public static final String TAG = "BitmapInfo";
|
||||
|
||||
public final Bitmap icon;
|
||||
public final int color;
|
||||
|
||||
@Nullable
|
||||
protected Bitmap mMono;
|
||||
protected Bitmap mWhiteShadowLayer;
|
||||
|
||||
public @BitmapInfoFlags int flags;
|
||||
private BitmapInfo badgeInfo;
|
||||
|
||||
public BitmapInfo(Bitmap icon, int color) {
|
||||
this.icon = icon;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public BitmapInfo withBadgeInfo(BitmapInfo badgeInfo) {
|
||||
BitmapInfo result = clone();
|
||||
result.badgeInfo = badgeInfo;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a bitmapInfo with the flagOP applied
|
||||
*/
|
||||
public BitmapInfo withFlags(@NonNull FlagOp op) {
|
||||
if (op == FlagOp.NO_OP) {
|
||||
return this;
|
||||
}
|
||||
BitmapInfo result = clone();
|
||||
result.flags = op.apply(result.flags);
|
||||
return result;
|
||||
}
|
||||
|
||||
protected BitmapInfo copyInternalsTo(BitmapInfo target) {
|
||||
target.mMono = mMono;
|
||||
target.mWhiteShadowLayer = mWhiteShadowLayer;
|
||||
target.flags = flags;
|
||||
target.badgeInfo = badgeInfo;
|
||||
return target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BitmapInfo clone() {
|
||||
return copyInternalsTo(new BitmapInfo(icon, color));
|
||||
}
|
||||
|
||||
public void setMonoIcon(Bitmap mono, BaseIconFactory iconFactory) {
|
||||
mMono = mono;
|
||||
mWhiteShadowLayer = iconFactory.getWhiteShadowLayer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ideally icon should not be null, except in cases when generating hardware bitmap failed
|
||||
*/
|
||||
public final boolean isNullOrLowRes() {
|
||||
return icon == null || icon == LOW_RES_ICON;
|
||||
}
|
||||
|
||||
public final boolean isLowRes() {
|
||||
return LOW_RES_ICON == icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* BitmapInfo can be stored on disk or other persistent storage
|
||||
*/
|
||||
public boolean canPersist() {
|
||||
return !isNullOrLowRes();
|
||||
}
|
||||
|
||||
public Bitmap getMono() {
|
||||
return mMono;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a drawable for the provided BitmapInfo
|
||||
*/
|
||||
public FastBitmapDrawable newIcon(Context context) {
|
||||
return newIcon(context, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a drawable for the provided BitmapInfo
|
||||
*/
|
||||
public FastBitmapDrawable newIcon(Context context, @DrawableCreationFlags int creationFlags) {
|
||||
FastBitmapDrawable drawable;
|
||||
if (isLowRes()) {
|
||||
drawable = new PlaceHolderIconDrawable(this, context);
|
||||
} else if ((creationFlags & FLAG_THEMED) != 0 && mMono != null) {
|
||||
drawable = ThemedIconDrawable.newDrawable(this, context);
|
||||
} else {
|
||||
drawable = new FastBitmapDrawable(this);
|
||||
}
|
||||
applyFlags(context, drawable, creationFlags);
|
||||
return drawable;
|
||||
}
|
||||
|
||||
protected void applyFlags(Context context, FastBitmapDrawable drawable,
|
||||
@DrawableCreationFlags int creationFlags) {
|
||||
drawable.mDisabledAlpha = GraphicsUtils.getFloat(context, R.attr.disabledIconAlpha, 1f);
|
||||
if ((creationFlags & FLAG_NO_BADGE) == 0) {
|
||||
if (badgeInfo != null) {
|
||||
drawable.setBadge(badgeInfo.newIcon(context, creationFlags));
|
||||
} else if ((flags & FLAG_INSTANT) != 0) {
|
||||
drawable.setBadge(context.getDrawable(R.drawable.ic_instant_app_badge));
|
||||
} else if ((flags & FLAG_WORK) != 0) {
|
||||
drawable.setBadge(context.getDrawable(R.drawable.ic_work_app_badge));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static BitmapInfo fromBitmap(@NonNull Bitmap bitmap) {
|
||||
return of(bitmap, 0);
|
||||
}
|
||||
|
||||
public static BitmapInfo of(@NonNull Bitmap bitmap, int color) {
|
||||
return new BitmapInfo(bitmap, color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface to be implemented by drawables to provide a custom BitmapInfo
|
||||
*/
|
||||
public interface Extender {
|
||||
|
||||
/**
|
||||
* Called for creating a custom BitmapInfo
|
||||
*/
|
||||
BitmapInfo getExtendedInfo(Bitmap bitmap, int color,
|
||||
BaseIconFactory iconFactory, float normalizationScale);
|
||||
|
||||
/**
|
||||
* Called to draw the UI independent of any runtime configurations like time or theme
|
||||
*/
|
||||
void drawForPersistence(Canvas canvas);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.Config;
|
||||
import android.graphics.Canvas;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.util.FlagOp;
|
||||
|
||||
public class BitmapInfo {
|
||||
|
||||
static final int FLAG_WORK = 1 << 0;
|
||||
static final int FLAG_INSTANT = 1 << 1;
|
||||
static final int FLAG_CLONE = 1 << 2;
|
||||
@IntDef(flag = true, value = {
|
||||
FLAG_WORK,
|
||||
FLAG_INSTANT,
|
||||
FLAG_CLONE
|
||||
})
|
||||
@interface BitmapInfoFlags {}
|
||||
|
||||
public static final int FLAG_THEMED = 1 << 0;
|
||||
public static final int FLAG_NO_BADGE = 1 << 1;
|
||||
@IntDef(flag = true, value = {
|
||||
FLAG_THEMED,
|
||||
FLAG_NO_BADGE,
|
||||
})
|
||||
public @interface DrawableCreationFlags {}
|
||||
|
||||
public static final Bitmap LOW_RES_ICON = Bitmap.createBitmap(1, 1, Config.ALPHA_8);
|
||||
public static final BitmapInfo LOW_RES_INFO = fromBitmap(LOW_RES_ICON);
|
||||
|
||||
public static final String TAG = "BitmapInfo";
|
||||
|
||||
public final Bitmap icon;
|
||||
public final int color;
|
||||
|
||||
@Nullable
|
||||
protected Bitmap mMono;
|
||||
protected Bitmap mWhiteShadowLayer;
|
||||
|
||||
public @BitmapInfoFlags int flags;
|
||||
private BitmapInfo badgeInfo;
|
||||
|
||||
public BitmapInfo(Bitmap icon, int color) {
|
||||
this.icon = icon;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public BitmapInfo withBadgeInfo(BitmapInfo badgeInfo) {
|
||||
BitmapInfo result = clone();
|
||||
result.badgeInfo = badgeInfo;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a bitmapInfo with the flagOP applied
|
||||
*/
|
||||
public BitmapInfo withFlags(@NonNull FlagOp op) {
|
||||
if (op == FlagOp.NO_OP) {
|
||||
return this;
|
||||
}
|
||||
BitmapInfo result = clone();
|
||||
result.flags = op.apply(result.flags);
|
||||
return result;
|
||||
}
|
||||
|
||||
protected BitmapInfo copyInternalsTo(BitmapInfo target) {
|
||||
target.mMono = mMono;
|
||||
target.mWhiteShadowLayer = mWhiteShadowLayer;
|
||||
target.flags = flags;
|
||||
target.badgeInfo = badgeInfo;
|
||||
return target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BitmapInfo clone() {
|
||||
return copyInternalsTo(new BitmapInfo(icon, color));
|
||||
}
|
||||
|
||||
public void setMonoIcon(Bitmap mono, BaseIconFactory iconFactory) {
|
||||
mMono = mono;
|
||||
mWhiteShadowLayer = iconFactory.getWhiteShadowLayer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ideally icon should not be null, except in cases when generating hardware bitmap failed
|
||||
*/
|
||||
public final boolean isNullOrLowRes() {
|
||||
return icon == null || icon == LOW_RES_ICON;
|
||||
}
|
||||
|
||||
public final boolean isLowRes() {
|
||||
return LOW_RES_ICON == icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* BitmapInfo can be stored on disk or other persistent storage
|
||||
*/
|
||||
public boolean canPersist() {
|
||||
return !isNullOrLowRes();
|
||||
}
|
||||
|
||||
public Bitmap getMono() {
|
||||
return mMono;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a drawable for the provided BitmapInfo
|
||||
*/
|
||||
public FastBitmapDrawable newIcon(Context context) {
|
||||
return newIcon(context, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a drawable for the provided BitmapInfo
|
||||
*/
|
||||
public FastBitmapDrawable newIcon(Context context, @DrawableCreationFlags int creationFlags) {
|
||||
FastBitmapDrawable drawable;
|
||||
if (isLowRes()) {
|
||||
drawable = new PlaceHolderIconDrawable(this, context);
|
||||
} else if ((creationFlags & FLAG_THEMED) != 0 && mMono != null) {
|
||||
drawable = ThemedIconDrawable.newDrawable(this, context);
|
||||
} else {
|
||||
drawable = new FastBitmapDrawable(this);
|
||||
}
|
||||
applyFlags(context, drawable, creationFlags);
|
||||
return drawable;
|
||||
}
|
||||
|
||||
protected void applyFlags(Context context, FastBitmapDrawable drawable,
|
||||
@DrawableCreationFlags int creationFlags) {
|
||||
drawable.mDisabledAlpha = GraphicsUtils.getFloat(context, R.attr.disabledIconAlpha, 1f);
|
||||
if ((creationFlags & FLAG_NO_BADGE) == 0) {
|
||||
if (badgeInfo != null) {
|
||||
drawable.setBadge(badgeInfo.newIcon(context, creationFlags));
|
||||
} else if ((flags & FLAG_INSTANT) != 0) {
|
||||
drawable.setBadge(context.getDrawable(drawable.isThemed()
|
||||
? R.drawable.ic_instant_app_badge_themed
|
||||
: R.drawable.ic_instant_app_badge));
|
||||
} else if ((flags & FLAG_WORK) != 0) {
|
||||
drawable.setBadge(context.getDrawable(drawable.isThemed()
|
||||
? R.drawable.ic_work_app_badge_themed
|
||||
: R.drawable.ic_work_app_badge));
|
||||
} else if ((flags & FLAG_CLONE) != 0) {
|
||||
drawable.setBadge(context.getDrawable(drawable.isThemed()
|
||||
? R.drawable.ic_clone_app_badge_themed
|
||||
: R.drawable.ic_clone_app_badge));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static BitmapInfo fromBitmap(@NonNull Bitmap bitmap) {
|
||||
return of(bitmap, 0);
|
||||
}
|
||||
|
||||
public static BitmapInfo of(@NonNull Bitmap bitmap, int color) {
|
||||
return new BitmapInfo(bitmap, color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface to be implemented by drawables to provide a custom BitmapInfo
|
||||
*/
|
||||
public interface Extender {
|
||||
|
||||
/**
|
||||
* Called for creating a custom BitmapInfo
|
||||
*/
|
||||
BitmapInfo getExtendedInfo(Bitmap bitmap, int color,
|
||||
BaseIconFactory iconFactory, float normalizationScale);
|
||||
|
||||
/**
|
||||
* Called to draw the UI independent of any runtime configurations like time or theme
|
||||
*/
|
||||
void drawForPersistence(Canvas canvas);
|
||||
}
|
||||
}
|
||||
@@ -1,70 +1,70 @@
|
||||
/*
|
||||
* 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.annotation.TargetApi;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.Config;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Picture;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.os.Build;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
|
||||
/**
|
||||
* Interface representing a bitmap draw operation.
|
||||
*/
|
||||
public interface BitmapRenderer {
|
||||
|
||||
boolean USE_HARDWARE_BITMAP = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
|
||||
|
||||
static Bitmap createSoftwareBitmap(int width, int height, BitmapRenderer renderer) {
|
||||
GraphicsUtils.noteNewBitmapCreated();
|
||||
Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||
renderer.draw(new Canvas(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.P)
|
||||
static Bitmap createHardwareBitmap(int width, int height, BitmapRenderer renderer) {
|
||||
if (!USE_HARDWARE_BITMAP) {
|
||||
return createSoftwareBitmap(width, height, renderer);
|
||||
}
|
||||
|
||||
GraphicsUtils.noteNewBitmapCreated();
|
||||
Picture picture = new Picture();
|
||||
renderer.draw(picture.beginRecording(width, height));
|
||||
picture.endRecording();
|
||||
return Bitmap.createBitmap(picture);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a bitmap from subset of the source bitmap. The new bitmap may be the
|
||||
* same object as source, or a copy may have been made.
|
||||
*/
|
||||
static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height) {
|
||||
if (Build.VERSION.SDK_INT >= VERSION_CODES.O && source.getConfig() == Config.HARDWARE) {
|
||||
return createHardwareBitmap(width, height, c -> c.drawBitmap(source,
|
||||
new Rect(x, y, x + width, y + height), new RectF(0, 0, width, height), null));
|
||||
} else {
|
||||
GraphicsUtils.noteNewBitmapCreated();
|
||||
return Bitmap.createBitmap(source, x, y, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
void draw(Canvas out);
|
||||
}
|
||||
/*
|
||||
* 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.annotation.TargetApi;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.Config;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Picture;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.os.Build;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
|
||||
/**
|
||||
* Interface representing a bitmap draw operation.
|
||||
*/
|
||||
public interface BitmapRenderer {
|
||||
|
||||
boolean USE_HARDWARE_BITMAP = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
|
||||
|
||||
static Bitmap createSoftwareBitmap(int width, int height, BitmapRenderer renderer) {
|
||||
GraphicsUtils.noteNewBitmapCreated();
|
||||
Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||
renderer.draw(new Canvas(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.P)
|
||||
static Bitmap createHardwareBitmap(int width, int height, BitmapRenderer renderer) {
|
||||
if (!USE_HARDWARE_BITMAP) {
|
||||
return createSoftwareBitmap(width, height, renderer);
|
||||
}
|
||||
|
||||
GraphicsUtils.noteNewBitmapCreated();
|
||||
Picture picture = new Picture();
|
||||
renderer.draw(picture.beginRecording(width, height));
|
||||
picture.endRecording();
|
||||
return Bitmap.createBitmap(picture);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a bitmap from subset of the source bitmap. The new bitmap may be the
|
||||
* same object as source, or a copy may have been made.
|
||||
*/
|
||||
static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height) {
|
||||
if (Build.VERSION.SDK_INT >= VERSION_CODES.O && source.getConfig() == Config.HARDWARE) {
|
||||
return createHardwareBitmap(width, height, c -> c.drawBitmap(source,
|
||||
new Rect(x, y, x + width, y + height), new RectF(0, 0, width, height), null));
|
||||
} else {
|
||||
GraphicsUtils.noteNewBitmapCreated();
|
||||
return Bitmap.createBitmap(source, x, y, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
void draw(Canvas out);
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package com.android.launcher3.icons;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.LauncherApps;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.AdaptiveIconDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Factory for creating normalized bubble icons and app badges.
|
||||
*/
|
||||
public class BubbleIconFactory extends BaseIconFactory {
|
||||
|
||||
private final int mRingColor;
|
||||
private final int mRingWidth;
|
||||
|
||||
private final BaseIconFactory mBadgeFactory;
|
||||
|
||||
/**
|
||||
* Creates a bubble icon factory.
|
||||
*
|
||||
* @param context the context for the factory.
|
||||
* @param iconSize the size of the bubble icon (i.e. the large icon for the bubble).
|
||||
* @param badgeSize the size of the badge (i.e. smaller icon shown on top of the large icon).
|
||||
* @param ringColor the color of the ring optionally shown around the badge.
|
||||
* @param ringWidth the width of the ring optionally shown around the badge.
|
||||
*/
|
||||
public BubbleIconFactory(Context context, int iconSize, int badgeSize, int ringColor,
|
||||
int ringWidth) {
|
||||
super(context, context.getResources().getConfiguration().densityDpi, iconSize);
|
||||
mRingColor = ringColor;
|
||||
mRingWidth = ringWidth;
|
||||
|
||||
mBadgeFactory = new BaseIconFactory(context,
|
||||
context.getResources().getConfiguration().densityDpi,
|
||||
badgeSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the drawable that the developer has provided to display in the bubble.
|
||||
*/
|
||||
public Drawable getBubbleDrawable(@NonNull final Context context,
|
||||
@Nullable final ShortcutInfo shortcutInfo, @Nullable final Icon ic) {
|
||||
if (shortcutInfo != null) {
|
||||
LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
|
||||
int density = context.getResources().getConfiguration().densityDpi;
|
||||
return launcherApps.getShortcutIconDrawable(shortcutInfo, density);
|
||||
} else {
|
||||
if (ic != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
if (ic.getType() == Icon.TYPE_URI
|
||||
|| ic.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) {
|
||||
context.grantUriPermission(context.getPackageName(),
|
||||
ic.getUri(),
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
}
|
||||
return ic.loadDrawable(context);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the bitmap for the provided drawable and returns the scale used for
|
||||
* drawing the actual drawable. This is used for the larger icon shown for the bubble.
|
||||
*/
|
||||
public Bitmap getBubbleBitmap(@NonNull Drawable icon, float[] outScale) {
|
||||
if (outScale == null) {
|
||||
outScale = new float[1];
|
||||
}
|
||||
icon = normalizeAndWrapToAdaptiveIcon(icon,
|
||||
true /* shrinkNonAdaptiveIcons */,
|
||||
null /* outscale */,
|
||||
outScale);
|
||||
return createIconBitmap(icon, outScale[0], MODE_WITH_SHADOW);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link BitmapInfo} for the app-badge that is shown on top of each bubble. This
|
||||
* will include the workprofile indicator on the badge if appropriate.
|
||||
*/
|
||||
public BitmapInfo getBadgeBitmap(Drawable userBadgedAppIcon, boolean isImportantConversation) {
|
||||
if (userBadgedAppIcon instanceof AdaptiveIconDrawable) {
|
||||
AdaptiveIconDrawable ad = (AdaptiveIconDrawable) userBadgedAppIcon;
|
||||
userBadgedAppIcon = new CircularAdaptiveIcon(ad.getBackground(),
|
||||
ad.getForeground());
|
||||
}
|
||||
if (isImportantConversation) {
|
||||
userBadgedAppIcon = new CircularRingDrawable(userBadgedAppIcon);
|
||||
}
|
||||
Bitmap userBadgedBitmap = mBadgeFactory.createIconBitmap(
|
||||
userBadgedAppIcon, 1, MODE_WITH_SHADOW);
|
||||
return mBadgeFactory.createIconBitmap(userBadgedBitmap);
|
||||
}
|
||||
|
||||
private class CircularRingDrawable extends CircularAdaptiveIcon {
|
||||
final Rect mInnerBounds = new Rect();
|
||||
|
||||
final Drawable mDr;
|
||||
|
||||
CircularRingDrawable(Drawable dr) {
|
||||
super(null, null);
|
||||
mDr = dr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
int save = canvas.save();
|
||||
canvas.clipPath(getIconMask());
|
||||
canvas.drawColor(mRingColor);
|
||||
mInnerBounds.set(getBounds());
|
||||
mInnerBounds.inset(mRingWidth, mRingWidth);
|
||||
canvas.translate(mInnerBounds.left, mInnerBounds.top);
|
||||
mDr.setBounds(0, 0, mInnerBounds.width(), mInnerBounds.height());
|
||||
mDr.draw(canvas);
|
||||
canvas.restoreToCount(save);
|
||||
}
|
||||
}
|
||||
|
||||
private static class CircularAdaptiveIcon extends AdaptiveIconDrawable {
|
||||
|
||||
final Path mPath = new Path();
|
||||
|
||||
CircularAdaptiveIcon(Drawable bg, Drawable fg) {
|
||||
super(bg, fg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getIconMask() {
|
||||
mPath.reset();
|
||||
Rect bounds = getBounds();
|
||||
mPath.addOval(bounds.left, bounds.top, bounds.right, bounds.bottom, Path.Direction.CW);
|
||||
return mPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
int save = canvas.save();
|
||||
canvas.clipPath(getIconMask());
|
||||
|
||||
Drawable d;
|
||||
if ((d = getBackground()) != null) {
|
||||
d.draw(canvas);
|
||||
}
|
||||
if ((d = getForeground()) != null) {
|
||||
d.draw(canvas);
|
||||
}
|
||||
canvas.restoreToCount(save);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,127 +1,138 @@
|
||||
/*
|
||||
* 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 java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Utility class for extracting colors from a bitmap.
|
||||
*/
|
||||
public class ColorExtractor {
|
||||
|
||||
private final int NUM_SAMPLES = 20;
|
||||
private final float[] mTmpHsv = new float[3];
|
||||
private final float[] mTmpHueScoreHistogram = new float[360];
|
||||
private final int[] mTmpPixels = new int[NUM_SAMPLES];
|
||||
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(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
|
||||
*/
|
||||
public int findDominantColorByHue(Bitmap bitmap, 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;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -1,182 +1,150 @@
|
||||
/*
|
||||
* 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.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PathMeasure;
|
||||
import android.graphics.Rect;
|
||||
import android.util.Log;
|
||||
import android.view.ViewDebug;
|
||||
|
||||
import static android.graphics.Paint.ANTI_ALIAS_FLAG;
|
||||
import static android.graphics.Paint.FILTER_BITMAP_FLAG;
|
||||
|
||||
/**
|
||||
* Used to draw a notification dot on top of an icon.
|
||||
*/
|
||||
public class DotRenderer {
|
||||
|
||||
private static final String TAG = "DotRenderer";
|
||||
|
||||
// The dot size is defined as a percentage of the app icon size.
|
||||
private static final float SIZE_PERCENTAGE = 0.228f;
|
||||
|
||||
private final float mCircleRadius;
|
||||
private final Paint mCirclePaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG);
|
||||
|
||||
private final Bitmap mBackgroundWithShadow;
|
||||
private final float mBitmapOffset;
|
||||
|
||||
// Stores the center x and y position as a percentage (0 to 1) of the icon size
|
||||
private final float[] mRightDotPosition;
|
||||
private final float[] mLeftDotPosition;
|
||||
|
||||
private static final int MIN_DOT_SIZE = 1;
|
||||
|
||||
private static final float SIZE_NUM_PERCENTAGE = 0.328f;
|
||||
private final int mTextHeight;
|
||||
private final float mNumCircleRadius;
|
||||
private final float mNumBitmapOffset;
|
||||
private boolean mShowNumInDot = false;
|
||||
private final Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
|
||||
public DotRenderer(int iconSizePx, Path iconShapePath, int pathSize) {
|
||||
int size = Math.round(SIZE_PERCENTAGE * iconSizePx);
|
||||
if (size <= 0) {
|
||||
size = MIN_DOT_SIZE;
|
||||
}
|
||||
ShadowGenerator.Builder builder = new ShadowGenerator.Builder(Color.TRANSPARENT);
|
||||
builder.ambientShadowAlpha = 88;
|
||||
mBackgroundWithShadow = builder.setupBlurForSize(size).createPill(size, size);
|
||||
mCircleRadius = builder.radius;
|
||||
|
||||
mBitmapOffset = -mBackgroundWithShadow.getHeight() * 0.5f; // Same as width.
|
||||
|
||||
// Find the points on the path that are closest to the top left and right corners.
|
||||
mLeftDotPosition = getPathPoint(iconShapePath, pathSize, -1);
|
||||
mRightDotPosition = getPathPoint(iconShapePath, pathSize, 1);
|
||||
|
||||
{
|
||||
int numSize = Math.round(SIZE_NUM_PERCENTAGE * iconSizePx);
|
||||
if (numSize <= 0) {
|
||||
numSize = MIN_DOT_SIZE;
|
||||
}
|
||||
mNumCircleRadius = numSize/2;//numBuilder.radius;
|
||||
mNumBitmapOffset = -mNumCircleRadius;//mNumBackgroundWithShadow.getHeight() * 0.5f;
|
||||
|
||||
mTextPaint.setTextSize(mNumCircleRadius);
|
||||
mTextPaint.setTextAlign(Paint.Align.CENTER);
|
||||
mTextPaint.setColor(Color.WHITE);
|
||||
Rect tempTextHeight = new Rect();
|
||||
mTextPaint.getTextBounds("0", 0, 1, tempTextHeight);
|
||||
mTextHeight = tempTextHeight.height();
|
||||
}
|
||||
}
|
||||
|
||||
private static float[] getPathPoint(Path path, float size, float direction) {
|
||||
float halfSize = size / 2;
|
||||
// Small delta so that we don't get a zero size triangle
|
||||
float delta = 1;
|
||||
|
||||
float x = halfSize + direction * halfSize;
|
||||
Path trianglePath = new Path();
|
||||
trianglePath.moveTo(halfSize, halfSize);
|
||||
trianglePath.lineTo(x + delta * direction, 0);
|
||||
trianglePath.lineTo(x, -delta);
|
||||
trianglePath.close();
|
||||
|
||||
trianglePath.op(path, Path.Op.INTERSECT);
|
||||
float[] pos = new float[2];
|
||||
new PathMeasure(trianglePath, false).getPosTan(0, pos, null);
|
||||
|
||||
pos[0] = pos[0] / size;
|
||||
pos[1] = pos[1] / size;
|
||||
return pos;
|
||||
}
|
||||
|
||||
public float[] getLeftDotPosition() {
|
||||
return mLeftDotPosition;
|
||||
}
|
||||
|
||||
public float[] getRightDotPosition() {
|
||||
return mRightDotPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a circle on top of the canvas according to the given params.
|
||||
*/
|
||||
public void draw(Canvas canvas, DrawParams params) {
|
||||
if (params == null) {
|
||||
Log.e(TAG, "Invalid null argument(s) passed in call to draw.");
|
||||
return;
|
||||
}
|
||||
canvas.save();
|
||||
|
||||
Rect iconBounds = params.iconBounds;
|
||||
float[] dotPosition = params.leftAlign ? mLeftDotPosition : mRightDotPosition;
|
||||
float dotCenterX = iconBounds.left + iconBounds.width() * dotPosition[0];
|
||||
float dotCenterY = iconBounds.top + iconBounds.height() * dotPosition[1];
|
||||
|
||||
float bitmapOffset = mShowNumInDot ? mNumBitmapOffset : mBitmapOffset;
|
||||
// Ensure dot fits entirely in canvas clip bounds.
|
||||
Rect canvasBounds = canvas.getClipBounds();
|
||||
float offsetX = params.leftAlign
|
||||
? Math.max(0, canvasBounds.left - (dotCenterX + bitmapOffset))
|
||||
: Math.min(0, canvasBounds.right - (dotCenterX - bitmapOffset));
|
||||
float offsetY = Math.max(0, canvasBounds.top - (dotCenterY + bitmapOffset));
|
||||
|
||||
// We draw the dot relative to its center.
|
||||
canvas.translate(dotCenterX + offsetX, dotCenterY + offsetY);
|
||||
canvas.scale(params.scale, params.scale);
|
||||
|
||||
mCirclePaint.setColor(Color.BLACK);
|
||||
canvas.drawBitmap(mBackgroundWithShadow, bitmapOffset, bitmapOffset, mCirclePaint);
|
||||
mCirclePaint.setColor(params.dotColor);
|
||||
canvas.drawCircle(0, 0, (mShowNumInDot ? mNumCircleRadius : mCircleRadius), mCirclePaint);
|
||||
if(mShowNumInDot) {
|
||||
canvas.drawText("" + params.num, 0, (float) mTextHeight / 2, mTextPaint);
|
||||
}
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
public void setShowNumInDot(boolean showNumInDot) {
|
||||
mShowNumInDot = showNumInDot;
|
||||
}
|
||||
|
||||
public static class DrawParams {
|
||||
/** The color (possibly based on the icon) to use for the dot. */
|
||||
@ViewDebug.ExportedProperty(category = "notification dot", formatToHexString = true)
|
||||
public int dotColor;
|
||||
/** The color (possibly based on the icon) to use for a predicted app. */
|
||||
@ViewDebug.ExportedProperty(category = "notification dot", formatToHexString = true)
|
||||
public int appColor;
|
||||
/** The bounds of the icon that the dot is drawn on top of. */
|
||||
@ViewDebug.ExportedProperty(category = "notification dot")
|
||||
public Rect iconBounds = new Rect();
|
||||
/** The progress of the animation, from 0 to 1. */
|
||||
@ViewDebug.ExportedProperty(category = "notification dot")
|
||||
public float scale;
|
||||
/** Whether the dot should align to the top left of the icon rather than the top right. */
|
||||
@ViewDebug.ExportedProperty(category = "notification dot")
|
||||
public boolean leftAlign;
|
||||
public int num;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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 static android.graphics.Paint.ANTI_ALIAS_FLAG;
|
||||
import static android.graphics.Paint.FILTER_BITMAP_FLAG;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PathMeasure;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.util.Log;
|
||||
import android.view.ViewDebug;
|
||||
|
||||
/**
|
||||
* Used to draw a notification dot on top of an icon.
|
||||
*/
|
||||
public class DotRenderer {
|
||||
|
||||
private static final String TAG = "DotRenderer";
|
||||
|
||||
// The dot size is defined as a percentage of the app icon size.
|
||||
private static final float SIZE_PERCENTAGE = 0.228f;
|
||||
|
||||
private final float mCircleRadius;
|
||||
private final Paint mCirclePaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG);
|
||||
|
||||
private final Bitmap mBackgroundWithShadow;
|
||||
private final float mBitmapOffset;
|
||||
|
||||
// Stores the center x and y position as a percentage (0 to 1) of the icon size
|
||||
private final float[] mRightDotPosition;
|
||||
private final float[] mLeftDotPosition;
|
||||
|
||||
private static final int MIN_DOT_SIZE = 1;
|
||||
public DotRenderer(int iconSizePx, Path iconShapePath, int pathSize) {
|
||||
int size = Math.round(SIZE_PERCENTAGE * iconSizePx);
|
||||
if (size <= 0) {
|
||||
size = MIN_DOT_SIZE;
|
||||
}
|
||||
ShadowGenerator.Builder builder = new ShadowGenerator.Builder(Color.TRANSPARENT);
|
||||
builder.ambientShadowAlpha = 88;
|
||||
mBackgroundWithShadow = builder.setupBlurForSize(size).createPill(size, size);
|
||||
mCircleRadius = builder.radius;
|
||||
|
||||
mBitmapOffset = -mBackgroundWithShadow.getHeight() * 0.5f; // Same as width.
|
||||
|
||||
// Find the points on the path that are closest to the top left and right corners.
|
||||
mLeftDotPosition = getPathPoint(iconShapePath, pathSize, -1);
|
||||
mRightDotPosition = getPathPoint(iconShapePath, pathSize, 1);
|
||||
}
|
||||
|
||||
private static float[] getPathPoint(Path path, float size, float direction) {
|
||||
float halfSize = size / 2;
|
||||
// Small delta so that we don't get a zero size triangle
|
||||
float delta = 1;
|
||||
|
||||
float x = halfSize + direction * halfSize;
|
||||
Path trianglePath = new Path();
|
||||
trianglePath.moveTo(halfSize, halfSize);
|
||||
trianglePath.lineTo(x + delta * direction, 0);
|
||||
trianglePath.lineTo(x, -delta);
|
||||
trianglePath.close();
|
||||
|
||||
trianglePath.op(path, Path.Op.INTERSECT);
|
||||
float[] pos = new float[2];
|
||||
new PathMeasure(trianglePath, false).getPosTan(0, pos, null);
|
||||
|
||||
pos[0] = pos[0] / size;
|
||||
pos[1] = pos[1] / size;
|
||||
return pos;
|
||||
}
|
||||
|
||||
public float[] getLeftDotPosition() {
|
||||
return mLeftDotPosition;
|
||||
}
|
||||
|
||||
public float[] getRightDotPosition() {
|
||||
return mRightDotPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a circle on top of the canvas according to the given params.
|
||||
*/
|
||||
public void draw(Canvas canvas, DrawParams params) {
|
||||
if (params == null) {
|
||||
Log.e(TAG, "Invalid null argument(s) passed in call to draw.");
|
||||
return;
|
||||
}
|
||||
canvas.save();
|
||||
|
||||
Rect iconBounds = params.iconBounds;
|
||||
float[] dotPosition = params.leftAlign ? mLeftDotPosition : mRightDotPosition;
|
||||
float dotCenterX = iconBounds.left + iconBounds.width() * dotPosition[0];
|
||||
float dotCenterY = iconBounds.top + iconBounds.height() * dotPosition[1];
|
||||
|
||||
// Ensure dot fits entirely in canvas clip bounds.
|
||||
Rect canvasBounds = canvas.getClipBounds();
|
||||
float offsetX = params.leftAlign
|
||||
? Math.max(0, canvasBounds.left - (dotCenterX + mBitmapOffset))
|
||||
: Math.min(0, canvasBounds.right - (dotCenterX - mBitmapOffset));
|
||||
float offsetY = Math.max(0, canvasBounds.top - (dotCenterY + mBitmapOffset));
|
||||
|
||||
// We draw the dot relative to its center.
|
||||
canvas.translate(dotCenterX + offsetX, dotCenterY + offsetY);
|
||||
canvas.scale(params.scale, params.scale);
|
||||
|
||||
mCirclePaint.setColor(Color.BLACK);
|
||||
canvas.drawBitmap(mBackgroundWithShadow, mBitmapOffset, mBitmapOffset, mCirclePaint);
|
||||
mCirclePaint.setColor(params.dotColor);
|
||||
canvas.drawCircle(0, 0, mCircleRadius, mCirclePaint);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
public static class DrawParams {
|
||||
/** The color (possibly based on the icon) to use for the dot. */
|
||||
@ViewDebug.ExportedProperty(category = "notification dot", formatToHexString = true)
|
||||
public int dotColor;
|
||||
/** The color (possibly based on the icon) to use for a predicted app. */
|
||||
@ViewDebug.ExportedProperty(category = "notification dot", formatToHexString = true)
|
||||
public int appColor;
|
||||
/** The bounds of the icon that the dot is drawn on top of. */
|
||||
@ViewDebug.ExportedProperty(category = "notification dot")
|
||||
public Rect iconBounds = new Rect();
|
||||
/** The progress of the animation, from 0 to 1. */
|
||||
@ViewDebug.ExportedProperty(category = "notification dot")
|
||||
public float scale;
|
||||
/** Whether the dot should align to the top left of the icon rather than the top right. */
|
||||
@ViewDebug.ExportedProperty(category = "notification dot")
|
||||
public boolean leftAlign;
|
||||
}
|
||||
}
|
||||
@@ -1,402 +1,402 @@
|
||||
/*
|
||||
* Copyright (C) 2008 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 static com.android.launcher3.icons.BaseIconFactory.getBadgeSizeForIconSize;
|
||||
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.ColorMatrix;
|
||||
import android.graphics.ColorMatrixColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.FloatProperty;
|
||||
import android.view.animation.AccelerateInterpolator;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.graphics.ColorUtils;
|
||||
|
||||
public class FastBitmapDrawable extends Drawable implements Drawable.Callback {
|
||||
|
||||
private static final Interpolator ACCEL = new AccelerateInterpolator();
|
||||
private static final Interpolator DEACCEL = new DecelerateInterpolator();
|
||||
|
||||
private static final float PRESSED_SCALE = 1.1f;
|
||||
public static final int WHITE_SCRIM_ALPHA = 138;
|
||||
|
||||
private static final float DISABLED_DESATURATION = 1f;
|
||||
private static final float DISABLED_BRIGHTNESS = 0.5f;
|
||||
protected static final int FULLY_OPAQUE = 255;
|
||||
|
||||
public static final int CLICK_FEEDBACK_DURATION = 200;
|
||||
|
||||
protected final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
|
||||
public final Bitmap mBitmap;
|
||||
protected final int mIconColor;
|
||||
|
||||
@Nullable private ColorFilter mColorFilter;
|
||||
|
||||
private boolean mIsPressed;
|
||||
protected boolean mIsDisabled;
|
||||
float mDisabledAlpha = 1f;
|
||||
|
||||
// Animator and properties for the fast bitmap drawable's scale
|
||||
private static final FloatProperty<FastBitmapDrawable> SCALE
|
||||
= new FloatProperty<FastBitmapDrawable>("scale") {
|
||||
@Override
|
||||
public Float get(FastBitmapDrawable fastBitmapDrawable) {
|
||||
return fastBitmapDrawable.mScale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(FastBitmapDrawable fastBitmapDrawable, float value) {
|
||||
fastBitmapDrawable.mScale = value;
|
||||
fastBitmapDrawable.invalidateSelf();
|
||||
}
|
||||
};
|
||||
private ObjectAnimator mScaleAnimation;
|
||||
private float mScale = 1;
|
||||
private int mAlpha = 255;
|
||||
|
||||
private Drawable mBadge;
|
||||
|
||||
public FastBitmapDrawable(Bitmap b) {
|
||||
this(b, Color.TRANSPARENT);
|
||||
}
|
||||
|
||||
public FastBitmapDrawable(BitmapInfo info) {
|
||||
this(info.icon, info.color);
|
||||
}
|
||||
|
||||
protected FastBitmapDrawable(Bitmap b, int iconColor) {
|
||||
mBitmap = b;
|
||||
mIconColor = iconColor;
|
||||
setFilterBitmap(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(Rect bounds) {
|
||||
super.onBoundsChange(bounds);
|
||||
updateBadgeBounds(bounds);
|
||||
}
|
||||
|
||||
private void updateBadgeBounds(Rect bounds) {
|
||||
if (mBadge != null) {
|
||||
setBadgeBounds(mBadge, bounds);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
if (mScale != 1f) {
|
||||
int count = canvas.save();
|
||||
Rect bounds = getBounds();
|
||||
canvas.scale(mScale, mScale, bounds.exactCenterX(), bounds.exactCenterY());
|
||||
drawInternal(canvas, bounds);
|
||||
if (mBadge != null) {
|
||||
mBadge.draw(canvas);
|
||||
}
|
||||
canvas.restoreToCount(count);
|
||||
} else {
|
||||
drawInternal(canvas, getBounds());
|
||||
if (mBadge != null) {
|
||||
mBadge.draw(canvas);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void drawInternal(Canvas canvas, Rect bounds) {
|
||||
canvas.drawBitmap(mBitmap, null, bounds, mPaint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the primary icon color, slightly tinted white
|
||||
*/
|
||||
public int getIconColor() {
|
||||
int whiteScrim = setColorAlphaBound(Color.WHITE, WHITE_SCRIM_ALPHA);
|
||||
return ColorUtils.compositeColors(whiteScrim, mIconColor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if this represents a themed icon
|
||||
*/
|
||||
public boolean isThemed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter cf) {
|
||||
mColorFilter = cf;
|
||||
updateFilter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
if (mAlpha != alpha) {
|
||||
mAlpha = alpha;
|
||||
mPaint.setAlpha(alpha);
|
||||
invalidateSelf();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFilterBitmap(boolean filterBitmap) {
|
||||
mPaint.setFilterBitmap(filterBitmap);
|
||||
mPaint.setAntiAlias(filterBitmap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAlpha() {
|
||||
return mAlpha;
|
||||
}
|
||||
|
||||
public void resetScale() {
|
||||
if (mScaleAnimation != null) {
|
||||
mScaleAnimation.cancel();
|
||||
mScaleAnimation = null;
|
||||
}
|
||||
mScale = 1;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
public float getAnimatedScale() {
|
||||
return mScaleAnimation == null ? 1 : mScale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicWidth() {
|
||||
return mBitmap.getWidth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicHeight() {
|
||||
return mBitmap.getHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinimumWidth() {
|
||||
return getBounds().width();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinimumHeight() {
|
||||
return getBounds().height();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStateful() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ColorFilter getColorFilter() {
|
||||
return mPaint.getColorFilter();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onStateChange(int[] state) {
|
||||
boolean isPressed = false;
|
||||
for (int s : state) {
|
||||
if (s == android.R.attr.state_pressed) {
|
||||
isPressed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (mIsPressed != isPressed) {
|
||||
mIsPressed = isPressed;
|
||||
|
||||
if (mScaleAnimation != null) {
|
||||
mScaleAnimation.cancel();
|
||||
mScaleAnimation = null;
|
||||
}
|
||||
|
||||
if (mIsPressed) {
|
||||
// Animate when going to pressed state
|
||||
mScaleAnimation = ObjectAnimator.ofFloat(this, SCALE, PRESSED_SCALE);
|
||||
mScaleAnimation.setDuration(CLICK_FEEDBACK_DURATION);
|
||||
mScaleAnimation.setInterpolator(ACCEL);
|
||||
mScaleAnimation.start();
|
||||
} else {
|
||||
if (isVisible()) {
|
||||
mScaleAnimation = ObjectAnimator.ofFloat(this, SCALE, 1f);
|
||||
mScaleAnimation.setDuration(CLICK_FEEDBACK_DURATION);
|
||||
mScaleAnimation.setInterpolator(DEACCEL);
|
||||
mScaleAnimation.start();
|
||||
} else {
|
||||
mScale = 1f;
|
||||
invalidateSelf();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setIsDisabled(boolean isDisabled) {
|
||||
if (mIsDisabled != isDisabled) {
|
||||
mIsDisabled = isDisabled;
|
||||
updateFilter();
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isDisabled() {
|
||||
return mIsDisabled;
|
||||
}
|
||||
|
||||
public void setBadge(Drawable badge) {
|
||||
if (mBadge != null) {
|
||||
mBadge.setCallback(null);
|
||||
}
|
||||
mBadge = badge;
|
||||
if (mBadge != null) {
|
||||
mBadge.setCallback(this);
|
||||
}
|
||||
updateBadgeBounds(getBounds());
|
||||
updateFilter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the paint to reflect the current brightness and saturation.
|
||||
*/
|
||||
protected void updateFilter() {
|
||||
mPaint.setColorFilter(mIsDisabled ? getDisabledColorFilter(mDisabledAlpha) : mColorFilter);
|
||||
if (mBadge != null) {
|
||||
mBadge.setColorFilter(getColorFilter());
|
||||
}
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
protected FastBitmapConstantState newConstantState() {
|
||||
return new FastBitmapConstantState(mBitmap, mIconColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ConstantState getConstantState() {
|
||||
FastBitmapConstantState cs = newConstantState();
|
||||
cs.mIsDisabled = mIsDisabled;
|
||||
if (mBadge != null) {
|
||||
cs.mBadgeConstantState = mBadge.getConstantState();
|
||||
}
|
||||
return cs;
|
||||
}
|
||||
|
||||
public static ColorFilter getDisabledColorFilter() {
|
||||
return getDisabledColorFilter(1);
|
||||
}
|
||||
|
||||
private static ColorFilter getDisabledColorFilter(float disabledAlpha) {
|
||||
ColorMatrix tempBrightnessMatrix = new ColorMatrix();
|
||||
ColorMatrix tempFilterMatrix = new ColorMatrix();
|
||||
|
||||
tempFilterMatrix.setSaturation(1f - DISABLED_DESATURATION);
|
||||
float scale = 1 - DISABLED_BRIGHTNESS;
|
||||
int brightnessI = (int) (255 * DISABLED_BRIGHTNESS);
|
||||
float[] mat = tempBrightnessMatrix.getArray();
|
||||
mat[0] = scale;
|
||||
mat[6] = scale;
|
||||
mat[12] = scale;
|
||||
mat[4] = brightnessI;
|
||||
mat[9] = brightnessI;
|
||||
mat[14] = brightnessI;
|
||||
mat[18] = disabledAlpha;
|
||||
tempFilterMatrix.preConcat(tempBrightnessMatrix);
|
||||
return new ColorMatrixColorFilter(tempFilterMatrix);
|
||||
}
|
||||
|
||||
protected static final int getDisabledColor(int color) {
|
||||
int component = (Color.red(color) + Color.green(color) + Color.blue(color)) / 3;
|
||||
float scale = 1 - DISABLED_BRIGHTNESS;
|
||||
int brightnessI = (int) (255 * DISABLED_BRIGHTNESS);
|
||||
component = Math.min(Math.round(scale * component + brightnessI), FULLY_OPAQUE);
|
||||
return Color.rgb(component, component, component);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the bounds for the badge drawable based on the main icon bounds
|
||||
*/
|
||||
public static void setBadgeBounds(Drawable badge, Rect iconBounds) {
|
||||
int size = getBadgeSizeForIconSize(iconBounds.width());
|
||||
badge.setBounds(iconBounds.right - size, iconBounds.bottom - size,
|
||||
iconBounds.right, iconBounds.bottom);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateDrawable(Drawable who) {
|
||||
if (who == mBadge) {
|
||||
invalidateSelf();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduleDrawable(Drawable who, Runnable what, long when) {
|
||||
if (who == mBadge) {
|
||||
scheduleSelf(what, when);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unscheduleDrawable(Drawable who, Runnable what) {
|
||||
unscheduleSelf(what);
|
||||
}
|
||||
|
||||
protected static class FastBitmapConstantState extends ConstantState {
|
||||
protected final Bitmap mBitmap;
|
||||
protected final int mIconColor;
|
||||
|
||||
// These are initialized later so that subclasses don't need to
|
||||
// pass everything in constructor
|
||||
protected boolean mIsDisabled;
|
||||
private ConstantState mBadgeConstantState;
|
||||
|
||||
public FastBitmapConstantState(Bitmap bitmap, int color) {
|
||||
mBitmap = bitmap;
|
||||
mIconColor = color;
|
||||
}
|
||||
|
||||
protected FastBitmapDrawable createDrawable() {
|
||||
return new FastBitmapDrawable(mBitmap, mIconColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final FastBitmapDrawable newDrawable() {
|
||||
FastBitmapDrawable drawable = createDrawable();
|
||||
drawable.setIsDisabled(mIsDisabled);
|
||||
if (mBadgeConstantState != null) {
|
||||
drawable.setBadge(mBadgeConstantState.newDrawable());
|
||||
}
|
||||
return drawable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getChangingConfigurations() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Copyright (C) 2008 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 static com.android.launcher3.icons.BaseIconFactory.getBadgeSizeForIconSize;
|
||||
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.ColorMatrix;
|
||||
import android.graphics.ColorMatrixColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.FloatProperty;
|
||||
import android.view.animation.AccelerateInterpolator;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.graphics.ColorUtils;
|
||||
|
||||
public class FastBitmapDrawable extends Drawable implements Drawable.Callback {
|
||||
|
||||
private static final Interpolator ACCEL = new AccelerateInterpolator();
|
||||
private static final Interpolator DEACCEL = new DecelerateInterpolator();
|
||||
|
||||
private static final float PRESSED_SCALE = 1.1f;
|
||||
public static final int WHITE_SCRIM_ALPHA = 138;
|
||||
|
||||
private static final float DISABLED_DESATURATION = 1f;
|
||||
private static final float DISABLED_BRIGHTNESS = 0.5f;
|
||||
protected static final int FULLY_OPAQUE = 255;
|
||||
|
||||
public static final int CLICK_FEEDBACK_DURATION = 200;
|
||||
|
||||
protected final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
|
||||
protected final Bitmap mBitmap;
|
||||
protected final int mIconColor;
|
||||
|
||||
@Nullable private ColorFilter mColorFilter;
|
||||
|
||||
private boolean mIsPressed;
|
||||
protected boolean mIsDisabled;
|
||||
float mDisabledAlpha = 1f;
|
||||
|
||||
// Animator and properties for the fast bitmap drawable's scale
|
||||
private static final FloatProperty<FastBitmapDrawable> SCALE
|
||||
= new FloatProperty<FastBitmapDrawable>("scale") {
|
||||
@Override
|
||||
public Float get(FastBitmapDrawable fastBitmapDrawable) {
|
||||
return fastBitmapDrawable.mScale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(FastBitmapDrawable fastBitmapDrawable, float value) {
|
||||
fastBitmapDrawable.mScale = value;
|
||||
fastBitmapDrawable.invalidateSelf();
|
||||
}
|
||||
};
|
||||
private ObjectAnimator mScaleAnimation;
|
||||
private float mScale = 1;
|
||||
private int mAlpha = 255;
|
||||
|
||||
private Drawable mBadge;
|
||||
|
||||
public FastBitmapDrawable(Bitmap b) {
|
||||
this(b, Color.TRANSPARENT);
|
||||
}
|
||||
|
||||
public FastBitmapDrawable(BitmapInfo info) {
|
||||
this(info.icon, info.color);
|
||||
}
|
||||
|
||||
protected FastBitmapDrawable(Bitmap b, int iconColor) {
|
||||
mBitmap = b;
|
||||
mIconColor = iconColor;
|
||||
setFilterBitmap(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(Rect bounds) {
|
||||
super.onBoundsChange(bounds);
|
||||
updateBadgeBounds(bounds);
|
||||
}
|
||||
|
||||
private void updateBadgeBounds(Rect bounds) {
|
||||
if (mBadge != null) {
|
||||
setBadgeBounds(mBadge, bounds);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void draw(Canvas canvas) {
|
||||
if (mScale != 1f) {
|
||||
int count = canvas.save();
|
||||
Rect bounds = getBounds();
|
||||
canvas.scale(mScale, mScale, bounds.exactCenterX(), bounds.exactCenterY());
|
||||
drawInternal(canvas, bounds);
|
||||
if (mBadge != null) {
|
||||
mBadge.draw(canvas);
|
||||
}
|
||||
canvas.restoreToCount(count);
|
||||
} else {
|
||||
drawInternal(canvas, getBounds());
|
||||
if (mBadge != null) {
|
||||
mBadge.draw(canvas);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void drawInternal(Canvas canvas, Rect bounds) {
|
||||
canvas.drawBitmap(mBitmap, null, bounds, mPaint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the primary icon color, slightly tinted white
|
||||
*/
|
||||
public int getIconColor() {
|
||||
int whiteScrim = setColorAlphaBound(Color.WHITE, WHITE_SCRIM_ALPHA);
|
||||
return ColorUtils.compositeColors(whiteScrim, mIconColor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if this represents a themed icon
|
||||
*/
|
||||
public boolean isThemed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter cf) {
|
||||
mColorFilter = cf;
|
||||
updateFilter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
if (mAlpha != alpha) {
|
||||
mAlpha = alpha;
|
||||
mPaint.setAlpha(alpha);
|
||||
invalidateSelf();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFilterBitmap(boolean filterBitmap) {
|
||||
mPaint.setFilterBitmap(filterBitmap);
|
||||
mPaint.setAntiAlias(filterBitmap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAlpha() {
|
||||
return mAlpha;
|
||||
}
|
||||
|
||||
public void resetScale() {
|
||||
if (mScaleAnimation != null) {
|
||||
mScaleAnimation.cancel();
|
||||
mScaleAnimation = null;
|
||||
}
|
||||
mScale = 1;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
public float getAnimatedScale() {
|
||||
return mScaleAnimation == null ? 1 : mScale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicWidth() {
|
||||
return mBitmap.getWidth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicHeight() {
|
||||
return mBitmap.getHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinimumWidth() {
|
||||
return getBounds().width();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinimumHeight() {
|
||||
return getBounds().height();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStateful() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ColorFilter getColorFilter() {
|
||||
return mPaint.getColorFilter();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onStateChange(int[] state) {
|
||||
boolean isPressed = false;
|
||||
for (int s : state) {
|
||||
if (s == android.R.attr.state_pressed) {
|
||||
isPressed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (mIsPressed != isPressed) {
|
||||
mIsPressed = isPressed;
|
||||
|
||||
if (mScaleAnimation != null) {
|
||||
mScaleAnimation.cancel();
|
||||
mScaleAnimation = null;
|
||||
}
|
||||
|
||||
if (mIsPressed) {
|
||||
// Animate when going to pressed state
|
||||
mScaleAnimation = ObjectAnimator.ofFloat(this, SCALE, PRESSED_SCALE);
|
||||
mScaleAnimation.setDuration(CLICK_FEEDBACK_DURATION);
|
||||
mScaleAnimation.setInterpolator(ACCEL);
|
||||
mScaleAnimation.start();
|
||||
} else {
|
||||
if (isVisible()) {
|
||||
mScaleAnimation = ObjectAnimator.ofFloat(this, SCALE, 1f);
|
||||
mScaleAnimation.setDuration(CLICK_FEEDBACK_DURATION);
|
||||
mScaleAnimation.setInterpolator(DEACCEL);
|
||||
mScaleAnimation.start();
|
||||
} else {
|
||||
mScale = 1f;
|
||||
invalidateSelf();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setIsDisabled(boolean isDisabled) {
|
||||
if (mIsDisabled != isDisabled) {
|
||||
mIsDisabled = isDisabled;
|
||||
updateFilter();
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isDisabled() {
|
||||
return mIsDisabled;
|
||||
}
|
||||
|
||||
public void setBadge(Drawable badge) {
|
||||
if (mBadge != null) {
|
||||
mBadge.setCallback(null);
|
||||
}
|
||||
mBadge = badge;
|
||||
if (mBadge != null) {
|
||||
mBadge.setCallback(this);
|
||||
}
|
||||
updateBadgeBounds(getBounds());
|
||||
updateFilter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the paint to reflect the current brightness and saturation.
|
||||
*/
|
||||
protected void updateFilter() {
|
||||
mPaint.setColorFilter(mIsDisabled ? getDisabledColorFilter(mDisabledAlpha) : mColorFilter);
|
||||
if (mBadge != null) {
|
||||
mBadge.setColorFilter(getColorFilter());
|
||||
}
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
protected FastBitmapConstantState newConstantState() {
|
||||
return new FastBitmapConstantState(mBitmap, mIconColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ConstantState getConstantState() {
|
||||
FastBitmapConstantState cs = newConstantState();
|
||||
cs.mIsDisabled = mIsDisabled;
|
||||
if (mBadge != null) {
|
||||
cs.mBadgeConstantState = mBadge.getConstantState();
|
||||
}
|
||||
return cs;
|
||||
}
|
||||
|
||||
public static ColorFilter getDisabledColorFilter() {
|
||||
return getDisabledColorFilter(1);
|
||||
}
|
||||
|
||||
private static ColorFilter getDisabledColorFilter(float disabledAlpha) {
|
||||
ColorMatrix tempBrightnessMatrix = new ColorMatrix();
|
||||
ColorMatrix tempFilterMatrix = new ColorMatrix();
|
||||
|
||||
tempFilterMatrix.setSaturation(1f - DISABLED_DESATURATION);
|
||||
float scale = 1 - DISABLED_BRIGHTNESS;
|
||||
int brightnessI = (int) (255 * DISABLED_BRIGHTNESS);
|
||||
float[] mat = tempBrightnessMatrix.getArray();
|
||||
mat[0] = scale;
|
||||
mat[6] = scale;
|
||||
mat[12] = scale;
|
||||
mat[4] = brightnessI;
|
||||
mat[9] = brightnessI;
|
||||
mat[14] = brightnessI;
|
||||
mat[18] = disabledAlpha;
|
||||
tempFilterMatrix.preConcat(tempBrightnessMatrix);
|
||||
return new ColorMatrixColorFilter(tempFilterMatrix);
|
||||
}
|
||||
|
||||
protected static final int getDisabledColor(int color) {
|
||||
int component = (Color.red(color) + Color.green(color) + Color.blue(color)) / 3;
|
||||
float scale = 1 - DISABLED_BRIGHTNESS;
|
||||
int brightnessI = (int) (255 * DISABLED_BRIGHTNESS);
|
||||
component = Math.min(Math.round(scale * component + brightnessI), FULLY_OPAQUE);
|
||||
return Color.rgb(component, component, component);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the bounds for the badge drawable based on the main icon bounds
|
||||
*/
|
||||
public static void setBadgeBounds(Drawable badge, Rect iconBounds) {
|
||||
int size = getBadgeSizeForIconSize(iconBounds.width());
|
||||
badge.setBounds(iconBounds.right - size, iconBounds.bottom - size,
|
||||
iconBounds.right, iconBounds.bottom);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateDrawable(Drawable who) {
|
||||
if (who == mBadge) {
|
||||
invalidateSelf();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduleDrawable(Drawable who, Runnable what, long when) {
|
||||
if (who == mBadge) {
|
||||
scheduleSelf(what, when);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unscheduleDrawable(Drawable who, Runnable what) {
|
||||
unscheduleSelf(what);
|
||||
}
|
||||
|
||||
protected static class FastBitmapConstantState extends ConstantState {
|
||||
protected final Bitmap mBitmap;
|
||||
protected final int mIconColor;
|
||||
|
||||
// These are initialized later so that subclasses don't need to
|
||||
// pass everything in constructor
|
||||
protected boolean mIsDisabled;
|
||||
private ConstantState mBadgeConstantState;
|
||||
|
||||
public FastBitmapConstantState(Bitmap bitmap, int color) {
|
||||
mBitmap = bitmap;
|
||||
mIconColor = color;
|
||||
}
|
||||
|
||||
protected FastBitmapDrawable createDrawable() {
|
||||
return new FastBitmapDrawable(mBitmap, mIconColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final FastBitmapDrawable newDrawable() {
|
||||
FastBitmapDrawable drawable = createDrawable();
|
||||
drawable.setIsDisabled(mIsDisabled);
|
||||
if (mBadgeConstantState != null) {
|
||||
drawable.setBadge(mBadgeConstantState.newDrawable());
|
||||
}
|
||||
return drawable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getChangingConfigurations() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +1,53 @@
|
||||
package com.android.launcher3.icons;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.Resources.Theme;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.DrawableWrapper;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
|
||||
/**
|
||||
* Extension of {@link DrawableWrapper} which scales the child drawables by a fixed amount.
|
||||
*/
|
||||
public class FixedScaleDrawable extends DrawableWrapper {
|
||||
|
||||
// TODO b/33553066 use the constant defined in MaskableIconDrawable
|
||||
private static final float LEGACY_ICON_SCALE = .7f * .6667f;
|
||||
private float mScaleX, mScaleY;
|
||||
|
||||
public FixedScaleDrawable() {
|
||||
super(new ColorDrawable());
|
||||
mScaleX = LEGACY_ICON_SCALE;
|
||||
mScaleY = LEGACY_ICON_SCALE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
int saveCount = canvas.save();
|
||||
canvas.scale(mScaleX, mScaleY,
|
||||
getBounds().exactCenterX(), getBounds().exactCenterY());
|
||||
super.draw(canvas);
|
||||
canvas.restoreToCount(saveCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) { }
|
||||
|
||||
@Override
|
||||
public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) { }
|
||||
|
||||
public void setScale(float scale) {
|
||||
float h = getIntrinsicHeight();
|
||||
float w = getIntrinsicWidth();
|
||||
mScaleX = scale * LEGACY_ICON_SCALE;
|
||||
mScaleY = scale * LEGACY_ICON_SCALE;
|
||||
if (h > w && w > 0) {
|
||||
mScaleX *= w / h;
|
||||
} else if (w > h && h > 0) {
|
||||
mScaleY *= h / w;
|
||||
}
|
||||
}
|
||||
}
|
||||
package com.android.launcher3.icons;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.Resources.Theme;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.DrawableWrapper;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
|
||||
/**
|
||||
* Extension of {@link DrawableWrapper} which scales the child drawables by a fixed amount.
|
||||
*/
|
||||
public class FixedScaleDrawable extends DrawableWrapper {
|
||||
|
||||
// TODO b/33553066 use the constant defined in MaskableIconDrawable
|
||||
private static final float LEGACY_ICON_SCALE = .7f * .6667f;
|
||||
private float mScaleX, mScaleY;
|
||||
|
||||
public FixedScaleDrawable() {
|
||||
super(new ColorDrawable());
|
||||
mScaleX = LEGACY_ICON_SCALE;
|
||||
mScaleY = LEGACY_ICON_SCALE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
int saveCount = canvas.save();
|
||||
canvas.scale(mScaleX, mScaleY,
|
||||
getBounds().exactCenterX(), getBounds().exactCenterY());
|
||||
super.draw(canvas);
|
||||
canvas.restoreToCount(saveCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) { }
|
||||
|
||||
@Override
|
||||
public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) { }
|
||||
|
||||
public void setScale(float scale) {
|
||||
float h = getIntrinsicHeight();
|
||||
float w = getIntrinsicWidth();
|
||||
mScaleX = scale * LEGACY_ICON_SCALE;
|
||||
mScaleY = scale * LEGACY_ICON_SCALE;
|
||||
if (h > w && w > 0) {
|
||||
mScaleX *= w / h;
|
||||
} else if (w > h && h > 0) {
|
||||
mScaleY *= h / w;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,127 +1,145 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
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 java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class GraphicsUtils {
|
||||
|
||||
private static final String TAG = "GraphicsUtils";
|
||||
|
||||
public static Runnable sOnNewBitmapRunnable = () -> { };
|
||||
|
||||
/**
|
||||
* Set the alpha component of {@code color} to be {@code alpha}. Unlike the support lib version,
|
||||
* it bounds the alpha in valid range instead of throwing an exception to allow for safer
|
||||
* interpolation of color animations
|
||||
*/
|
||||
@ColorInt
|
||||
public static int setColorAlphaBound(int color, int alpha) {
|
||||
if (alpha < 0) {
|
||||
alpha = 0;
|
||||
} else if (alpha > 255) {
|
||||
alpha = 255;
|
||||
}
|
||||
return (color & 0x00ffffff) | (alpha << 24);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compresses the bitmap to a byte array for serialization.
|
||||
*/
|
||||
public static byte[] flattenBitmap(Bitmap bitmap) {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(getExpectedBitmapSize(bitmap));
|
||||
try {
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
|
||||
out.flush();
|
||||
out.close();
|
||||
return out.toByteArray();
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Could not write bitmap");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try go guesstimate how much space the icon will take when serialized to avoid unnecessary
|
||||
* allocations/copies during the write (4 bytes per pixel).
|
||||
*/
|
||||
static int getExpectedBitmapSize(Bitmap bitmap) {
|
||||
return bitmap.getWidth() * bitmap.getHeight() * 4;
|
||||
}
|
||||
|
||||
public static int getArea(Region r) {
|
||||
RegionIterator itr = new RegionIterator(r);
|
||||
int area = 0;
|
||||
Rect tempRect = new Rect();
|
||||
while (itr.next(tempRect)) {
|
||||
area += tempRect.width() * tempRect.height();
|
||||
}
|
||||
return area;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to track new bitmap creation
|
||||
*/
|
||||
public static void noteNewBitmapCreated() {
|
||||
sOnNewBitmapRunnable.run();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the default path to be used by an icon
|
||||
*/
|
||||
public static Path getShapePath(int size) {
|
||||
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
|
||||
*/
|
||||
public static int getAttrColor(Context context, int attr) {
|
||||
TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
|
||||
int colorAccent = ta.getColor(0, 0);
|
||||
ta.recycle();
|
||||
return colorAccent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the alpha corresponding to the theme attribute {@param attr}
|
||||
*/
|
||||
public static float getFloat(Context context, int attr, float defValue) {
|
||||
TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
|
||||
float value = ta.getFloat(0, defValue);
|
||||
ta.recycle();
|
||||
return value;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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;
|
||||
|
||||
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;
|
||||
|
||||
public class GraphicsUtils {
|
||||
|
||||
private static final String TAG = "GraphicsUtils";
|
||||
private static final float MASK_SIZE = 100f;
|
||||
|
||||
public static Runnable sOnNewBitmapRunnable = () -> { };
|
||||
|
||||
/**
|
||||
* Set the alpha component of {@code color} to be {@code alpha}. Unlike the support lib version,
|
||||
* it bounds the alpha in valid range instead of throwing an exception to allow for safer
|
||||
* interpolation of color animations
|
||||
*/
|
||||
@ColorInt
|
||||
public static int setColorAlphaBound(int color, int alpha) {
|
||||
if (alpha < 0) {
|
||||
alpha = 0;
|
||||
} else if (alpha > 255) {
|
||||
alpha = 255;
|
||||
}
|
||||
return (color & 0x00ffffff) | (alpha << 24);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compresses the bitmap to a byte array for serialization.
|
||||
*/
|
||||
public static byte[] flattenBitmap(Bitmap bitmap) {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(getExpectedBitmapSize(bitmap));
|
||||
try {
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
|
||||
out.flush();
|
||||
out.close();
|
||||
return out.toByteArray();
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Could not write bitmap");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try go guesstimate how much space the icon will take when serialized to avoid unnecessary
|
||||
* allocations/copies during the write (4 bytes per pixel).
|
||||
*/
|
||||
static int getExpectedBitmapSize(Bitmap bitmap) {
|
||||
return bitmap.getWidth() * bitmap.getHeight() * 4;
|
||||
}
|
||||
|
||||
public static int getArea(Region r) {
|
||||
RegionIterator itr = new RegionIterator(r);
|
||||
int area = 0;
|
||||
Rect tempRect = new Rect();
|
||||
while (itr.next(tempRect)) {
|
||||
area += tempRect.width() * tempRect.height();
|
||||
}
|
||||
return area;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to track new bitmap creation
|
||||
*/
|
||||
public static void noteNewBitmapCreated() {
|
||||
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
|
||||
*/
|
||||
public static int getAttrColor(Context context, int attr) {
|
||||
TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
|
||||
int colorAccent = ta.getColor(0, 0);
|
||||
ta.recycle();
|
||||
return colorAccent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the alpha corresponding to the theme attribute {@param attr}
|
||||
*/
|
||||
public static float getFloat(Context context, int attr, float defValue) {
|
||||
TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
|
||||
float value = ta.getFloat(0, defValue);
|
||||
ta.recycle();
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -1,410 +1,411 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
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 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
|
||||
private static final float MAX_CIRCLE_AREA_FACTOR = 380.0f / 576;
|
||||
|
||||
private static final float CIRCLE_AREA_BY_RECT = (float) Math.PI / 4;
|
||||
|
||||
// Slope used to calculate icon visible area to full icon size for any generic shaped icon.
|
||||
private static final float LINEAR_SCALE_SLOPE =
|
||||
(MAX_CIRCLE_AREA_FACTOR - MAX_SQUARE_AREA_FACTOR) / (1 - CIRCLE_AREA_BY_RECT);
|
||||
|
||||
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) {
|
||||
// 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);
|
||||
mCanvas = new Canvas(mBitmap);
|
||||
mPixels = new byte[mMaxSize * mMaxSize];
|
||||
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) {
|
||||
float hullByRect = hullArea / boundingArea;
|
||||
float scaleRequired;
|
||||
if (hullByRect < CIRCLE_AREA_BY_RECT) {
|
||||
scaleRequired = MAX_CIRCLE_AREA_FACTOR;
|
||||
} else {
|
||||
scaleRequired = MAX_SQUARE_AREA_FACTOR + LINEAR_SCALE_SLOPE * (1 - hullByRect);
|
||||
}
|
||||
|
||||
float areaScale = hullArea / fullArea;
|
||||
// Use sqrt of the final ratio as the images is scaled across both width and height.
|
||||
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.
|
||||
*
|
||||
* We first calculate the convex hull of the visible portion of the icon.
|
||||
* This hull then compared with the bounding rectangle of the hull to find how closely it
|
||||
* resembles a circle and a square, by comparing the ratio of the areas. Note that this is not an
|
||||
* ideal solution but it gives satisfactory result without affecting the performance.
|
||||
*
|
||||
* 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) {
|
||||
if (d instanceof AdaptiveIconDrawable) {
|
||||
if (mAdaptiveIconScale == SCALE_NOT_INITIALIZED) {
|
||||
mAdaptiveIconScale = normalizeAdaptiveIcon(d, mMaxSize, mAdaptiveIconBounds);
|
||||
}
|
||||
if (outBounds != null) {
|
||||
outBounds.set(mAdaptiveIconBounds);
|
||||
}
|
||||
return mAdaptiveIconScale;
|
||||
}
|
||||
int width = d.getIntrinsicWidth();
|
||||
int height = d.getIntrinsicHeight();
|
||||
if (width <= 0 || height <= 0) {
|
||||
width = width <= 0 || width > mMaxSize ? mMaxSize : width;
|
||||
height = height <= 0 || height > mMaxSize ? mMaxSize : height;
|
||||
} else if (width > mMaxSize || height > mMaxSize) {
|
||||
int max = Math.max(width, height);
|
||||
width = mMaxSize * width / max;
|
||||
height = mMaxSize * height / max;
|
||||
}
|
||||
|
||||
mBitmap.eraseColor(Color.TRANSPARENT);
|
||||
d.setBounds(0, 0, width, height);
|
||||
d.draw(mCanvas);
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.wrap(mPixels);
|
||||
buffer.rewind();
|
||||
mBitmap.copyPixelsToBuffer(buffer);
|
||||
|
||||
// Overall bounds of the visible icon.
|
||||
int topY = -1;
|
||||
int bottomY = -1;
|
||||
int leftX = mMaxSize + 1;
|
||||
int rightX = -1;
|
||||
|
||||
// Create border by going through all pixels one row at a time and for each row find
|
||||
// the first and the last non-transparent pixel. Set those values to mLeftBorder and
|
||||
// mRightBorder and use -1 if there are no visible pixel in the row.
|
||||
|
||||
// buffer position
|
||||
int index = 0;
|
||||
// buffer shift after every row, width of buffer = mMaxSize
|
||||
int rowSizeDiff = mMaxSize - width;
|
||||
// first and last position for any row.
|
||||
int firstX, lastX;
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
firstX = lastX = -1;
|
||||
for (int x = 0; x < width; x++) {
|
||||
if ((mPixels[index] & 0xFF) > MIN_VISIBLE_ALPHA) {
|
||||
if (firstX == -1) {
|
||||
firstX = x;
|
||||
}
|
||||
lastX = x;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
index += rowSizeDiff;
|
||||
|
||||
mLeftBorder[y] = firstX;
|
||||
mRightBorder[y] = lastX;
|
||||
|
||||
// If there is at least one visible pixel, update the overall bounds.
|
||||
if (firstX != -1) {
|
||||
bottomY = y;
|
||||
if (topY == -1) {
|
||||
topY = y;
|
||||
}
|
||||
|
||||
leftX = Math.min(leftX, firstX);
|
||||
rightX = Math.max(rightX, lastX);
|
||||
}
|
||||
}
|
||||
|
||||
if (topY == -1 || rightX == -1) {
|
||||
// No valid pixels found. Do not scale.
|
||||
return 1;
|
||||
}
|
||||
|
||||
convertToConvexArray(mLeftBorder, 1, topY, bottomY);
|
||||
convertToConvexArray(mRightBorder, -1, topY, bottomY);
|
||||
|
||||
// Area of the convex hull
|
||||
float area = 0;
|
||||
for (int y = 0; y < height; y++) {
|
||||
if (mLeftBorder[y] <= -1) {
|
||||
continue;
|
||||
}
|
||||
area += mRightBorder[y] - mLeftBorder[y] + 1;
|
||||
}
|
||||
|
||||
mBounds.left = leftX;
|
||||
mBounds.right = rightX;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies {@param xCoordinates} to represent a convex border. Fills in all missing values
|
||||
* (except on either ends) with appropriate values.
|
||||
* @param xCoordinates map of x coordinate per y.
|
||||
* @param direction 1 for left border and -1 for right border.
|
||||
* @param topY the first Y position (inclusive) with a valid value.
|
||||
* @param bottomY the last Y position (inclusive) with a valid value.
|
||||
*/
|
||||
private static void convertToConvexArray(
|
||||
float[] xCoordinates, int direction, int topY, int bottomY) {
|
||||
int total = xCoordinates.length;
|
||||
// The tangent at each pixel.
|
||||
float[] angles = new float[total - 1];
|
||||
|
||||
int first = topY; // First valid y coordinate
|
||||
int last = -1; // Last valid y coordinate which didn't have a missing value
|
||||
|
||||
float lastAngle = Float.MAX_VALUE;
|
||||
|
||||
for (int i = topY + 1; i <= bottomY; i++) {
|
||||
if (xCoordinates[i] <= -1) {
|
||||
continue;
|
||||
}
|
||||
int start;
|
||||
|
||||
if (lastAngle == Float.MAX_VALUE) {
|
||||
start = first;
|
||||
} else {
|
||||
float currentAngle = (xCoordinates[i] - xCoordinates[last]) / (i - last);
|
||||
start = last;
|
||||
// If this position creates a concave angle, keep moving up until we find a
|
||||
// position which creates a convex angle.
|
||||
if ((currentAngle - lastAngle) * direction < 0) {
|
||||
while (start > first) {
|
||||
start --;
|
||||
currentAngle = (xCoordinates[i] - xCoordinates[start]) / (i - start);
|
||||
if ((currentAngle - angles[start]) * direction >= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset from last check
|
||||
lastAngle = (xCoordinates[i] - xCoordinates[start]) / (i - start);
|
||||
// Update all the points from start.
|
||||
for (int j = start; j < i; j++) {
|
||||
angles[j] = lastAngle;
|
||||
xCoordinates[j] = xCoordinates[start] + lastAngle * (j - start);
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Copyright (C) 2015 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.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 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
|
||||
private static final float MAX_CIRCLE_AREA_FACTOR = 380.0f / 576;
|
||||
|
||||
private static final float CIRCLE_AREA_BY_RECT = (float) Math.PI / 4;
|
||||
|
||||
// Slope used to calculate icon visible area to full icon size for any generic shaped icon.
|
||||
private static final float LINEAR_SCALE_SLOPE =
|
||||
(MAX_CIRCLE_AREA_FACTOR - MAX_SQUARE_AREA_FACTOR) / (1 - CIRCLE_AREA_BY_RECT);
|
||||
|
||||
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) {
|
||||
// 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);
|
||||
mCanvas = new Canvas(mBitmap);
|
||||
mPixels = new byte[mMaxSize * mMaxSize];
|
||||
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) {
|
||||
float hullByRect = hullArea / boundingArea;
|
||||
float scaleRequired;
|
||||
if (hullByRect < CIRCLE_AREA_BY_RECT) {
|
||||
scaleRequired = MAX_CIRCLE_AREA_FACTOR;
|
||||
} else {
|
||||
scaleRequired = MAX_SQUARE_AREA_FACTOR + LINEAR_SCALE_SLOPE * (1 - hullByRect);
|
||||
}
|
||||
|
||||
float areaScale = hullArea / fullArea;
|
||||
// Use sqrt of the final ratio as the images is scaled across both width and height.
|
||||
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.
|
||||
*
|
||||
* We first calculate the convex hull of the visible portion of the icon.
|
||||
* This hull then compared with the bounding rectangle of the hull to find how closely it
|
||||
* resembles a circle and a square, by comparing the ratio of the areas. Note that this is not an
|
||||
* ideal solution but it gives satisfactory result without affecting the performance.
|
||||
*
|
||||
* 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) {
|
||||
if (d instanceof AdaptiveIconDrawable) {
|
||||
if (mAdaptiveIconScale == SCALE_NOT_INITIALIZED) {
|
||||
mAdaptiveIconScale = normalizeAdaptiveIcon(d, mMaxSize, mAdaptiveIconBounds);
|
||||
}
|
||||
if (outBounds != null) {
|
||||
outBounds.set(mAdaptiveIconBounds);
|
||||
}
|
||||
return mAdaptiveIconScale;
|
||||
}
|
||||
int width = d.getIntrinsicWidth();
|
||||
int height = d.getIntrinsicHeight();
|
||||
if (width <= 0 || height <= 0) {
|
||||
width = width <= 0 || width > mMaxSize ? mMaxSize : width;
|
||||
height = height <= 0 || height > mMaxSize ? mMaxSize : height;
|
||||
} else if (width > mMaxSize || height > mMaxSize) {
|
||||
int max = Math.max(width, height);
|
||||
width = mMaxSize * width / max;
|
||||
height = mMaxSize * height / max;
|
||||
}
|
||||
|
||||
mBitmap.eraseColor(Color.TRANSPARENT);
|
||||
d.setBounds(0, 0, width, height);
|
||||
d.draw(mCanvas);
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.wrap(mPixels);
|
||||
buffer.rewind();
|
||||
mBitmap.copyPixelsToBuffer(buffer);
|
||||
|
||||
// Overall bounds of the visible icon.
|
||||
int topY = -1;
|
||||
int bottomY = -1;
|
||||
int leftX = mMaxSize + 1;
|
||||
int rightX = -1;
|
||||
|
||||
// Create border by going through all pixels one row at a time and for each row find
|
||||
// the first and the last non-transparent pixel. Set those values to mLeftBorder and
|
||||
// mRightBorder and use -1 if there are no visible pixel in the row.
|
||||
|
||||
// buffer position
|
||||
int index = 0;
|
||||
// buffer shift after every row, width of buffer = mMaxSize
|
||||
int rowSizeDiff = mMaxSize - width;
|
||||
// first and last position for any row.
|
||||
int firstX, lastX;
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
firstX = lastX = -1;
|
||||
for (int x = 0; x < width; x++) {
|
||||
if ((mPixels[index] & 0xFF) > MIN_VISIBLE_ALPHA) {
|
||||
if (firstX == -1) {
|
||||
firstX = x;
|
||||
}
|
||||
lastX = x;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
index += rowSizeDiff;
|
||||
|
||||
mLeftBorder[y] = firstX;
|
||||
mRightBorder[y] = lastX;
|
||||
|
||||
// If there is at least one visible pixel, update the overall bounds.
|
||||
if (firstX != -1) {
|
||||
bottomY = y;
|
||||
if (topY == -1) {
|
||||
topY = y;
|
||||
}
|
||||
|
||||
leftX = Math.min(leftX, firstX);
|
||||
rightX = Math.max(rightX, lastX);
|
||||
}
|
||||
}
|
||||
|
||||
if (topY == -1 || rightX == -1) {
|
||||
// No valid pixels found. Do not scale.
|
||||
return 1;
|
||||
}
|
||||
|
||||
convertToConvexArray(mLeftBorder, 1, topY, bottomY);
|
||||
convertToConvexArray(mRightBorder, -1, topY, bottomY);
|
||||
|
||||
// Area of the convex hull
|
||||
float area = 0;
|
||||
for (int y = 0; y < height; y++) {
|
||||
if (mLeftBorder[y] <= -1) {
|
||||
continue;
|
||||
}
|
||||
area += mRightBorder[y] - mLeftBorder[y] + 1;
|
||||
}
|
||||
|
||||
mBounds.left = leftX;
|
||||
mBounds.right = rightX;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies {@param xCoordinates} to represent a convex border. Fills in all missing values
|
||||
* (except on either ends) with appropriate values.
|
||||
* @param xCoordinates map of x coordinate per y.
|
||||
* @param direction 1 for left border and -1 for right border.
|
||||
* @param topY the first Y position (inclusive) with a valid value.
|
||||
* @param bottomY the last Y position (inclusive) with a valid value.
|
||||
*/
|
||||
private static void convertToConvexArray(
|
||||
float[] xCoordinates, int direction, int topY, int bottomY) {
|
||||
int total = xCoordinates.length;
|
||||
// The tangent at each pixel.
|
||||
float[] angles = new float[total - 1];
|
||||
|
||||
int first = topY; // First valid y coordinate
|
||||
int last = -1; // Last valid y coordinate which didn't have a missing value
|
||||
|
||||
float lastAngle = Float.MAX_VALUE;
|
||||
|
||||
for (int i = topY + 1; i <= bottomY; i++) {
|
||||
if (xCoordinates[i] <= -1) {
|
||||
continue;
|
||||
}
|
||||
int start;
|
||||
|
||||
if (lastAngle == Float.MAX_VALUE) {
|
||||
start = first;
|
||||
} else {
|
||||
float currentAngle = (xCoordinates[i] - xCoordinates[last]) / (i - last);
|
||||
start = last;
|
||||
// If this position creates a concave angle, keep moving up until we find a
|
||||
// position which creates a convex angle.
|
||||
if ((currentAngle - lastAngle) * direction < 0) {
|
||||
while (start > first) {
|
||||
start --;
|
||||
currentAngle = (xCoordinates[i] - xCoordinates[start]) / (i - start);
|
||||
if ((currentAngle - angles[start]) * direction >= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset from last check
|
||||
lastAngle = (xCoordinates[i] - xCoordinates[start]) / (i - start);
|
||||
// Update all the points from start.
|
||||
for (int j = start; j < i; j++) {
|
||||
angles[j] = lastAngle;
|
||||
xCoordinates[j] = xCoordinates[start] + lastAngle * (j - start);
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -1,351 +1,351 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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 static android.content.Intent.ACTION_DATE_CHANGED;
|
||||
import static android.content.Intent.ACTION_TIMEZONE_CHANGED;
|
||||
import static android.content.Intent.ACTION_TIME_CHANGED;
|
||||
import static android.content.res.Resources.ID_NULL;
|
||||
import static android.graphics.drawable.AdaptiveIconDrawable.getExtraInsetFraction;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.BroadcastReceiver;
|
||||
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.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.AdaptiveIconDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
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.Nullable;
|
||||
import androidx.core.os.BuildCompat;
|
||||
|
||||
import com.android.launcher3.util.SafeCloseable;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Class to handle icon loading from different packages
|
||||
*/
|
||||
public class IconProvider {
|
||||
|
||||
private final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
|
||||
private 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();
|
||||
|
||||
private static final String ICON_METADATA_KEY_PREFIX = ".dynamic_icons";
|
||||
|
||||
private static final String SYSTEM_STATE_SEPARATOR = " ";
|
||||
|
||||
protected final Context mContext;
|
||||
private final ComponentName mCalendar;
|
||||
private final ComponentName mClock;
|
||||
|
||||
public IconProvider(Context context) {
|
||||
mContext = context;
|
||||
mCalendar = parseComponentOrNull(context, R.string.calendar_component_name);
|
||||
mClock = parseComponentOrNull(context, R.string.clock_component_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds any modification to the provided systemState for dynamic icons. This system state
|
||||
* is used by caches to check for icon invalidation.
|
||||
*/
|
||||
public String getSystemStateForPackage(String systemState, String packageName) {
|
||||
if (mCalendar != null && mCalendar.getPackageName().equals(packageName)) {
|
||||
return systemState + SYSTEM_STATE_SEPARATOR + getDay();
|
||||
} else {
|
||||
return systemState;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the icon for the provided LauncherActivityInfo
|
||||
*/
|
||||
public Drawable getIcon(LauncherActivityInfo info, int iconDpi) {
|
||||
return getIconWithOverrides(info.getApplicationInfo().packageName, iconDpi,
|
||||
() -> info.getIcon(iconDpi));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the icon for the provided activity info
|
||||
*/
|
||||
public Drawable getIcon(ActivityInfo info) {
|
||||
return getIcon(info, mContext.getResources().getConfiguration().densityDpi);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the icon for the provided activity info
|
||||
*/
|
||||
public Drawable getIcon(ActivityInfo info, int iconDpi) {
|
||||
return getIconWithOverrides(info.applicationInfo.packageName, iconDpi,
|
||||
() -> loadActivityInfoIcon(info, iconDpi));
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.TIRAMISU)
|
||||
private Drawable getIconWithOverrides(String packageName, int iconDpi,
|
||||
Supplier<Drawable> fallback) {
|
||||
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);
|
||||
}
|
||||
if (icon == null) {
|
||||
icon = fallback.get();
|
||||
if (ATLEAST_T && icon instanceof AdaptiveIconDrawable && td != null) {
|
||||
AdaptiveIconDrawable aid = (AdaptiveIconDrawable) icon;
|
||||
if (aid.getMonochrome() == null) {
|
||||
icon = new AdaptiveIconDrawable(aid.getBackground(),
|
||||
aid.getForeground(), td.loadPaddedDrawable());
|
||||
}
|
||||
}
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
protected ThemeData getThemeDataForPackage(String packageName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private Drawable loadActivityInfoIcon(ActivityInfo ai, int density) {
|
||||
final int iconRes = ai.getIconResource();
|
||||
Drawable icon = null;
|
||||
// Get the preferred density icon from the app's resources
|
||||
if (density != 0 && iconRes != 0) {
|
||||
try {
|
||||
final Resources resources = mContext.getPackageManager()
|
||||
.getResourcesForApplication(ai.applicationInfo);
|
||||
icon = resources.getDrawableForDensity(iconRes, density);
|
||||
} catch (NameNotFoundException | Resources.NotFoundException exc) { }
|
||||
}
|
||||
// Get the default density icon
|
||||
if (icon == null) {
|
||||
icon = ai.loadIcon(mContext.getPackageManager());
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.TIRAMISU)
|
||||
private Drawable loadCalendarDrawable(int iconDpi, @Nullable ThemeData td) {
|
||||
PackageManager pm = mContext.getPackageManager();
|
||||
try {
|
||||
final Bundle metadata = pm.getActivityInfo(
|
||||
mCalendar,
|
||||
PackageManager.GET_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA)
|
||||
.metaData;
|
||||
final Resources resources = pm.getResourcesForApplication(mCalendar.getPackageName());
|
||||
final int id = getDynamicIconId(metadata, resources);
|
||||
if (id != ID_NULL) {
|
||||
if (DEBUG) Log.d(TAG, "Got icon #" + id);
|
||||
Drawable drawable = resources.getDrawableForDensity(id, iconDpi, null /* theme */);
|
||||
if (ATLEAST_T && drawable instanceof AdaptiveIconDrawable && td != null) {
|
||||
AdaptiveIconDrawable aid = (AdaptiveIconDrawable) drawable;
|
||||
if (aid.getMonochrome() != null) {
|
||||
return drawable;
|
||||
}
|
||||
if ("array".equals(td.mResources.getResourceTypeName(td.mResID))) {
|
||||
TypedArray ta = td.mResources.obtainTypedArray(td.mResID);
|
||||
int monoId = ta.getResourceId(IconProvider.getDay(), ID_NULL);
|
||||
ta.recycle();
|
||||
return monoId == ID_NULL ? drawable
|
||||
: new AdaptiveIconDrawable(aid.getBackground(), aid.getForeground(),
|
||||
new ThemeData(td.mResources, monoId).loadPaddedDrawable());
|
||||
}
|
||||
}
|
||||
return drawable;
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Could not get activityinfo or resources for package: "
|
||||
+ mCalendar.getPackageName());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param metadata metadata of the default activity of Calendar
|
||||
* @param resources from the Calendar package
|
||||
* @return the resource id for today's Calendar icon; 0 if resources cannot be found.
|
||||
*/
|
||||
private int getDynamicIconId(Bundle metadata, Resources resources) {
|
||||
if (metadata == null) {
|
||||
return ID_NULL;
|
||||
}
|
||||
String key = mCalendar.getPackageName() + ICON_METADATA_KEY_PREFIX;
|
||||
final int arrayId = metadata.getInt(key, ID_NULL);
|
||||
if (arrayId == ID_NULL) {
|
||||
return ID_NULL;
|
||||
}
|
||||
try {
|
||||
return resources.obtainTypedArray(arrayId).getResourceId(getDay(), ID_NULL);
|
||||
} catch (Resources.NotFoundException e) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "package defines '" + key + "' but corresponding array not found");
|
||||
}
|
||||
return ID_NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Today's day of the month, zero-indexed.
|
||||
*/
|
||||
private static int getDay() {
|
||||
return Calendar.getInstance().get(Calendar.DAY_OF_MONTH) - 1;
|
||||
}
|
||||
|
||||
private static ComponentName parseComponentOrNull(Context context, int resId) {
|
||||
String cn = context.getString(resId);
|
||||
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.
|
||||
*/
|
||||
public SafeCloseable registerIconChangeListener(IconChangeListener listener, Handler handler) {
|
||||
return new IconChangeReceiver(listener, handler);
|
||||
}
|
||||
|
||||
public static class ThemeData {
|
||||
|
||||
final Resources mResources;
|
||||
final int mResID;
|
||||
|
||||
public ThemeData(Resources resources, int resID) {
|
||||
mResources = resources;
|
||||
mResID = resID;
|
||||
}
|
||||
|
||||
Drawable loadPaddedDrawable() {
|
||||
if (!"drawable".equals(mResources.getResourceTypeName(mResID))) {
|
||||
return null;
|
||||
}
|
||||
Drawable d = mResources.getDrawable(mResID).mutate();
|
||||
d = new InsetDrawable(d, .2f);
|
||||
float inset = getExtraInsetFraction() / (1 + 2 * getExtraInsetFraction());
|
||||
Drawable fg = new InsetDrawable(d, inset);
|
||||
return fg;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
filter.addAction(Intent.ACTION_TIME_CHANGED);
|
||||
filter.addAction(ACTION_DATE_CHANGED);
|
||||
}
|
||||
mContext.registerReceiver(this, filter, null, handler);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
switch (intent.getAction()) {
|
||||
case ACTION_TIMEZONE_CHANGED:
|
||||
if (mClock != null) {
|
||||
mCallback.onAppIconChanged(mClock.getPackageName(), Process.myUserHandle());
|
||||
}
|
||||
// follow through
|
||||
case ACTION_DATE_CHANGED:
|
||||
case ACTION_TIME_CHANGED:
|
||||
if (mCalendar != null) {
|
||||
for (UserHandle user
|
||||
: context.getSystemService(UserManager.class).getUserProfiles()) {
|
||||
mCallback.onAppIconChanged(mCalendar.getPackageName(), user);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener for receiving icon changes
|
||||
*/
|
||||
public interface IconChangeListener {
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Copyright (C) 2019 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 static android.content.Intent.ACTION_DATE_CHANGED;
|
||||
import static android.content.Intent.ACTION_TIMEZONE_CHANGED;
|
||||
import static android.content.Intent.ACTION_TIME_CHANGED;
|
||||
import static android.content.res.Resources.ID_NULL;
|
||||
import static android.graphics.drawable.AdaptiveIconDrawable.getExtraInsetFraction;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.BroadcastReceiver;
|
||||
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.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.AdaptiveIconDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
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.Nullable;
|
||||
import androidx.core.os.BuildCompat;
|
||||
|
||||
import com.android.launcher3.util.SafeCloseable;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
private static final String ICON_METADATA_KEY_PREFIX = ".dynamic_icons";
|
||||
|
||||
private static final String SYSTEM_STATE_SEPARATOR = " ";
|
||||
|
||||
protected final Context mContext;
|
||||
private final ComponentName mCalendar;
|
||||
private final ComponentName mClock;
|
||||
|
||||
public IconProvider(Context context) {
|
||||
mContext = context;
|
||||
mCalendar = parseComponentOrNull(context, R.string.calendar_component_name);
|
||||
mClock = parseComponentOrNull(context, R.string.clock_component_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds any modification to the provided systemState for dynamic icons. This system state
|
||||
* is used by caches to check for icon invalidation.
|
||||
*/
|
||||
public String getSystemStateForPackage(String systemState, String packageName) {
|
||||
if (mCalendar != null && mCalendar.getPackageName().equals(packageName)) {
|
||||
return systemState + SYSTEM_STATE_SEPARATOR + getDay();
|
||||
} else {
|
||||
return systemState;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the icon for the provided LauncherActivityInfo
|
||||
*/
|
||||
public Drawable getIcon(LauncherActivityInfo info, int iconDpi) {
|
||||
return getIconWithOverrides(info.getApplicationInfo().packageName, iconDpi,
|
||||
() -> info.getIcon(iconDpi));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the icon for the provided activity info
|
||||
*/
|
||||
public Drawable getIcon(ActivityInfo info) {
|
||||
return getIcon(info, mContext.getResources().getConfiguration().densityDpi);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the icon for the provided activity info
|
||||
*/
|
||||
public Drawable getIcon(ActivityInfo info, int iconDpi) {
|
||||
return getIconWithOverrides(info.applicationInfo.packageName, iconDpi,
|
||||
() -> loadActivityInfoIcon(info, iconDpi));
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.TIRAMISU)
|
||||
private Drawable getIconWithOverrides(String packageName, int iconDpi,
|
||||
Supplier<Drawable> fallback) {
|
||||
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);
|
||||
}
|
||||
if (icon == null) {
|
||||
icon = fallback.get();
|
||||
if (ATLEAST_T && icon instanceof AdaptiveIconDrawable && td != null) {
|
||||
AdaptiveIconDrawable aid = (AdaptiveIconDrawable) icon;
|
||||
if (aid.getMonochrome() == null) {
|
||||
icon = new AdaptiveIconDrawable(aid.getBackground(),
|
||||
aid.getForeground(), td.loadPaddedDrawable());
|
||||
}
|
||||
}
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
protected ThemeData getThemeDataForPackage(String packageName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private Drawable loadActivityInfoIcon(ActivityInfo ai, int density) {
|
||||
final int iconRes = ai.getIconResource();
|
||||
Drawable icon = null;
|
||||
// Get the preferred density icon from the app's resources
|
||||
if (density != 0 && iconRes != 0) {
|
||||
try {
|
||||
final Resources resources = mContext.getPackageManager()
|
||||
.getResourcesForApplication(ai.applicationInfo);
|
||||
icon = resources.getDrawableForDensity(iconRes, density);
|
||||
} catch (NameNotFoundException | Resources.NotFoundException exc) { }
|
||||
}
|
||||
// Get the default density icon
|
||||
if (icon == null) {
|
||||
icon = ai.loadIcon(mContext.getPackageManager());
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.TIRAMISU)
|
||||
private Drawable loadCalendarDrawable(int iconDpi, @Nullable ThemeData td) {
|
||||
PackageManager pm = mContext.getPackageManager();
|
||||
try {
|
||||
final Bundle metadata = pm.getActivityInfo(
|
||||
mCalendar,
|
||||
PackageManager.GET_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA)
|
||||
.metaData;
|
||||
final Resources resources = pm.getResourcesForApplication(mCalendar.getPackageName());
|
||||
final int id = getDynamicIconId(metadata, resources);
|
||||
if (id != ID_NULL) {
|
||||
if (DEBUG) Log.d(TAG, "Got icon #" + id);
|
||||
Drawable drawable = resources.getDrawableForDensity(id, iconDpi, null /* theme */);
|
||||
if (ATLEAST_T && drawable instanceof AdaptiveIconDrawable && td != null) {
|
||||
AdaptiveIconDrawable aid = (AdaptiveIconDrawable) drawable;
|
||||
if (aid.getMonochrome() != null) {
|
||||
return drawable;
|
||||
}
|
||||
if ("array".equals(td.mResources.getResourceTypeName(td.mResID))) {
|
||||
TypedArray ta = td.mResources.obtainTypedArray(td.mResID);
|
||||
int monoId = ta.getResourceId(IconProvider.getDay(), ID_NULL);
|
||||
ta.recycle();
|
||||
return monoId == ID_NULL ? drawable
|
||||
: new AdaptiveIconDrawable(aid.getBackground(), aid.getForeground(),
|
||||
new ThemeData(td.mResources, monoId).loadPaddedDrawable());
|
||||
}
|
||||
}
|
||||
return drawable;
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Could not get activityinfo or resources for package: "
|
||||
+ mCalendar.getPackageName());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param metadata metadata of the default activity of Calendar
|
||||
* @param resources from the Calendar package
|
||||
* @return the resource id for today's Calendar icon; 0 if resources cannot be found.
|
||||
*/
|
||||
private int getDynamicIconId(Bundle metadata, Resources resources) {
|
||||
if (metadata == null) {
|
||||
return ID_NULL;
|
||||
}
|
||||
String key = mCalendar.getPackageName() + ICON_METADATA_KEY_PREFIX;
|
||||
final int arrayId = metadata.getInt(key, ID_NULL);
|
||||
if (arrayId == ID_NULL) {
|
||||
return ID_NULL;
|
||||
}
|
||||
try {
|
||||
return resources.obtainTypedArray(arrayId).getResourceId(getDay(), ID_NULL);
|
||||
} catch (Resources.NotFoundException e) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "package defines '" + key + "' but corresponding array not found");
|
||||
}
|
||||
return ID_NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Today's day of the month, zero-indexed.
|
||||
*/
|
||||
private static int getDay() {
|
||||
return Calendar.getInstance().get(Calendar.DAY_OF_MONTH) - 1;
|
||||
}
|
||||
|
||||
private static ComponentName parseComponentOrNull(Context context, int resId) {
|
||||
String cn = context.getString(resId);
|
||||
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.
|
||||
*/
|
||||
public SafeCloseable registerIconChangeListener(IconChangeListener listener, Handler handler) {
|
||||
return new IconChangeReceiver(listener, handler);
|
||||
}
|
||||
|
||||
public static class ThemeData {
|
||||
|
||||
final Resources mResources;
|
||||
final int mResID;
|
||||
|
||||
public ThemeData(Resources resources, int resID) {
|
||||
mResources = resources;
|
||||
mResID = resID;
|
||||
}
|
||||
|
||||
Drawable loadPaddedDrawable() {
|
||||
if (!"drawable".equals(mResources.getResourceTypeName(mResID))) {
|
||||
return null;
|
||||
}
|
||||
Drawable d = mResources.getDrawable(mResID).mutate();
|
||||
d = new InsetDrawable(d, .2f);
|
||||
float inset = getExtraInsetFraction() / (1 + 2 * getExtraInsetFraction());
|
||||
Drawable fg = new InsetDrawable(d, inset);
|
||||
return fg;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
filter.addAction(Intent.ACTION_TIME_CHANGED);
|
||||
filter.addAction(ACTION_DATE_CHANGED);
|
||||
}
|
||||
mContext.registerReceiver(this, filter, null, handler);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
switch (intent.getAction()) {
|
||||
case ACTION_TIMEZONE_CHANGED:
|
||||
if (mClock != null) {
|
||||
mCallback.onAppIconChanged(mClock.getPackageName(), Process.myUserHandle());
|
||||
}
|
||||
// follow through
|
||||
case ACTION_DATE_CHANGED:
|
||||
case ACTION_TIME_CHANGED:
|
||||
if (mCalendar != null) {
|
||||
for (UserHandle user
|
||||
: context.getSystemService(UserManager.class).getUserProfiles()) {
|
||||
mCallback.onAppIconChanged(mCalendar.getPackageName(), user);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener for receiving icon changes
|
||||
*/
|
||||
public interface IconChangeListener {
|
||||
|
||||
/**
|
||||
* Called when the icon for a particular app changes
|
||||
*/
|
||||
void onAppIconChanged(String packageName, UserHandle user);
|
||||
|
||||
/**
|
||||
* Called when the global icon state changed, which can typically affect all icons
|
||||
*/
|
||||
void onSystemIconStateChanged(String iconState);
|
||||
}
|
||||
}
|
||||
@@ -1,79 +1,79 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffColorFilter;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import androidx.core.graphics.ColorUtils;
|
||||
|
||||
/**
|
||||
* Subclass which draws a placeholder icon when the actual icon is not yet loaded
|
||||
*/
|
||||
public class PlaceHolderIconDrawable extends FastBitmapDrawable {
|
||||
|
||||
// Path in [0, 100] bounds.
|
||||
private final Path mProgressPath;
|
||||
|
||||
public PlaceHolderIconDrawable(BitmapInfo info, Context context) {
|
||||
super(info);
|
||||
|
||||
mProgressPath = GraphicsUtils.getShapePath(100);
|
||||
mPaint.setColor(ColorUtils.compositeColors(
|
||||
GraphicsUtils.getAttrColor(context, R.attr.loadingIconColor), info.color));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawInternal(Canvas canvas, Rect bounds) {
|
||||
int saveCount = canvas.save();
|
||||
canvas.translate(bounds.left, bounds.top);
|
||||
canvas.scale(bounds.width() / 100f, bounds.height() / 100f);
|
||||
canvas.drawPath(mProgressPath, mPaint);
|
||||
canvas.restoreToCount(saveCount);
|
||||
}
|
||||
|
||||
/** Updates this placeholder to {@code newIcon} with animation. */
|
||||
public void animateIconUpdate(Drawable newIcon) {
|
||||
int placeholderColor = mPaint.getColor();
|
||||
int originalAlpha = Color.alpha(placeholderColor);
|
||||
|
||||
ValueAnimator iconUpdateAnimation = ValueAnimator.ofInt(originalAlpha, 0);
|
||||
iconUpdateAnimation.setDuration(375);
|
||||
iconUpdateAnimation.addUpdateListener(valueAnimator -> {
|
||||
int newAlpha = (int) valueAnimator.getAnimatedValue();
|
||||
int newColor = ColorUtils.setAlphaComponent(placeholderColor, newAlpha);
|
||||
|
||||
newIcon.setColorFilter(new PorterDuffColorFilter(newColor, PorterDuff.Mode.SRC_ATOP));
|
||||
});
|
||||
iconUpdateAnimation.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
newIcon.setColorFilter(null);
|
||||
}
|
||||
});
|
||||
iconUpdateAnimation.start();
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffColorFilter;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import androidx.core.graphics.ColorUtils;
|
||||
|
||||
/**
|
||||
* Subclass which draws a placeholder icon when the actual icon is not yet loaded
|
||||
*/
|
||||
public class PlaceHolderIconDrawable extends FastBitmapDrawable {
|
||||
|
||||
// Path in [0, 100] bounds.
|
||||
private final Path mProgressPath;
|
||||
|
||||
public PlaceHolderIconDrawable(BitmapInfo info, Context context) {
|
||||
super(info);
|
||||
|
||||
mProgressPath = GraphicsUtils.getShapePath(context, 100);
|
||||
mPaint.setColor(ColorUtils.compositeColors(
|
||||
GraphicsUtils.getAttrColor(context, R.attr.loadingIconColor), info.color));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawInternal(Canvas canvas, Rect bounds) {
|
||||
int saveCount = canvas.save();
|
||||
canvas.translate(bounds.left, bounds.top);
|
||||
canvas.scale(bounds.width() / 100f, bounds.height() / 100f);
|
||||
canvas.drawPath(mProgressPath, mPaint);
|
||||
canvas.restoreToCount(saveCount);
|
||||
}
|
||||
|
||||
/** Updates this placeholder to {@code newIcon} with animation. */
|
||||
public void animateIconUpdate(Drawable newIcon) {
|
||||
int placeholderColor = mPaint.getColor();
|
||||
int originalAlpha = Color.alpha(placeholderColor);
|
||||
|
||||
ValueAnimator iconUpdateAnimation = ValueAnimator.ofInt(originalAlpha, 0);
|
||||
iconUpdateAnimation.setDuration(375);
|
||||
iconUpdateAnimation.addUpdateListener(valueAnimator -> {
|
||||
int newAlpha = (int) valueAnimator.getAnimatedValue();
|
||||
int newColor = ColorUtils.setAlphaComponent(placeholderColor, newAlpha);
|
||||
|
||||
newIcon.setColorFilter(new PorterDuffColorFilter(newColor, PorterDuff.Mode.SRC_ATOP));
|
||||
});
|
||||
iconUpdateAnimation.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
newIcon.setColorFilter(null);
|
||||
}
|
||||
});
|
||||
iconUpdateAnimation.start();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,56 +1,56 @@
|
||||
/*
|
||||
* 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.graphics.Canvas;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.DrawableWrapper;
|
||||
|
||||
/**
|
||||
* A drawable which clips rounded corner around a child drawable
|
||||
*/
|
||||
public class RoundDrawableWrapper extends DrawableWrapper {
|
||||
|
||||
private final RectF mTempRect = new RectF();
|
||||
private final Path mClipPath = new Path();
|
||||
private final float mRoundedCornersRadius;
|
||||
|
||||
public RoundDrawableWrapper(Drawable dr, float radius) {
|
||||
super(dr);
|
||||
mRoundedCornersRadius = radius;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(Rect bounds) {
|
||||
mTempRect.set(getBounds());
|
||||
mClipPath.reset();
|
||||
mClipPath.addRoundRect(mTempRect, mRoundedCornersRadius,
|
||||
mRoundedCornersRadius, Path.Direction.CCW);
|
||||
super.onBoundsChange(bounds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void draw(Canvas canvas) {
|
||||
int saveCount = canvas.save();
|
||||
canvas.clipPath(mClipPath);
|
||||
super.draw(canvas);
|
||||
canvas.restoreToCount(saveCount);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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.graphics.Canvas;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.DrawableWrapper;
|
||||
|
||||
/**
|
||||
* A drawable which clips rounded corner around a child drawable
|
||||
*/
|
||||
public class RoundDrawableWrapper extends DrawableWrapper {
|
||||
|
||||
private final RectF mTempRect = new RectF();
|
||||
private final Path mClipPath = new Path();
|
||||
private final float mRoundedCornersRadius;
|
||||
|
||||
public RoundDrawableWrapper(Drawable dr, float radius) {
|
||||
super(dr);
|
||||
mRoundedCornersRadius = radius;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(Rect bounds) {
|
||||
mTempRect.set(getBounds());
|
||||
mClipPath.reset();
|
||||
mClipPath.addRoundRect(mTempRect, mRoundedCornersRadius,
|
||||
mRoundedCornersRadius, Path.Direction.CCW);
|
||||
super.onBoundsChange(bounds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void draw(Canvas canvas) {
|
||||
int saveCount = canvas.save();
|
||||
canvas.clipPath(mClipPath);
|
||||
super.draw(canvas);
|
||||
canvas.restoreToCount(saveCount);
|
||||
}
|
||||
}
|
||||
@@ -1,182 +1,195 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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 static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BlurMaskFilter;
|
||||
import android.graphics.BlurMaskFilter.Blur;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.RectF;
|
||||
|
||||
/**
|
||||
* Utility class to add shadows to bitmaps.
|
||||
*/
|
||||
public class ShadowGenerator {
|
||||
|
||||
public static final boolean ENABLE_SHADOWS = true;
|
||||
|
||||
public static final float BLUR_FACTOR = 1.68f/48;
|
||||
|
||||
// Percent of actual icon size
|
||||
public static final float KEY_SHADOW_DISTANCE = 1f/48;
|
||||
private static final int KEY_SHADOW_ALPHA = 7;
|
||||
// Percent of actual icon size
|
||||
private static final float HALF_DISTANCE = 0.5f;
|
||||
private static final int AMBIENT_SHADOW_ALPHA = 25;
|
||||
|
||||
private final int mIconSize;
|
||||
|
||||
private final Paint mBlurPaint;
|
||||
private final Paint mDrawPaint;
|
||||
private final BlurMaskFilter mDefaultBlurMaskFilter;
|
||||
|
||||
public ShadowGenerator(int iconSize) {
|
||||
mIconSize = iconSize;
|
||||
mBlurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
|
||||
mDrawPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
|
||||
mDefaultBlurMaskFilter = new BlurMaskFilter(mIconSize * BLUR_FACTOR, Blur.NORMAL);
|
||||
}
|
||||
|
||||
public synchronized void recreateIcon(Bitmap icon, Canvas out) {
|
||||
recreateIcon(icon, mDefaultBlurMaskFilter, AMBIENT_SHADOW_ALPHA, KEY_SHADOW_ALPHA, out);
|
||||
}
|
||||
|
||||
public synchronized void recreateIcon(Bitmap icon, BlurMaskFilter blurMaskFilter,
|
||||
int ambientAlpha, int keyAlpha, Canvas out) {
|
||||
if (ENABLE_SHADOWS) {
|
||||
int[] offset = new int[2];
|
||||
mBlurPaint.setMaskFilter(blurMaskFilter);
|
||||
Bitmap shadow = icon.extractAlpha(mBlurPaint, offset);
|
||||
|
||||
// Draw ambient shadow
|
||||
mDrawPaint.setAlpha(ambientAlpha);
|
||||
out.drawBitmap(shadow, offset[0], offset[1], mDrawPaint);
|
||||
|
||||
// Draw key shadow
|
||||
mDrawPaint.setAlpha(keyAlpha);
|
||||
out.drawBitmap(shadow, offset[0], offset[1] + KEY_SHADOW_DISTANCE * mIconSize,
|
||||
mDrawPaint);
|
||||
}
|
||||
|
||||
// Draw the icon
|
||||
mDrawPaint.setAlpha(255);
|
||||
out.drawBitmap(icon, 0, 0, mDrawPaint);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
float bottomSpace = BLUR_FACTOR + KEY_SHADOW_DISTANCE;
|
||||
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();
|
||||
public final int color;
|
||||
|
||||
public int ambientShadowAlpha = AMBIENT_SHADOW_ALPHA;
|
||||
|
||||
public float shadowBlur;
|
||||
|
||||
public float keyShadowDistance;
|
||||
public int keyShadowAlpha = KEY_SHADOW_ALPHA;
|
||||
public float radius;
|
||||
|
||||
public Builder(int color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public Builder setupBlurForSize(int height) {
|
||||
if (ENABLE_SHADOWS) {
|
||||
shadowBlur = height * 1f / 24;
|
||||
keyShadowDistance = height * 1f / 16;
|
||||
} else {
|
||||
shadowBlur = 0;
|
||||
keyShadowDistance = 0;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Bitmap createPill(int width, int height) {
|
||||
return createPill(width, height, height / 2f);
|
||||
}
|
||||
|
||||
public Bitmap createPill(int width, int height, float r) {
|
||||
radius = r;
|
||||
|
||||
int centerX = Math.round(width / 2f + shadowBlur);
|
||||
int centerY = Math.round(radius + shadowBlur + keyShadowDistance);
|
||||
int center = Math.max(centerX, centerY);
|
||||
bounds.set(0, 0, width, height);
|
||||
bounds.offsetTo(center - width / 2f, center - height / 2f);
|
||||
|
||||
int size = center * 2;
|
||||
return BitmapRenderer.createHardwareBitmap(size, size, this::drawShadow);
|
||||
}
|
||||
|
||||
public void drawShadow(Canvas c) {
|
||||
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
|
||||
p.setColor(color);
|
||||
|
||||
if (ENABLE_SHADOWS) {
|
||||
// Key shadow
|
||||
p.setShadowLayer(shadowBlur, 0, keyShadowDistance,
|
||||
setColorAlphaBound(Color.BLACK, keyShadowAlpha));
|
||||
c.drawRoundRect(bounds, radius, radius, p);
|
||||
|
||||
// Ambient shadow
|
||||
p.setShadowLayer(shadowBlur, 0, 0,
|
||||
setColorAlphaBound(Color.BLACK, ambientShadowAlpha));
|
||||
c.drawRoundRect(bounds, radius, radius, p);
|
||||
}
|
||||
|
||||
if (Color.alpha(color) < 255) {
|
||||
// Clear any content inside the pill-rect for translucent fill.
|
||||
p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
|
||||
p.clearShadowLayer();
|
||||
p.setColor(Color.BLACK);
|
||||
c.drawRoundRect(bounds, radius, radius, p);
|
||||
|
||||
p.setXfermode(null);
|
||||
p.setColor(color);
|
||||
c.drawRoundRect(bounds, radius, radius, p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Copyright (C) 2016 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 static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BlurMaskFilter;
|
||||
import android.graphics.BlurMaskFilter.Blur;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.RectF;
|
||||
|
||||
/**
|
||||
* Utility class to add shadows to bitmaps.
|
||||
*/
|
||||
public class ShadowGenerator {
|
||||
|
||||
public static final boolean ENABLE_SHADOWS = true;
|
||||
|
||||
public static final float BLUR_FACTOR = 1.68f/48;
|
||||
|
||||
// Percent of actual icon size
|
||||
public static final float KEY_SHADOW_DISTANCE = 1f/48;
|
||||
private static final int KEY_SHADOW_ALPHA = 7;
|
||||
// Percent of actual icon size
|
||||
private static final float HALF_DISTANCE = 0.5f;
|
||||
private static final int AMBIENT_SHADOW_ALPHA = 25;
|
||||
|
||||
private final int mIconSize;
|
||||
|
||||
private final Paint mBlurPaint;
|
||||
private final Paint mDrawPaint;
|
||||
private final BlurMaskFilter mDefaultBlurMaskFilter;
|
||||
|
||||
public ShadowGenerator(int iconSize) {
|
||||
mIconSize = iconSize;
|
||||
mBlurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
|
||||
mDrawPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
|
||||
mDefaultBlurMaskFilter = new BlurMaskFilter(mIconSize * BLUR_FACTOR, Blur.NORMAL);
|
||||
}
|
||||
|
||||
public synchronized void drawShadow(Bitmap icon, Canvas out) {
|
||||
if (ENABLE_SHADOWS) {
|
||||
int[] offset = new int[2];
|
||||
mBlurPaint.setMaskFilter(mDefaultBlurMaskFilter);
|
||||
Bitmap shadow = icon.extractAlpha(mBlurPaint, offset);
|
||||
|
||||
// Draw ambient shadow
|
||||
mDrawPaint.setAlpha(AMBIENT_SHADOW_ALPHA);
|
||||
out.drawBitmap(shadow, offset[0], offset[1], mDrawPaint);
|
||||
|
||||
// Draw key shadow
|
||||
mDrawPaint.setAlpha(KEY_SHADOW_ALPHA);
|
||||
out.drawBitmap(shadow, offset[0], offset[1] + KEY_SHADOW_DISTANCE * mIconSize,
|
||||
mDrawPaint);
|
||||
}
|
||||
}
|
||||
|
||||
/** package private **/
|
||||
void addPathShadow(Path path, Canvas out) {
|
||||
if (ENABLE_SHADOWS) {
|
||||
mDrawPaint.setMaskFilter(mDefaultBlurMaskFilter);
|
||||
|
||||
// Draw ambient shadow
|
||||
mDrawPaint.setAlpha(AMBIENT_SHADOW_ALPHA);
|
||||
out.drawPath(path, mDrawPaint);
|
||||
|
||||
// Draw key shadow
|
||||
int save = out.save();
|
||||
mDrawPaint.setAlpha(KEY_SHADOW_ALPHA);
|
||||
out.translate(0, KEY_SHADOW_DISTANCE * mIconSize);
|
||||
out.drawPath(path, mDrawPaint);
|
||||
out.restoreToCount(save);
|
||||
|
||||
mDrawPaint.setMaskFilter(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
float bottomSpace = BLUR_FACTOR + KEY_SHADOW_DISTANCE;
|
||||
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();
|
||||
public final int color;
|
||||
|
||||
public int ambientShadowAlpha = AMBIENT_SHADOW_ALPHA;
|
||||
|
||||
public float shadowBlur;
|
||||
|
||||
public float keyShadowDistance;
|
||||
public int keyShadowAlpha = KEY_SHADOW_ALPHA;
|
||||
public float radius;
|
||||
|
||||
public Builder(int color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public Builder setupBlurForSize(int height) {
|
||||
if (ENABLE_SHADOWS) {
|
||||
shadowBlur = height * 1f / 24;
|
||||
keyShadowDistance = height * 1f / 16;
|
||||
} else {
|
||||
shadowBlur = 0;
|
||||
keyShadowDistance = 0;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Bitmap createPill(int width, int height) {
|
||||
return createPill(width, height, height / 2f);
|
||||
}
|
||||
|
||||
public Bitmap createPill(int width, int height, float r) {
|
||||
radius = r;
|
||||
|
||||
int centerX = Math.round(width / 2f + shadowBlur);
|
||||
int centerY = Math.round(radius + shadowBlur + keyShadowDistance);
|
||||
int center = Math.max(centerX, centerY);
|
||||
bounds.set(0, 0, width, height);
|
||||
bounds.offsetTo(center - width / 2f, center - height / 2f);
|
||||
|
||||
int size = center * 2;
|
||||
return BitmapRenderer.createHardwareBitmap(size, size, this::drawShadow);
|
||||
}
|
||||
|
||||
public void drawShadow(Canvas c) {
|
||||
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
|
||||
p.setColor(color);
|
||||
|
||||
if (ENABLE_SHADOWS) {
|
||||
// Key shadow
|
||||
p.setShadowLayer(shadowBlur, 0, keyShadowDistance,
|
||||
setColorAlphaBound(Color.BLACK, keyShadowAlpha));
|
||||
c.drawRoundRect(bounds, radius, radius, p);
|
||||
|
||||
// Ambient shadow
|
||||
p.setShadowLayer(shadowBlur, 0, 0,
|
||||
setColorAlphaBound(Color.BLACK, ambientShadowAlpha));
|
||||
c.drawRoundRect(bounds, radius, radius, p);
|
||||
}
|
||||
|
||||
if (Color.alpha(color) < 255) {
|
||||
// Clear any content inside the pill-rect for translucent fill.
|
||||
p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
|
||||
p.clearShadowLayer();
|
||||
p.setColor(Color.BLACK);
|
||||
c.drawRoundRect(bounds, radius, radius, p);
|
||||
|
||||
p.setXfermode(null);
|
||||
p.setColor(color);
|
||||
c.drawRoundRect(bounds, radius, radius, p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,142 +1,141 @@
|
||||
/*
|
||||
* 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 static android.content.res.Configuration.UI_MODE_NIGHT_MASK;
|
||||
import static android.content.res.Configuration.UI_MODE_NIGHT_YES;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public void changeBackgroundColor(int colorBg){
|
||||
mBgPaint.setColorFilter(new BlendModeColorFilter(colorBg, BlendMode.SRC_IN));
|
||||
}
|
||||
|
||||
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];
|
||||
if ((res.getConfiguration().uiMode & UI_MODE_NIGHT_MASK) == UI_MODE_NIGHT_YES) {
|
||||
colors[0] = res.getColor(android.R.color.system_neutral1_800);
|
||||
colors[1] = res.getColor(android.R.color.system_accent1_100);
|
||||
} else {
|
||||
colors[0] = res.getColor(android.R.color.system_accent1_100);
|
||||
colors[1] = res.getColor(android.R.color.system_neutral2_700);
|
||||
}
|
||||
return colors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIconColor() {
|
||||
return colorFg;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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 static android.content.res.Configuration.UI_MODE_NIGHT_MASK;
|
||||
import static android.content.res.Configuration.UI_MODE_NIGHT_YES;
|
||||
|
||||
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.Color;
|
||||
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);
|
||||
}
|
||||
|
||||
public void changeBackgroundColor(int colorBg) {
|
||||
if (mIsDisabled) return;
|
||||
|
||||
mBgPaint.setColorFilter(new BlendModeColorFilter(colorBg, BlendMode.SRC_IN));
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
+781
-714
File diff suppressed because it is too large.
Load diff
@@ -1,65 +1,70 @@
|
||||
/*
|
||||
* 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> {
|
||||
|
||||
ComponentName getComponent(T object);
|
||||
|
||||
UserHandle getUser(T object);
|
||||
|
||||
CharSequence getLabel(T object);
|
||||
|
||||
default CharSequence getDescription(T object, CharSequence fallback) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
BitmapInfo loadIcon(Context context, T object);
|
||||
|
||||
/**
|
||||
* Provides a option list of keywords to associate with this object
|
||||
*/
|
||||
@Nullable
|
||||
default String getKeywords(T object, LocaleList localeList) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the timestamp the entry was last updated in cache.
|
||||
*/
|
||||
default long getLastUpdatedTime(T object, PackageInfo info) {
|
||||
return info.lastUpdateTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true the object should be added to mem cache; otherwise returns false.
|
||||
*/
|
||||
default boolean addToMemCache() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -1,79 +1,79 @@
|
||||
/*
|
||||
* 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.os.Handler;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* A runnable that can be posted to a {@link Handler} which can be canceled.
|
||||
*/
|
||||
public class HandlerRunnable<T> implements Runnable {
|
||||
|
||||
private final Handler mWorkerHandler;
|
||||
private final Supplier<T> mTask;
|
||||
|
||||
private final Executor mCallbackExecutor;
|
||||
private final Consumer<T> mCallback;
|
||||
private final Runnable mEndRunnable;
|
||||
|
||||
private boolean mEnded = false;
|
||||
private boolean mCanceled = false;
|
||||
|
||||
public HandlerRunnable(Handler workerHandler, Supplier<T> task, Executor callbackExecutor,
|
||||
Consumer<T> callback) {
|
||||
this(workerHandler, task, callbackExecutor, callback, () -> { });
|
||||
}
|
||||
|
||||
public HandlerRunnable(Handler workerHandler, Supplier<T> task, Executor callbackExecutor,
|
||||
Consumer<T> callback, Runnable endRunnable) {
|
||||
mWorkerHandler = workerHandler;
|
||||
mTask = task;
|
||||
mCallbackExecutor = callbackExecutor;
|
||||
mCallback = callback;
|
||||
mEndRunnable = endRunnable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels this runnable from being run, only if it has not already run.
|
||||
*/
|
||||
public void cancel() {
|
||||
mWorkerHandler.removeCallbacks(this);
|
||||
mCanceled = true;
|
||||
mCallbackExecutor.execute(this::onEnd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
T value = mTask.get();
|
||||
mCallbackExecutor.execute(() -> {
|
||||
if (!mCanceled) {
|
||||
mCallback.accept(value);
|
||||
}
|
||||
onEnd();
|
||||
});
|
||||
}
|
||||
|
||||
private void onEnd() {
|
||||
if (!mEnded) {
|
||||
mEnded = true;
|
||||
mEndRunnable.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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.os.Handler;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* A runnable that can be posted to a {@link Handler} which can be canceled.
|
||||
*/
|
||||
public class HandlerRunnable<T> implements Runnable {
|
||||
|
||||
private final Handler mWorkerHandler;
|
||||
private final Supplier<T> mTask;
|
||||
|
||||
private final Executor mCallbackExecutor;
|
||||
private final Consumer<T> mCallback;
|
||||
private final Runnable mEndRunnable;
|
||||
|
||||
private boolean mEnded = false;
|
||||
private boolean mCanceled = false;
|
||||
|
||||
public HandlerRunnable(Handler workerHandler, Supplier<T> task, Executor callbackExecutor,
|
||||
Consumer<T> callback) {
|
||||
this(workerHandler, task, callbackExecutor, callback, () -> { });
|
||||
}
|
||||
|
||||
public HandlerRunnable(Handler workerHandler, Supplier<T> task, Executor callbackExecutor,
|
||||
Consumer<T> callback, Runnable endRunnable) {
|
||||
mWorkerHandler = workerHandler;
|
||||
mTask = task;
|
||||
mCallbackExecutor = callbackExecutor;
|
||||
mCallback = callback;
|
||||
mEndRunnable = endRunnable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels this runnable from being run, only if it has not already run.
|
||||
*/
|
||||
public void cancel() {
|
||||
mWorkerHandler.removeCallbacks(this);
|
||||
mCanceled = true;
|
||||
mCallbackExecutor.execute(this::onEnd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
T value = mTask.get();
|
||||
mCallbackExecutor.execute(() -> {
|
||||
if (!mCanceled) {
|
||||
mCallback.accept(value);
|
||||
}
|
||||
onEnd();
|
||||
});
|
||||
}
|
||||
|
||||
private void onEnd() {
|
||||
if (!mEnded) {
|
||||
mEnded = true;
|
||||
mEndRunnable.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
+314
-313
@@ -1,313 +1,314 @@
|
||||
/*
|
||||
* 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.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.Stack;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
private 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);
|
||||
|
||||
Stack<T> appsToUpdate = new Stack<>();
|
||||
|
||||
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 == info.lastUpdateTime
|
||||
&& 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()) {
|
||||
Stack<T> appsToAdd = new Stack<>();
|
||||
appsToAdd.addAll(componentMap.values());
|
||||
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 Stack<T> mAppsToAdd;
|
||||
private final Stack<T> mAppsToUpdate;
|
||||
private final CachingLogic<T> mCachingLogic;
|
||||
private final HashSet<String> mUpdatedPackages = new HashSet<>();
|
||||
private final OnUpdateCallback mOnUpdateCallback;
|
||||
|
||||
SerializedIconUpdateTask(long userSerial, UserHandle userHandle,
|
||||
Stack<T> appsToAdd, Stack<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.pop();
|
||||
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.pop();
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void scheduleNext() {
|
||||
mIconCache.mWorkerHandler.postAtTime(this, ICON_UPDATE_TOKEN,
|
||||
SystemClock.uptimeMillis() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnUpdateCallback {
|
||||
|
||||
void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandle user);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.Stack;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
private 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);
|
||||
|
||||
Stack<T> appsToUpdate = new Stack<>();
|
||||
|
||||
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()) {
|
||||
Stack<T> appsToAdd = new Stack<>();
|
||||
appsToAdd.addAll(componentMap.values());
|
||||
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 Stack<T> mAppsToAdd;
|
||||
private final Stack<T> mAppsToUpdate;
|
||||
private final CachingLogic<T> mCachingLogic;
|
||||
private final HashSet<String> mUpdatedPackages = new HashSet<>();
|
||||
private final OnUpdateCallback mOnUpdateCallback;
|
||||
|
||||
SerializedIconUpdateTask(long userSerial, UserHandle userHandle,
|
||||
Stack<T> appsToAdd, Stack<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.pop();
|
||||
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.pop();
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void scheduleNext() {
|
||||
mIconCache.mWorkerHandler.postAtTime(this, ICON_UPDATE_TOKEN,
|
||||
SystemClock.uptimeMillis() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnUpdateCallback {
|
||||
|
||||
void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandle user);
|
||||
}
|
||||
}
|
||||
@@ -1,84 +1,84 @@
|
||||
package com.android.launcher3.util;
|
||||
|
||||
/**
|
||||
* Copyright (C) 2015 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 android.content.ComponentName;
|
||||
import android.os.UserHandle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class ComponentKey {
|
||||
|
||||
public final ComponentName componentName;
|
||||
public final UserHandle user;
|
||||
|
||||
private final int mHashCode;
|
||||
|
||||
public ComponentKey(ComponentName componentName, UserHandle user) {
|
||||
if (componentName == null || user == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
this.componentName = componentName;
|
||||
this.user = user;
|
||||
mHashCode = Arrays.hashCode(new Object[] {componentName, user});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mHashCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
ComponentKey other = (ComponentKey) o;
|
||||
return other.componentName.equals(componentName) && other.user.equals(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a component key as a string of the form [flattenedComponentString#userId].
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return componentName.flattenToString() + "#" + user.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses and returns ComponentKey objected from string representation
|
||||
* Returns null if string is not properly formatted
|
||||
*/
|
||||
@Nullable
|
||||
public static ComponentKey fromString(@NonNull String str) {
|
||||
int sep = str.indexOf('#');
|
||||
if (sep < 0 || (sep + 1) >= str.length()) {
|
||||
return null;
|
||||
}
|
||||
ComponentName componentName = ComponentName.unflattenFromString(str.substring(0, sep));
|
||||
if (componentName == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return new ComponentKey(componentName,
|
||||
UserHandle.getUserHandleForUid(Integer.parseInt(str.substring(sep + 1))));
|
||||
} catch (NumberFormatException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
package com.android.launcher3.util;
|
||||
|
||||
/**
|
||||
* Copyright (C) 2015 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 android.content.ComponentName;
|
||||
import android.os.UserHandle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class ComponentKey {
|
||||
|
||||
public final ComponentName componentName;
|
||||
public final UserHandle user;
|
||||
|
||||
private final int mHashCode;
|
||||
|
||||
public ComponentKey(ComponentName componentName, UserHandle user) {
|
||||
if (componentName == null || user == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
this.componentName = componentName;
|
||||
this.user = user;
|
||||
mHashCode = Arrays.hashCode(new Object[] {componentName, user});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mHashCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
ComponentKey other = (ComponentKey) o;
|
||||
return other.componentName.equals(componentName) && other.user.equals(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a component key as a string of the form [flattenedComponentString#userId].
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return componentName.flattenToString() + "#" + user.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses and returns ComponentKey objected from string representation
|
||||
* Returns null if string is not properly formatted
|
||||
*/
|
||||
@Nullable
|
||||
public static ComponentKey fromString(@NonNull String str) {
|
||||
int sep = str.indexOf('#');
|
||||
if (sep < 0 || (sep + 1) >= str.length()) {
|
||||
return null;
|
||||
}
|
||||
ComponentName componentName = ComponentName.unflattenFromString(str.substring(0, sep));
|
||||
if (componentName == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return new ComponentKey(componentName,
|
||||
UserHandle.getUserHandleForUid(Integer.parseInt(str.substring(sep + 1))));
|
||||
} catch (NumberFormatException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +1,48 @@
|
||||
/**
|
||||
* Copyright (C) 2022 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;
|
||||
|
||||
/**
|
||||
* Utility interface for representing flag operations
|
||||
*/
|
||||
public interface FlagOp {
|
||||
|
||||
FlagOp NO_OP = i -> i;
|
||||
|
||||
int apply(int flags);
|
||||
|
||||
/**
|
||||
* Returns a new OP which adds the provided flag after applying all previous operations
|
||||
*/
|
||||
default FlagOp addFlag(int flag) {
|
||||
return i -> apply(i) | flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new OP which removes the provided flag after applying all previous operations
|
||||
*/
|
||||
default FlagOp removeFlag(int flag) {
|
||||
return i -> apply(i) & ~flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new OP which adds or removed the provided flag based on {@code enable} after
|
||||
* applying all previous operations
|
||||
*/
|
||||
default FlagOp setFlag(int flag, boolean enable) {
|
||||
return enable ? addFlag(flag) : removeFlag(flag);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Copyright (C) 2022 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;
|
||||
|
||||
/**
|
||||
* Utility interface for representing flag operations
|
||||
*/
|
||||
public interface FlagOp {
|
||||
|
||||
FlagOp NO_OP = i -> i;
|
||||
|
||||
int apply(int flags);
|
||||
|
||||
/**
|
||||
* Returns a new OP which adds the provided flag after applying all previous operations
|
||||
*/
|
||||
default FlagOp addFlag(int flag) {
|
||||
return i -> apply(i) | flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new OP which removes the provided flag after applying all previous operations
|
||||
*/
|
||||
default FlagOp removeFlag(int flag) {
|
||||
return i -> apply(i) & ~flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new OP which adds or removed the provided flag based on {@code enable} after
|
||||
* applying all previous operations
|
||||
*/
|
||||
default FlagOp setFlag(int flag, boolean enable) {
|
||||
return enable ? addFlag(flag) : removeFlag(flag);
|
||||
}
|
||||
}
|
||||
@@ -1,58 +1,58 @@
|
||||
/*
|
||||
* 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 android.database.sqlite.SQLiteDatabase.NO_LOCALIZED_COLLATORS;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.database.DatabaseErrorHandler;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteDatabase.CursorFactory;
|
||||
import android.database.sqlite.SQLiteDatabase.OpenParams;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.os.Build;
|
||||
|
||||
/**
|
||||
* Extension of {@link SQLiteOpenHelper} which avoids creating default locale table by
|
||||
* A context wrapper which creates databases without support for localized collators.
|
||||
*/
|
||||
public abstract class NoLocaleSQLiteHelper extends SQLiteOpenHelper {
|
||||
|
||||
private static final boolean ATLEAST_P =
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
|
||||
|
||||
public NoLocaleSQLiteHelper(Context context, String name, int version) {
|
||||
super(ATLEAST_P ? context : new NoLocalContext(context), name, null, version);
|
||||
if (ATLEAST_P) {
|
||||
setOpenParams(new OpenParams.Builder().addOpenFlags(NO_LOCALIZED_COLLATORS).build());
|
||||
}
|
||||
}
|
||||
|
||||
private static class NoLocalContext extends ContextWrapper {
|
||||
public NoLocalContext(Context base) {
|
||||
super(base);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SQLiteDatabase openOrCreateDatabase(
|
||||
String name, int mode, CursorFactory factory, DatabaseErrorHandler errorHandler) {
|
||||
return super.openOrCreateDatabase(
|
||||
name, mode | Context.MODE_NO_LOCALIZED_COLLATORS, factory, errorHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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 android.database.sqlite.SQLiteDatabase.NO_LOCALIZED_COLLATORS;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.database.DatabaseErrorHandler;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteDatabase.CursorFactory;
|
||||
import android.database.sqlite.SQLiteDatabase.OpenParams;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.os.Build;
|
||||
|
||||
/**
|
||||
* Extension of {@link SQLiteOpenHelper} which avoids creating default locale table by
|
||||
* A context wrapper which creates databases without support for localized collators.
|
||||
*/
|
||||
public abstract class NoLocaleSQLiteHelper extends SQLiteOpenHelper {
|
||||
|
||||
private static final boolean ATLEAST_P =
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
|
||||
|
||||
public NoLocaleSQLiteHelper(Context context, String name, int version) {
|
||||
super(ATLEAST_P ? context : new NoLocalContext(context), name, null, version);
|
||||
if (ATLEAST_P) {
|
||||
setOpenParams(new OpenParams.Builder().addOpenFlags(NO_LOCALIZED_COLLATORS).build());
|
||||
}
|
||||
}
|
||||
|
||||
private static class NoLocalContext extends ContextWrapper {
|
||||
public NoLocalContext(Context base) {
|
||||
super(base);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SQLiteDatabase openOrCreateDatabase(
|
||||
String name, int mode, CursorFactory factory, DatabaseErrorHandler errorHandler) {
|
||||
return super.openOrCreateDatabase(
|
||||
name, mode | Context.MODE_NO_LOCALIZED_COLLATORS, factory, errorHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,125 +1,125 @@
|
||||
package com.android.launcher3.util;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
import android.database.sqlite.SQLiteFullException;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* An extension of {@link SQLiteOpenHelper} with utility methods for a single table cache DB.
|
||||
* Any exception during write operations are ignored, and any version change causes a DB reset.
|
||||
*/
|
||||
public abstract class SQLiteCacheHelper {
|
||||
private static final String TAG = "SQLiteCacheHelper";
|
||||
|
||||
private static final boolean IN_MEMORY_CACHE = false;
|
||||
|
||||
private final String mTableName;
|
||||
private final MySQLiteOpenHelper mOpenHelper;
|
||||
|
||||
private boolean mIgnoreWrites;
|
||||
|
||||
public SQLiteCacheHelper(Context context, String name, int version, String tableName) {
|
||||
if (IN_MEMORY_CACHE) {
|
||||
name = null;
|
||||
}
|
||||
mTableName = tableName;
|
||||
mOpenHelper = new MySQLiteOpenHelper(context, name, version);
|
||||
|
||||
mIgnoreWrites = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see SQLiteDatabase#delete(String, String, String[])
|
||||
*/
|
||||
public void delete(String whereClause, String[] whereArgs) {
|
||||
if (mIgnoreWrites) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mOpenHelper.getWritableDatabase().delete(mTableName, whereClause, whereArgs);
|
||||
} catch (SQLiteFullException e) {
|
||||
onDiskFull(e);
|
||||
} catch (SQLiteException e) {
|
||||
Log.d(TAG, "Ignoring sqlite exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see SQLiteDatabase#insertWithOnConflict(String, String, ContentValues, int)
|
||||
*/
|
||||
public void insertOrReplace(ContentValues values) {
|
||||
if (mIgnoreWrites) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mOpenHelper.getWritableDatabase().insertWithOnConflict(
|
||||
mTableName, null, values, SQLiteDatabase.CONFLICT_REPLACE);
|
||||
} catch (SQLiteFullException e) {
|
||||
onDiskFull(e);
|
||||
} catch (SQLiteException e) {
|
||||
Log.d(TAG, "Ignoring sqlite exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void onDiskFull(SQLiteFullException e) {
|
||||
Log.e(TAG, "Disk full, all write operations will be ignored", e);
|
||||
mIgnoreWrites = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see SQLiteDatabase#query(String, String[], String, String[], String, String, String)
|
||||
*/
|
||||
public Cursor query(String[] columns, String selection, String[] selectionArgs) {
|
||||
return mOpenHelper.getReadableDatabase().query(
|
||||
mTableName, columns, selection, selectionArgs, null, null, null);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
mOpenHelper.clearDB(mOpenHelper.getWritableDatabase());
|
||||
}
|
||||
|
||||
public void close() {
|
||||
mOpenHelper.close();
|
||||
}
|
||||
|
||||
protected abstract void onCreateTable(SQLiteDatabase db);
|
||||
|
||||
/**
|
||||
* A private inner class to prevent direct DB access.
|
||||
*/
|
||||
private class MySQLiteOpenHelper extends NoLocaleSQLiteHelper {
|
||||
|
||||
public MySQLiteOpenHelper(Context context, String name, int version) {
|
||||
super(context, name, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
onCreateTable(db);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
if (oldVersion != newVersion) {
|
||||
clearDB(db);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
if (oldVersion != newVersion) {
|
||||
clearDB(db);
|
||||
}
|
||||
}
|
||||
|
||||
private void clearDB(SQLiteDatabase db) {
|
||||
db.execSQL("DROP TABLE IF EXISTS " + mTableName);
|
||||
onCreate(db);
|
||||
}
|
||||
}
|
||||
}
|
||||
package com.android.launcher3.util;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
import android.database.sqlite.SQLiteFullException;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* An extension of {@link SQLiteOpenHelper} with utility methods for a single table cache DB.
|
||||
* Any exception during write operations are ignored, and any version change causes a DB reset.
|
||||
*/
|
||||
public abstract class SQLiteCacheHelper {
|
||||
private static final String TAG = "SQLiteCacheHelper";
|
||||
|
||||
private static final boolean IN_MEMORY_CACHE = false;
|
||||
|
||||
private final String mTableName;
|
||||
private final MySQLiteOpenHelper mOpenHelper;
|
||||
|
||||
private boolean mIgnoreWrites;
|
||||
|
||||
public SQLiteCacheHelper(Context context, String name, int version, String tableName) {
|
||||
if (IN_MEMORY_CACHE) {
|
||||
name = null;
|
||||
}
|
||||
mTableName = tableName;
|
||||
mOpenHelper = new MySQLiteOpenHelper(context, name, version);
|
||||
|
||||
mIgnoreWrites = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see SQLiteDatabase#delete(String, String, String[])
|
||||
*/
|
||||
public void delete(String whereClause, String[] whereArgs) {
|
||||
if (mIgnoreWrites) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mOpenHelper.getWritableDatabase().delete(mTableName, whereClause, whereArgs);
|
||||
} catch (SQLiteFullException e) {
|
||||
onDiskFull(e);
|
||||
} catch (SQLiteException e) {
|
||||
Log.d(TAG, "Ignoring sqlite exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see SQLiteDatabase#insertWithOnConflict(String, String, ContentValues, int)
|
||||
*/
|
||||
public void insertOrReplace(ContentValues values) {
|
||||
if (mIgnoreWrites) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mOpenHelper.getWritableDatabase().insertWithOnConflict(
|
||||
mTableName, null, values, SQLiteDatabase.CONFLICT_REPLACE);
|
||||
} catch (SQLiteFullException e) {
|
||||
onDiskFull(e);
|
||||
} catch (SQLiteException e) {
|
||||
Log.d(TAG, "Ignoring sqlite exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void onDiskFull(SQLiteFullException e) {
|
||||
Log.e(TAG, "Disk full, all write operations will be ignored", e);
|
||||
mIgnoreWrites = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see SQLiteDatabase#query(String, String[], String, String[], String, String, String)
|
||||
*/
|
||||
public Cursor query(String[] columns, String selection, String[] selectionArgs) {
|
||||
return mOpenHelper.getReadableDatabase().query(
|
||||
mTableName, columns, selection, selectionArgs, null, null, null);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
mOpenHelper.clearDB(mOpenHelper.getWritableDatabase());
|
||||
}
|
||||
|
||||
public void close() {
|
||||
mOpenHelper.close();
|
||||
}
|
||||
|
||||
protected abstract void onCreateTable(SQLiteDatabase db);
|
||||
|
||||
/**
|
||||
* A private inner class to prevent direct DB access.
|
||||
*/
|
||||
private class MySQLiteOpenHelper extends NoLocaleSQLiteHelper {
|
||||
|
||||
public MySQLiteOpenHelper(Context context, String name, int version) {
|
||||
super(context, name, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
onCreateTable(db);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
if (oldVersion != newVersion) {
|
||||
clearDB(db);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
if (oldVersion != newVersion) {
|
||||
clearDB(db);
|
||||
}
|
||||
}
|
||||
|
||||
private void clearDB(SQLiteDatabase db) {
|
||||
db.execSQL("DROP TABLE IF EXISTS " + mTableName);
|
||||
onCreate(db);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,26 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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;
|
||||
|
||||
/**
|
||||
* Extension of closeable which does not throw an exception
|
||||
*/
|
||||
public interface SafeCloseable extends AutoCloseable {
|
||||
|
||||
@Override
|
||||
void close();
|
||||
}
|
||||
/*
|
||||
* Copyright (C) 2019 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;
|
||||
|
||||
/**
|
||||
* Extension of closeable which does not throw an exception
|
||||
*/
|
||||
public interface SafeCloseable extends AutoCloseable {
|
||||
|
||||
@Override
|
||||
void close();
|
||||
}
|
||||
@@ -1,89 +1,89 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
/**
|
||||
* Wrapper class to provide access to {@link BaseIconFactory} and also to provide pool of this class
|
||||
* that are threadsafe.
|
||||
*/
|
||||
public class IconFactory extends BaseIconFactory {
|
||||
|
||||
private static final Object sPoolSync = new Object();
|
||||
private static IconFactory sPool;
|
||||
private static int sPoolId = 0;
|
||||
|
||||
/**
|
||||
* Return a new Message instance from the global pool. Allows us to
|
||||
* avoid allocating new objects in many cases.
|
||||
*/
|
||||
public static IconFactory obtain(Context context) {
|
||||
int poolId;
|
||||
synchronized (sPoolSync) {
|
||||
if (sPool != null) {
|
||||
IconFactory m = sPool;
|
||||
sPool = m.next;
|
||||
m.next = null;
|
||||
return m;
|
||||
}
|
||||
poolId = sPoolId;
|
||||
}
|
||||
|
||||
return new IconFactory(context,
|
||||
context.getResources().getConfiguration().densityDpi,
|
||||
context.getResources().getDimensionPixelSize(R.dimen.default_icon_bitmap_size),
|
||||
poolId);
|
||||
}
|
||||
|
||||
public static void clearPool() {
|
||||
synchronized (sPoolSync) {
|
||||
sPool = null;
|
||||
sPoolId++;
|
||||
}
|
||||
}
|
||||
|
||||
private final int mPoolId;
|
||||
|
||||
private IconFactory next;
|
||||
|
||||
private IconFactory(Context context, int fillResIconDpi, int iconBitmapSize, int poolId) {
|
||||
super(context, fillResIconDpi, iconBitmapSize);
|
||||
mPoolId = poolId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recycles a LauncherIcons that may be in-use.
|
||||
*/
|
||||
public void recycle() {
|
||||
synchronized (sPoolSync) {
|
||||
if (sPoolId != mPoolId) {
|
||||
return;
|
||||
}
|
||||
// Clear any temporary state variables
|
||||
clear();
|
||||
|
||||
next = sPool;
|
||||
sPool = this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
recycle();
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
/**
|
||||
* Wrapper class to provide access to {@link BaseIconFactory} and also to provide pool of this class
|
||||
* that are threadsafe.
|
||||
*/
|
||||
public class IconFactory extends BaseIconFactory {
|
||||
|
||||
private static final Object sPoolSync = new Object();
|
||||
private static IconFactory sPool;
|
||||
private static int sPoolId = 0;
|
||||
|
||||
/**
|
||||
* Return a new Message instance from the global pool. Allows us to
|
||||
* avoid allocating new objects in many cases.
|
||||
*/
|
||||
public static IconFactory obtain(Context context) {
|
||||
int poolId;
|
||||
synchronized (sPoolSync) {
|
||||
if (sPool != null) {
|
||||
IconFactory m = sPool;
|
||||
sPool = m.next;
|
||||
m.next = null;
|
||||
return m;
|
||||
}
|
||||
poolId = sPoolId;
|
||||
}
|
||||
|
||||
return new IconFactory(context,
|
||||
context.getResources().getConfiguration().densityDpi,
|
||||
context.getResources().getDimensionPixelSize(R.dimen.default_icon_bitmap_size),
|
||||
poolId);
|
||||
}
|
||||
|
||||
public static void clearPool() {
|
||||
synchronized (sPoolSync) {
|
||||
sPool = null;
|
||||
sPoolId++;
|
||||
}
|
||||
}
|
||||
|
||||
private final int mPoolId;
|
||||
|
||||
private IconFactory next;
|
||||
|
||||
private IconFactory(Context context, int fillResIconDpi, int iconBitmapSize, int poolId) {
|
||||
super(context, fillResIconDpi, iconBitmapSize);
|
||||
mPoolId = poolId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recycles a LauncherIcons that may be in-use.
|
||||
*/
|
||||
public void recycle() {
|
||||
synchronized (sPoolSync) {
|
||||
if (sPoolId != mPoolId) {
|
||||
return;
|
||||
}
|
||||
// Clear any temporary state variables
|
||||
clear();
|
||||
|
||||
next = sPool;
|
||||
sPool = this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
recycle();
|
||||
}
|
||||
}
|
||||
@@ -1,117 +1,120 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import static android.content.Intent.ACTION_MANAGED_PROFILE_ADDED;
|
||||
import static android.content.Intent.ACTION_MANAGED_PROFILE_REMOVED;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Looper;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.util.SparseLongArray;
|
||||
|
||||
import com.android.launcher3.icons.cache.BaseIconCache;
|
||||
|
||||
/**
|
||||
* Wrapper class to provide access to {@link BaseIconFactory} and also to provide pool of this class
|
||||
* that are threadsafe.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.P)
|
||||
public class SimpleIconCache extends BaseIconCache {
|
||||
|
||||
private static SimpleIconCache sIconCache = null;
|
||||
private static final Object CACHE_LOCK = new Object();
|
||||
|
||||
private final SparseLongArray mUserSerialMap = new SparseLongArray(2);
|
||||
private final UserManager mUserManager;
|
||||
|
||||
public SimpleIconCache(Context context, String dbFileName, Looper bgLooper, int iconDpi,
|
||||
int iconPixelSize, boolean inMemoryCache) {
|
||||
super(context, dbFileName, bgLooper, iconDpi, iconPixelSize, inMemoryCache);
|
||||
mUserManager = context.getSystemService(UserManager.class);
|
||||
|
||||
// Listen for user cache changes.
|
||||
IntentFilter filter = new IntentFilter(ACTION_MANAGED_PROFILE_ADDED);
|
||||
filter.addAction(ACTION_MANAGED_PROFILE_REMOVED);
|
||||
context.registerReceiver(new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
resetUserCache();
|
||||
}
|
||||
}, filter, null, new Handler(bgLooper), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long getSerialNumberForUser(UserHandle user) {
|
||||
synchronized (mUserSerialMap) {
|
||||
// int index = mUserSerialMap.indexOfKey(user.getIdentifier());
|
||||
int index = mUserSerialMap.indexOfKey(0);
|
||||
if (index >= 0) {
|
||||
return mUserSerialMap.valueAt(index);
|
||||
}
|
||||
long serial = mUserManager.getSerialNumberForUser(user);
|
||||
// mUserSerialMap.put(user.getIdentifier(), serial);
|
||||
mUserSerialMap.put(0, serial);
|
||||
return serial;
|
||||
}
|
||||
}
|
||||
|
||||
private void resetUserCache() {
|
||||
synchronized (mUserSerialMap) {
|
||||
mUserSerialMap.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isInstantApp(ApplicationInfo info) {
|
||||
// return info.isInstantApp();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseIconFactory getIconFactory() {
|
||||
return IconFactory.obtain(mContext);
|
||||
}
|
||||
|
||||
public static SimpleIconCache getIconCache(Context context) {
|
||||
synchronized (CACHE_LOCK) {
|
||||
if (sIconCache != null) {
|
||||
return sIconCache;
|
||||
}
|
||||
boolean inMemoryCache =
|
||||
context.getResources().getBoolean(R.bool.simple_cache_enable_im_memory);
|
||||
String dbFileName = context.getString(R.string.cache_db_name);
|
||||
|
||||
HandlerThread bgThread = new HandlerThread("simple-icon-cache");
|
||||
bgThread.start();
|
||||
|
||||
sIconCache = new SimpleIconCache(context.getApplicationContext(), dbFileName,
|
||||
bgThread.getLooper(), context.getResources().getConfiguration().densityDpi,
|
||||
context.getResources().getDimensionPixelSize(R.dimen.default_icon_bitmap_size),
|
||||
inMemoryCache);
|
||||
return sIconCache;
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import static android.content.Intent.ACTION_MANAGED_PROFILE_ADDED;
|
||||
import static android.content.Intent.ACTION_MANAGED_PROFILE_REMOVED;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Looper;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.util.SparseLongArray;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.launcher3.icons.cache.BaseIconCache;
|
||||
|
||||
/**
|
||||
* Wrapper class to provide access to {@link BaseIconFactory} and also to provide pool of this class
|
||||
* that are threadsafe.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.P)
|
||||
public class SimpleIconCache extends BaseIconCache {
|
||||
|
||||
private static SimpleIconCache sIconCache = null;
|
||||
private static final Object CACHE_LOCK = new Object();
|
||||
|
||||
private final SparseLongArray mUserSerialMap = new SparseLongArray(2);
|
||||
private final UserManager mUserManager;
|
||||
|
||||
public SimpleIconCache(Context context, String dbFileName, Looper bgLooper, int iconDpi,
|
||||
int iconPixelSize, boolean inMemoryCache) {
|
||||
super(context, dbFileName, bgLooper, iconDpi, iconPixelSize, inMemoryCache);
|
||||
mUserManager = context.getSystemService(UserManager.class);
|
||||
|
||||
// Listen for user cache changes.
|
||||
IntentFilter filter = new IntentFilter(ACTION_MANAGED_PROFILE_ADDED);
|
||||
filter.addAction(ACTION_MANAGED_PROFILE_REMOVED);
|
||||
context.registerReceiver(new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
resetUserCache();
|
||||
}
|
||||
}, filter, null, new Handler(bgLooper), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long getSerialNumberForUser(@NonNull UserHandle user) {
|
||||
synchronized (mUserSerialMap) {
|
||||
int index = mUserSerialMap.indexOfKey(0);
|
||||
// int index = mUserSerialMap.indexOfKey(user.getIdentifier());
|
||||
if (index >= 0) {
|
||||
return mUserSerialMap.valueAt(index);
|
||||
}
|
||||
long serial = mUserManager.getSerialNumberForUser(user);
|
||||
mUserSerialMap.put(0, serial);
|
||||
// mUserSerialMap.put(user.getIdentifier(), serial);
|
||||
return serial;
|
||||
}
|
||||
}
|
||||
|
||||
private void resetUserCache() {
|
||||
synchronized (mUserSerialMap) {
|
||||
mUserSerialMap.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isInstantApp(@NonNull ApplicationInfo info) {
|
||||
// return info.isInstantApp();
|
||||
return false;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public BaseIconFactory getIconFactory() {
|
||||
return IconFactory.obtain(mContext);
|
||||
}
|
||||
|
||||
public static SimpleIconCache getIconCache(Context context) {
|
||||
synchronized (CACHE_LOCK) {
|
||||
if (sIconCache != null) {
|
||||
return sIconCache;
|
||||
}
|
||||
boolean inMemoryCache =
|
||||
context.getResources().getBoolean(R.bool.simple_cache_enable_im_memory);
|
||||
String dbFileName = context.getString(R.string.cache_db_name);
|
||||
|
||||
HandlerThread bgThread = new HandlerThread("simple-icon-cache");
|
||||
bgThread.start();
|
||||
|
||||
sIconCache = new SimpleIconCache(context.getApplicationContext(), dbFileName,
|
||||
bgThread.getLooper(), context.getResources().getConfiguration().densityDpi,
|
||||
context.getResources().getDimensionPixelSize(R.dimen.default_icon_bitmap_size),
|
||||
inMemoryCache);
|
||||
return sIconCache;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -606,4 +606,20 @@
|
||||
column="61"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="NewApi"
|
||||
message="Call requires API level 33 (current min is 26): `android.app.Activity#getOnBackInvokedDispatcher`">
|
||||
<location
|
||||
file="packages/apps/Launcher3/src/com/android/launcher3/BaseActivity.java"
|
||||
line="182"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="NewApi"
|
||||
message="Call requires API level 33 (current min is 26): `android.window.OnBackInvokedDispatcher#registerOnBackInvokedCallback`">
|
||||
<location
|
||||
file="packages/apps/Launcher3/src/com/android/launcher3/BaseActivity.java"
|
||||
line="182"/>
|
||||
</issue>
|
||||
|
||||
</issues>
|
||||
+2
-2
@@ -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 Aug 25 00:28:06 CST 2022
|
||||
sdk.dir=/Users/yuchuan/AndroidSDK
|
||||
#Sat Dec 09 10:04:16 CST 2023
|
||||
sdk.dir=/Users/yuchuan/Library/Android/sdk
|
||||
@@ -2,10 +2,6 @@
|
||||
*;
|
||||
}
|
||||
|
||||
-keep class com.android.launcher3.graphics.ShadowDrawable {
|
||||
public <init>(...);
|
||||
}
|
||||
|
||||
# The support library contains references to newer platform versions.
|
||||
# Don't warn about those in case this app is linking against an older
|
||||
# platform version. We know about them, and they are safe.
|
||||
|
||||
@@ -45,6 +45,9 @@ message ItemInfo {
|
||||
|
||||
// Stores the origin of the Item
|
||||
repeated Attribute item_attributes = 12;
|
||||
|
||||
// Stores whether the navigation bar is in kids mode.
|
||||
optional bool is_kids_mode = 13;
|
||||
}
|
||||
|
||||
message LauncherAttributes{
|
||||
@@ -125,10 +128,17 @@ message TaskBarContainer {
|
||||
|
||||
// Bit encoded value to capture pinned and predicted taskbar positions.
|
||||
optional int32 cardinality = 2;
|
||||
|
||||
// Container where taskbar was invoked.
|
||||
oneof ParentContainer {
|
||||
TaskSwitcherContainer task_switcher_container = 3;
|
||||
}
|
||||
}
|
||||
|
||||
// Next value 40
|
||||
// Next value 44
|
||||
enum Attribute {
|
||||
option allow_alias = true;
|
||||
|
||||
UNKNOWN = 0;
|
||||
DEFAULT_LAYOUT = 1; // icon automatically placed in workspace, folder, hotseat
|
||||
BACKUP_RESTORE = 2; // icon layout restored from backup
|
||||
@@ -163,7 +173,8 @@ enum Attribute {
|
||||
ALL_APPS_SEARCH_RESULT_SLICE = 19;
|
||||
ALL_APPS_SEARCH_RESULT_WIDGETS = 20;
|
||||
ALL_APPS_SEARCH_RESULT_PLAY = 21;
|
||||
ALL_APPS_SEARCH_RESULT_SUGGEST = 22;
|
||||
ALL_APPS_SEARCH_RESULT_FALLBACK = 22;
|
||||
ALL_APPS_SEARCH_RESULT_SUGGEST = 22 [deprecated = true];
|
||||
ALL_APPS_SEARCH_RESULT_ASSISTANT = 23;
|
||||
ALL_APPS_SEARCH_RESULT_CHROMETAB = 24;
|
||||
ALL_APPS_SEARCH_RESULT_NAVVYSITE = 25;
|
||||
@@ -171,6 +182,9 @@ enum Attribute {
|
||||
ALL_APPS_SEARCH_RESULT_PEOPLE_TILE = 27;
|
||||
ALL_APPS_SEARCH_RESULT_LEGACY_SHORTCUT = 30;
|
||||
ALL_APPS_SEARCH_RESULT_ASSISTANT_MEMORY = 31;
|
||||
ALL_APPS_SEARCH_RESULT_VIDEO = 41;
|
||||
ALL_APPS_SEARCH_RESULT_SYSTEM_POINTER = 42;
|
||||
ALL_APPS_SEARCH_RESULT_EDUCARD = 43;
|
||||
|
||||
// Web suggestions provided by AGA
|
||||
ALL_APPS_SEARCH_RESULT_WEB_SUGGEST = 39;
|
||||
@@ -183,6 +197,7 @@ enum Attribute {
|
||||
WEB_SEARCH_RESULT_PERSONAL = 36;
|
||||
WEB_SEARCH_RESULT_CALCULATOR = 37;
|
||||
WEB_SEARCH_RESULT_URL = 38;
|
||||
WEB_SEARCH_RESULT_RICH_ANSWER = 40;
|
||||
|
||||
WIDGETS_BOTTOM_TRAY = 28;
|
||||
WIDGETS_TRAY_PREDICTION = 29;
|
||||
|
||||
@@ -17,6 +17,11 @@ package {
|
||||
default_applicable_licenses: ["Android-Apache-2.0"],
|
||||
}
|
||||
|
||||
filegroup {
|
||||
name: "launcher3-quickstep-manifest",
|
||||
srcs: ["AndroidManifest.xml"],
|
||||
}
|
||||
|
||||
filegroup {
|
||||
name: "launcher3-quickstep-robolectric-src",
|
||||
path: "robolectric_tests",
|
||||
@@ -26,13 +31,14 @@ filegroup {
|
||||
filegroup {
|
||||
name: "launcher3-quickstep-tests-src",
|
||||
path: "tests",
|
||||
srcs: ["tests/src/**/*.java"],
|
||||
srcs: ["tests/src/**/*.java", "tests/src/**/*.kt"],
|
||||
}
|
||||
|
||||
filegroup {
|
||||
name: "launcher3-quickstep-oop-tests-src",
|
||||
path: "tests",
|
||||
srcs: [
|
||||
"tests/src/com/android/quickstep/TaskbarModeSwitchRule.java",
|
||||
"tests/src/com/android/quickstep/NavigationModeSwitchRule.java",
|
||||
"tests/src/com/android/quickstep/AbstractQuickStepTest.java",
|
||||
"tests/src/com/android/quickstep/TaplTestsQuickstep.java",
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
<uses-permission android:name="android.permission.MANAGE_ACCESSIBILITY"/>
|
||||
<uses-permission android:name="android.permission.MONITOR_INPUT"/>
|
||||
<uses-permission android:name="android.permission.ALLOW_SLIPPERY_TOUCHES"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_SHORTCUTS"/>
|
||||
|
||||
<uses-permission android:name="android.permission.SYSTEM_APPLICATION_OVERLAY" />
|
||||
|
||||
@@ -102,6 +103,7 @@
|
||||
<activity android:name="com.android.quickstep.interaction.GestureSandboxActivity"
|
||||
android:autoRemoveFromRecents="true"
|
||||
android:excludeFromRecents="true"
|
||||
android:theme="@style/GestureTutorialActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="com.android.quickstep.action.GESTURE_SANDBOX"/>
|
||||
|
||||
+8
-77
@@ -15,22 +15,13 @@
|
||||
*/
|
||||
package com.android.quickstep;
|
||||
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.BaseQuickstepLauncher;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.taskbar.LauncherTaskbarUIController;
|
||||
import com.android.launcher3.testing.DebugTestInformationHandler;
|
||||
import com.android.launcher3.testing.TestProtocol;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import com.android.launcher3.testing.shared.TestProtocol;
|
||||
|
||||
/**
|
||||
* Class to handle requests from tests, including debug ones, to Quickstep Launcher builds.
|
||||
@@ -47,74 +38,14 @@ public abstract class DebugQuickstepTestInformationHandler extends QuickstepTest
|
||||
@Override
|
||||
public Bundle call(String method, String arg, @Nullable Bundle extras) {
|
||||
Bundle response = new Bundle();
|
||||
switch (method) {
|
||||
case TestProtocol.REQUEST_ENABLE_MANUAL_TASKBAR_STASHING:
|
||||
runOnUIThread(l -> {
|
||||
enableManualTaskbarStashing(l, true);
|
||||
});
|
||||
return response;
|
||||
|
||||
case TestProtocol.REQUEST_DISABLE_MANUAL_TASKBAR_STASHING:
|
||||
runOnUIThread(l -> {
|
||||
enableManualTaskbarStashing(l, false);
|
||||
});
|
||||
return response;
|
||||
|
||||
case TestProtocol.REQUEST_UNSTASH_TASKBAR_IF_STASHED:
|
||||
runOnUIThread(l -> {
|
||||
enableManualTaskbarStashing(l, true);
|
||||
|
||||
BaseQuickstepLauncher quickstepLauncher = (BaseQuickstepLauncher) l;
|
||||
LauncherTaskbarUIController taskbarUIController =
|
||||
quickstepLauncher.getTaskbarUIController();
|
||||
|
||||
// Allow null-pointer to catch illegal states.
|
||||
taskbarUIController.unstashTaskbarIfStashed();
|
||||
|
||||
enableManualTaskbarStashing(l, false);
|
||||
});
|
||||
return response;
|
||||
|
||||
case TestProtocol.REQUEST_STASHED_TASKBAR_HEIGHT: {
|
||||
final Resources resources = mContext.getResources();
|
||||
response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
|
||||
resources.getDimensionPixelSize(R.dimen.taskbar_stashed_size));
|
||||
return response;
|
||||
}
|
||||
|
||||
default:
|
||||
response = super.call(method, arg, extras);
|
||||
if (response != null) return response;
|
||||
return mDebugTestInformationHandler.call(method, arg, extras);
|
||||
if (TestProtocol.REQUEST_RECREATE_TASKBAR.equals(method)) {
|
||||
// Allow null-pointer to catch illegal states.
|
||||
runOnTISBinder(tisBinder -> tisBinder.getTaskbarManager().recreateTaskbar());
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
private void enableManualTaskbarStashing(Launcher launcher, boolean enable) {
|
||||
BaseQuickstepLauncher quickstepLauncher = (BaseQuickstepLauncher) launcher;
|
||||
LauncherTaskbarUIController taskbarUIController =
|
||||
quickstepLauncher.getTaskbarUIController();
|
||||
|
||||
// Allow null-pointer to catch illegal states.
|
||||
taskbarUIController.enableManualStashingForTests(enable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the given command on the UI thread.
|
||||
*/
|
||||
private static void runOnUIThread(UIThreadCommand command) {
|
||||
try {
|
||||
MAIN_EXECUTOR.submit(() -> {
|
||||
command.execute(Launcher.ACTIVITY_TRACKER.getCreatedActivity());
|
||||
return null;
|
||||
}).get();
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private interface UIThreadCommand {
|
||||
|
||||
void execute(Launcher launcher);
|
||||
response = super.call(method, arg, extras);
|
||||
if (response != null) return response;
|
||||
return mDebugTestInformationHandler.call(method, arg, extras);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<!-- Copyright (C) 2023 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.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="84dp"
|
||||
android:height="208dp"
|
||||
android:viewportWidth="84"
|
||||
android:viewportHeight="208">
|
||||
<path
|
||||
android:pathData="M24.53,169.2L32.09,165.56C77.7,143.55 77.7,64.45 32.09,42.35L24.53,38.71C14.55,33.95 6.06,25.56 0,14.92V193.08C6.06,182.44 14.55,174.05 24.53,169.2Z"
|
||||
android:fillColor="#217500"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,27 @@
|
||||
<!-- Copyright (C) 2023 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.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="232dp"
|
||||
android:height="67dp"
|
||||
android:viewportWidth="232"
|
||||
android:viewportHeight="67">
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M0,0h232v67h-232z"/>
|
||||
<path
|
||||
android:pathData="M180.9,0.6H51.1C22.9,0.6 0,23.4 0,51.7V67.6H232V51.7C232,23.4 209.1,0.6 180.9,0.6Z"
|
||||
android:fillColor="#4B67AE"/>
|
||||
</group>
|
||||
</vector>
|
||||
@@ -0,0 +1,27 @@
|
||||
<!-- Copyright (C) 2023 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.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="194dp"
|
||||
android:height="94dp"
|
||||
android:viewportWidth="194"
|
||||
android:viewportHeight="94">
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M0,0h194v94.09h-194z"/>
|
||||
<path
|
||||
android:pathData="M185.56,76.95C184.79,75.3 184.3,73.56 184.21,71.81L182.85,55.81C182.46,51.34 180.13,47.27 176.45,44.65L163.25,35.44C161.8,34.37 160.54,33.11 159.47,31.65L150.16,18.46C147.54,14.77 143.47,12.45 139,12.06L123,10.6C121.25,10.41 119.51,10.02 117.86,9.24L103.31,2.45C101.27,1.49 99.04,1 96.91,1C94.77,1 92.54,1.49 90.5,2.45L75.95,9.24C74.31,10.02 72.56,10.51 70.81,10.6L54.81,11.96C50.35,12.35 46.27,14.68 43.65,18.36L34.44,31.56C33.37,33.01 32.11,34.27 30.66,35.34L17.27,44.65C13.58,47.27 11.26,51.34 10.87,55.81L9.41,71.81C9.22,73.56 8.83,75.3 8.05,76.95L1.26,91.5C0.78,92.67 0.39,93.83 0.1,94.99H193.52C193.32,93.83 192.94,92.57 192.35,91.5L185.56,76.95Z"
|
||||
android:fillColor="#7E44AD"/>
|
||||
</group>
|
||||
</vector>
|
||||
@@ -0,0 +1,23 @@
|
||||
<!-- Copyright (C) 2023 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.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="122dp"
|
||||
android:height="303dp"
|
||||
android:viewportWidth="122"
|
||||
android:viewportHeight="303">
|
||||
<path
|
||||
android:pathData="M35.65,245.9L46.63,240.61C112.92,208.62 112.92,93.67 46.63,61.54L35.65,56.26C21.15,49.34 8.81,37.14 0,21.69V280.6C8.81,265.15 21.15,252.95 35.65,245.9Z"
|
||||
android:fillColor="#217500"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,27 @@
|
||||
<!-- Copyright (C) 2023 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.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="362dp"
|
||||
android:height="73dp"
|
||||
android:viewportWidth="362"
|
||||
android:viewportHeight="73">
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M0,0h362v73h-362z"/>
|
||||
<path
|
||||
android:pathData="M282.3,0H79.7C38,0 3.7,32.1 0.3,73H361.7C358.3,32.1 324,0 282.3,0Z"
|
||||
android:fillColor="#4B67AE"/>
|
||||
</group>
|
||||
</vector>
|
||||
@@ -0,0 +1,27 @@
|
||||
<!-- Copyright (C) 2023 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.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="297dp"
|
||||
android:height="144dp"
|
||||
android:viewportWidth="297"
|
||||
android:viewportHeight="144">
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M0,0h297v144.04h-297z"/>
|
||||
<path
|
||||
android:pathData="M284.38,116.48C283.19,113.95 282.45,111.28 282.3,108.61L280.22,84.1C279.63,77.27 276.06,71.03 270.42,67.03L250.22,52.92C247.99,51.28 246.06,49.35 244.43,47.13L230.18,26.93C226.16,21.29 219.93,17.72 213.1,17.13L188.6,14.9C185.92,14.6 183.25,14.01 180.72,12.82L158.45,2.43C155.33,0.94 151.91,0.2 148.65,0.2C145.38,0.2 141.97,0.94 138.85,2.43L116.57,12.82C114.05,14.01 111.38,14.75 108.7,14.9L84.2,16.98C77.37,17.57 71.13,21.14 67.12,26.78L53.01,46.98C51.38,49.21 49.45,51.14 47.22,52.77L26.73,67.03C21.09,71.03 17.52,77.27 16.93,84.1L14.7,108.61C14.4,111.28 13.81,113.95 12.62,116.48L2.23,138.75C1.48,140.53 0.89,142.32 0.45,144.1H296.55C296.26,142.32 295.66,140.38 294.77,138.75L284.38,116.48Z"
|
||||
android:fillColor="#7E44AD"/>
|
||||
</group>
|
||||
</vector>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2023 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.
|
||||
-->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<corners android:radius="@dimen/dialogCornerRadius" />
|
||||
<solid android:color="?androidprv:attr/colorSurface" />
|
||||
</shape>
|
||||
@@ -1,10 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="#909090"
|
||||
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,27 @@
|
||||
<!-- Copyright (C) 2023 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.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="83dp"
|
||||
android:height="208dp"
|
||||
android:viewportWidth="83"
|
||||
android:viewportHeight="208">
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M0,0h83.95v208h-83.95z"/>
|
||||
<path
|
||||
android:pathData="M23.53,169.2L31.09,165.56C76.7,143.55 76.7,64.45 31.09,42.35L23.53,38.71C13.55,33.95 5.06,25.56 -1,14.92V193.08C5.06,182.44 13.55,174.05 23.53,169.2Z"
|
||||
android:fillColor="#217500"/>
|
||||
</group>
|
||||
</vector>
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2023 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.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
<path
|
||||
android:fillColor="@android:color/black"
|
||||
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41L9,16.17z" />
|
||||
</vector>
|
||||
+14
-15
@@ -1,12 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2018 The Android Open Source Project
|
||||
|
||||
<!--
|
||||
Copyright (C) 2023 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.
|
||||
@@ -14,16 +12,17 @@
|
||||
limitations under the License.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?android:attr/textColorPrimary">
|
||||
android:width="148dp"
|
||||
android:height="148dp"
|
||||
android:viewportHeight="148"
|
||||
android:viewportWidth="148">
|
||||
|
||||
<group>
|
||||
<clip-path android:pathData="M74 0C114.869 0 148 33.1309 148 74C148 114.869 114.869 148 74 148C33.1309 148 0 114.869 0 74C0 33.1309 33.1309 0 74 0Z" />
|
||||
|
||||
<path
|
||||
android:fillColor="#E0E994"
|
||||
android:pathData="M0 0V148H148V0" />
|
||||
</group>
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M18,4v5H6V4H18 M18,2H6C4.9,2,4,2.9,4,4v5c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2V4C20,2.9,19.1,2,18,2L18,2z" />
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M18,15v5H6v-5H18 M18,13H6c-1.1,0-2,0.9-2,2v5c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2v-5C20,13.9,19.1,13,18,13L18,13z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,23 @@
|
||||
<!-- Copyright (C) 2023 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.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="364dp"
|
||||
android:height="66dp"
|
||||
android:viewportWidth="364"
|
||||
android:viewportHeight="66">
|
||||
<path
|
||||
android:pathData="M283.8,0H80.2C40.7,0 8,28.5 1.3,66H362.8C356,28.5 323.3,0 283.8,0Z"
|
||||
android:fillColor="#4B67AE"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,19 @@
|
||||
<!-- Copyright (C) 2023 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.
|
||||
-->
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="@dimen/gesture_tutorial_menu_button_radius" />
|
||||
</shape>
|
||||
@@ -0,0 +1,23 @@
|
||||
<!-- Copyright (C) 2023 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.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="200dp"
|
||||
android:height="76dp"
|
||||
android:viewportWidth="200"
|
||||
android:viewportHeight="76">
|
||||
<path
|
||||
android:pathData="M188.6,56.5C188.2,51.9 185.8,47.7 182,45L168.4,35.5C166.9,34.4 165.6,33.1 164.5,31.6L155,18C152.3,14.2 148.1,11.8 143.5,11.4L127,9.9C125.2,9.7 123.4,9.3 121.7,8.5L106.7,1.5C104.6,0.5 102.3,0 100.1,0C97.8,0 95.6,0.5 93.5,1.5L78.5,8.5C76.8,9.3 75,9.8 73.2,9.9L56.7,11.3C52.1,11.7 47.9,14.1 45.2,17.9L35.7,31.5C34.6,33 33.3,34.3 31.8,35.4L18,45C14.2,47.7 11.8,51.9 11.4,56.5L9.9,73C9.8,74 9.6,75 9.3,76H190.6C190.3,75 190.1,74 190,73L188.6,56.5Z"
|
||||
android:fillColor="#7E44AD"/>
|
||||
</vector>
|
||||
@@ -1,11 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2020 The Android Open Source Project
|
||||
<!-- Copyright (C) 2023 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
|
||||
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,
|
||||
@@ -13,7 +13,9 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<com.android.launcher3.graphics.ShadowDrawable
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:src="@drawable/ic_block_no_shadow"
|
||||
android:elevation="@dimen/drop_target_shadow_elevation" />
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/mock_app_icon" />
|
||||
<corners android:radius="@dimen/gesture_tutorial_hotseat_icon_corner_radius" />
|
||||
</shape>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2023 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.
|
||||
-->
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/gesture_home_tutorial_background" />
|
||||
<corners android:radius="@dimen/gesture_tutorial_hotseat_icon_corner_radius" />
|
||||
</shape>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2023 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.
|
||||
-->
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/mock_search_bar" />
|
||||
<corners android:radius="@dimen/gesture_tutorial_hotseat_search_corner_radius" />
|
||||
</shape>
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2023 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.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32.0dp"
|
||||
android:height="32.0dp"
|
||||
android:viewportWidth="32.0"
|
||||
android:viewportHeight="32.0"
|
||||
>
|
||||
<group android:scaleX="0.5"
|
||||
android:scaleY="0.5"
|
||||
android:translateX="6.0"
|
||||
android:translateY="6.0">
|
||||
<path
|
||||
android:fillColor="@android:color/black"
|
||||
android:pathData="M5.958,37.708Q4.458,37.708 3.354,36.604Q2.25,35.5 2.25,34V18.292Q2.25,16.792 3.354,15.688Q4.458,14.583 5.958,14.583H9.5V5.958Q9.5,4.458 10.625,3.354Q11.75,2.25 13.208,2.25H34Q35.542,2.25 36.646,3.354Q37.75,4.458 37.75,5.958V21.667Q37.75,23.167 36.646,24.271Q35.542,25.375 34,25.375H30.5V34Q30.5,35.5 29.396,36.604Q28.292,37.708 26.792,37.708ZM5.958,34H26.792Q26.792,34 26.792,34Q26.792,34 26.792,34V21.542H5.958V34Q5.958,34 5.958,34Q5.958,34 5.958,34ZM30.5,21.667H34Q34,21.667 34,21.667Q34,21.667 34,21.667V9.208H13.208V14.583H26.833Q28.375,14.583 29.438,15.667Q30.5,16.75 30.5,18.25Z"/>
|
||||
</group>
|
||||
</vector>
|
||||
@@ -16,13 +16,13 @@
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="28dp"
|
||||
android:height="28dp"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:autoMirrored="true"
|
||||
android:viewportWidth="28"
|
||||
android:viewportHeight="28">
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M6.49,14.86c-0.66-0.39-0.66-1.34,0-1.73l6.02-3.53l5.89-3.46C19.11,5.73,20,6.26,20,7.1V14v6.9 c0,0.84-0.89,1.37-1.6,0.95l-5.89-3.46L6.49,14.86z" />
|
||||
android:pathData="M15.5417 1.66669C15.1833 1.66669 14.8417 1.76669 14.5333 1.94169L3.21667 8.74169C2.775 9.00002 2.5 9.48335 2.5 10C2.5 10.5167 2.775 11 3.21667 11.2584L14.5333 18.05C14.8417 18.2334 15.1833 18.325 15.5417 18.325C16.625 18.325 17.5 17.45 17.5 16.3667V3.62502C17.5 2.54169 16.625 1.66669 15.5417 1.66669Z" />
|
||||
</vector>
|
||||
@@ -1,6 +1,7 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
|
||||
@@ -16,12 +16,12 @@
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="28dp"
|
||||
android:height="28dp"
|
||||
android:viewportWidth="28"
|
||||
android:viewportHeight="28">
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M 14 7 C 17.8659932488 7 21 10.1340067512 21 14 C 21 17.8659932488 17.8659932488 21 14 21 C 10.1340067512 21 7 17.8659932488 7 14 C 7 10.1340067512 10.1340067512 7 14 7 Z" />
|
||||
android:pathData="M10.0001 18.3334C5.40008 18.3334 1.66675 14.6 1.66675 10C1.66675 5.40002 5.40008 1.66669 10.0001 1.66669C14.6001 1.66669 18.3334 5.40002 18.3334 10C18.3334 14.6 14.6001 18.3334 10.0001 18.3334Z" />
|
||||
</vector>
|
||||
@@ -16,12 +16,12 @@
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="28dp"
|
||||
android:height="28dp"
|
||||
android:viewportWidth="28"
|
||||
android:viewportHeight="28">
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19.9,21.5H8.1c-0.88,0-1.6-0.72-1.6-1.6V8.1c0-0.88,0.72-1.6,1.6-1.6h11.8c0.88,0,1.6,0.72,1.6,1.6v11.8 C21.5,20.78,20.78,21.5,19.9,21.5z" />
|
||||
android:pathData="M4.47634 2.5H15.5241C16.6164 2.5 17.5002 3.38382 17.5002 4.4761V15.5239C17.5002 16.6162 16.6164 17.5 15.5241 17.5H4.47634C3.38407 17.5 2.50024 16.6162 2.50024 15.5239V4.4761C2.50024 3.38382 3.38407 2.5 4.47634 2.5Z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2023 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.
|
||||
-->
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="?androidprv:attr/colorSurfaceVariant" />
|
||||
<corners android:radius="@dimen/keyboard_quick_switch_task_view_radius" />
|
||||
</shape>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2023 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.
|
||||
-->
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@android:color/transparent" />
|
||||
<corners android:radius="@dimen/keyboard_quick_switch_task_view_radius" />
|
||||
</shape>
|
||||
+7
-5
@@ -1,11 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2017 The Android Open Source Project
|
||||
<!-- Copyright (C) 2023 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
|
||||
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,
|
||||
@@ -13,7 +13,9 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<com.android.launcher3.graphics.ShadowDrawable
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:src="@drawable/ic_uninstall_no_shadow"
|
||||
android:elevation="@dimen/drop_target_shadow_elevation" />
|
||||
android:shape="rectangle">
|
||||
<solid android:color="?attr/overviewScrimColor" />
|
||||
<corners android:radius="@dimen/keyboard_quick_switch_view_radius" />
|
||||
</shape>
|
||||
@@ -1,11 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2017 The Android Open Source Project
|
||||
<!-- Copyright (C) 2023 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
|
||||
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,
|
||||
@@ -13,7 +13,9 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<com.android.launcher3.graphics.ShadowDrawable
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:src="@drawable/ic_remove_no_shadow"
|
||||
android:elevation="@dimen/drop_target_shadow_elevation" />
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/mock_app_icon" />
|
||||
<corners android:radius="@dimen/gesture_tutorial_taskbar_icon_corner_radius" />
|
||||
</shape>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2023 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.
|
||||
-->
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/gesture_tutorial_taskbar_color" />
|
||||
<corners android:radius="@dimen/gesture_tutorial_taskbar_corner_radius" />
|
||||
</shape>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2023 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.
|
||||
-->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<solid android:color="@color/gesture_home_tutorial_background" />
|
||||
</shape>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2022 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.
|
||||
-->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="?androidprv:attr/colorAccentPrimary" />
|
||||
<corners android:radius="@dimen/split_instructions_radius" />
|
||||
</shape>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.5 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.5 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.5 KiB |
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2023 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.
|
||||
-->
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/gesture_tutorial_fake_previous_task_view_color" />
|
||||
<corners android:radius="@dimen/gesture_tutorial_small_task_view_corner_radius" />
|
||||
</shape>
|
||||
Loaded 100 of 1130 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user