├── viewcapturelib ├── OWNERS ├── .gitignore ├── TEST_MAPPING ├── README.md ├── AndroidManifest.xml ├── tests │ ├── AndroidManifest.xml │ └── com │ │ └── android │ │ └── app │ │ └── viewcapture │ │ ├── TestActivity.kt │ │ ├── SettingsAwareViewCaptureTest.kt │ │ └── ViewCaptureTest.kt ├── src │ └── com │ │ └── android │ │ └── app │ │ └── viewcapture │ │ ├── proto │ │ └── view_capture.proto │ │ ├── LooperExecutor.java │ │ └── SettingsAwareViewCapture.kt ├── build.gradle └── Android.bp ├── motiontoollib ├── OWNERS ├── .gitignore ├── TEST_MAPPING ├── tests │ ├── com │ │ └── android │ │ │ └── app │ │ │ └── motiontool │ │ │ ├── util │ │ │ └── TestActivity.kt │ │ │ ├── MotionToolManagerTest.kt │ │ │ └── DdmHandleMotionToolTest.kt │ └── AndroidManifest.xml ├── AndroidManifest.xml ├── build.gradle ├── Android.bp └── src │ └── com │ └── android │ └── app │ └── motiontool │ ├── proto │ └── motion_tool.proto │ ├── MotionToolManager.kt │ └── DdmHandleMotionTool.kt ├── iconloaderlib ├── .gitignore ├── build.gradle ├── res │ ├── values │ │ ├── dimens.xml │ │ ├── attrs.xml │ │ ├── colors.xml │ │ └── config.xml │ ├── drawable-v26 │ │ └── adaptive_icon_drawable_wrapper.xml │ └── drawable │ │ ├── ic_work_app_badge.xml │ │ ├── ic_clone_app_badge.xml │ │ └── ic_instant_app_badge.xml ├── AndroidManifest.xml ├── src │ └── com │ │ └── android │ │ └── launcher3 │ │ ├── util │ │ ├── SafeCloseable.java │ │ ├── FlagOp.java │ │ ├── override │ │ │ ├── ResourceBasedOverride.java │ │ │ ├── TraceHelper.java │ │ │ ├── LooperExecutor.java │ │ │ ├── Executors.java │ │ │ └── MainThreadInitializedObject.java │ │ ├── NoLocaleSQLiteHelper.java │ │ ├── ComponentKey.java │ │ └── SQLiteCacheHelper.java │ │ └── icons │ │ ├── FixedScaleDrawable.java │ │ ├── RoundDrawableWrapper.java │ │ ├── cache │ │ ├── CachingLogic.java │ │ ├── HandlerRunnable.java │ │ └── IconCacheUpdateHandler.java │ │ ├── BitmapRenderer.java │ │ ├── PlaceHolderIconDrawable.java │ │ ├── GraphicsUtils.java │ │ ├── ColorExtractor.java │ │ ├── ThemedIconDrawable.java │ │ ├── DotRenderer.java │ │ ├── ShadowGenerator.java │ │ └── BitmapInfo.java ├── Android.bp └── src_full_lib │ └── com │ └── android │ └── launcher3 │ └── icons │ ├── IconFactory.java │ └── SimpleIconCache.java └── searchuilib ├── .gitignore ├── AndroidManifest.xml ├── build.gradle ├── Android.bp └── src └── com └── android └── app └── search ├── QueryExtras.java ├── SearchTargetGenerator.java ├── SearchActionExtras.java ├── SearchTargetConverter.java ├── ResultType.java ├── SearchTargetEventHelper.java ├── LayoutType.java └── SearchTargetExtras.java /viewcapturelib/OWNERS: -------------------------------------------------------------------------------- 1 | sunnygoyal@google.com 2 | andonian@google.com 3 | -------------------------------------------------------------------------------- /motiontoollib/OWNERS: -------------------------------------------------------------------------------- 1 | gallmann@google.com 2 | michschn@google.com 3 | cinek@google.com -------------------------------------------------------------------------------- /iconloaderlib/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .project 3 | .classpath 4 | .project.properties 5 | gen/ 6 | bin/ 7 | .idea/ 8 | .gradle/ 9 | local.properties 10 | gradle/ 11 | build/ 12 | gradlew* 13 | .DS_Store 14 | -------------------------------------------------------------------------------- /motiontoollib/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .project 3 | .classpath 4 | .project.properties 5 | gen/ 6 | bin/ 7 | .idea/ 8 | .gradle/ 9 | local.properties 10 | gradle/ 11 | build/ 12 | gradlew* 13 | .DS_Store 14 | -------------------------------------------------------------------------------- /searchuilib/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .project 3 | .classpath 4 | .project.properties 5 | gen/ 6 | bin/ 7 | .idea/ 8 | .gradle/ 9 | local.properties 10 | gradle/ 11 | build/ 12 | gradlew* 13 | .DS_Store 14 | -------------------------------------------------------------------------------- /viewcapturelib/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .project 3 | .classpath 4 | .project.properties 5 | gen/ 6 | bin/ 7 | .idea/ 8 | .gradle/ 9 | local.properties 10 | gradle/ 11 | build/ 12 | gradlew* 13 | .DS_Store 14 | -------------------------------------------------------------------------------- /motiontoollib/TEST_MAPPING: -------------------------------------------------------------------------------- 1 | { 2 | "presubmit": [ 3 | { 4 | "name": "motion_tool_lib_tests", 5 | "options": [ 6 | { 7 | "exclude-annotation": "org.junit.Ignore" 8 | }, 9 | { 10 | "exclude-annotation": "androidx.test.filters.FlakyTest" 11 | } 12 | ] 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /viewcapturelib/TEST_MAPPING: -------------------------------------------------------------------------------- 1 | { 2 | "presubmit": [ 3 | { 4 | "name": "view_capture_tests", 5 | "options": [ 6 | { 7 | "exclude-annotation": "org.junit.Ignore" 8 | }, 9 | { 10 | "exclude-annotation": "androidx.test.filters.FlakyTest" 11 | } 12 | ] 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /viewcapturelib/README.md: -------------------------------------------------------------------------------- 1 | ###ViewCapture Library Readme 2 | 3 | ViewCapture.java is extremely performance sensitive. Any changes should be carried out with great caution not to hurt performance. 4 | 5 | The following measurements should serve as a performance baseline (as of 02.10.2022): 6 | 7 | 8 | The onDraw() function invocation time in WindowListener within ViewCapture is measured with System.nanoTime(). The following scenario was measured: 9 | 10 | 1. Capturing the notification shade window root view on a freshly rebooted bluejay device (2 notifications present) -> avg. time = 204237ns (0.2ms) 11 | 12 | -------------------------------------------------------------------------------- /iconloaderlib/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'sysuigradleproject.android-library-conventions' 3 | } 4 | 5 | android { 6 | sourceSets { 7 | main { 8 | java.srcDirs = ['src', 'src_full_lib'] 9 | manifest.srcFile 'AndroidManifest.xml' 10 | res.srcDirs = ['res'] 11 | } 12 | } 13 | 14 | lintOptions { 15 | abortOnError false 16 | } 17 | 18 | tasks.withType(JavaCompile) { 19 | options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" 20 | } 21 | } 22 | 23 | dependencies { 24 | implementation "androidx.core:core" 25 | } 26 | -------------------------------------------------------------------------------- /iconloaderlib/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | 24dp 19 | 20 | -------------------------------------------------------------------------------- /motiontoollib/tests/com/android/app/motiontool/util/TestActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.app.motiontool.util 18 | 19 | import android.app.Activity 20 | 21 | class TestActivity : Activity() 22 | -------------------------------------------------------------------------------- /searchuilib/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 20 | 21 | -------------------------------------------------------------------------------- /motiontoollib/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 20 | 21 | -------------------------------------------------------------------------------- /iconloaderlib/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 20 | 21 | -------------------------------------------------------------------------------- /iconloaderlib/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /searchuilib/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | } 4 | 5 | android { 6 | compileSdk TARGET_SDK.toInteger() 7 | buildToolsVersion = BUILD_TOOLS_VERSION 8 | 9 | defaultConfig { 10 | minSdkVersion TARGET_SDK.toInteger() 11 | targetSdkVersion TARGET_SDK.toInteger() 12 | } 13 | 14 | sourceSets { 15 | main { 16 | java.srcDirs = ['src'] 17 | manifest.srcFile 'AndroidManifest.xml' 18 | } 19 | } 20 | 21 | lintOptions { 22 | abortOnError false 23 | } 24 | 25 | tasks.withType(JavaCompile) { 26 | options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" 27 | } 28 | 29 | compileOptions { 30 | sourceCompatibility JavaVersion.VERSION_1_8 31 | targetCompatibility JavaVersion.VERSION_1_8 32 | } 33 | } 34 | 35 | dependencies { 36 | implementation "androidx.core:core:+" 37 | } 38 | -------------------------------------------------------------------------------- /iconloaderlib/src/com/android/launcher3/util/SafeCloseable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.launcher3.util; 18 | 19 | /** 20 | * Extension of closeable which does not throw an exception 21 | */ 22 | public interface SafeCloseable extends AutoCloseable { 23 | 24 | @Override 25 | void close(); 26 | } 27 | -------------------------------------------------------------------------------- /searchuilib/Android.bp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020 The Android Open Source Project 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package { 16 | default_applicable_licenses: ["Android-Apache-2.0"], 17 | } 18 | 19 | android_library { 20 | name: "search_ui", 21 | 22 | sdk_version: "system_current", 23 | 24 | static_libs: [ 25 | "androidx.annotation_annotation", 26 | ], 27 | srcs: [ 28 | "src/**/*.java", 29 | ], 30 | } 31 | -------------------------------------------------------------------------------- /iconloaderlib/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | #FFFFFF 21 | 22 | 23 | #f9ab00 24 | 25 | -------------------------------------------------------------------------------- /iconloaderlib/res/drawable-v26/adaptive_icon_drawable_wrapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /viewcapturelib/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 21 | 23 | 24 | -------------------------------------------------------------------------------- /iconloaderlib/res/values/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | 22 | 23 | 56dp 24 | false 25 | app_icons.db 26 | 27 | 28 | 29 | com.android.launcher3.icons.ThirdPartyIconProvider 30 | 31 | -------------------------------------------------------------------------------- /iconloaderlib/Android.bp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2018 The Android Open Source Project 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package { 16 | default_applicable_licenses: ["Android-Apache-2.0"], 17 | } 18 | 19 | android_library { 20 | name: "iconloader_base", 21 | sdk_version: "current", 22 | min_sdk_version: "26", 23 | static_libs: [ 24 | "androidx.core_core", 25 | ], 26 | resource_dirs: [ 27 | "res", 28 | ], 29 | srcs: [ 30 | "src/**/*.java", 31 | ], 32 | } 33 | 34 | android_library { 35 | name: "iconloader", 36 | sdk_version: "system_current", 37 | min_sdk_version: "26", 38 | static_libs: [ 39 | "androidx.core_core", 40 | ], 41 | resource_dirs: [ 42 | "res", 43 | ], 44 | srcs: [ 45 | "src/**/*.java", 46 | "src_full_lib/**/*.java", 47 | ], 48 | } 49 | -------------------------------------------------------------------------------- /motiontoollib/tests/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 19 | 20 | 23 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /viewcapturelib/tests/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 19 | 20 | 23 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /viewcapturelib/tests/com/android/app/viewcapture/TestActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.app.viewcapture 18 | 19 | import android.app.Activity 20 | import android.os.Bundle 21 | import android.widget.LinearLayout 22 | import android.widget.TextView 23 | 24 | /** 25 | * Activity with the content set to a [LinearLayout] with [TextView] children. 26 | */ 27 | class TestActivity : Activity() { 28 | 29 | companion object { 30 | const val TEXT_VIEW_COUNT = 1000 31 | } 32 | 33 | override fun onCreate(savedInstanceState: Bundle?) { 34 | super.onCreate(savedInstanceState) 35 | setContentView(createContentView()) 36 | } 37 | 38 | private fun createContentView(): LinearLayout { 39 | val root = LinearLayout(this) 40 | for (i in 0 until TEXT_VIEW_COUNT) { 41 | root.addView(TextView(this)) 42 | } 43 | return root 44 | } 45 | } -------------------------------------------------------------------------------- /iconloaderlib/src/com/android/launcher3/util/FlagOp.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2022 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.android.launcher3.util; 17 | 18 | /** 19 | * Utility interface for representing flag operations 20 | */ 21 | public interface FlagOp { 22 | 23 | FlagOp NO_OP = i -> i; 24 | 25 | int apply(int flags); 26 | 27 | /** 28 | * Returns a new OP which adds the provided flag after applying all previous operations 29 | */ 30 | default FlagOp addFlag(int flag) { 31 | return i -> apply(i) | flag; 32 | } 33 | 34 | /** 35 | * Returns a new OP which removes the provided flag after applying all previous operations 36 | */ 37 | default FlagOp removeFlag(int flag) { 38 | return i -> apply(i) & ~flag; 39 | } 40 | 41 | /** 42 | * Returns a new OP which adds or removed the provided flag based on {@code enable} after 43 | * applying all previous operations 44 | */ 45 | default FlagOp setFlag(int flag, boolean enable) { 46 | return enable ? addFlag(flag) : removeFlag(flag); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /searchuilib/src/com/android/app/search/QueryExtras.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.app.search; 18 | 19 | import android.app.search.Query; 20 | 21 | /** 22 | * Utility class used to define implicit contract between aiai and launcher regarding 23 | * what constant string key should be used to pass sub session information inside 24 | * the {@link Query} object. 25 | * 26 | * This decorated query object is passed to aiai using two method calls: 27 | * 31 | */ 32 | public class QueryExtras { 33 | 34 | // Can be either 1 (ALLAPPS) or 2 (QSB) 35 | public static final String EXTRAS_KEY_ENTRY = "entry"; 36 | 37 | // This value overrides the timeout that is defined inside {@link SearchContext#getTimeout} 38 | public static final String EXTRAS_KEY_TIMEOUT_OVERRIDE = "timeout"; 39 | 40 | // Used to know which target is deleted. 41 | public static final String EXTRAS_BUNDLE_DELETED_TARGET_ID = "deleted_target_id"; 42 | } 43 | -------------------------------------------------------------------------------- /iconloaderlib/res/drawable/ic_work_app_badge.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 21 | 22 | 27 | 28 | 33 | 34 | 35 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /viewcapturelib/src/com/android/app/viewcapture/proto/view_capture.proto: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | syntax = "proto2"; 18 | 19 | package com.android.app.viewcapture.data; 20 | 21 | option java_multiple_files = true; 22 | 23 | message ExportedData { 24 | 25 | repeated FrameData frameData = 1; 26 | repeated string classname = 2; 27 | } 28 | 29 | message FrameData { 30 | optional int64 timestamp = 1; // choreographer timestamp in nanoseconds 31 | optional ViewNode node = 2; 32 | } 33 | 34 | message ViewNode { 35 | optional int32 classname_index = 1; 36 | optional int32 hashcode = 2; 37 | 38 | repeated ViewNode children = 3; 39 | 40 | optional string id = 4; 41 | optional int32 left = 5; 42 | optional int32 top = 6; 43 | optional int32 width = 7; 44 | optional int32 height = 8; 45 | optional int32 scrollX = 9; 46 | optional int32 scrollY = 10; 47 | 48 | optional float translationX = 11; 49 | optional float translationY = 12; 50 | optional float scaleX = 13 [default = 1]; 51 | optional float scaleY = 14 [default = 1]; 52 | optional float alpha = 15 [default = 1]; 53 | 54 | optional bool willNotDraw = 16; 55 | optional bool clipChildren = 17; 56 | optional int32 visibility = 18; 57 | 58 | optional float elevation = 19; 59 | } 60 | -------------------------------------------------------------------------------- /iconloaderlib/src/com/android/launcher3/icons/FixedScaleDrawable.java: -------------------------------------------------------------------------------- 1 | package com.android.launcher3.icons; 2 | 3 | import android.content.res.Resources; 4 | import android.content.res.Resources.Theme; 5 | import android.graphics.Canvas; 6 | import android.graphics.drawable.ColorDrawable; 7 | import android.graphics.drawable.DrawableWrapper; 8 | import android.util.AttributeSet; 9 | 10 | import org.xmlpull.v1.XmlPullParser; 11 | 12 | /** 13 | * Extension of {@link DrawableWrapper} which scales the child drawables by a fixed amount. 14 | */ 15 | public class FixedScaleDrawable extends DrawableWrapper { 16 | 17 | // TODO b/33553066 use the constant defined in MaskableIconDrawable 18 | private static final float LEGACY_ICON_SCALE = .7f * .6667f; 19 | private float mScaleX, mScaleY; 20 | 21 | public FixedScaleDrawable() { 22 | super(new ColorDrawable()); 23 | mScaleX = LEGACY_ICON_SCALE; 24 | mScaleY = LEGACY_ICON_SCALE; 25 | } 26 | 27 | @Override 28 | public void draw(Canvas canvas) { 29 | int saveCount = canvas.save(); 30 | canvas.scale(mScaleX, mScaleY, 31 | getBounds().exactCenterX(), getBounds().exactCenterY()); 32 | super.draw(canvas); 33 | canvas.restoreToCount(saveCount); 34 | } 35 | 36 | @Override 37 | public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) { } 38 | 39 | @Override 40 | public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) { } 41 | 42 | public void setScale(float scale) { 43 | float h = getIntrinsicHeight(); 44 | float w = getIntrinsicWidth(); 45 | mScaleX = scale * LEGACY_ICON_SCALE; 46 | mScaleY = scale * LEGACY_ICON_SCALE; 47 | if (h > w && w > 0) { 48 | mScaleX *= w / h; 49 | } else if (w > h && h > 0) { 50 | mScaleY *= h / w; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /searchuilib/src/com/android/app/search/SearchTargetGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.app.search; 18 | 19 | import static com.android.app.search.LayoutType.EMPTY_DIVIDER; 20 | import static com.android.app.search.LayoutType.SECTION_HEADER; 21 | import static com.android.app.search.ResultType.NO_FULFILLMENT; 22 | 23 | import android.app.search.SearchTarget; 24 | import android.os.Bundle; 25 | import android.os.Process; 26 | import android.os.UserHandle; 27 | 28 | public class SearchTargetGenerator { 29 | private static final UserHandle USERHANDLE = Process.myUserHandle(); 30 | 31 | public static SearchTarget EMPTY_DIVIDER_TARGET = 32 | new SearchTarget.Builder(NO_FULFILLMENT, EMPTY_DIVIDER, "divider") 33 | .setPackageName("") /* required but not used*/ 34 | .setUserHandle(USERHANDLE) /* required */ 35 | .setExtras(new Bundle()) 36 | .build(); 37 | 38 | public static SearchTarget SECTION_HEADER_TARGET = 39 | new SearchTarget.Builder(NO_FULFILLMENT, SECTION_HEADER, "section_header") 40 | .setPackageName("") /* required but not used*/ 41 | .setUserHandle(USERHANDLE) /* required */ 42 | .setExtras(new Bundle()) 43 | .build(); 44 | } 45 | -------------------------------------------------------------------------------- /iconloaderlib/src/com/android/launcher3/icons/RoundDrawableWrapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.launcher3.icons; 18 | 19 | import android.graphics.Canvas; 20 | import android.graphics.Path; 21 | import android.graphics.Rect; 22 | import android.graphics.RectF; 23 | import android.graphics.drawable.Drawable; 24 | import android.graphics.drawable.DrawableWrapper; 25 | 26 | /** 27 | * A drawable which clips rounded corner around a child drawable 28 | */ 29 | public class RoundDrawableWrapper extends DrawableWrapper { 30 | 31 | private final RectF mTempRect = new RectF(); 32 | private final Path mClipPath = new Path(); 33 | private final float mRoundedCornersRadius; 34 | 35 | public RoundDrawableWrapper(Drawable dr, float radius) { 36 | super(dr); 37 | mRoundedCornersRadius = radius; 38 | } 39 | 40 | @Override 41 | protected void onBoundsChange(Rect bounds) { 42 | mTempRect.set(getBounds()); 43 | mClipPath.reset(); 44 | mClipPath.addRoundRect(mTempRect, mRoundedCornersRadius, 45 | mRoundedCornersRadius, Path.Direction.CCW); 46 | super.onBoundsChange(bounds); 47 | } 48 | 49 | @Override 50 | public final void draw(Canvas canvas) { 51 | int saveCount = canvas.save(); 52 | canvas.clipPath(mClipPath); 53 | super.draw(canvas); 54 | canvas.restoreToCount(saveCount); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /iconloaderlib/res/drawable/ic_clone_app_badge.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 21 | 22 | 27 | 28 | 33 | 34 | 35 | 38 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /viewcapturelib/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'sysuigradleproject.android-library-conventions' 3 | id 'org.jetbrains.kotlin.android' 4 | id 'com.google.protobuf' 5 | } 6 | 7 | final String PROTOS_DIR = "${ANDROID_TOP}/frameworks/libs/systemui/viewcapturelib/src/com/android/app/viewcapture/proto" 8 | 9 | android { 10 | compileSdk TARGET_SDK.toInteger() 11 | buildToolsVersion = BUILD_TOOLS_VERSION 12 | 13 | defaultConfig { 14 | minSdkVersion TARGET_SDK.toInteger() 15 | targetSdkVersion TARGET_SDK.toInteger() 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | } 18 | 19 | sourceSets { 20 | main { 21 | java.srcDirs = ['src'] 22 | manifest.srcFile 'AndroidManifest.xml' 23 | proto.srcDirs = ["${PROTOS_DIR}"] 24 | } 25 | androidTest { 26 | java.srcDirs = ["tests"] 27 | manifest.srcFile "tests/AndroidManifest.xml" 28 | } 29 | } 30 | 31 | lintOptions { 32 | abortOnError false 33 | } 34 | } 35 | 36 | dependencies { 37 | implementation "androidx.core:core:1.9.0" 38 | implementation "com.google.protobuf:protobuf-lite:${protobuf_version}" 39 | androidTestImplementation project(':SharedTestLib') 40 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 41 | androidTestImplementation "androidx.test:rules:1.4.0" 42 | } 43 | 44 | protobuf { 45 | // Configure the protoc executable 46 | protoc { 47 | artifact = "com.google.protobuf:protoc:${protobuf_version}${PROTO_ARCH_SUFFIX}" 48 | } 49 | plugins { 50 | javalite { 51 | // The codegen for lite comes as a separate artifact 52 | artifact = "com.google.protobuf:protoc-gen-javalite:${protobuf_version}${PROTO_ARCH_SUFFIX}" 53 | } 54 | } 55 | generateProtoTasks { 56 | all().each { task -> 57 | task.builtins { 58 | remove java 59 | } 60 | task.plugins { 61 | javalite { } 62 | } 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /viewcapturelib/src/com/android/app/viewcapture/LooperExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.android.app.viewcapture; 17 | 18 | import android.os.Handler; 19 | import android.os.Looper; 20 | 21 | import java.util.concurrent.Callable; 22 | import java.util.concurrent.Executor; 23 | import java.util.concurrent.Future; 24 | import java.util.concurrent.FutureTask; 25 | import java.util.concurrent.RejectedExecutionException; 26 | import java.util.concurrent.RunnableFuture; 27 | 28 | /** 29 | * Implementation of {@link Executor} which executes on a provided looper. 30 | */ 31 | public class LooperExecutor implements Executor { 32 | 33 | private final Handler mHandler; 34 | 35 | public LooperExecutor(Looper looper) { 36 | mHandler = new Handler(looper); 37 | } 38 | 39 | @Override 40 | public void execute(Runnable runnable) { 41 | if (mHandler.getLooper() == Looper.myLooper()) { 42 | runnable.run(); 43 | } else { 44 | mHandler.post(runnable); 45 | } 46 | } 47 | 48 | /** 49 | * @throws RejectedExecutionException {@inheritDoc} 50 | * @throws NullPointerException {@inheritDoc} 51 | */ 52 | public Future submit(Callable task) { 53 | if (task == null) throw new NullPointerException(); 54 | RunnableFuture ftask = new FutureTask(task); 55 | execute(ftask); 56 | return ftask; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /motiontoollib/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'sysuigradleproject.android-library-conventions' 3 | id 'org.jetbrains.kotlin.android' 4 | id 'com.google.protobuf' 5 | } 6 | 7 | final String PROTOS_DIR = "${ANDROID_TOP}/frameworks/libs/systemui/motiontoollib/src/com/android/app/motiontool/proto" 8 | 9 | android { 10 | compileSdk TARGET_SDK.toInteger() 11 | buildToolsVersion = BUILD_TOOLS_VERSION 12 | 13 | defaultConfig { 14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 15 | } 16 | 17 | defaultConfig { 18 | minSdkVersion TARGET_SDK.toInteger() 19 | targetSdkVersion TARGET_SDK.toInteger() 20 | } 21 | 22 | sourceSets { 23 | main { 24 | java.srcDirs = ['src'] 25 | manifest.srcFile 'AndroidManifest.xml' 26 | proto.srcDirs = ["${PROTOS_DIR}"] 27 | } 28 | androidTest { 29 | java.srcDirs = ["tests"] 30 | manifest.srcFile "tests/AndroidManifest.xml" 31 | } 32 | } 33 | 34 | lintOptions { 35 | abortOnError false 36 | } 37 | } 38 | 39 | dependencies { 40 | implementation "androidx.core:core:1.9.0" 41 | implementation "com.google.protobuf:protobuf-lite:${protobuf_version}" 42 | api project(":ViewCaptureLib") 43 | androidTestImplementation project(':SharedTestLib') 44 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 45 | androidTestImplementation "androidx.test:rules:1.4.0" 46 | } 47 | 48 | protobuf { 49 | // Configure the protoc executable 50 | protoc { 51 | artifact = "com.google.protobuf:protoc:${protobuf_version}${PROTO_ARCH_SUFFIX}" 52 | } 53 | plugins { 54 | javalite { 55 | // The codegen for lite comes as a separate artifact 56 | artifact = "com.google.protobuf:protoc-gen-javalite:${protobuf_version}${PROTO_ARCH_SUFFIX}" 57 | } 58 | } 59 | generateProtoTasks { 60 | all().each { task -> 61 | task.builtins { 62 | remove java 63 | } 64 | task.plugins { 65 | javalite { } 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /viewcapturelib/Android.bp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 The Android Open Source Project 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package { 16 | default_applicable_licenses: ["Android-Apache-2.0"], 17 | } 18 | 19 | java_library { 20 | name: "view_capture_proto", 21 | srcs: ["src/com/android/app/viewcapture/proto/*.proto"], 22 | proto: { 23 | type: "lite", 24 | local_include_dirs:[ 25 | "src/com/android/app/viewcapture/proto" 26 | ], 27 | }, 28 | static_libs: ["libprotobuf-java-lite"], 29 | java_version: "1.8", 30 | } 31 | 32 | android_library { 33 | name: "view_capture", 34 | manifest: "AndroidManifest.xml", 35 | platform_apis: true, 36 | min_sdk_version: "26", 37 | 38 | static_libs: [ 39 | "androidx.core_core", 40 | "view_capture_proto", 41 | ], 42 | 43 | srcs: [ 44 | "src/**/*.java", 45 | "src/**/*.kt" 46 | ], 47 | } 48 | 49 | android_test { 50 | name: "view_capture_tests", 51 | manifest: "tests/AndroidManifest.xml", 52 | platform_apis: true, 53 | min_sdk_version: "26", 54 | 55 | static_libs: [ 56 | "androidx.core_core", 57 | "view_capture", 58 | "androidx.test.ext.junit", 59 | "androidx.test.rules", 60 | "testables", 61 | "mockito-target-extended-minus-junit4", 62 | ], 63 | srcs: [ 64 | "**/*.java", 65 | "**/*.kt" 66 | ], 67 | libs: [ 68 | "android.test.runner", 69 | "android.test.base", 70 | "android.test.mock", 71 | ], 72 | test_suites: ["device-tests"], 73 | } 74 | -------------------------------------------------------------------------------- /iconloaderlib/res/drawable/ic_instant_app_badge.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 21 | 22 | 26 | 30 | 34 | 39 | 40 | -------------------------------------------------------------------------------- /searchuilib/src/com/android/app/search/SearchActionExtras.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.app.search; 18 | 19 | import android.app.search.SearchAction; 20 | 21 | /** 22 | * Helper class that defines key string value for {@link SearchAction#getExtras()} 23 | */ 24 | public class SearchActionExtras { 25 | public static final String BUNDLE_EXTRA_HIDE_SUBTITLE = "hide_subtitle"; 26 | public static final String BUNDLE_EXTRA_HIDE_ICON = "hide_icon"; 27 | public static final String BUNDLE_EXTRA_ALLOW_PINNING = "allow_pinning"; 28 | public static final String BUNDLE_EXTRA_BADGE_WITH_PACKAGE = "badge_with_package"; 29 | public static final String BUNDLE_EXTRA_PRIMARY_ICON_FROM_TITLE = "primary_icon_from_title"; 30 | public static final String BUNDLE_EXTRA_IS_SEARCH_IN_APP = "is_search_in_app"; 31 | public static final String BUNDLE_EXTRA_BADGE_WITH_COMPONENT_NAME = "badge_with_component_name"; 32 | public static final String BUNDLE_EXTRA_ICON_CACHE_KEY = "icon_cache_key"; 33 | public static final String BUNDLE_EXTRA_ICON_TOKEN_INTEGER = "icon_integer"; 34 | public static final String BUNDLE_EXTRA_SHOULD_START = "should_start"; 35 | public static final String BUNDLE_EXTRA_SHOULD_START_FOR_RESULT = "should_start_for_result"; 36 | public static final String BUNDLE_EXTRA_SUGGESTION_ACTION_TEXT = "suggestion_action_text"; 37 | public static final String BUNDLE_EXTRA_SUGGESTION_ACTION_RPC = "suggestion_action_rpc"; 38 | public static final String BUNDLE_EXTRA_SKIP_LOGGING_IN_TARGET_HANDLER = 39 | "skip_logging_in_target_handler"; 40 | } 41 | -------------------------------------------------------------------------------- /iconloaderlib/src/com/android/launcher3/util/override/ResourceBasedOverride.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.android.launcher3.util.override; 17 | 18 | import android.content.Context; 19 | import android.text.TextUtils; 20 | import android.util.Log; 21 | 22 | import java.lang.reflect.InvocationTargetException; 23 | 24 | /** 25 | * An interface to indicate that a class is dynamically loaded using resource overlay, hence its 26 | * class name and constructor should be preserved by proguard 27 | */ 28 | public interface ResourceBasedOverride { 29 | 30 | class Overrides { 31 | 32 | private static final String TAG = "Overrides"; 33 | 34 | public static T getObject( 35 | Class clazz, Context context, int resId) { 36 | String className = context.getString(resId); 37 | if (!TextUtils.isEmpty(className)) { 38 | try { 39 | Class cls = Class.forName(className); 40 | return (T) cls.getDeclaredConstructor(Context.class).newInstance(context); 41 | } catch (ClassNotFoundException | InstantiationException | IllegalAccessException 42 | | ClassCastException | NoSuchMethodException | InvocationTargetException e) { 43 | Log.e(TAG, "Bad overriden class", e); 44 | } 45 | } 46 | 47 | try { 48 | return clazz.newInstance(); 49 | } catch (InstantiationException|IllegalAccessException e) { 50 | throw new RuntimeException(e); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /motiontoollib/Android.bp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 The Android Open Source Project 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package { 16 | default_applicable_licenses: ["Android-Apache-2.0"], 17 | } 18 | 19 | java_library { 20 | name: "motion_tool_proto", 21 | srcs: ["src/com/android/app/motiontool/proto/*.proto"], 22 | proto: { 23 | type: "lite", 24 | local_include_dirs:[ 25 | "src/com/android/app/motiontool/proto" 26 | ], 27 | include_dirs: [ 28 | "frameworks/libs/systemui/viewcapturelib/src/com/android/app/viewcapture/proto" 29 | ], 30 | }, 31 | static_libs: [ 32 | "libprotobuf-java-lite", 33 | "view_capture_proto", 34 | ], 35 | java_version: "1.8", 36 | } 37 | 38 | android_library { 39 | name: "motion_tool_lib", 40 | manifest: "AndroidManifest.xml", 41 | platform_apis: true, 42 | min_sdk_version: "26", 43 | 44 | static_libs: [ 45 | "androidx.core_core", 46 | "view_capture", 47 | "motion_tool_proto", 48 | ], 49 | 50 | srcs: [ 51 | "src/**/*.java", 52 | "src/**/*.kt" 53 | ], 54 | } 55 | 56 | android_test { 57 | name: "motion_tool_lib_tests", 58 | manifest: "tests/AndroidManifest.xml", 59 | platform_apis: true, 60 | min_sdk_version: "26", 61 | 62 | static_libs: [ 63 | "androidx.core_core", 64 | "view_capture", 65 | "motion_tool_proto", 66 | "androidx.test.ext.junit", 67 | "androidx.test.rules", 68 | "testables" 69 | ], 70 | srcs: [ 71 | "**/*.java", 72 | "**/*.kt" 73 | ], 74 | libs: [ 75 | "android.test.runner", 76 | "android.test.base", 77 | ], 78 | test_suites: ["device-tests"], 79 | } 80 | 81 | -------------------------------------------------------------------------------- /iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.android.launcher3.icons.cache; 17 | 18 | import android.content.ComponentName; 19 | import android.content.Context; 20 | import android.content.pm.PackageInfo; 21 | import android.os.LocaleList; 22 | import android.os.UserHandle; 23 | 24 | import androidx.annotation.NonNull; 25 | import androidx.annotation.Nullable; 26 | 27 | import com.android.launcher3.icons.BitmapInfo; 28 | 29 | public interface CachingLogic { 30 | 31 | @NonNull 32 | ComponentName getComponent(@NonNull final T object); 33 | 34 | @NonNull 35 | UserHandle getUser(@NonNull final T object); 36 | 37 | @NonNull 38 | CharSequence getLabel(@NonNull final T object); 39 | 40 | @NonNull 41 | default CharSequence getDescription(@NonNull final T object, 42 | @NonNull final CharSequence fallback) { 43 | return fallback; 44 | } 45 | 46 | @NonNull 47 | BitmapInfo loadIcon(@NonNull final Context context, @NonNull final T object); 48 | 49 | /** 50 | * Provides a option list of keywords to associate with this object 51 | */ 52 | @Nullable 53 | default String getKeywords(@NonNull final T object, @NonNull final LocaleList localeList) { 54 | return null; 55 | } 56 | 57 | /** 58 | * Returns the timestamp the entry was last updated in cache. 59 | */ 60 | default long getLastUpdatedTime(@Nullable final T object, @NonNull final PackageInfo info) { 61 | return info.lastUpdateTime; 62 | } 63 | 64 | /** 65 | * Returns true the object should be added to mem cache; otherwise returns false. 66 | */ 67 | default boolean addToMemCache() { 68 | return true; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /iconloaderlib/src/com/android/launcher3/util/NoLocaleSQLiteHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.launcher3.util; 18 | 19 | import static android.database.sqlite.SQLiteDatabase.NO_LOCALIZED_COLLATORS; 20 | 21 | import android.content.Context; 22 | import android.content.ContextWrapper; 23 | import android.database.DatabaseErrorHandler; 24 | import android.database.sqlite.SQLiteDatabase; 25 | import android.database.sqlite.SQLiteDatabase.CursorFactory; 26 | import android.database.sqlite.SQLiteDatabase.OpenParams; 27 | import android.database.sqlite.SQLiteOpenHelper; 28 | import android.os.Build; 29 | 30 | /** 31 | * Extension of {@link SQLiteOpenHelper} which avoids creating default locale table by 32 | * A context wrapper which creates databases without support for localized collators. 33 | */ 34 | public abstract class NoLocaleSQLiteHelper extends SQLiteOpenHelper { 35 | 36 | private static final boolean ATLEAST_P = 37 | Build.VERSION.SDK_INT >= Build.VERSION_CODES.P; 38 | 39 | public NoLocaleSQLiteHelper(Context context, String name, int version) { 40 | super(ATLEAST_P ? context : new NoLocalContext(context), name, null, version); 41 | if (ATLEAST_P) { 42 | setOpenParams(new OpenParams.Builder().addOpenFlags(NO_LOCALIZED_COLLATORS).build()); 43 | } 44 | } 45 | 46 | private static class NoLocalContext extends ContextWrapper { 47 | public NoLocalContext(Context base) { 48 | super(base); 49 | } 50 | 51 | @Override 52 | public SQLiteDatabase openOrCreateDatabase( 53 | String name, int mode, CursorFactory factory, DatabaseErrorHandler errorHandler) { 54 | return super.openOrCreateDatabase( 55 | name, mode | Context.MODE_NO_LOCALIZED_COLLATORS, factory, errorHandler); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /iconloaderlib/src/com/android/launcher3/icons/cache/HandlerRunnable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.android.launcher3.icons.cache; 17 | 18 | import android.os.Handler; 19 | 20 | import java.util.concurrent.Executor; 21 | import java.util.function.Consumer; 22 | import java.util.function.Supplier; 23 | 24 | /** 25 | * A runnable that can be posted to a {@link Handler} which can be canceled. 26 | */ 27 | public class HandlerRunnable implements Runnable { 28 | 29 | private final Handler mWorkerHandler; 30 | private final Supplier mTask; 31 | 32 | private final Executor mCallbackExecutor; 33 | private final Consumer mCallback; 34 | private final Runnable mEndRunnable; 35 | 36 | private boolean mEnded = false; 37 | private boolean mCanceled = false; 38 | 39 | public HandlerRunnable(Handler workerHandler, Supplier task, Executor callbackExecutor, 40 | Consumer callback) { 41 | this(workerHandler, task, callbackExecutor, callback, () -> { }); 42 | } 43 | 44 | public HandlerRunnable(Handler workerHandler, Supplier task, Executor callbackExecutor, 45 | Consumer callback, Runnable endRunnable) { 46 | mWorkerHandler = workerHandler; 47 | mTask = task; 48 | mCallbackExecutor = callbackExecutor; 49 | mCallback = callback; 50 | mEndRunnable = endRunnable; 51 | } 52 | 53 | /** 54 | * Cancels this runnable from being run, only if it has not already run. 55 | */ 56 | public void cancel() { 57 | mWorkerHandler.removeCallbacks(this); 58 | mCanceled = true; 59 | mCallbackExecutor.execute(this::onEnd); 60 | } 61 | 62 | @Override 63 | public void run() { 64 | T value = mTask.get(); 65 | mCallbackExecutor.execute(() -> { 66 | if (!mCanceled) { 67 | mCallback.accept(value); 68 | } 69 | onEnd(); 70 | }); 71 | } 72 | 73 | private void onEnd() { 74 | if (!mEnded) { 75 | mEnded = true; 76 | mEndRunnable.run(); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /iconloaderlib/src/com/android/launcher3/icons/BitmapRenderer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.android.launcher3.icons; 17 | 18 | import android.annotation.TargetApi; 19 | import android.graphics.Bitmap; 20 | import android.graphics.Bitmap.Config; 21 | import android.graphics.Canvas; 22 | import android.graphics.Picture; 23 | import android.graphics.Rect; 24 | import android.graphics.RectF; 25 | import android.os.Build; 26 | import android.os.Build.VERSION_CODES; 27 | 28 | /** 29 | * Interface representing a bitmap draw operation. 30 | */ 31 | public interface BitmapRenderer { 32 | 33 | boolean USE_HARDWARE_BITMAP = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P; 34 | 35 | static Bitmap createSoftwareBitmap(int width, int height, BitmapRenderer renderer) { 36 | GraphicsUtils.noteNewBitmapCreated(); 37 | Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 38 | renderer.draw(new Canvas(result)); 39 | return result; 40 | } 41 | 42 | @TargetApi(Build.VERSION_CODES.P) 43 | static Bitmap createHardwareBitmap(int width, int height, BitmapRenderer renderer) { 44 | if (!USE_HARDWARE_BITMAP) { 45 | return createSoftwareBitmap(width, height, renderer); 46 | } 47 | 48 | GraphicsUtils.noteNewBitmapCreated(); 49 | Picture picture = new Picture(); 50 | renderer.draw(picture.beginRecording(width, height)); 51 | picture.endRecording(); 52 | return Bitmap.createBitmap(picture); 53 | } 54 | 55 | /** 56 | * Returns a bitmap from subset of the source bitmap. The new bitmap may be the 57 | * same object as source, or a copy may have been made. 58 | */ 59 | static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height) { 60 | if (Build.VERSION.SDK_INT >= VERSION_CODES.O && source.getConfig() == Config.HARDWARE) { 61 | return createHardwareBitmap(width, height, c -> c.drawBitmap(source, 62 | new Rect(x, y, x + width, y + height), new RectF(0, 0, width, height), null)); 63 | } else { 64 | GraphicsUtils.noteNewBitmapCreated(); 65 | return Bitmap.createBitmap(source, x, y, width, height); 66 | } 67 | } 68 | 69 | void draw(Canvas out); 70 | } 71 | -------------------------------------------------------------------------------- /iconloaderlib/src_full_lib/com/android/launcher3/icons/IconFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.launcher3.icons; 18 | 19 | import android.content.Context; 20 | 21 | /** 22 | * Wrapper class to provide access to {@link BaseIconFactory} and also to provide pool of this class 23 | * that are threadsafe. 24 | */ 25 | public class IconFactory extends BaseIconFactory { 26 | 27 | private static final Object sPoolSync = new Object(); 28 | private static IconFactory sPool; 29 | private static int sPoolId = 0; 30 | 31 | /** 32 | * Return a new Message instance from the global pool. Allows us to 33 | * avoid allocating new objects in many cases. 34 | */ 35 | public static IconFactory obtain(Context context) { 36 | int poolId; 37 | synchronized (sPoolSync) { 38 | if (sPool != null) { 39 | IconFactory m = sPool; 40 | sPool = m.next; 41 | m.next = null; 42 | return m; 43 | } 44 | poolId = sPoolId; 45 | } 46 | 47 | return new IconFactory(context, 48 | context.getResources().getConfiguration().densityDpi, 49 | context.getResources().getDimensionPixelSize(R.dimen.default_icon_bitmap_size), 50 | poolId); 51 | } 52 | 53 | public static void clearPool() { 54 | synchronized (sPoolSync) { 55 | sPool = null; 56 | sPoolId++; 57 | } 58 | } 59 | 60 | private final int mPoolId; 61 | 62 | private IconFactory next; 63 | 64 | private IconFactory(Context context, int fillResIconDpi, int iconBitmapSize, int poolId) { 65 | super(context, fillResIconDpi, iconBitmapSize); 66 | mPoolId = poolId; 67 | } 68 | 69 | /** 70 | * Recycles a LauncherIcons that may be in-use. 71 | */ 72 | public void recycle() { 73 | synchronized (sPoolSync) { 74 | if (sPoolId != mPoolId) { 75 | return; 76 | } 77 | // Clear any temporary state variables 78 | clear(); 79 | 80 | next = sPool; 81 | sPool = this; 82 | } 83 | } 84 | 85 | @Override 86 | public void close() { 87 | recycle(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /iconloaderlib/src/com/android/launcher3/util/ComponentKey.java: -------------------------------------------------------------------------------- 1 | package com.android.launcher3.util; 2 | 3 | /** 4 | * Copyright (C) 2015 The Android Open Source Project 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import android.content.ComponentName; 20 | import android.os.UserHandle; 21 | 22 | import androidx.annotation.NonNull; 23 | import androidx.annotation.Nullable; 24 | 25 | import java.util.Arrays; 26 | 27 | public class ComponentKey { 28 | 29 | public final ComponentName componentName; 30 | public final UserHandle user; 31 | 32 | private final int mHashCode; 33 | 34 | public ComponentKey(ComponentName componentName, UserHandle user) { 35 | if (componentName == null || user == null) { 36 | throw new NullPointerException(); 37 | } 38 | this.componentName = componentName; 39 | this.user = user; 40 | mHashCode = Arrays.hashCode(new Object[] {componentName, user}); 41 | 42 | } 43 | 44 | @Override 45 | public int hashCode() { 46 | return mHashCode; 47 | } 48 | 49 | @Override 50 | public boolean equals(Object o) { 51 | ComponentKey other = (ComponentKey) o; 52 | return other.componentName.equals(componentName) && other.user.equals(user); 53 | } 54 | 55 | /** 56 | * Encodes a component key as a string of the form [flattenedComponentString#userId]. 57 | */ 58 | @Override 59 | public String toString() { 60 | return componentName.flattenToString() + "#" + user.hashCode(); 61 | } 62 | 63 | /** 64 | * Parses and returns ComponentKey objected from string representation 65 | * Returns null if string is not properly formatted 66 | */ 67 | @Nullable 68 | public static ComponentKey fromString(@NonNull String str) { 69 | int sep = str.indexOf('#'); 70 | if (sep < 0 || (sep + 1) >= str.length()) { 71 | return null; 72 | } 73 | ComponentName componentName = ComponentName.unflattenFromString(str.substring(0, sep)); 74 | if (componentName == null) { 75 | return null; 76 | } 77 | try { 78 | return new ComponentKey(componentName, 79 | UserHandle.getUserHandleForUid(Integer.parseInt(str.substring(sep + 1)))); 80 | } catch (NumberFormatException ex) { 81 | return null; 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /iconloaderlib/src/com/android/launcher3/util/override/TraceHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.android.launcher3.util.override; 17 | 18 | import android.os.Trace; 19 | 20 | import androidx.annotation.MainThread; 21 | 22 | import java.util.function.Supplier; 23 | 24 | /** 25 | * A wrapper around {@link Trace} to allow better testing. 26 | * 27 | * To enable any tracing log, execute the following command: 28 | * $ adb shell setprop log.tag.LAUNCHER_TRACE VERBOSE 29 | * $ adb shell setprop log.tag.TAGNAME VERBOSE 30 | */ 31 | public class TraceHelper { 32 | 33 | // Track binder class for this trace 34 | public static final int FLAG_ALLOW_BINDER_TRACKING = 1 << 0; 35 | 36 | // Temporarily ignore blocking binder calls for this trace. 37 | public static final int FLAG_IGNORE_BINDERS = 1 << 1; 38 | 39 | public static final int FLAG_CHECK_FOR_RACE_CONDITIONS = 1 << 2; 40 | 41 | public static final int FLAG_UI_EVENT = 42 | FLAG_ALLOW_BINDER_TRACKING | FLAG_CHECK_FOR_RACE_CONDITIONS; 43 | 44 | /** 45 | * Static instance of Trace helper, overridden in tests. 46 | */ 47 | public static TraceHelper INSTANCE = new TraceHelper(); 48 | 49 | /** 50 | * @return a token to pass into {@link #endSection(Object)}. 51 | */ 52 | public Object beginSection(String sectionName) { 53 | return beginSection(sectionName, 0); 54 | } 55 | 56 | public Object beginSection(String sectionName, int flags) { 57 | Trace.beginSection(sectionName); 58 | return null; 59 | } 60 | 61 | /** 62 | * @param token the token returned from {@link #beginSection(String, int)} 63 | */ 64 | public void endSection(Object token) { 65 | Trace.endSection(); 66 | } 67 | 68 | /** 69 | * Similar to {@link #beginSection} but doesn't add a trace section. 70 | */ 71 | public Object beginFlagsOverride(int flags) { 72 | return null; 73 | } 74 | 75 | public void endFlagsOverride(Object token) { } 76 | 77 | /** 78 | * Temporarily ignore blocking binder calls for the duration of this {@link Supplier}. 79 | */ 80 | @MainThread 81 | public static T allowIpcs(String rpcName, Supplier supplier) { 82 | Object traceToken = INSTANCE.beginSection(rpcName, FLAG_IGNORE_BINDERS); 83 | try { 84 | return supplier.get(); 85 | } finally { 86 | INSTANCE.endSection(traceToken); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /iconloaderlib/src/com/android/launcher3/icons/PlaceHolderIconDrawable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.android.launcher3.icons; 17 | 18 | import android.animation.Animator; 19 | import android.animation.AnimatorListenerAdapter; 20 | import android.animation.ValueAnimator; 21 | import android.content.Context; 22 | import android.graphics.Canvas; 23 | import android.graphics.Color; 24 | import android.graphics.Path; 25 | import android.graphics.PorterDuff; 26 | import android.graphics.PorterDuffColorFilter; 27 | import android.graphics.Rect; 28 | import android.graphics.drawable.Drawable; 29 | 30 | import androidx.core.graphics.ColorUtils; 31 | 32 | /** 33 | * Subclass which draws a placeholder icon when the actual icon is not yet loaded 34 | */ 35 | public class PlaceHolderIconDrawable extends FastBitmapDrawable { 36 | 37 | // Path in [0, 100] bounds. 38 | private final Path mProgressPath; 39 | 40 | public PlaceHolderIconDrawable(BitmapInfo info, Context context) { 41 | super(info); 42 | 43 | mProgressPath = GraphicsUtils.getShapePath(context, 100); 44 | mPaint.setColor(ColorUtils.compositeColors( 45 | GraphicsUtils.getAttrColor(context, R.attr.loadingIconColor), info.color)); 46 | } 47 | 48 | @Override 49 | protected void drawInternal(Canvas canvas, Rect bounds) { 50 | int saveCount = canvas.save(); 51 | canvas.translate(bounds.left, bounds.top); 52 | canvas.scale(bounds.width() / 100f, bounds.height() / 100f); 53 | canvas.drawPath(mProgressPath, mPaint); 54 | canvas.restoreToCount(saveCount); 55 | } 56 | 57 | /** Updates this placeholder to {@code newIcon} with animation. */ 58 | public void animateIconUpdate(Drawable newIcon) { 59 | int placeholderColor = mPaint.getColor(); 60 | int originalAlpha = Color.alpha(placeholderColor); 61 | 62 | ValueAnimator iconUpdateAnimation = ValueAnimator.ofInt(originalAlpha, 0); 63 | iconUpdateAnimation.setDuration(375); 64 | iconUpdateAnimation.addUpdateListener(valueAnimator -> { 65 | int newAlpha = (int) valueAnimator.getAnimatedValue(); 66 | int newColor = ColorUtils.setAlphaComponent(placeholderColor, newAlpha); 67 | 68 | newIcon.setColorFilter(new PorterDuffColorFilter(newColor, PorterDuff.Mode.SRC_ATOP)); 69 | }); 70 | iconUpdateAnimation.addListener(new AnimatorListenerAdapter() { 71 | @Override 72 | public void onAnimationEnd(Animator animation) { 73 | newIcon.setColorFilter(null); 74 | } 75 | }); 76 | iconUpdateAnimation.start(); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /viewcapturelib/src/com/android/app/viewcapture/SettingsAwareViewCapture.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.app.viewcapture 18 | 19 | import android.content.Context 20 | import android.database.ContentObserver 21 | import android.os.Handler 22 | import android.os.Looper 23 | import android.os.Process 24 | import android.provider.Settings 25 | import android.view.Choreographer 26 | import androidx.annotation.AnyThread 27 | import androidx.annotation.VisibleForTesting 28 | import java.util.concurrent.Executor 29 | 30 | /** 31 | * ViewCapture that listens to system updates and enables / disables attached ViewCapture 32 | * WindowListeners accordingly. The Settings toggle is currently controlled by the Winscope 33 | * developer tile in the System developer options. 34 | */ 35 | class SettingsAwareViewCapture 36 | @VisibleForTesting 37 | internal constructor(private val context: Context, choreographer: Choreographer, executor: Executor) 38 | : ViewCapture(DEFAULT_MEMORY_SIZE, DEFAULT_INIT_POOL_SIZE, choreographer, executor) { 39 | 40 | init { 41 | enableOrDisableWindowListeners() 42 | context.contentResolver.registerContentObserver( 43 | Settings.Global.getUriFor(VIEW_CAPTURE_ENABLED), 44 | false, 45 | object : ContentObserver(Handler()) { 46 | override fun onChange(selfChange: Boolean) { 47 | enableOrDisableWindowListeners() 48 | } 49 | }) 50 | } 51 | 52 | @AnyThread 53 | private fun enableOrDisableWindowListeners() { 54 | mBgExecutor.execute { 55 | val isEnabled = Settings.Global.getInt(context.contentResolver, VIEW_CAPTURE_ENABLED, 56 | 0) != 0 57 | MAIN_EXECUTOR.execute { 58 | enableOrDisableWindowListeners(isEnabled) 59 | } 60 | } 61 | } 62 | 63 | companion object { 64 | @VisibleForTesting internal const val VIEW_CAPTURE_ENABLED = "view_capture_enabled" 65 | 66 | private var INSTANCE: ViewCapture? = null 67 | 68 | @JvmStatic 69 | fun getInstance(context: Context): ViewCapture = when { 70 | INSTANCE != null -> INSTANCE!! 71 | Looper.myLooper() == Looper.getMainLooper() -> SettingsAwareViewCapture( 72 | context.applicationContext, Choreographer.getInstance(), 73 | createAndStartNewLooperExecutor("SAViewCapture", 74 | Process.THREAD_PRIORITY_FOREGROUND)).also { INSTANCE = it } 75 | else -> try { 76 | MAIN_EXECUTOR.submit { getInstance(context) }.get() 77 | } catch (e: Exception) { 78 | throw e 79 | } 80 | } 81 | 82 | } 83 | } -------------------------------------------------------------------------------- /motiontoollib/src/com/android/app/motiontool/proto/motion_tool.proto: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | syntax = "proto2"; 18 | 19 | package com.android.app.motiontool; 20 | 21 | import "view_capture.proto"; 22 | 23 | option java_multiple_files = true; 24 | 25 | message MotionToolsRequest { 26 | oneof type { 27 | HandshakeRequest handshake = 1; 28 | BeginTraceRequest begin_trace = 2; 29 | EndTraceRequest end_trace = 3; 30 | PollTraceRequest poll_trace = 4; 31 | } 32 | } 33 | 34 | // RPC response messages. 35 | // 36 | // Returns the result from the corresponding request. 37 | message MotionToolsResponse { 38 | oneof type { 39 | // Contains error information whenever the request failed. 40 | ErrorResponse error = 1; 41 | 42 | HandshakeResponse handshake = 2; 43 | BeginTraceResponse begin_trace = 3; 44 | EndTraceResponse end_trace = 4; 45 | PollTraceResponse poll_trace = 5; 46 | } 47 | } 48 | 49 | message ErrorResponse { 50 | enum Code { 51 | UNKNOWN = 0; 52 | INVALID_REQUEST = 1; 53 | UNKNOWN_TRACE_ID = 2; 54 | WINDOW_NOT_FOUND = 3; 55 | } 56 | 57 | optional Code code = 1; 58 | // Human readable error message. 59 | optional string message = 2; 60 | } 61 | 62 | // Identifies the window, in which context the motion tools are executed 63 | message WindowIdentifier { 64 | // An identifier for the root view, as accepted by 65 | // WindowManagerGlobal#getRootView. This is formatted as 66 | // `windowName/rootViewClassName@rootViewIdentityHashCode`, 67 | // for example `NotificationShade/android.view.ViewRootImpl@bab6a53`. 68 | optional string root_window = 1; 69 | } 70 | 71 | // Verifies the motion tools are available for the specified window. 72 | message HandshakeRequest { 73 | optional WindowIdentifier window = 1; 74 | optional int32 client_version = 2; 75 | } 76 | 77 | message HandshakeResponse { 78 | enum Status { 79 | OK = 1; 80 | WINDOW_NOT_FOUND = 2; 81 | } 82 | optional Status status = 1; 83 | optional int32 server_version = 2; 84 | } 85 | 86 | // Enables motion tracing for the specified window 87 | message BeginTraceRequest { 88 | optional WindowIdentifier window = 1; 89 | } 90 | 91 | message BeginTraceResponse { 92 | optional int32 trace_id = 1; 93 | } 94 | 95 | // Disabled motion tracing for the specified window 96 | message EndTraceRequest { 97 | optional int32 trace_id = 1; 98 | } 99 | 100 | message EndTraceResponse { 101 | optional com.android.app.viewcapture.data.ExportedData exported_data = 1; 102 | } 103 | 104 | // Polls collected motion trace data collected since the last PollTraceRequest (or the 105 | // BeginTraceRequest) 106 | message PollTraceRequest { 107 | optional int32 trace_id = 1; 108 | } 109 | 110 | message PollTraceResponse { 111 | optional com.android.app.viewcapture.data.ExportedData exported_data = 1; 112 | } 113 | 114 | -------------------------------------------------------------------------------- /searchuilib/src/com/android/app/search/SearchTargetConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.app.search; 18 | 19 | import static com.android.app.search.LayoutType.SMALL_ICON_HORIZONTAL_TEXT; 20 | import static com.android.app.search.SearchActionExtras.BUNDLE_EXTRA_HIDE_ICON; 21 | import static com.android.app.search.SearchActionExtras.BUNDLE_EXTRA_HIDE_SUBTITLE; 22 | import static com.android.app.search.SearchTargetExtras.BUNDLE_EXTRA_CLASS; 23 | import static com.android.app.search.SearchTargetExtras.BUNDLE_EXTRA_SUBTITLE_OVERRIDE; 24 | import static com.android.app.search.SearchTargetExtras.BUNDLE_EXTRA_SUPPORT_QUERY_BUILDER; 25 | import static com.android.app.search.SearchTargetExtras.EXTRAS_RECENT_BLOCK_TARGET; 26 | 27 | import android.app.search.SearchAction; 28 | import android.app.search.SearchTarget; 29 | import android.content.pm.ShortcutInfo; 30 | import android.os.Bundle; 31 | 32 | public class SearchTargetConverter { 33 | /** 34 | * Generate a searchTarget that uses {@link LayoutType#SMALL_ICON_HORIZONTAL_TEXT} from a 35 | * searchTarget where original layout type may not have been SMALL_ICON_HORIZONTAL_TEXT. Only 36 | * possible if the given SearchTarget contains a searchAction or shortcutInfo, otherwise the 37 | * original searchTarget will be returned. 38 | */ 39 | public static SearchTarget convertLayoutTypeToSmallIconHorizontalText( 40 | SearchTarget searchTarget) { 41 | SearchAction searchTargetAction = searchTarget.getSearchAction(); 42 | ShortcutInfo shortcutInfo = searchTarget.getShortcutInfo(); 43 | int resultType = searchTarget.getResultType(); 44 | String subtitle = ""; 45 | 46 | Bundle searchTargetBundle = searchTarget.getExtras(); 47 | searchTargetBundle.putString(BUNDLE_EXTRA_CLASS, 48 | searchTargetBundle.getString(BUNDLE_EXTRA_CLASS)); 49 | searchTargetBundle.putBoolean(BUNDLE_EXTRA_SUPPORT_QUERY_BUILDER, true); 50 | searchTargetBundle.putBoolean(BUNDLE_EXTRA_HIDE_SUBTITLE, false); 51 | searchTargetBundle.putString(BUNDLE_EXTRA_SUBTITLE_OVERRIDE, subtitle); 52 | searchTargetBundle.putBoolean(BUNDLE_EXTRA_HIDE_ICON, false); 53 | searchTargetBundle.putBoolean(EXTRAS_RECENT_BLOCK_TARGET, true); 54 | 55 | SearchTarget.Builder builder = new SearchTarget.Builder(resultType, 56 | SMALL_ICON_HORIZONTAL_TEXT, searchTarget.getId()) 57 | .setPackageName(searchTarget.getPackageName()) 58 | .setExtras(searchTargetBundle) 59 | .setUserHandle(searchTarget.getUserHandle()); 60 | if (searchTargetAction != null) { 61 | builder.setSearchAction(searchTargetAction); 62 | } else if (shortcutInfo != null) { 63 | builder.setShortcutInfo(shortcutInfo); 64 | } else { 65 | return searchTarget; 66 | } 67 | return builder.build(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /iconloaderlib/src/com/android/launcher3/util/override/LooperExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | package com.android.launcher3.util.override; 17 | 18 | import android.os.Handler; 19 | import android.os.HandlerThread; 20 | import android.os.Looper; 21 | import android.os.Process; 22 | 23 | import java.util.List; 24 | import java.util.concurrent.AbstractExecutorService; 25 | import java.util.concurrent.TimeUnit; 26 | 27 | /** 28 | * Extension of {@link AbstractExecutorService} which executed on a provided looper. 29 | */ 30 | public class LooperExecutor extends AbstractExecutorService { 31 | 32 | private final Handler mHandler; 33 | 34 | public LooperExecutor(Looper looper) { 35 | mHandler = new Handler(looper); 36 | } 37 | 38 | public Handler getHandler() { 39 | return mHandler; 40 | } 41 | 42 | @Override 43 | public void execute(Runnable runnable) { 44 | if (getHandler().getLooper() == Looper.myLooper()) { 45 | runnable.run(); 46 | } else { 47 | getHandler().post(runnable); 48 | } 49 | } 50 | 51 | /** 52 | * Same as execute, but never runs the action inline. 53 | */ 54 | public void post(Runnable runnable) { 55 | getHandler().post(runnable); 56 | } 57 | 58 | /** 59 | * Not supported and throws an exception when used. 60 | */ 61 | @Override 62 | @Deprecated 63 | public void shutdown() { 64 | throw new UnsupportedOperationException(); 65 | } 66 | 67 | /** 68 | * Not supported and throws an exception when used. 69 | */ 70 | @Override 71 | @Deprecated 72 | public List shutdownNow() { 73 | throw new UnsupportedOperationException(); 74 | } 75 | 76 | @Override 77 | public boolean isShutdown() { 78 | return false; 79 | } 80 | 81 | @Override 82 | public boolean isTerminated() { 83 | return false; 84 | } 85 | 86 | /** 87 | * Not supported and throws an exception when used. 88 | */ 89 | @Override 90 | @Deprecated 91 | public boolean awaitTermination(long l, TimeUnit timeUnit) { 92 | throw new UnsupportedOperationException(); 93 | } 94 | 95 | /** 96 | * Returns the thread for this executor 97 | */ 98 | public Thread getThread() { 99 | return getHandler().getLooper().getThread(); 100 | } 101 | 102 | /** 103 | * Returns the looper for this executor 104 | */ 105 | public Looper getLooper() { 106 | return getHandler().getLooper(); 107 | } 108 | 109 | /** 110 | * Set the priority of a thread, based on Linux priorities. 111 | * @param priority Linux priority level, from -20 for highest scheduling priority 112 | * to 19 for lowest scheduling priority. 113 | * @see Process#setThreadPriority(int, int) 114 | */ 115 | public void setThreadPriority(int priority) { 116 | Process.setThreadPriority(((HandlerThread) getThread()).getThreadId(), priority); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /searchuilib/src/com/android/app/search/ResultType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.app.search; 18 | 19 | /** 20 | * Constants to be used with {@link android.app.search.SearchContext} and 21 | * {@link android.app.search.SearchTarget}. 22 | * 23 | * Note, a result type could be a of two types. 24 | * For example, unpublished settings result type could be in slices: 25 | * resultType = SETTING | SLICE 26 | */ 27 | public class ResultType { 28 | 29 | // published corpus by 3rd party app, supported by SystemService 30 | public static final int APPLICATION = 1 << 0; 31 | public static final int SHORTCUT = 1 << 1; 32 | public static final int SLICE = 1 << 6; 33 | public static final int WIDGETS = 1 << 7; 34 | 35 | // Not extracted from any of the SystemService 36 | public static final int PEOPLE = 1 << 2; 37 | public static final int ACTION = 1 << 3; 38 | public static final int SETTING = 1 << 4; 39 | public static final int IMAGE = 1 << 5; 40 | public static final int PLAY = 1 << 8; 41 | public static final int SUGGEST = 1 << 9; 42 | public static final int ASSISTANT = 1 << 10; 43 | public static final int CHROMETAB = 1 << 11; 44 | public static final int NAVVYSITE = 1 << 12; 45 | public static final int TIPS = 1 << 13; 46 | public static final int PEOPLE_TILE = 1 << 14; 47 | public static final int LEGACY_SHORTCUT = 1 << 15; 48 | public static final int MEMORY = 1 << 16; 49 | public static final int WEB_SUGGEST = 1 << 17; 50 | public static final int NO_FULFILLMENT = 1 << 18; 51 | public static final int EDUCARD = 1 << 19; 52 | public static final int SYSTEM_POINTER = 1 << 20; 53 | public static final int VIDEO = 1 << 21; 54 | 55 | public static final int PUBLIC_DATA_TYPE = APPLICATION | SETTING | PLAY | WEB_SUGGEST; 56 | public static final int PRIMITIVE_TYPE = APPLICATION | SLICE | SHORTCUT | WIDGETS | ACTION | 57 | LEGACY_SHORTCUT; 58 | public static final int CORPUS_TYPE = 59 | PEOPLE | SETTING | IMAGE | PLAY | SUGGEST | ASSISTANT | CHROMETAB | NAVVYSITE | TIPS 60 | | PEOPLE_TILE | MEMORY | WEB_SUGGEST | VIDEO; 61 | public static final int RANK_TYPE = SYSTEM_POINTER; 62 | public static final int UI_TYPE = EDUCARD | NO_FULFILLMENT; 63 | 64 | public static boolean isSlice(int resultType) { 65 | return (resultType & SLICE) != 0; 66 | } 67 | 68 | public static boolean isSystemPointer(int resultType) { 69 | return (resultType & SYSTEM_POINTER) != 0; 70 | } 71 | 72 | /** 73 | * Returns result type integer where only {@code #CORPUS_TYPE} bit will turned on. 74 | */ 75 | public static int getCorpusType(int resultType) { 76 | return (resultType & CORPUS_TYPE); 77 | } 78 | 79 | /** 80 | * Returns result type integer where only {@code #PRIMITIVE_TYPE} bit will be turned on. 81 | */ 82 | public static int getPrimitiveType(int resultType) { 83 | return (resultType & PRIMITIVE_TYPE); 84 | } 85 | 86 | /** 87 | * Returns whether the given result type is privacy safe or not. 88 | */ 89 | public static boolean isPrivacySafe(int resultType) { 90 | return (resultType & PUBLIC_DATA_TYPE) != 0; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /viewcapturelib/tests/com/android/app/viewcapture/SettingsAwareViewCaptureTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.app.viewcapture 18 | 19 | import android.content.Context 20 | import android.content.Intent 21 | import android.media.permission.SafeCloseable 22 | import android.provider.Settings 23 | import android.testing.AndroidTestingRunner 24 | import android.view.Choreographer 25 | import android.view.View 26 | import androidx.test.ext.junit.rules.ActivityScenarioRule 27 | import androidx.test.filters.SmallTest 28 | import androidx.test.platform.app.InstrumentationRegistry 29 | import com.android.app.viewcapture.SettingsAwareViewCapture.Companion.VIEW_CAPTURE_ENABLED 30 | import com.android.app.viewcapture.ViewCapture.MAIN_EXECUTOR 31 | import junit.framework.Assert.assertEquals 32 | import org.junit.Rule 33 | import org.junit.Test 34 | import org.junit.runner.RunWith 35 | 36 | @SmallTest 37 | @RunWith(AndroidTestingRunner::class) 38 | class SettingsAwareViewCaptureTest { 39 | private val context: Context = InstrumentationRegistry.getInstrumentation().context 40 | private val activityIntent = Intent(context, TestActivity::class.java) 41 | 42 | @get:Rule val activityScenarioRule = ActivityScenarioRule(activityIntent) 43 | 44 | @Test 45 | fun do_not_capture_view_hierarchies_if_setting_is_disabled() { 46 | Settings.Global.putInt(context.contentResolver, VIEW_CAPTURE_ENABLED, 0) 47 | 48 | activityScenarioRule.scenario.onActivity { activity -> 49 | val viewCapture: ViewCapture = 50 | SettingsAwareViewCapture(context, Choreographer.getInstance(), MAIN_EXECUTOR) 51 | val rootView: View = activity.findViewById(android.R.id.content) 52 | 53 | val closeable: SafeCloseable = viewCapture.startCapture(rootView, "rootViewId") 54 | Choreographer.getInstance().postFrameCallback { 55 | rootView.viewTreeObserver.dispatchOnDraw() 56 | 57 | assertEquals(0, viewCapture.getDumpTask( 58 | activity.findViewById(android.R.id.content)).get().get().frameDataList.size) 59 | closeable.close() 60 | } 61 | } 62 | } 63 | 64 | @Test 65 | fun capture_view_hierarchies_if_setting_is_enabled() { 66 | Settings.Global.putInt(context.contentResolver, VIEW_CAPTURE_ENABLED, 1) 67 | 68 | activityScenarioRule.scenario.onActivity { activity -> 69 | val viewCapture: ViewCapture = 70 | SettingsAwareViewCapture(context, Choreographer.getInstance(), MAIN_EXECUTOR) 71 | val rootView: View = activity.findViewById(android.R.id.content) 72 | 73 | val closeable: SafeCloseable = viewCapture.startCapture(rootView, "rootViewId") 74 | Choreographer.getInstance().postFrameCallback { 75 | rootView.viewTreeObserver.dispatchOnDraw() 76 | 77 | assertEquals(1, viewCapture.getDumpTask(activity.findViewById( 78 | android.R.id.content)).get().get().frameDataList.size) 79 | 80 | closeable.close() 81 | } 82 | } 83 | } 84 | 85 | @Test 86 | fun getInstance_calledTwiceInARow_returnsSameObject() { 87 | assertEquals( 88 | SettingsAwareViewCapture.getInstance(context).hashCode(), 89 | SettingsAwareViewCapture.getInstance(context).hashCode() 90 | ) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /iconloaderlib/src/com/android/launcher3/util/override/Executors.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.android.launcher3.util.override; 17 | 18 | import android.os.HandlerThread; 19 | import android.os.Looper; 20 | import android.os.Process; 21 | 22 | import java.util.concurrent.LinkedBlockingQueue; 23 | import java.util.concurrent.ThreadFactory; 24 | import java.util.concurrent.ThreadPoolExecutor; 25 | import java.util.concurrent.TimeUnit; 26 | import java.util.concurrent.atomic.AtomicInteger; 27 | 28 | /** 29 | * Various different executors used in Launcher 30 | */ 31 | public class Executors { 32 | 33 | private static final int POOL_SIZE = 34 | Math.max(Runtime.getRuntime().availableProcessors(), 2); 35 | private static final int KEEP_ALIVE = 1; 36 | 37 | /** 38 | * An {@link ThreadPoolExecutor} to be used with async task with no limit on the queue size. 39 | */ 40 | public static final ThreadPoolExecutor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor( 41 | POOL_SIZE, POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, new LinkedBlockingQueue<>()); 42 | 43 | /** 44 | * Returns the executor for running tasks on the main thread. 45 | */ 46 | public static final LooperExecutor MAIN_EXECUTOR = 47 | new LooperExecutor(Looper.getMainLooper()); 48 | 49 | /** 50 | * A background executor for using time sensitive actions where user is waiting for response. 51 | */ 52 | public static final LooperExecutor UI_HELPER_EXECUTOR = 53 | new LooperExecutor( 54 | createAndStartNewLooper("UiThreadHelper", Process.THREAD_PRIORITY_FOREGROUND)); 55 | 56 | /** 57 | * Utility method to get a started handler thread statically 58 | */ 59 | public static Looper createAndStartNewLooper(String name) { 60 | return createAndStartNewLooper(name, Process.THREAD_PRIORITY_DEFAULT); 61 | } 62 | 63 | /** 64 | * Utility method to get a started handler thread statically with the provided priority 65 | */ 66 | public static Looper createAndStartNewLooper(String name, int priority) { 67 | HandlerThread thread = new HandlerThread(name, priority); 68 | thread.start(); 69 | return thread.getLooper(); 70 | } 71 | 72 | /** 73 | * Executor used for running Launcher model related tasks (eg loading icons or updated db) 74 | */ 75 | public static final LooperExecutor MODEL_EXECUTOR = 76 | new LooperExecutor(createAndStartNewLooper("launcher-loader")); 77 | 78 | /** 79 | * A simple ThreadFactory to set the thread name and priority when used with executors. 80 | */ 81 | public static class SimpleThreadFactory implements ThreadFactory { 82 | 83 | private final int mPriority; 84 | private final String mNamePrefix; 85 | 86 | private final AtomicInteger mCount = new AtomicInteger(0); 87 | 88 | public SimpleThreadFactory(String namePrefix, int priority) { 89 | mNamePrefix = namePrefix; 90 | mPriority = priority; 91 | } 92 | 93 | @Override 94 | public Thread newThread(Runnable runnable) { 95 | Thread t = new Thread(() -> { 96 | Process.setThreadPriority(mPriority); 97 | runnable.run(); 98 | }, mNamePrefix + mCount.incrementAndGet()); 99 | return t; 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /iconloaderlib/src/com/android/launcher3/util/SQLiteCacheHelper.java: -------------------------------------------------------------------------------- 1 | package com.android.launcher3.util; 2 | 3 | import android.content.ContentValues; 4 | import android.content.Context; 5 | import android.database.Cursor; 6 | import android.database.sqlite.SQLiteDatabase; 7 | import android.database.sqlite.SQLiteException; 8 | import android.database.sqlite.SQLiteFullException; 9 | import android.database.sqlite.SQLiteOpenHelper; 10 | import android.util.Log; 11 | 12 | /** 13 | * An extension of {@link SQLiteOpenHelper} with utility methods for a single table cache DB. 14 | * Any exception during write operations are ignored, and any version change causes a DB reset. 15 | */ 16 | public abstract class SQLiteCacheHelper { 17 | private static final String TAG = "SQLiteCacheHelper"; 18 | 19 | private static final boolean IN_MEMORY_CACHE = false; 20 | 21 | private final String mTableName; 22 | private final MySQLiteOpenHelper mOpenHelper; 23 | 24 | private boolean mIgnoreWrites; 25 | 26 | public SQLiteCacheHelper(Context context, String name, int version, String tableName) { 27 | if (IN_MEMORY_CACHE) { 28 | name = null; 29 | } 30 | mTableName = tableName; 31 | mOpenHelper = new MySQLiteOpenHelper(context, name, version); 32 | 33 | mIgnoreWrites = false; 34 | } 35 | 36 | /** 37 | * @see SQLiteDatabase#delete(String, String, String[]) 38 | */ 39 | public void delete(String whereClause, String[] whereArgs) { 40 | if (mIgnoreWrites) { 41 | return; 42 | } 43 | try { 44 | mOpenHelper.getWritableDatabase().delete(mTableName, whereClause, whereArgs); 45 | } catch (SQLiteFullException e) { 46 | onDiskFull(e); 47 | } catch (SQLiteException e) { 48 | Log.d(TAG, "Ignoring sqlite exception", e); 49 | } 50 | } 51 | 52 | /** 53 | * @see SQLiteDatabase#insertWithOnConflict(String, String, ContentValues, int) 54 | */ 55 | public void insertOrReplace(ContentValues values) { 56 | if (mIgnoreWrites) { 57 | return; 58 | } 59 | try { 60 | mOpenHelper.getWritableDatabase().insertWithOnConflict( 61 | mTableName, null, values, SQLiteDatabase.CONFLICT_REPLACE); 62 | } catch (SQLiteFullException e) { 63 | onDiskFull(e); 64 | } catch (SQLiteException e) { 65 | Log.d(TAG, "Ignoring sqlite exception", e); 66 | } 67 | } 68 | 69 | private void onDiskFull(SQLiteFullException e) { 70 | Log.e(TAG, "Disk full, all write operations will be ignored", e); 71 | mIgnoreWrites = true; 72 | } 73 | 74 | /** 75 | * @see SQLiteDatabase#query(String, String[], String, String[], String, String, String) 76 | */ 77 | public Cursor query(String[] columns, String selection, String[] selectionArgs) { 78 | return mOpenHelper.getReadableDatabase().query( 79 | mTableName, columns, selection, selectionArgs, null, null, null); 80 | } 81 | 82 | public void clear() { 83 | mOpenHelper.clearDB(mOpenHelper.getWritableDatabase()); 84 | } 85 | 86 | public void close() { 87 | mOpenHelper.close(); 88 | } 89 | 90 | protected abstract void onCreateTable(SQLiteDatabase db); 91 | 92 | /** 93 | * A private inner class to prevent direct DB access. 94 | */ 95 | private class MySQLiteOpenHelper extends NoLocaleSQLiteHelper { 96 | 97 | public MySQLiteOpenHelper(Context context, String name, int version) { 98 | super(context, name, version); 99 | } 100 | 101 | @Override 102 | public void onCreate(SQLiteDatabase db) { 103 | onCreateTable(db); 104 | } 105 | 106 | @Override 107 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 108 | if (oldVersion != newVersion) { 109 | clearDB(db); 110 | } 111 | } 112 | 113 | @Override 114 | public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { 115 | if (oldVersion != newVersion) { 116 | clearDB(db); 117 | } 118 | } 119 | 120 | private void clearDB(SQLiteDatabase db) { 121 | db.execSQL("DROP TABLE IF EXISTS " + mTableName); 122 | onCreate(db); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /motiontoollib/tests/com/android/app/motiontool/MotionToolManagerTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.app.motiontool 18 | 19 | import android.content.Intent 20 | import android.testing.AndroidTestingRunner 21 | import android.view.Choreographer 22 | import android.view.View 23 | import android.view.WindowManagerGlobal 24 | import androidx.test.ext.junit.rules.ActivityScenarioRule 25 | import androidx.test.filters.SmallTest 26 | import androidx.test.platform.app.InstrumentationRegistry 27 | import com.android.app.motiontool.util.TestActivity 28 | import junit.framework.Assert.assertEquals 29 | import junit.framework.Assert.assertTrue 30 | import org.junit.Rule 31 | import org.junit.Test 32 | import org.junit.runner.RunWith 33 | 34 | @SmallTest 35 | @RunWith(AndroidTestingRunner::class) 36 | class MotionToolManagerTest { 37 | 38 | private val windowManagerGlobal = WindowManagerGlobal.getInstance() 39 | private val motionToolManager = MotionToolManager.getInstance(windowManagerGlobal) 40 | 41 | private val activityIntent = 42 | Intent(InstrumentationRegistry.getInstrumentation().context, TestActivity::class.java) 43 | 44 | @get:Rule 45 | val activityScenarioRule = ActivityScenarioRule(activityIntent) 46 | 47 | @Test(expected = UnknownTraceIdException::class) 48 | fun testEndTraceThrowsWithoutPrecedingBeginTrace() { 49 | motionToolManager.endTrace(0) 50 | } 51 | 52 | @Test(expected = UnknownTraceIdException::class) 53 | fun testPollTraceThrowsWithoutPrecedingBeginTrace() { 54 | motionToolManager.pollTrace(0) 55 | } 56 | 57 | @Test(expected = UnknownTraceIdException::class) 58 | fun testEndTraceThrowsWithInvalidTraceId() { 59 | val traceId = motionToolManager.beginTrace(getActivityViewRootId()) 60 | motionToolManager.endTrace(traceId + 1) 61 | } 62 | 63 | @Test(expected = UnknownTraceIdException::class) 64 | fun testPollTraceThrowsWithInvalidTraceId() { 65 | val traceId = motionToolManager.beginTrace(getActivityViewRootId()) 66 | motionToolManager.pollTrace(traceId + 1) 67 | } 68 | 69 | @Test(expected = WindowNotFoundException::class) 70 | fun testBeginTraceThrowsWithInvalidWindowId() { 71 | motionToolManager.beginTrace("InvalidWindowId") 72 | } 73 | 74 | @Test 75 | fun testNoOnDrawCallReturnsEmptyResponse() { 76 | activityScenarioRule.scenario.onActivity { 77 | val traceId = motionToolManager.beginTrace(getActivityViewRootId()) 78 | val result = motionToolManager.endTrace(traceId) 79 | assertTrue(result.frameDataList.isEmpty()) 80 | } 81 | } 82 | 83 | @Test 84 | fun testOneOnDrawCallReturnsOneFrameResponse() { 85 | activityScenarioRule.scenario.onActivity { activity -> 86 | val traceId = motionToolManager.beginTrace(getActivityViewRootId()) 87 | Choreographer.getInstance().postFrameCallback { 88 | activity.findViewById(android.R.id.content).viewTreeObserver.dispatchOnDraw() 89 | 90 | val polledExportedData = motionToolManager.pollTrace(traceId) 91 | assertEquals(1, polledExportedData.frameDataList.size) 92 | 93 | // Verify that frameData is only included once and is not returned again 94 | val endExportedData = motionToolManager.endTrace(traceId) 95 | assertEquals(0, endExportedData.frameDataList.size) 96 | } 97 | } 98 | } 99 | 100 | private fun getActivityViewRootId(): String { 101 | var activityViewRootId = "" 102 | activityScenarioRule.scenario.onActivity { 103 | activityViewRootId = WindowManagerGlobal.getInstance().viewRootNames.first() 104 | } 105 | return activityViewRootId 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /iconloaderlib/src_full_lib/com/android/launcher3/icons/SimpleIconCache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.android.launcher3.icons; 17 | 18 | import static android.content.Intent.ACTION_MANAGED_PROFILE_ADDED; 19 | import static android.content.Intent.ACTION_MANAGED_PROFILE_REMOVED; 20 | 21 | import android.annotation.TargetApi; 22 | import android.content.BroadcastReceiver; 23 | import android.content.Context; 24 | import android.content.Intent; 25 | import android.content.IntentFilter; 26 | import android.content.pm.ApplicationInfo; 27 | import android.os.Build; 28 | import android.os.Handler; 29 | import android.os.HandlerThread; 30 | import android.os.Looper; 31 | import android.os.UserHandle; 32 | import android.os.UserManager; 33 | import android.util.SparseLongArray; 34 | 35 | import androidx.annotation.NonNull; 36 | 37 | import com.android.launcher3.icons.cache.BaseIconCache; 38 | 39 | /** 40 | * Wrapper class to provide access to {@link BaseIconFactory} and also to provide pool of this class 41 | * that are threadsafe. 42 | */ 43 | @TargetApi(Build.VERSION_CODES.P) 44 | public class SimpleIconCache extends BaseIconCache { 45 | 46 | private static SimpleIconCache sIconCache = null; 47 | private static final Object CACHE_LOCK = new Object(); 48 | 49 | private final SparseLongArray mUserSerialMap = new SparseLongArray(2); 50 | private final UserManager mUserManager; 51 | 52 | public SimpleIconCache(Context context, String dbFileName, Looper bgLooper, int iconDpi, 53 | int iconPixelSize, boolean inMemoryCache) { 54 | super(context, dbFileName, bgLooper, iconDpi, iconPixelSize, inMemoryCache); 55 | mUserManager = context.getSystemService(UserManager.class); 56 | 57 | // Listen for user cache changes. 58 | IntentFilter filter = new IntentFilter(ACTION_MANAGED_PROFILE_ADDED); 59 | filter.addAction(ACTION_MANAGED_PROFILE_REMOVED); 60 | context.registerReceiver(new BroadcastReceiver() { 61 | @Override 62 | public void onReceive(Context context, Intent intent) { 63 | resetUserCache(); 64 | } 65 | }, filter, null, new Handler(bgLooper), 0); 66 | } 67 | 68 | @Override 69 | protected long getSerialNumberForUser(@NonNull UserHandle user) { 70 | synchronized (mUserSerialMap) { 71 | int index = mUserSerialMap.indexOfKey(user.getIdentifier()); 72 | if (index >= 0) { 73 | return mUserSerialMap.valueAt(index); 74 | } 75 | long serial = mUserManager.getSerialNumberForUser(user); 76 | mUserSerialMap.put(user.getIdentifier(), serial); 77 | return serial; 78 | } 79 | } 80 | 81 | private void resetUserCache() { 82 | synchronized (mUserSerialMap) { 83 | mUserSerialMap.clear(); 84 | } 85 | } 86 | 87 | @Override 88 | protected boolean isInstantApp(@NonNull ApplicationInfo info) { 89 | return info.isInstantApp(); 90 | } 91 | 92 | @NonNull 93 | @Override 94 | public BaseIconFactory getIconFactory() { 95 | return IconFactory.obtain(mContext); 96 | } 97 | 98 | public static SimpleIconCache getIconCache(Context context) { 99 | synchronized (CACHE_LOCK) { 100 | if (sIconCache != null) { 101 | return sIconCache; 102 | } 103 | boolean inMemoryCache = 104 | context.getResources().getBoolean(R.bool.simple_cache_enable_im_memory); 105 | String dbFileName = context.getString(R.string.cache_db_name); 106 | 107 | HandlerThread bgThread = new HandlerThread("simple-icon-cache"); 108 | bgThread.start(); 109 | 110 | sIconCache = new SimpleIconCache(context.getApplicationContext(), dbFileName, 111 | bgThread.getLooper(), context.getResources().getConfiguration().densityDpi, 112 | context.getResources().getDimensionPixelSize(R.dimen.default_icon_bitmap_size), 113 | inMemoryCache); 114 | return sIconCache; 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /searchuilib/src/com/android/app/search/SearchTargetEventHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.android.app.search; 17 | 18 | import static com.android.app.search.SearchTargetExtras.isRichAnswer; 19 | 20 | import android.app.search.SearchTarget; 21 | import android.content.ComponentName; 22 | import android.os.Process; 23 | 24 | import androidx.annotation.Nullable; 25 | 26 | /** 27 | * Helper class that defines helper methods for {@link android.app.search.SearchTargetEvent} to 28 | * define the contract between Launcher and AiAi for notifyEvent. 29 | */ 30 | 31 | public class SearchTargetEventHelper { 32 | 33 | public static final String PKG_NAME_AGSA = "com.google.android.googlequicksearchbox"; 34 | 35 | /** 36 | * Generate web target id similar to AiAi targetId for logging search button tap and Launcher 37 | * sends raw query to AGA. 38 | * AiAi target id is of format "resultType:userId:packageName:extraInfo" 39 | * 40 | * @return string webTargetId 41 | * Example webTargetId for 42 | * web suggestion - WEB_SUGGEST:0:com.google.android.googlequicksearchbox:SUGGESTION 43 | */ 44 | public static String generateWebTargetIdForRawQuery() { 45 | // For raw query, there is no search target, so we pass null. 46 | return generateWebTargetIdForLogging(null); 47 | } 48 | 49 | /** 50 | * Generate web target id similar to AiAi targetId for logging both 0-state and n-state. 51 | * AiAi target id is of format "resultType:userId:packageName:extraInfo" 52 | * 53 | * @return string webTargetId 54 | * Example webTargetId for 55 | * web suggestion - WEB_SUGGEST:0:com.google.android.googlequicksearchbox:SUGGESTION 56 | * rich answer - WEB_SUGGEST:0:com.google.android.googlequicksearchbox:RICH_ANSWER 57 | */ 58 | public static String generateWebTargetIdForLogging(@Nullable SearchTarget webTarget) { 59 | StringBuilder webTargetId = new StringBuilder( 60 | "WEB_SUGGEST" + ":" + Process.myUserHandle().getIdentifier() + ":"); 61 | if (webTarget == null) { 62 | webTargetId.append(PKG_NAME_AGSA + ":SUGGESTION"); 63 | return webTargetId.toString(); 64 | } 65 | webTargetId.append(webTarget.getPackageName()); 66 | if (isRichAnswer(webTarget)) { 67 | webTargetId.append(":RICH_ANSWER"); 68 | } else { 69 | webTargetId.append(":SUGGESTION"); 70 | } 71 | return webTargetId.toString(); 72 | } 73 | 74 | /** 75 | * Generate application target id similar to AiAi targetId for logging only 0-state. 76 | * For n-state, AiAi already populates the target id in right format. 77 | * AiAi target id is of format "resultType:userId:packageName:extraInfo" 78 | * 79 | * When the apps from AiAi's AppPredictionService are converted to {@link SearchTarget}, we need 80 | * to construct the targetId using componentName. 81 | * 82 | * @return string appTargetId 83 | * Example appTargetId for 84 | * maps - APPLICATION:0:com.google.android.apps.maps:com.google.android.maps.MapsActivity 85 | * clock - APPLICATION:0:com.google.android.deskclock:com.android.deskclock.DeskClock 86 | */ 87 | public static String generateAppTargetIdForLogging(@Nullable ComponentName appComponentName) { 88 | StringBuilder appTargetId = new StringBuilder( 89 | "APPLICATION" + ":" + Process.myUserHandle().getIdentifier() + ":"); 90 | if (appComponentName == null) return appTargetId.append(" : ").toString(); 91 | return appTargetId + appComponentName.getPackageName() + ":" 92 | + appComponentName.getClassName(); 93 | } 94 | 95 | /** 96 | * Generate gms play target id similar to AiAi targetId for logging only n-state. 97 | * AiAi target id is of format "resultType:userId:packageName:extraInfo" 98 | * 99 | * @return string playTargetId 100 | * Example playTargetId for Candy Crush 101 | * PLAY:0:com.king.candycrushsaga:Gms 102 | */ 103 | public static String generatePlayTargetIdForLogging(String appPackage) { 104 | return "PLAY" + ":" + Process.myUserHandle().getIdentifier() + ":" + appPackage + ":Gms"; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /searchuilib/src/com/android/app/search/LayoutType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.app.search; 18 | 19 | /** 20 | * Constants to be used with {@link SearchTarget}. 21 | */ 22 | public class LayoutType { 23 | 24 | // ------ 25 | // | icon | 26 | // ------ 27 | // text 28 | public static final String ICON_SINGLE_VERTICAL_TEXT = "icon"; 29 | 30 | // Below three layouts (to be deprecated) and two layouts render 31 | // {@link SearchTarget}s in following layout. 32 | // ------ ------ ------ 33 | // | | title |(opt)| |(opt)| 34 | // | icon | subtitle (optional) | icon| | icon| 35 | // ------ ------ ------ 36 | @Deprecated 37 | public static final String ICON_SINGLE_HORIZONTAL_TEXT = "icon_text_row"; 38 | @Deprecated 39 | public static final String ICON_DOUBLE_HORIZONTAL_TEXT = "icon_texts_row"; 40 | @Deprecated 41 | public static final String ICON_DOUBLE_HORIZONTAL_TEXT_BUTTON = "icon_texts_button"; 42 | 43 | // will replace ICON_DOUBLE_* ICON_SINGLE_* layouts 44 | public static final String ICON_HORIZONTAL_TEXT = "icon_row"; 45 | public static final String HORIZONTAL_MEDIUM_TEXT = "icon_row_medium"; 46 | public static final String EXTRA_TALL_ICON_ROW = "extra_tall_icon_row"; 47 | public static final String SMALL_ICON_HORIZONTAL_TEXT = "short_icon_row"; 48 | public static final String SMALL_ICON_HORIZONTAL_TEXT_THUMBNAIL = "short_icon_row_thumbnail"; 49 | 50 | // This layout contains a series of icon results (currently up to 4 per row). 51 | // The container does not support stretching for its children, and can only contain 52 | // {@link #ICON_SINGLE_VERTICAL_TEXT} layout types. 53 | public static final String ICON_CONTAINER = "icon_container"; 54 | 55 | // This layout contains a series of thumbnails (currently up to 3 per row). 56 | // The container supports stretching for its children, and can only contain {@link #THUMBNAIL} 57 | // layout types. 58 | public static final String THUMBNAIL_CONTAINER = "thumbnail_container"; 59 | 60 | // This layout creates a container for people grouping 61 | // Only available above version code 2 62 | public static final String BIG_ICON_MEDIUM_HEIGHT_ROW = "big_icon_medium_row"; 63 | 64 | // This layout creates square thumbnail image (currently 3 column) 65 | public static final String THUMBNAIL = "thumbnail"; 66 | 67 | // This layout contains an icon and slice 68 | public static final String ICON_SLICE = "slice"; 69 | 70 | // Widget bitmap preview 71 | public static final String WIDGET_PREVIEW = "widget_preview"; 72 | 73 | // Live widget search result 74 | public static final String WIDGET_LIVE = "widget_live"; 75 | 76 | // Layout type used to display people tiles using shortcut info 77 | public static final String PEOPLE_TILE = "people_tile"; 78 | 79 | // Deprecated 80 | // text based header to group various layouts in low confidence section of the results. 81 | public static final String TEXT_HEADER = "header"; 82 | 83 | // horizontal bar to be inserted between fallback search results and low confidence section 84 | public static final String EMPTY_DIVIDER = "empty_divider"; 85 | 86 | // layout representing quick calculations 87 | public static final String CALCULATOR = "calculator"; 88 | 89 | // From version code 4, if TEXT_HEADER_ROW is used, no need to insert this on-device 90 | // section header. 91 | public static final String SECTION_HEADER = "section_header"; 92 | 93 | // layout for a tall card with header and image, and no icon. 94 | public static final String TALL_CARD_WITH_IMAGE_NO_ICON = "tall_card_with_image_no_icon"; 95 | 96 | // Layout for a text header 97 | // Available for SearchUiManager proxy service to use above version code 3 98 | public static final String TEXT_HEADER_ROW = "text_header_row"; 99 | 100 | // Layout for a quick settings tile 101 | public static final String QS_TILE = "qs_tile"; 102 | 103 | // Placeholder for web suggest. 104 | public static final String PLACEHOLDER = "placeholder"; 105 | 106 | // Placeholder for rich answer cards. 107 | // Only available on or above version code 3. 108 | public static final String RICHANSWER_PLACEHOLDER = "richanswer_placeholder"; 109 | 110 | } 111 | -------------------------------------------------------------------------------- /viewcapturelib/tests/com/android/app/viewcapture/ViewCaptureTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.app.viewcapture 18 | 19 | import android.content.Intent 20 | import android.media.permission.SafeCloseable 21 | import android.testing.AndroidTestingRunner 22 | import android.view.Choreographer 23 | import android.view.View 24 | import android.widget.LinearLayout 25 | import android.widget.TextView 26 | import androidx.test.ext.junit.rules.ActivityScenarioRule 27 | import androidx.test.filters.SmallTest 28 | import androidx.test.platform.app.InstrumentationRegistry 29 | import com.android.app.viewcapture.TestActivity.Companion.TEXT_VIEW_COUNT 30 | import com.android.app.viewcapture.data.ExportedData 31 | import junit.framework.Assert.assertEquals 32 | import org.junit.Rule 33 | import org.junit.Test 34 | import org.junit.runner.RunWith 35 | 36 | @SmallTest 37 | @RunWith(AndroidTestingRunner::class) 38 | class ViewCaptureTest { 39 | 40 | private val memorySize = 100 41 | private val initPoolSize = 15 42 | private val viewCapture by lazy { 43 | object : 44 | ViewCapture(memorySize, initPoolSize, Choreographer.getInstance(), MAIN_EXECUTOR) {} 45 | } 46 | 47 | private val activityIntent = 48 | Intent(InstrumentationRegistry.getInstrumentation().context, TestActivity::class.java) 49 | 50 | @get:Rule val activityScenarioRule = ActivityScenarioRule(activityIntent) 51 | 52 | @Test 53 | fun testViewCaptureDumpsOneFrameAfterInvalidate() { 54 | activityScenarioRule.scenario.onActivity { activity -> 55 | Choreographer.getInstance().postFrameCallback { 56 | val closeable = startViewCaptureAndInvalidateNTimes(1, activity) 57 | val rootView = activity.findViewById(android.R.id.content) 58 | val exportedData = viewCapture.getDumpTask(rootView).get().get() 59 | 60 | assertEquals(1, exportedData.frameDataList.size) 61 | verifyTestActivityViewHierarchy(exportedData) 62 | closeable.close() 63 | } 64 | } 65 | } 66 | 67 | @Test 68 | fun testViewCaptureDumpsCorrectlyAfterRecyclingStarted() { 69 | activityScenarioRule.scenario.onActivity { activity -> 70 | Choreographer.getInstance().postFrameCallback { 71 | val closeable = startViewCaptureAndInvalidateNTimes(memorySize + 5, activity) 72 | val rootView = activity.findViewById(android.R.id.content) 73 | val exportedData = viewCapture.getDumpTask(rootView).get().get() 74 | 75 | // since ViewCapture MEMORY_SIZE is [viewCaptureMemorySize], only 76 | // [viewCaptureMemorySize] frames are exported, although the view is invalidated 77 | // [viewCaptureMemorySize + 5] times 78 | assertEquals(memorySize, exportedData.frameDataList.size) 79 | verifyTestActivityViewHierarchy(exportedData) 80 | closeable.close() 81 | } 82 | } 83 | } 84 | 85 | private fun startViewCaptureAndInvalidateNTimes(n: Int, activity: TestActivity): SafeCloseable { 86 | val rootView: View = activity.findViewById(android.R.id.content) 87 | val closeable: SafeCloseable = viewCapture.startCapture(rootView, "rootViewId") 88 | dispatchOnDraw(rootView, times = n) 89 | return closeable 90 | } 91 | 92 | private fun dispatchOnDraw(view: View, times: Int) { 93 | if (times > 0) { 94 | view.viewTreeObserver.dispatchOnDraw() 95 | dispatchOnDraw(view, times - 1) 96 | } 97 | } 98 | 99 | private fun verifyTestActivityViewHierarchy(exportedData: ExportedData) { 100 | for (frame in exportedData.frameDataList) { 101 | val testActivityRoot = 102 | frame.node // FrameLayout (android.R.id.content) 103 | .childrenList 104 | .first() // LinearLayout (set by setContentView()) 105 | assertEquals(TEXT_VIEW_COUNT, testActivityRoot.childrenList.size) 106 | assertEquals( 107 | LinearLayout::class.qualifiedName, 108 | exportedData.getClassname(testActivityRoot.classnameIndex) 109 | ) 110 | assertEquals( 111 | TextView::class.qualifiedName, 112 | exportedData.getClassname(testActivityRoot.childrenList.first().classnameIndex) 113 | ) 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.android.launcher3.icons; 17 | 18 | import android.content.Context; 19 | import android.content.res.Resources; 20 | import android.content.res.TypedArray; 21 | import android.graphics.Bitmap; 22 | import android.graphics.Color; 23 | import android.graphics.Matrix; 24 | import android.graphics.Path; 25 | import android.graphics.Rect; 26 | import android.graphics.Region; 27 | import android.graphics.RegionIterator; 28 | import android.graphics.drawable.AdaptiveIconDrawable; 29 | import android.graphics.drawable.ColorDrawable; 30 | import android.util.Log; 31 | 32 | import androidx.annotation.ColorInt; 33 | import androidx.annotation.NonNull; 34 | import androidx.core.graphics.PathParser; 35 | 36 | import java.io.ByteArrayOutputStream; 37 | import java.io.IOException; 38 | 39 | public class GraphicsUtils { 40 | 41 | private static final String TAG = "GraphicsUtils"; 42 | private static final float MASK_SIZE = 100f; 43 | 44 | public static Runnable sOnNewBitmapRunnable = () -> { }; 45 | 46 | /** 47 | * Set the alpha component of {@code color} to be {@code alpha}. Unlike the support lib version, 48 | * it bounds the alpha in valid range instead of throwing an exception to allow for safer 49 | * interpolation of color animations 50 | */ 51 | @ColorInt 52 | public static int setColorAlphaBound(int color, int alpha) { 53 | if (alpha < 0) { 54 | alpha = 0; 55 | } else if (alpha > 255) { 56 | alpha = 255; 57 | } 58 | return (color & 0x00ffffff) | (alpha << 24); 59 | } 60 | 61 | /** 62 | * Compresses the bitmap to a byte array for serialization. 63 | */ 64 | public static byte[] flattenBitmap(Bitmap bitmap) { 65 | ByteArrayOutputStream out = new ByteArrayOutputStream(getExpectedBitmapSize(bitmap)); 66 | try { 67 | bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); 68 | out.flush(); 69 | out.close(); 70 | return out.toByteArray(); 71 | } catch (IOException e) { 72 | Log.w(TAG, "Could not write bitmap"); 73 | return null; 74 | } 75 | } 76 | 77 | /** 78 | * Try go guesstimate how much space the icon will take when serialized to avoid unnecessary 79 | * allocations/copies during the write (4 bytes per pixel). 80 | */ 81 | static int getExpectedBitmapSize(Bitmap bitmap) { 82 | return bitmap.getWidth() * bitmap.getHeight() * 4; 83 | } 84 | 85 | public static int getArea(Region r) { 86 | RegionIterator itr = new RegionIterator(r); 87 | int area = 0; 88 | Rect tempRect = new Rect(); 89 | while (itr.next(tempRect)) { 90 | area += tempRect.width() * tempRect.height(); 91 | } 92 | return area; 93 | } 94 | 95 | /** 96 | * Utility method to track new bitmap creation 97 | */ 98 | public static void noteNewBitmapCreated() { 99 | sOnNewBitmapRunnable.run(); 100 | } 101 | 102 | 103 | /** 104 | * Returns the default path to be used by an icon 105 | */ 106 | public static Path getShapePath(@NonNull Context context, int size) { 107 | if (IconProvider.CONFIG_ICON_MASK_RES_ID != Resources.ID_NULL) { 108 | Path path = PathParser.createPathFromPathData( 109 | context.getString(IconProvider.CONFIG_ICON_MASK_RES_ID)); 110 | if (path != null) { 111 | if (size != MASK_SIZE) { 112 | Matrix m = new Matrix(); 113 | float scale = ((float) size) / MASK_SIZE; 114 | m.setScale(scale, scale); 115 | path.transform(m); 116 | } 117 | return path; 118 | } 119 | } 120 | AdaptiveIconDrawable drawable = new AdaptiveIconDrawable( 121 | new ColorDrawable(Color.BLACK), new ColorDrawable(Color.BLACK)); 122 | drawable.setBounds(0, 0, size, size); 123 | return new Path(drawable.getIconMask()); 124 | } 125 | 126 | /** 127 | * Returns the color associated with the attribute 128 | */ 129 | public static int getAttrColor(Context context, int attr) { 130 | TypedArray ta = context.obtainStyledAttributes(new int[]{attr}); 131 | int colorAccent = ta.getColor(0, 0); 132 | ta.recycle(); 133 | return colorAccent; 134 | } 135 | 136 | /** 137 | * Returns the alpha corresponding to the theme attribute {@param attr} 138 | */ 139 | public static float getFloat(Context context, int attr, float defValue) { 140 | TypedArray ta = context.obtainStyledAttributes(new int[]{attr}); 141 | float value = ta.getFloat(0, defValue); 142 | ta.recycle(); 143 | return value; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /iconloaderlib/src/com/android/launcher3/icons/ColorExtractor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.android.launcher3.icons; 17 | 18 | import android.graphics.Bitmap; 19 | import android.graphics.Color; 20 | import android.util.SparseArray; 21 | 22 | import androidx.annotation.NonNull; 23 | 24 | import java.util.Arrays; 25 | 26 | /** 27 | * Utility class for extracting colors from a bitmap. 28 | */ 29 | public class ColorExtractor { 30 | 31 | private final int NUM_SAMPLES = 20; 32 | 33 | @NonNull 34 | private final float[] mTmpHsv = new float[3]; 35 | 36 | @NonNull 37 | private final float[] mTmpHueScoreHistogram = new float[360]; 38 | 39 | @NonNull 40 | private final int[] mTmpPixels = new int[NUM_SAMPLES]; 41 | 42 | @NonNull 43 | private final SparseArray mTmpRgbScores = new SparseArray<>(); 44 | 45 | /** 46 | * This picks a dominant color, looking for high-saturation, high-value, repeated hues. 47 | * @param bitmap The bitmap to scan 48 | */ 49 | public int findDominantColorByHue(@NonNull final Bitmap bitmap) { 50 | return findDominantColorByHue(bitmap, NUM_SAMPLES); 51 | } 52 | 53 | /** 54 | * This picks a dominant color, looking for high-saturation, high-value, repeated hues. 55 | * @param bitmap The bitmap to scan 56 | */ 57 | protected int findDominantColorByHue(@NonNull final Bitmap bitmap, final int samples) { 58 | final int height = bitmap.getHeight(); 59 | final int width = bitmap.getWidth(); 60 | int sampleStride = (int) Math.sqrt((height * width) / samples); 61 | if (sampleStride < 1) { 62 | sampleStride = 1; 63 | } 64 | 65 | // This is an out-param, for getting the hsv values for an rgb 66 | float[] hsv = mTmpHsv; 67 | Arrays.fill(hsv, 0); 68 | 69 | // First get the best hue, by creating a histogram over 360 hue buckets, 70 | // where each pixel contributes a score weighted by saturation, value, and alpha. 71 | float[] hueScoreHistogram = mTmpHueScoreHistogram; 72 | Arrays.fill(hueScoreHistogram, 0); 73 | float highScore = -1; 74 | int bestHue = -1; 75 | 76 | int[] pixels = mTmpPixels.length <= samples ? mTmpPixels : new int[samples]; 77 | Arrays.fill(pixels, 0); 78 | int pixelCount = 0; 79 | 80 | for (int y = 0; y < height; y += sampleStride) { 81 | for (int x = 0; x < width; x += sampleStride) { 82 | int argb = bitmap.getPixel(x, y); 83 | int alpha = 0xFF & (argb >> 24); 84 | if (alpha < 0x80) { 85 | // Drop mostly-transparent pixels. 86 | continue; 87 | } 88 | // Remove the alpha channel. 89 | int rgb = argb | 0xFF000000; 90 | Color.colorToHSV(rgb, hsv); 91 | // Bucket colors by the 360 integer hues. 92 | int hue = (int) hsv[0]; 93 | if (hue < 0 || hue >= hueScoreHistogram.length) { 94 | // Defensively avoid array bounds violations. 95 | continue; 96 | } 97 | if (pixelCount < samples) { 98 | pixels[pixelCount++] = rgb; 99 | } 100 | float score = hsv[1] * hsv[2]; 101 | hueScoreHistogram[hue] += score; 102 | if (hueScoreHistogram[hue] > highScore) { 103 | highScore = hueScoreHistogram[hue]; 104 | bestHue = hue; 105 | } 106 | } 107 | } 108 | 109 | SparseArray rgbScores = mTmpRgbScores; 110 | rgbScores.clear(); 111 | int bestColor = 0xff000000; 112 | highScore = -1; 113 | // Go back over the RGB colors that match the winning hue, 114 | // creating a histogram of weighted s*v scores, for up to 100*100 [s,v] buckets. 115 | // The highest-scoring RGB color wins. 116 | for (int i = 0; i < pixelCount; i++) { 117 | int rgb = pixels[i]; 118 | Color.colorToHSV(rgb, hsv); 119 | int hue = (int) hsv[0]; 120 | if (hue == bestHue) { 121 | float s = hsv[1]; 122 | float v = hsv[2]; 123 | int bucket = (int) (s * 100) + (int) (v * 10000); 124 | // Score by cumulative saturation * value. 125 | float score = s * v; 126 | Float oldTotal = rgbScores.get(bucket); 127 | float newTotal = oldTotal == null ? score : oldTotal + score; 128 | rgbScores.put(bucket, newTotal); 129 | if (newTotal > highScore) { 130 | highScore = newTotal; 131 | // All the colors in the winning bucket are very similar. Last in wins. 132 | bestColor = rgb; 133 | } 134 | } 135 | } 136 | return bestColor; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /iconloaderlib/src/com/android/launcher3/icons/ThemedIconDrawable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.android.launcher3.icons; 17 | 18 | import static android.content.res.Configuration.UI_MODE_NIGHT_MASK; 19 | import static android.content.res.Configuration.UI_MODE_NIGHT_YES; 20 | 21 | import android.content.Context; 22 | import android.content.res.Resources; 23 | import android.graphics.Bitmap; 24 | import android.graphics.BlendMode; 25 | import android.graphics.BlendModeColorFilter; 26 | import android.graphics.Canvas; 27 | import android.graphics.ColorFilter; 28 | import android.graphics.Paint; 29 | import android.graphics.Rect; 30 | 31 | /** 32 | * Class to handle monochrome themed app icons 33 | */ 34 | @SuppressWarnings("NewApi") 35 | public class ThemedIconDrawable extends FastBitmapDrawable { 36 | 37 | public static final String TAG = "ThemedIconDrawable"; 38 | 39 | final BitmapInfo bitmapInfo; 40 | final int colorFg, colorBg; 41 | 42 | // The foreground/monochrome icon for the app 43 | private final Bitmap mMonoIcon; 44 | private final Paint mMonoPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); 45 | 46 | private final Bitmap mBgBitmap; 47 | private final Paint mBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); 48 | 49 | private final ColorFilter mBgFilter, mMonoFilter; 50 | 51 | protected ThemedIconDrawable(ThemedConstantState constantState) { 52 | super(constantState.mBitmap, constantState.colorFg); 53 | bitmapInfo = constantState.bitmapInfo; 54 | colorBg = constantState.colorBg; 55 | colorFg = constantState.colorFg; 56 | 57 | mMonoIcon = bitmapInfo.mMono; 58 | mMonoFilter = new BlendModeColorFilter(colorFg, BlendMode.SRC_IN); 59 | mMonoPaint.setColorFilter(mMonoFilter); 60 | 61 | mBgBitmap = bitmapInfo.mWhiteShadowLayer; 62 | mBgFilter = new BlendModeColorFilter(colorBg, BlendMode.SRC_IN); 63 | mBgPaint.setColorFilter(mBgFilter); 64 | } 65 | 66 | @Override 67 | protected void drawInternal(Canvas canvas, Rect bounds) { 68 | canvas.drawBitmap(mBgBitmap, null, bounds, mBgPaint); 69 | canvas.drawBitmap(mMonoIcon, null, bounds, mMonoPaint); 70 | } 71 | 72 | @Override 73 | protected void updateFilter() { 74 | super.updateFilter(); 75 | int alpha = mIsDisabled ? (int) (mDisabledAlpha * FULLY_OPAQUE) : FULLY_OPAQUE; 76 | mBgPaint.setAlpha(alpha); 77 | mBgPaint.setColorFilter(mIsDisabled ? new BlendModeColorFilter( 78 | getDisabledColor(colorBg), BlendMode.SRC_IN) : mBgFilter); 79 | 80 | mMonoPaint.setAlpha(alpha); 81 | mMonoPaint.setColorFilter(mIsDisabled ? new BlendModeColorFilter( 82 | getDisabledColor(colorFg), BlendMode.SRC_IN) : mMonoFilter); 83 | } 84 | 85 | @Override 86 | public boolean isThemed() { 87 | return true; 88 | } 89 | 90 | @Override 91 | public FastBitmapConstantState newConstantState() { 92 | return new ThemedConstantState(bitmapInfo, colorBg, colorFg); 93 | } 94 | 95 | public void changeBackgroundColor(int colorBg) { 96 | if (mIsDisabled) return; 97 | 98 | mBgPaint.setColorFilter(new BlendModeColorFilter(colorBg, BlendMode.SRC_IN)); 99 | invalidateSelf(); 100 | } 101 | 102 | static class ThemedConstantState extends FastBitmapConstantState { 103 | 104 | final BitmapInfo bitmapInfo; 105 | final int colorFg, colorBg; 106 | 107 | public ThemedConstantState(BitmapInfo bitmapInfo, int colorBg, int colorFg) { 108 | super(bitmapInfo.icon, bitmapInfo.color); 109 | this.bitmapInfo = bitmapInfo; 110 | this.colorBg = colorBg; 111 | this.colorFg = colorFg; 112 | } 113 | 114 | @Override 115 | public FastBitmapDrawable createDrawable() { 116 | return new ThemedIconDrawable(this); 117 | } 118 | } 119 | 120 | public static FastBitmapDrawable newDrawable(BitmapInfo info, Context context) { 121 | int[] colors = getColors(context); 122 | return new ThemedConstantState(info, colors[0], colors[1]).newDrawable(); 123 | } 124 | 125 | /** 126 | * Get an int array representing background and foreground colors for themed icons 127 | */ 128 | public static int[] getColors(Context context) { 129 | Resources res = context.getResources(); 130 | int[] colors = new int[2]; 131 | if ((res.getConfiguration().uiMode & UI_MODE_NIGHT_MASK) == UI_MODE_NIGHT_YES) { 132 | colors[0] = res.getColor(android.R.color.system_neutral1_800); 133 | colors[1] = res.getColor(android.R.color.system_accent1_100); 134 | } else { 135 | colors[0] = res.getColor(android.R.color.system_accent1_100); 136 | colors[1] = res.getColor(android.R.color.system_neutral2_700); 137 | } 138 | return colors; 139 | } 140 | 141 | @Override 142 | public int getIconColor() { 143 | return colorFg; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /iconloaderlib/src/com/android/launcher3/util/override/MainThreadInitializedObject.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.android.launcher3.util.override; 17 | 18 | import static com.android.launcher3.util.override.Executors.MAIN_EXECUTOR; 19 | 20 | import android.content.Context; 21 | import android.content.ContextWrapper; 22 | import android.os.Looper; 23 | import android.util.Log; 24 | 25 | import androidx.annotation.UiThread; 26 | import androidx.annotation.VisibleForTesting; 27 | 28 | import com.android.launcher3.util.override.ResourceBasedOverride.Overrides; 29 | import com.android.launcher3.util.SafeCloseable; 30 | 31 | import java.util.ArrayList; 32 | import java.util.Arrays; 33 | import java.util.HashMap; 34 | import java.util.HashSet; 35 | import java.util.Map; 36 | import java.util.Set; 37 | import java.util.concurrent.ExecutionException; 38 | 39 | /** 40 | * Utility class for defining singletons which are initiated on main thread. 41 | */ 42 | public class MainThreadInitializedObject { 43 | 44 | private final ObjectProvider mProvider; 45 | private T mValue; 46 | 47 | public MainThreadInitializedObject(ObjectProvider provider) { 48 | mProvider = provider; 49 | } 50 | 51 | public T get(Context context) { 52 | if (context instanceof SandboxContext) { 53 | return ((SandboxContext) context).getObject(this, mProvider); 54 | } 55 | 56 | if (mValue == null) { 57 | if (Looper.myLooper() == Looper.getMainLooper()) { 58 | mValue = TraceHelper.allowIpcs("main.thread.object", 59 | () -> mProvider.get(context.getApplicationContext())); 60 | } else { 61 | try { 62 | return MAIN_EXECUTOR.submit(() -> get(context)).get(); 63 | } catch (InterruptedException|ExecutionException e) { 64 | throw new RuntimeException(e); 65 | } 66 | } 67 | } 68 | return mValue; 69 | } 70 | 71 | public T getNoCreate() { 72 | return mValue; 73 | } 74 | 75 | @VisibleForTesting 76 | public void initializeForTesting(T value) { 77 | mValue = value; 78 | } 79 | 80 | /** 81 | * Initializes a provider based on resource overrides 82 | */ 83 | public static MainThreadInitializedObject forOverride( 84 | Class clazz, int resourceId) { 85 | return new MainThreadInitializedObject<>(c -> Overrides.getObject(clazz, c, resourceId)); 86 | } 87 | 88 | public interface ObjectProvider { 89 | 90 | T get(Context context); 91 | } 92 | 93 | /** 94 | * Abstract Context which allows custom implementations for 95 | * {@link MainThreadInitializedObject} providers 96 | */ 97 | public static abstract class SandboxContext extends ContextWrapper { 98 | 99 | private static final String TAG = "SandboxContext"; 100 | 101 | protected final Set mAllowedObjects; 102 | protected final Map mObjectMap = new HashMap<>(); 103 | protected final ArrayList mOrderedObjects = new ArrayList<>(); 104 | 105 | private final Object mDestroyLock = new Object(); 106 | private boolean mDestroyed = false; 107 | 108 | public SandboxContext(Context base, MainThreadInitializedObject... allowedObjects) { 109 | super(base); 110 | mAllowedObjects = new HashSet<>(Arrays.asList(allowedObjects)); 111 | } 112 | 113 | @Override 114 | public Context getApplicationContext() { 115 | return this; 116 | } 117 | 118 | public void onDestroy() { 119 | synchronized (mDestroyLock) { 120 | // Destroy in reverse order 121 | for (int i = mOrderedObjects.size() - 1; i >= 0; i--) { 122 | Object o = mOrderedObjects.get(i); 123 | if (o instanceof SafeCloseable) { 124 | ((SafeCloseable) o).close(); 125 | } 126 | } 127 | mDestroyed = true; 128 | } 129 | } 130 | 131 | /** 132 | * Find a cached object from mObjectMap if we have already created one. If not, generate 133 | * an object using the provider. 134 | */ 135 | private T getObject(MainThreadInitializedObject object, ObjectProvider provider) { 136 | synchronized (mDestroyLock) { 137 | if (mDestroyed) { 138 | Log.e(TAG, "Static object access with a destroyed context"); 139 | } 140 | if (!mAllowedObjects.contains(object)) { 141 | throw new IllegalStateException( 142 | "Leaking unknown objects " + object + " " + provider); 143 | } 144 | T t = (T) mObjectMap.get(object); 145 | if (t != null) { 146 | return t; 147 | } 148 | if (Looper.myLooper() == Looper.getMainLooper()) { 149 | t = createObject(provider); 150 | mObjectMap.put(object, t); 151 | mOrderedObjects.add(t); 152 | return t; 153 | } 154 | } 155 | 156 | try { 157 | return MAIN_EXECUTOR.submit(() -> getObject(object, provider)).get(); 158 | } catch (InterruptedException | ExecutionException e) { 159 | throw new RuntimeException(e); 160 | } 161 | } 162 | 163 | @UiThread 164 | protected T createObject(ObjectProvider provider) { 165 | return provider.get(this); 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.launcher3.icons; 18 | 19 | import static android.graphics.Paint.ANTI_ALIAS_FLAG; 20 | import static android.graphics.Paint.FILTER_BITMAP_FLAG; 21 | 22 | import android.graphics.Bitmap; 23 | import android.graphics.Canvas; 24 | import android.graphics.Color; 25 | import android.graphics.Paint; 26 | import android.graphics.Path; 27 | import android.graphics.PathMeasure; 28 | import android.graphics.Rect; 29 | import android.graphics.RectF; 30 | import android.util.Log; 31 | import android.view.ViewDebug; 32 | 33 | /** 34 | * Used to draw a notification dot on top of an icon. 35 | */ 36 | public class DotRenderer { 37 | 38 | private static final String TAG = "DotRenderer"; 39 | 40 | // The dot size is defined as a percentage of the app icon size. 41 | private static final float SIZE_PERCENTAGE = 0.228f; 42 | 43 | private final float mCircleRadius; 44 | private final Paint mCirclePaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG); 45 | 46 | private final Bitmap mBackgroundWithShadow; 47 | private final float mBitmapOffset; 48 | 49 | // Stores the center x and y position as a percentage (0 to 1) of the icon size 50 | private final float[] mRightDotPosition; 51 | private final float[] mLeftDotPosition; 52 | 53 | private static final int MIN_DOT_SIZE = 1; 54 | public DotRenderer(int iconSizePx, Path iconShapePath, int pathSize) { 55 | int size = Math.round(SIZE_PERCENTAGE * iconSizePx); 56 | if (size <= 0) { 57 | size = MIN_DOT_SIZE; 58 | } 59 | ShadowGenerator.Builder builder = new ShadowGenerator.Builder(Color.TRANSPARENT); 60 | builder.ambientShadowAlpha = 88; 61 | mBackgroundWithShadow = builder.setupBlurForSize(size).createPill(size, size); 62 | mCircleRadius = builder.radius; 63 | 64 | mBitmapOffset = -mBackgroundWithShadow.getHeight() * 0.5f; // Same as width. 65 | 66 | // Find the points on the path that are closest to the top left and right corners. 67 | mLeftDotPosition = getPathPoint(iconShapePath, pathSize, -1); 68 | mRightDotPosition = getPathPoint(iconShapePath, pathSize, 1); 69 | } 70 | 71 | private static float[] getPathPoint(Path path, float size, float direction) { 72 | float halfSize = size / 2; 73 | // Small delta so that we don't get a zero size triangle 74 | float delta = 1; 75 | 76 | float x = halfSize + direction * halfSize; 77 | Path trianglePath = new Path(); 78 | trianglePath.moveTo(halfSize, halfSize); 79 | trianglePath.lineTo(x + delta * direction, 0); 80 | trianglePath.lineTo(x, -delta); 81 | trianglePath.close(); 82 | 83 | trianglePath.op(path, Path.Op.INTERSECT); 84 | float[] pos = new float[2]; 85 | new PathMeasure(trianglePath, false).getPosTan(0, pos, null); 86 | 87 | pos[0] = pos[0] / size; 88 | pos[1] = pos[1] / size; 89 | return pos; 90 | } 91 | 92 | public float[] getLeftDotPosition() { 93 | return mLeftDotPosition; 94 | } 95 | 96 | public float[] getRightDotPosition() { 97 | return mRightDotPosition; 98 | } 99 | 100 | /** 101 | * Draw a circle on top of the canvas according to the given params. 102 | */ 103 | public void draw(Canvas canvas, DrawParams params) { 104 | if (params == null) { 105 | Log.e(TAG, "Invalid null argument(s) passed in call to draw."); 106 | return; 107 | } 108 | canvas.save(); 109 | 110 | Rect iconBounds = params.iconBounds; 111 | float[] dotPosition = params.leftAlign ? mLeftDotPosition : mRightDotPosition; 112 | float dotCenterX = iconBounds.left + iconBounds.width() * dotPosition[0]; 113 | float dotCenterY = iconBounds.top + iconBounds.height() * dotPosition[1]; 114 | 115 | // Ensure dot fits entirely in canvas clip bounds. 116 | Rect canvasBounds = canvas.getClipBounds(); 117 | float offsetX = params.leftAlign 118 | ? Math.max(0, canvasBounds.left - (dotCenterX + mBitmapOffset)) 119 | : Math.min(0, canvasBounds.right - (dotCenterX - mBitmapOffset)); 120 | float offsetY = Math.max(0, canvasBounds.top - (dotCenterY + mBitmapOffset)); 121 | 122 | // We draw the dot relative to its center. 123 | canvas.translate(dotCenterX + offsetX, dotCenterY + offsetY); 124 | canvas.scale(params.scale, params.scale); 125 | 126 | mCirclePaint.setColor(Color.BLACK); 127 | canvas.drawBitmap(mBackgroundWithShadow, mBitmapOffset, mBitmapOffset, mCirclePaint); 128 | mCirclePaint.setColor(params.dotColor); 129 | canvas.drawCircle(0, 0, mCircleRadius, mCirclePaint); 130 | canvas.restore(); 131 | } 132 | 133 | public static class DrawParams { 134 | /** The color (possibly based on the icon) to use for the dot. */ 135 | @ViewDebug.ExportedProperty(category = "notification dot", formatToHexString = true) 136 | public int dotColor; 137 | /** The color (possibly based on the icon) to use for a predicted app. */ 138 | @ViewDebug.ExportedProperty(category = "notification dot", formatToHexString = true) 139 | public int appColor; 140 | /** The bounds of the icon that the dot is drawn on top of. */ 141 | @ViewDebug.ExportedProperty(category = "notification dot") 142 | public Rect iconBounds = new Rect(); 143 | /** The progress of the animation, from 0 to 1. */ 144 | @ViewDebug.ExportedProperty(category = "notification dot") 145 | public float scale; 146 | /** Whether the dot should align to the top left of the icon rather than the top right. */ 147 | @ViewDebug.ExportedProperty(category = "notification dot") 148 | public boolean leftAlign; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /motiontoollib/src/com/android/app/motiontool/MotionToolManager.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.app.motiontool 18 | 19 | import android.os.Process 20 | import android.util.Log 21 | import android.view.Choreographer 22 | import android.view.View 23 | import android.view.WindowManagerGlobal 24 | import androidx.annotation.VisibleForTesting 25 | import com.android.app.viewcapture.ViewCapture 26 | import com.android.app.viewcapture.data.ExportedData 27 | 28 | /** 29 | * Singleton to manage motion tracing sessions. 30 | * 31 | * A motion tracing session captures motion-relevant data on a frame-by-frame basis for a given 32 | * window, as long as the trace is running. 33 | * 34 | * To start a trace, use [beginTrace]. The returned handle must be used to terminate tracing and 35 | * receive the data by calling [endTrace]. While the trace is active, data is buffered, however 36 | * the buffer size is limited (@see [ViewCapture.mMemorySize]. Use [pollTrace] periodically to 37 | * ensure no data is dropped. Both, [pollTrace] and [endTrace] only return data captured since the 38 | * last call to either [beginTrace] or [endTrace]. 39 | * 40 | * NOTE: a running trace will incur some performance penalty. Only keep traces running while a user 41 | * requested it. 42 | * 43 | * @see [DdmHandleMotionTool] 44 | */ 45 | class MotionToolManager private constructor(private val windowManagerGlobal: WindowManagerGlobal) { 46 | private val viewCapture: ViewCapture = SimpleViewCapture() 47 | 48 | companion object { 49 | private const val TAG = "MotionToolManager" 50 | 51 | private var INSTANCE: MotionToolManager? = null 52 | 53 | @Synchronized 54 | fun getInstance(windowManagerGlobal: WindowManagerGlobal): MotionToolManager { 55 | return INSTANCE ?: MotionToolManager(windowManagerGlobal).also { INSTANCE = it } 56 | } 57 | } 58 | 59 | private var traceIdCounter = 0 60 | private val traces = mutableMapOf() 61 | 62 | @Synchronized 63 | fun hasWindow(windowId: WindowIdentifier): Boolean { 64 | val rootView = getRootView(windowId.rootWindow) 65 | return rootView != null 66 | } 67 | 68 | /** Starts [ViewCapture] and returns a traceId. */ 69 | @Synchronized 70 | fun beginTrace(windowId: String): Int { 71 | val traceId = ++traceIdCounter 72 | Log.d(TAG, "Begin Trace for id: $traceId") 73 | val rootView = getRootView(windowId) ?: throw WindowNotFoundException(windowId) 74 | val autoCloseable = viewCapture.startCapture(rootView, windowId) 75 | traces[traceId] = TraceMetadata(windowId, 0, autoCloseable::close) 76 | return traceId 77 | } 78 | 79 | /** 80 | * Ends [ViewCapture] and returns the captured [ExportedData] since the [beginTrace] call or the 81 | * last [pollTrace] call. 82 | */ 83 | @Synchronized 84 | fun endTrace(traceId: Int): ExportedData { 85 | Log.d(TAG, "End Trace for id: $traceId") 86 | val traceMetadata = traces.getOrElse(traceId) { throw UnknownTraceIdException(traceId) } 87 | val exportedData = pollTrace(traceId) 88 | traceMetadata.stopTrace() 89 | traces.remove(traceId) 90 | return exportedData 91 | } 92 | 93 | /** 94 | * Returns the [ExportedData] captured since the [beginTrace] call or the last [pollTrace] call. 95 | * This function can only be used after [beginTrace] is called and before [endTrace] is called. 96 | */ 97 | @Synchronized 98 | fun pollTrace(traceId: Int): ExportedData { 99 | val traceMetadata = traces.getOrElse(traceId) { throw UnknownTraceIdException(traceId) } 100 | val exportedData = getExportedDataFromViewCapture(traceMetadata) 101 | traceMetadata.updateLastPolledTime(exportedData) 102 | return exportedData 103 | } 104 | 105 | /** 106 | * Stops and deletes all active [traces] and resets the [traceIdCounter]. 107 | */ 108 | @VisibleForTesting 109 | @Synchronized 110 | fun reset() { 111 | for (traceMetadata in traces.values) { 112 | traceMetadata.stopTrace() 113 | } 114 | traces.clear() 115 | traceIdCounter = 0 116 | } 117 | 118 | private fun getExportedDataFromViewCapture(traceMetadata: TraceMetadata): ExportedData { 119 | val rootView = 120 | getRootView(traceMetadata.windowId) 121 | ?: throw WindowNotFoundException(traceMetadata.windowId) 122 | 123 | val exportedData = viewCapture 124 | .getDumpTask(rootView) 125 | ?.orElse(null) 126 | ?.get() ?: return ExportedData.newBuilder().build() 127 | 128 | val filteredFrameData = exportedData.frameDataList 129 | ?.filter { it.timestamp > traceMetadata.lastPolledTime } 130 | 131 | return exportedData.toBuilder() 132 | .clearFrameData() 133 | .addAllFrameData(filteredFrameData) 134 | .build() 135 | } 136 | 137 | private fun getRootView(windowId: String): View? { 138 | return windowManagerGlobal.getRootView(windowId) 139 | } 140 | 141 | class SimpleViewCapture : ViewCapture(DEFAULT_MEMORY_SIZE, DEFAULT_INIT_POOL_SIZE, 142 | MAIN_EXECUTOR.submit { Choreographer.getInstance() }.get(), 143 | createAndStartNewLooperExecutor("MTViewCapture", Process.THREAD_PRIORITY_FOREGROUND)) 144 | } 145 | 146 | private data class TraceMetadata( 147 | val windowId: String, 148 | var lastPolledTime: Long, 149 | var stopTrace: () -> Unit 150 | ) { 151 | fun updateLastPolledTime(exportedData: ExportedData?) { 152 | exportedData?.frameDataList?.maxOfOrNull { it.timestamp }?.let { maxFrameTimestamp -> 153 | lastPolledTime = maxFrameTimestamp 154 | } 155 | } 156 | } 157 | 158 | class UnknownTraceIdException(val traceId: Int) : Exception() 159 | 160 | class WindowNotFoundException(val windowId: String) : Exception() -------------------------------------------------------------------------------- /motiontoollib/src/com/android/app/motiontool/DdmHandleMotionTool.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.app.motiontool 18 | 19 | import android.ddm.DdmHandle 20 | import com.google.protobuf.InvalidProtocolBufferException 21 | import org.apache.harmony.dalvik.ddmc.Chunk 22 | import org.apache.harmony.dalvik.ddmc.ChunkHandler 23 | import org.apache.harmony.dalvik.ddmc.DdmServer 24 | 25 | /** 26 | * This class handles the 'MOTO' type DDM requests (defined in [motion_tool.proto]). 27 | * 28 | * It executes some validity checks and forwards valid requests to the [MotionToolManager]. It 29 | * requires a [MotionToolsRequest] as parameter and returns a [MotionToolsResponse]. Failures will 30 | * return a [MotionToolsResponse] with the [error][MotionToolsResponse.error] field set instead of 31 | * the respective return value. 32 | * 33 | * To activate this server, call [register]. This will register the DdmHandleMotionTool with the 34 | * [DdmServer]. The DdmHandleMotionTool can be registered once per process. To unregister from the 35 | * DdmServer, call [unregister]. 36 | */ 37 | class DdmHandleMotionTool private constructor( 38 | private val motionToolManager: MotionToolManager 39 | ) : DdmHandle() { 40 | 41 | companion object { 42 | val CHUNK_MOTO = ChunkHandler.type("MOTO") 43 | private const val SERVER_VERSION = 1 44 | 45 | private var INSTANCE: DdmHandleMotionTool? = null 46 | 47 | @Synchronized 48 | fun getInstance(motionToolManager: MotionToolManager): DdmHandleMotionTool { 49 | return INSTANCE ?: DdmHandleMotionTool(motionToolManager).also { 50 | INSTANCE = it 51 | } 52 | } 53 | } 54 | 55 | fun register() { 56 | DdmServer.registerHandler(CHUNK_MOTO, this) 57 | } 58 | 59 | fun unregister() { 60 | DdmServer.unregisterHandler(CHUNK_MOTO) 61 | } 62 | 63 | override fun handleChunk(request: Chunk): Chunk { 64 | val requestDataBuffer = wrapChunk(request) 65 | val protoRequest = 66 | try { 67 | MotionToolsRequest.parseFrom(requestDataBuffer.array()) 68 | } catch (e: InvalidProtocolBufferException) { 69 | val responseData: ByteArray = MotionToolsResponse.newBuilder() 70 | .setError(ErrorResponse.newBuilder() 71 | .setCode(ErrorResponse.Code.INVALID_REQUEST) 72 | .setMessage("Invalid request format (Protobuf parse exception)")) 73 | .build() 74 | .toByteArray() 75 | return Chunk(CHUNK_MOTO, responseData, 0, responseData.size) 76 | } 77 | 78 | val response = 79 | when (protoRequest.typeCase.number) { 80 | MotionToolsRequest.HANDSHAKE_FIELD_NUMBER -> 81 | handleHandshakeRequest(protoRequest.handshake) 82 | MotionToolsRequest.BEGIN_TRACE_FIELD_NUMBER -> 83 | handleBeginTraceRequest(protoRequest.beginTrace) 84 | MotionToolsRequest.POLL_TRACE_FIELD_NUMBER -> 85 | handlePollTraceRequest(protoRequest.pollTrace) 86 | MotionToolsRequest.END_TRACE_FIELD_NUMBER -> 87 | handleEndTraceRequest(protoRequest.endTrace) 88 | else -> 89 | MotionToolsResponse.newBuilder().setError(ErrorResponse.newBuilder() 90 | .setCode(ErrorResponse.Code.INVALID_REQUEST) 91 | .setMessage("Unknown request type")).build() 92 | } 93 | 94 | val responseData = response.toByteArray() 95 | return Chunk(CHUNK_MOTO, responseData, 0, responseData.size) 96 | } 97 | 98 | private fun handleBeginTraceRequest(beginTraceRequest: BeginTraceRequest): MotionToolsResponse = 99 | MotionToolsResponse.newBuilder().apply { 100 | tryCatchingMotionToolManagerExceptions { 101 | setBeginTrace(BeginTraceResponse.newBuilder().setTraceId( 102 | motionToolManager.beginTrace(beginTraceRequest.window.rootWindow))) 103 | } 104 | }.build() 105 | 106 | private fun handlePollTraceRequest(pollTraceRequest: PollTraceRequest): MotionToolsResponse = 107 | MotionToolsResponse.newBuilder().apply { 108 | tryCatchingMotionToolManagerExceptions { 109 | setPollTrace(PollTraceResponse.newBuilder() 110 | .setExportedData(motionToolManager.pollTrace(pollTraceRequest.traceId))) 111 | } 112 | }.build() 113 | 114 | private fun handleEndTraceRequest(endTraceRequest: EndTraceRequest): MotionToolsResponse = 115 | MotionToolsResponse.newBuilder().apply { 116 | tryCatchingMotionToolManagerExceptions { 117 | setEndTrace(EndTraceResponse.newBuilder() 118 | .setExportedData(motionToolManager.endTrace(endTraceRequest.traceId))) 119 | } 120 | }.build() 121 | 122 | private fun handleHandshakeRequest(handshakeRequest: HandshakeRequest): MotionToolsResponse { 123 | val status = if (motionToolManager.hasWindow(handshakeRequest.window)) 124 | HandshakeResponse.Status.OK 125 | else 126 | HandshakeResponse.Status.WINDOW_NOT_FOUND 127 | 128 | return MotionToolsResponse.newBuilder() 129 | .setHandshake(HandshakeResponse.newBuilder() 130 | .setServerVersion(SERVER_VERSION) 131 | .setStatus(status)) 132 | .build() 133 | } 134 | 135 | /** 136 | * Executes the [block] and catches all Exceptions thrown by [MotionToolManager]. In case of an 137 | * exception being caught, the error response field of the [MotionToolsResponse] is being set 138 | * with the according [ErrorResponse]. 139 | */ 140 | private fun MotionToolsResponse.Builder.tryCatchingMotionToolManagerExceptions(block: () -> Unit) { 141 | try { 142 | block() 143 | } catch (e: UnknownTraceIdException) { 144 | setError(createUnknownTraceIdResponse(e.traceId)) 145 | } catch (e: WindowNotFoundException) { 146 | setError(createWindowNotFoundResponse(e.windowId)) 147 | } 148 | } 149 | 150 | private fun createUnknownTraceIdResponse(traceId: Int) = 151 | ErrorResponse.newBuilder().apply { 152 | this.code = ErrorResponse.Code.UNKNOWN_TRACE_ID 153 | this.message = "No running Trace found with traceId $traceId" 154 | } 155 | 156 | private fun createWindowNotFoundResponse(windowId: String) = 157 | ErrorResponse.newBuilder().apply { 158 | this.code = ErrorResponse.Code.WINDOW_NOT_FOUND 159 | this.message = "No window found with windowId $windowId" 160 | } 161 | 162 | override fun onConnected() {} 163 | 164 | override fun onDisconnected() {} 165 | } 166 | -------------------------------------------------------------------------------- /iconloaderlib/src/com/android/launcher3/icons/ShadowGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.launcher3.icons; 18 | 19 | import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound; 20 | 21 | import android.graphics.Bitmap; 22 | import android.graphics.BlurMaskFilter; 23 | import android.graphics.BlurMaskFilter.Blur; 24 | import android.graphics.Canvas; 25 | import android.graphics.Color; 26 | import android.graphics.Paint; 27 | import android.graphics.Path; 28 | import android.graphics.PorterDuff; 29 | import android.graphics.PorterDuffXfermode; 30 | import android.graphics.RectF; 31 | 32 | /** 33 | * Utility class to add shadows to bitmaps. 34 | */ 35 | public class ShadowGenerator { 36 | 37 | public static final boolean ENABLE_SHADOWS = true; 38 | 39 | public static final float BLUR_FACTOR = 1.68f/48; 40 | 41 | // Percent of actual icon size 42 | public static final float KEY_SHADOW_DISTANCE = 1f/48; 43 | private static final int KEY_SHADOW_ALPHA = 7; 44 | // Percent of actual icon size 45 | private static final float HALF_DISTANCE = 0.5f; 46 | private static final int AMBIENT_SHADOW_ALPHA = 25; 47 | 48 | private final int mIconSize; 49 | 50 | private final Paint mBlurPaint; 51 | private final Paint mDrawPaint; 52 | private final BlurMaskFilter mDefaultBlurMaskFilter; 53 | 54 | public ShadowGenerator(int iconSize) { 55 | mIconSize = iconSize; 56 | mBlurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); 57 | mDrawPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); 58 | mDefaultBlurMaskFilter = new BlurMaskFilter(mIconSize * BLUR_FACTOR, Blur.NORMAL); 59 | } 60 | 61 | public synchronized void drawShadow(Bitmap icon, Canvas out) { 62 | if (ENABLE_SHADOWS) { 63 | int[] offset = new int[2]; 64 | mBlurPaint.setMaskFilter(mDefaultBlurMaskFilter); 65 | Bitmap shadow = icon.extractAlpha(mBlurPaint, offset); 66 | 67 | // Draw ambient shadow 68 | mDrawPaint.setAlpha(AMBIENT_SHADOW_ALPHA); 69 | out.drawBitmap(shadow, offset[0], offset[1], mDrawPaint); 70 | 71 | // Draw key shadow 72 | mDrawPaint.setAlpha(KEY_SHADOW_ALPHA); 73 | out.drawBitmap(shadow, offset[0], offset[1] + KEY_SHADOW_DISTANCE * mIconSize, 74 | mDrawPaint); 75 | } 76 | } 77 | 78 | /** package private **/ 79 | void addPathShadow(Path path, Canvas out) { 80 | if (ENABLE_SHADOWS) { 81 | mDrawPaint.setMaskFilter(mDefaultBlurMaskFilter); 82 | 83 | // Draw ambient shadow 84 | mDrawPaint.setAlpha(AMBIENT_SHADOW_ALPHA); 85 | out.drawPath(path, mDrawPaint); 86 | 87 | // Draw key shadow 88 | int save = out.save(); 89 | mDrawPaint.setAlpha(KEY_SHADOW_ALPHA); 90 | out.translate(0, KEY_SHADOW_DISTANCE * mIconSize); 91 | out.drawPath(path, mDrawPaint); 92 | out.restoreToCount(save); 93 | 94 | mDrawPaint.setMaskFilter(null); 95 | } 96 | } 97 | 98 | /** 99 | * Returns the minimum amount by which an icon with {@param bounds} should be scaled 100 | * so that the shadows do not get clipped. 101 | */ 102 | public static float getScaleForBounds(RectF bounds) { 103 | float scale = 1; 104 | 105 | if (ENABLE_SHADOWS) { 106 | // For top, left & right, we need same space. 107 | float minSide = Math.min(Math.min(bounds.left, bounds.right), bounds.top); 108 | if (minSide < BLUR_FACTOR) { 109 | scale = (HALF_DISTANCE - BLUR_FACTOR) / (HALF_DISTANCE - minSide); 110 | } 111 | 112 | float bottomSpace = BLUR_FACTOR + KEY_SHADOW_DISTANCE; 113 | if (bounds.bottom < bottomSpace) { 114 | scale = Math.min(scale, 115 | (HALF_DISTANCE - bottomSpace) / (HALF_DISTANCE - bounds.bottom)); 116 | } 117 | } 118 | return scale; 119 | } 120 | 121 | public static class Builder { 122 | 123 | public final RectF bounds = new RectF(); 124 | public final int color; 125 | 126 | public int ambientShadowAlpha = AMBIENT_SHADOW_ALPHA; 127 | 128 | public float shadowBlur; 129 | 130 | public float keyShadowDistance; 131 | public int keyShadowAlpha = KEY_SHADOW_ALPHA; 132 | public float radius; 133 | 134 | public Builder(int color) { 135 | this.color = color; 136 | } 137 | 138 | public Builder setupBlurForSize(int height) { 139 | if (ENABLE_SHADOWS) { 140 | shadowBlur = height * 1f / 24; 141 | keyShadowDistance = height * 1f / 16; 142 | } else { 143 | shadowBlur = 0; 144 | keyShadowDistance = 0; 145 | } 146 | return this; 147 | } 148 | 149 | public Bitmap createPill(int width, int height) { 150 | return createPill(width, height, height / 2f); 151 | } 152 | 153 | public Bitmap createPill(int width, int height, float r) { 154 | radius = r; 155 | 156 | int centerX = Math.round(width / 2f + shadowBlur); 157 | int centerY = Math.round(radius + shadowBlur + keyShadowDistance); 158 | int center = Math.max(centerX, centerY); 159 | bounds.set(0, 0, width, height); 160 | bounds.offsetTo(center - width / 2f, center - height / 2f); 161 | 162 | int size = center * 2; 163 | return BitmapRenderer.createHardwareBitmap(size, size, this::drawShadow); 164 | } 165 | 166 | public void drawShadow(Canvas c) { 167 | Paint p = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); 168 | p.setColor(color); 169 | 170 | if (ENABLE_SHADOWS) { 171 | // Key shadow 172 | p.setShadowLayer(shadowBlur, 0, keyShadowDistance, 173 | setColorAlphaBound(Color.BLACK, keyShadowAlpha)); 174 | c.drawRoundRect(bounds, radius, radius, p); 175 | 176 | // Ambient shadow 177 | p.setShadowLayer(shadowBlur, 0, 0, 178 | setColorAlphaBound(Color.BLACK, ambientShadowAlpha)); 179 | c.drawRoundRect(bounds, radius, radius, p); 180 | } 181 | 182 | if (Color.alpha(color) < 255) { 183 | // Clear any content inside the pill-rect for translucent fill. 184 | p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); 185 | p.clearShadowLayer(); 186 | p.setColor(Color.BLACK); 187 | c.drawRoundRect(bounds, radius, radius, p); 188 | 189 | p.setXfermode(null); 190 | p.setColor(color); 191 | c.drawRoundRect(bounds, radius, radius, p); 192 | } 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.android.launcher3.icons; 17 | 18 | import android.content.Context; 19 | import android.graphics.Bitmap; 20 | import android.graphics.Bitmap.Config; 21 | import android.graphics.Canvas; 22 | import android.graphics.drawable.Drawable; 23 | import android.os.UserHandle; 24 | 25 | import androidx.annotation.IntDef; 26 | import androidx.annotation.NonNull; 27 | import androidx.annotation.Nullable; 28 | 29 | import com.android.launcher3.util.FlagOp; 30 | 31 | public class BitmapInfo { 32 | 33 | static final int FLAG_INSTANT = 1 << 1; 34 | static final int FLAG_CLONE = 1 << 2; 35 | @IntDef(flag = true, value = { 36 | FLAG_INSTANT, 37 | FLAG_CLONE 38 | }) 39 | @interface BitmapInfoFlags {} 40 | 41 | public static final int FLAG_THEMED = 1 << 0; 42 | public static final int FLAG_NO_BADGE = 1 << 1; 43 | @IntDef(flag = true, value = { 44 | FLAG_THEMED, 45 | FLAG_NO_BADGE, 46 | }) 47 | public @interface DrawableCreationFlags {} 48 | 49 | public static final Bitmap LOW_RES_ICON = Bitmap.createBitmap(1, 1, Config.ALPHA_8); 50 | public static final BitmapInfo LOW_RES_INFO = fromBitmap(LOW_RES_ICON); 51 | 52 | public static final String TAG = "BitmapInfo"; 53 | 54 | public final Bitmap icon; 55 | public final int color; 56 | 57 | @Nullable 58 | protected Bitmap mMono; 59 | protected Bitmap mWhiteShadowLayer; 60 | 61 | public @BitmapInfoFlags int flags; 62 | private BitmapInfo badgeInfo; 63 | 64 | @Nullable 65 | private UserHandle mUserHandle; 66 | 67 | @Nullable 68 | private Drawable mUserBadge; 69 | 70 | public BitmapInfo(Bitmap icon, int color) { 71 | this.icon = icon; 72 | this.color = color; 73 | } 74 | 75 | public BitmapInfo withBadgeInfo(BitmapInfo badgeInfo) { 76 | BitmapInfo result = clone(); 77 | result.badgeInfo = badgeInfo; 78 | return result; 79 | } 80 | 81 | /** 82 | * Returns a bitmapInfo with the flagOP applied 83 | */ 84 | public BitmapInfo withFlags(@NonNull FlagOp op) { 85 | if (op == FlagOp.NO_OP) { 86 | return this; 87 | } 88 | BitmapInfo result = clone(); 89 | result.flags = op.apply(result.flags); 90 | return result; 91 | } 92 | 93 | public BitmapInfo withUser(@Nullable UserHandle user, @NonNull BaseIconFactory iconFactory) { 94 | BitmapInfo result = clone(); 95 | result.setUser(user, iconFactory); 96 | return result; 97 | } 98 | 99 | protected BitmapInfo copyInternalsTo(BitmapInfo target) { 100 | target.mMono = mMono; 101 | target.mWhiteShadowLayer = mWhiteShadowLayer; 102 | target.flags = flags; 103 | target.badgeInfo = badgeInfo; 104 | target.mUserHandle = mUserHandle; 105 | target.mUserBadge = mUserBadge; 106 | return target; 107 | } 108 | 109 | @Override 110 | public BitmapInfo clone() { 111 | return copyInternalsTo(new BitmapInfo(icon, color)); 112 | } 113 | 114 | public void setMonoIcon(Bitmap mono, BaseIconFactory iconFactory) { 115 | mMono = mono; 116 | mWhiteShadowLayer = iconFactory.getWhiteShadowLayer(); 117 | } 118 | 119 | public void setUser(@Nullable UserHandle user, @NonNull BaseIconFactory iconFactory) { 120 | mUserHandle = user; 121 | if (user != null) { 122 | mUserBadge = iconFactory.getBadgeForUser(mUserHandle); 123 | /* if (isNullOrLowRes()) { 124 | mUserBadge = iconFactory.getBadgeForUser(mUserHandle); 125 | } else { 126 | mUserBadge = iconFactory.getBadgeForUser(mUserHandle, icon.getWidth()); 127 | } */ 128 | } else { 129 | mUserBadge = null; 130 | } 131 | } 132 | 133 | /** 134 | * Ideally icon should not be null, except in cases when generating hardware bitmap failed 135 | */ 136 | public final boolean isNullOrLowRes() { 137 | return icon == null || icon == LOW_RES_ICON; 138 | } 139 | 140 | public final boolean isLowRes() { 141 | return LOW_RES_ICON == icon; 142 | } 143 | 144 | /** 145 | * BitmapInfo can be stored on disk or other persistent storage 146 | */ 147 | public boolean canPersist() { 148 | return !isNullOrLowRes(); 149 | } 150 | 151 | public Bitmap getMono() { 152 | return mMono; 153 | } 154 | 155 | public UserHandle getUser() { return mUserHandle; } 156 | 157 | /** 158 | * Creates a drawable for the provided BitmapInfo 159 | */ 160 | public FastBitmapDrawable newIcon(Context context) { 161 | return newIcon(context, 0); 162 | } 163 | 164 | /** 165 | * Creates a drawable for the provided BitmapInfo 166 | */ 167 | public FastBitmapDrawable newIcon(Context context, @DrawableCreationFlags int creationFlags) { 168 | FastBitmapDrawable drawable; 169 | if (isLowRes()) { 170 | drawable = new PlaceHolderIconDrawable(this, context); 171 | } else if ((creationFlags & FLAG_THEMED) != 0 && mMono != null) { 172 | drawable = ThemedIconDrawable.newDrawable(this, context); 173 | } else { 174 | drawable = new FastBitmapDrawable(this); 175 | } 176 | applyFlags(context, drawable, creationFlags); 177 | return drawable; 178 | } 179 | 180 | protected void applyFlags(Context context, FastBitmapDrawable drawable, 181 | @DrawableCreationFlags int creationFlags) { 182 | drawable.mDisabledAlpha = GraphicsUtils.getFloat(context, R.attr.disabledIconAlpha, 1f); 183 | if ((creationFlags & FLAG_NO_BADGE) == 0) { 184 | if (badgeInfo != null) { 185 | drawable.setBadge(badgeInfo.newIcon(context, creationFlags)); 186 | } else if ((flags & FLAG_INSTANT) != 0) { 187 | drawable.setBadge(context.getDrawable(R.drawable.ic_instant_app_badge)); 188 | } else if (mUserBadge != null) { 189 | // We use a copy of the badge, or changes will affect everywhere it is used; 190 | // e.g., shortcuts/widget user badges are very small, and these could affect 191 | // regular launcher icons, and the other way around. 192 | drawable.setBadge(mUserBadge.getConstantState().newDrawable().mutate()); 193 | } else if ((flags & FLAG_CLONE) != 0) { 194 | drawable.setBadge(context.getDrawable(R.drawable.ic_clone_app_badge)); 195 | } 196 | } 197 | } 198 | 199 | public static BitmapInfo fromBitmap(@NonNull Bitmap bitmap) { 200 | return of(bitmap, 0); 201 | } 202 | 203 | public static BitmapInfo of(@NonNull Bitmap bitmap, int color) { 204 | return new BitmapInfo(bitmap, color); 205 | } 206 | 207 | /** 208 | * Interface to be implemented by drawables to provide a custom BitmapInfo 209 | */ 210 | public interface Extender { 211 | 212 | /** 213 | * Called for creating a custom BitmapInfo 214 | */ 215 | BitmapInfo getExtendedInfo(Bitmap bitmap, int color, 216 | BaseIconFactory iconFactory, float normalizationScale); 217 | 218 | /** 219 | * Called to draw the UI independent of any runtime configurations like time or theme 220 | */ 221 | void drawForPersistence(Canvas canvas); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /motiontoollib/tests/com/android/app/motiontool/DdmHandleMotionToolTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.app.motiontool 18 | 19 | import android.content.Intent 20 | import android.testing.AndroidTestingRunner 21 | import android.view.Choreographer 22 | import android.view.View 23 | import android.view.WindowManagerGlobal 24 | import androidx.test.ext.junit.rules.ActivityScenarioRule 25 | import androidx.test.filters.SmallTest 26 | import androidx.test.platform.app.InstrumentationRegistry 27 | import com.android.app.motiontool.DdmHandleMotionTool.Companion.CHUNK_MOTO 28 | import com.android.app.motiontool.util.TestActivity 29 | import junit.framework.Assert 30 | import junit.framework.Assert.assertEquals 31 | import org.apache.harmony.dalvik.ddmc.Chunk 32 | import org.apache.harmony.dalvik.ddmc.ChunkHandler.wrapChunk 33 | import org.junit.After 34 | import org.junit.Before 35 | import org.junit.Rule 36 | import org.junit.Test 37 | import org.junit.runner.RunWith 38 | 39 | @SmallTest 40 | @RunWith(AndroidTestingRunner::class) 41 | class DdmHandleMotionToolTest { 42 | 43 | private val windowManagerGlobal = WindowManagerGlobal.getInstance() 44 | private val motionToolManager = MotionToolManager.getInstance(windowManagerGlobal) 45 | private val ddmHandleMotionTool = DdmHandleMotionTool.getInstance(motionToolManager) 46 | private val CLIENT_VERSION = 1 47 | 48 | private val activityIntent = 49 | Intent(InstrumentationRegistry.getInstrumentation().context, TestActivity::class.java) 50 | 51 | @get:Rule 52 | val activityScenarioRule = ActivityScenarioRule(activityIntent) 53 | 54 | @Before 55 | fun setup() { 56 | ddmHandleMotionTool.register() 57 | } 58 | 59 | @After 60 | fun cleanup() { 61 | ddmHandleMotionTool.unregister() 62 | } 63 | 64 | @Test 65 | fun testHandshakeErrorWithInvalidWindowId() { 66 | val handshakeResponse = performHandshakeRequest("InvalidWindowId") 67 | assertEquals(HandshakeResponse.Status.WINDOW_NOT_FOUND, handshakeResponse.handshake.status) 68 | } 69 | 70 | @Test 71 | fun testHandshakeOkWithValidWindowId() { 72 | val handshakeResponse = performHandshakeRequest(getActivityViewRootId()) 73 | assertEquals(HandshakeResponse.Status.OK, handshakeResponse.handshake.status) 74 | } 75 | 76 | @Test 77 | fun testBeginFailsWithInvalidWindowId() { 78 | val errorResponse = performBeginTraceRequest("InvalidWindowId") 79 | assertEquals(ErrorResponse.Code.WINDOW_NOT_FOUND, errorResponse.error.code) 80 | } 81 | 82 | @Test 83 | fun testEndTraceFailsWithoutPrecedingBeginTrace() { 84 | val errorResponse = performEndTraceRequest(0) 85 | assertEquals(ErrorResponse.Code.UNKNOWN_TRACE_ID, errorResponse.error.code) 86 | } 87 | 88 | @Test 89 | fun testPollTraceFailsWithoutPrecedingBeginTrace() { 90 | val errorResponse = performPollTraceRequest(0) 91 | assertEquals(ErrorResponse.Code.UNKNOWN_TRACE_ID, errorResponse.error.code) 92 | } 93 | 94 | @Test 95 | fun testEndTraceFailsWithInvalidTraceId() { 96 | val beginTraceResponse = performBeginTraceRequest(getActivityViewRootId()) 97 | val endTraceResponse = performEndTraceRequest(beginTraceResponse.beginTrace.traceId + 1) 98 | assertEquals(ErrorResponse.Code.UNKNOWN_TRACE_ID, endTraceResponse.error.code) 99 | } 100 | 101 | @Test 102 | fun testPollTraceFailsWithInvalidTraceId() { 103 | val beginTraceResponse = performBeginTraceRequest(getActivityViewRootId()) 104 | val endTraceResponse = performPollTraceRequest(beginTraceResponse.beginTrace.traceId + 1) 105 | assertEquals(ErrorResponse.Code.UNKNOWN_TRACE_ID, endTraceResponse.error.code) 106 | } 107 | 108 | @Test 109 | fun testMalformedRequestFails() { 110 | val requestBytes = ByteArray(9) 111 | val requestChunk = Chunk(CHUNK_MOTO, requestBytes, 0, requestBytes.size) 112 | val responseChunk = ddmHandleMotionTool.handleChunk(requestChunk) 113 | val response = MotionToolsResponse.parseFrom(wrapChunk(responseChunk).array()).error 114 | assertEquals(ErrorResponse.Code.INVALID_REQUEST, response.code) 115 | } 116 | 117 | @Test 118 | fun testNoOnDrawCallReturnsEmptyTrace() { 119 | activityScenarioRule.scenario.onActivity { 120 | val beginTraceResponse = performBeginTraceRequest(getActivityViewRootId()) 121 | val endTraceResponse = performEndTraceRequest(beginTraceResponse.beginTrace.traceId) 122 | Assert.assertTrue(endTraceResponse.endTrace.exportedData.frameDataList.isEmpty()) 123 | } 124 | } 125 | 126 | @Test 127 | fun testOneOnDrawCallReturnsOneFrameResponse() { 128 | activityScenarioRule.scenario.onActivity { activity -> 129 | val beginTraceResponse = performBeginTraceRequest(getActivityViewRootId()) 130 | val traceId = beginTraceResponse.beginTrace.traceId 131 | 132 | Choreographer.getInstance().postFrameCallback { 133 | activity.findViewById(android.R.id.content).viewTreeObserver.dispatchOnDraw() 134 | 135 | val pollTraceResponse = performPollTraceRequest(traceId) 136 | assertEquals(1, pollTraceResponse.pollTrace.exportedData.frameDataList.size) 137 | 138 | // Verify that frameData is only included once and is not returned again 139 | val endTraceResponse = performEndTraceRequest(traceId) 140 | assertEquals(0, endTraceResponse.endTrace.exportedData.frameDataList.size) 141 | } 142 | } 143 | } 144 | 145 | private fun performPollTraceRequest(requestTraceId: Int): MotionToolsResponse { 146 | val pollTraceRequest = MotionToolsRequest.newBuilder() 147 | .setPollTrace(PollTraceRequest.newBuilder() 148 | .setTraceId(requestTraceId)) 149 | .build() 150 | return performRequest(pollTraceRequest) 151 | } 152 | 153 | private fun performEndTraceRequest(requestTraceId: Int): MotionToolsResponse { 154 | val endTraceRequest = MotionToolsRequest.newBuilder() 155 | .setEndTrace(EndTraceRequest.newBuilder() 156 | .setTraceId(requestTraceId)) 157 | .build() 158 | return performRequest(endTraceRequest) 159 | } 160 | 161 | private fun performBeginTraceRequest(windowId: String): MotionToolsResponse { 162 | val beginTraceRequest = MotionToolsRequest.newBuilder() 163 | .setBeginTrace(BeginTraceRequest.newBuilder() 164 | .setWindow(WindowIdentifier.newBuilder() 165 | .setRootWindow(windowId))) 166 | .build() 167 | return performRequest(beginTraceRequest) 168 | } 169 | 170 | private fun performHandshakeRequest(windowId: String): MotionToolsResponse { 171 | val handshakeRequest = MotionToolsRequest.newBuilder() 172 | .setHandshake(HandshakeRequest.newBuilder() 173 | .setWindow(WindowIdentifier.newBuilder() 174 | .setRootWindow(windowId)) 175 | .setClientVersion(CLIENT_VERSION)) 176 | .build() 177 | return performRequest(handshakeRequest) 178 | } 179 | 180 | private fun performRequest(motionToolsRequest: MotionToolsRequest): MotionToolsResponse { 181 | val requestBytes = motionToolsRequest.toByteArray() 182 | val requestChunk = Chunk(CHUNK_MOTO, requestBytes, 0, requestBytes.size) 183 | val responseChunk = ddmHandleMotionTool.handleChunk(requestChunk) 184 | return MotionToolsResponse.parseFrom(wrapChunk(responseChunk).array()) 185 | } 186 | 187 | private fun getActivityViewRootId(): String { 188 | var activityViewRootId = "" 189 | activityScenarioRule.scenario.onActivity { 190 | activityViewRootId = WindowManagerGlobal.getInstance().viewRootNames.first() 191 | } 192 | return activityViewRootId 193 | } 194 | 195 | } 196 | -------------------------------------------------------------------------------- /searchuilib/src/com/android/app/search/SearchTargetExtras.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.app.search; 18 | 19 | import static com.android.app.search.LayoutType.TALL_CARD_WITH_IMAGE_NO_ICON; 20 | 21 | import android.app.blob.BlobHandle; 22 | import android.app.search.SearchAction; 23 | import android.app.search.SearchTarget; 24 | import android.os.Bundle; 25 | import android.text.TextUtils; 26 | 27 | import androidx.annotation.Nullable; 28 | 29 | /** 30 | * Helper class that defines key string value for {@link SearchTarget#getExtras()} 31 | * and also defines helper methods 32 | */ 33 | public class SearchTargetExtras { 34 | 35 | /** on device data related extras and helper methods */ 36 | // Used to extra component name. 37 | public static final String BUNDLE_EXTRA_CLASS = "class"; 38 | 39 | // Used for UI treatment. Labels whether search target should support quick launch. 40 | public static final String BUNDLE_EXTRA_QUICK_LAUNCH = "quick_launch"; 41 | // Used for UI treatment. Targets grouped with same group id are decorated together. 42 | public static final String BUNDLE_EXTRA_GROUP_ID = "group_id"; 43 | public static final String BUNDLE_EXTRA_GROUP_DECORATE_TOGETHER = "decorate_together"; 44 | // Used if slice title should be rendered else where outside of slice (e.g., edit text). 45 | public static final String BUNDLE_EXTRA_SLICE_TITLE = "slice_title"; 46 | // Used if slice view should be rendered using full height mode. 47 | public static final String BUNDLE_EXTRA_USE_FULL_HEIGHT = "use_full_height"; 48 | public static final String BUNDLE_EXTRA_IS_NON_TAPPABLE = "is_non_tappable"; 49 | public static final String BUNDLE_EXTRA_TITLE_OVERWRITE = "title_overwrite"; 50 | // Used if subtitle view should be overridden to string that is not natively defined by the 51 | // search target. 52 | public static final String BUNDLE_EXTRA_SUBTITLE_OVERRIDE = "subtitle_override"; 53 | 54 | // Used for logging. Returns whether spelling correction was applied. 55 | public static final String BUNDLE_EXTRA_IS_QUERY_CORRECTED = "is_query_corrected"; 56 | // Used for logging. Returns whether the result matched block title or the inline item. 57 | public static final String BUNDLE_EXTRA_RESULT_MATCH_USER_TYPED = "result_match_user_typed"; 58 | // Used for logging. Returns the timestamp when system service received the data. 59 | public static final String BUNDLE_EXTRA_START_TIMESTAMP = "start_timestamp"; 60 | // Indicates the search result app location column. 61 | public static final String BUNDLE_EXTRA_RESULT_APP_GRIDX = "app_gridx"; 62 | 63 | // Used for thumbnail loading. Contains handle to retrieve Blobstore asset. 64 | public static final String BUNDLE_EXTRA_BLOBSTORE_HANDLE = "blobstore_handle_key"; 65 | 66 | // Used to denote this searchTarget is for recent block in 0-state. 67 | public static final String EXTRAS_RECENT_BLOCK_TARGET = "recent_block_target"; 68 | 69 | public static final int GROUPING = 1 << 1; 70 | 71 | @Nullable 72 | public static String getDecoratorId(@Nullable SearchTarget target) { 73 | return isTargetOrExtrasNull(target) ? null : 74 | target.getExtras().getString(BUNDLE_EXTRA_GROUP_ID); 75 | } 76 | 77 | public static int getDecoratorType(@Nullable SearchTarget target) { 78 | int type = 0; 79 | if (isTargetOrExtrasNull(target)) { 80 | return type; 81 | } 82 | if (!TextUtils.isEmpty(target.getExtras().getString(BUNDLE_EXTRA_GROUP_ID))) { 83 | type |= GROUPING; 84 | } 85 | return type; 86 | } 87 | 88 | /** Whether or not the SearchTarget's Extras contains a blobstore image. */ 89 | public static boolean isSearchTargetBlobstoreAsset(@Nullable SearchTarget target) { 90 | if (isTargetOrExtrasNull(target)) { 91 | return false; 92 | } 93 | return target.getExtras().getParcelable( 94 | BUNDLE_EXTRA_BLOBSTORE_HANDLE) instanceof BlobHandle; 95 | } 96 | 97 | /** Check if SearchTarget contains information to tell if this target is from recent block. */ 98 | public static boolean isSearchTargetRecentItem(@Nullable SearchTarget target) { 99 | if (isTargetOrExtrasNull(target)) { 100 | return false; 101 | } 102 | return target.getExtras().getBoolean(EXTRAS_RECENT_BLOCK_TARGET, false); 103 | } 104 | 105 | private static boolean isTargetOrExtrasNull(@Nullable SearchTarget target) { 106 | return target == null || target.getExtras() == null; 107 | } 108 | 109 | /** Web data related extras and helper methods */ 110 | public static final String BUNDLE_EXTRA_PROXY_WEB_ITEM = "proxy_web_item"; 111 | public static final String BUNDLE_EXTRA_ENTITY = "is_entity"; 112 | public static final String BUNDLE_EXTRA_ANSWER = "is_answer"; 113 | public static final String BUNDLE_EXTRA_RESPONSE_ID = "response_id"; 114 | public static final String BUNDLE_EXTRA_LEARN_MORE_URL = "learn_more_url"; 115 | public static final String BUNDLE_EXTRA_PERSONAL = "is_personal"; 116 | public static final String BUNDLE_EXTRA_SUGGESTION_TYPE = "suggestion_type"; 117 | public static final String BUNDLE_EXTRA_SUGGEST_RENDER_TEXT = "suggest_render_text"; 118 | public static final String BUNDLE_EXTRA_ZERO_STATE_CACHE = "zero_state_cache"; 119 | public static final String BUNDLE_EXTRA_TALL_CARD_HEADER = "tall_card_header"; 120 | public static final String BUNDLE_EXTRA_TALL_CARD_IMAGE_DESCRIPTION = 121 | "tall_card_image_description"; 122 | public static final String BUNDLE_EXTRA_BITMAP_URL = "bitmap_url"; 123 | 124 | // Used for web suggestions count for both AA+ and QSB entry point. 125 | // Returns the number of web suggestions to be shown. 126 | public static final String WEB_SUG_COUNT = "web_sug_count"; 127 | 128 | /** 129 | * Replaced with thumbnail crop type 130 | * 131 | * Flag to control whether thumbnail(s) should fill the thumbnail container's width or not. 132 | * When this flag is true, when there are less than the maximum number of thumbnails in the 133 | * container, the thumbnails will stretch to fill the container's width. 134 | * When this flag is false, thumbnails will always be cropped to a square ratio even if 135 | * there aren't enough thumbnails to fill the container. 136 | * 137 | * Only relevant in {@link LayoutType#THUMBNAIL_CONTAINER} and {@link LayoutType#THUMBNAIL}. 138 | */ 139 | @Deprecated 140 | public static final String BUNDLE_EXTRA_SHOULD_FILL_CONTAINER_WIDTH = 141 | "should_fill_container_width"; 142 | 143 | /** 144 | * Flag to control thumbnail container's crop mode, controlling the layout 145 | * 146 | *
    147 | *
  • SQUARE: Thumbnail(s) will be cropped to a square aspect ratio around the center.
  • 148 | *
  • FILL_WIDTH: Thumbnail(s) should collectively fill the thumbnail container's width. 149 | * When there are less than the maximum number of thumbnails in the container, the 150 | * layouts' width will stretch to fit the container, the images will fill the width 151 | * and then the top/bottom cropped to fit.
  • 152 | *
  • FILL_HEIGHT: Thumbnail(s) should fill height and be cropped to fit in the width 153 | * based on {@link BUNDLE_EXTRA_THUMBNAIL_MAX_COUNT} as the column count. When the image 154 | * width is larger than the width / column, both sides will be cropped while maintaining 155 | * the center. 156 | * When there are less thumbnails than the max count, the layout will be constrained to 157 | * equally divide the width of the container. If there are more thumbnails than the max 158 | * count, the excessive thumbnails will be ignored.
  • 159 | *
160 | * 161 | * Only relevant in {@link LayoutType#THUMBNAIL_CONTAINER} and {@link LayoutType#THUMBNAIL}. 162 | */ 163 | public static final String BUNDLE_EXTRA_THUMBNAIL_CROP_TYPE = "thumbnail_crop_type"; 164 | public enum ThumbnailCropType { 165 | DEFAULT(0), // defaults to SQUARE behavior by {@link LayoutType#THUMBNAIL_CONTAINER}. 166 | SQUARE(1), 167 | FILL_WIDTH(2), 168 | FILL_HEIGHT(3); 169 | 170 | private final int mTypeId; 171 | 172 | ThumbnailCropType(int typeId) { 173 | mTypeId = typeId; 174 | } 175 | 176 | public int toTypeId() { 177 | return mTypeId; 178 | } 179 | }; 180 | 181 | /** 182 | * How many grid spaces for the thumbnail container should be reserved. 183 | * Only relevant for {@link ThumbnailCropType#FILL_HEIGHT} crop type. 184 | */ 185 | public static final String BUNDLE_EXTRA_THUMBNAIL_MAX_COUNT = "thumbnail_max_count"; 186 | 187 | /** 188 | * Flag to control whether the SearchTarget's label should be hidden. 189 | * When this flag is true, label will be hidden. 190 | * When this flag is false (or omitted), {@link SearchAction#mTitle} will be shown. 191 | */ 192 | public static final String BUNDLE_EXTRA_HIDE_LABEL = 193 | "hide_label"; 194 | public static final String BUNDLE_EXTRA_SUGGESTION_ACTION_TEXT = "suggestion_action_text"; 195 | public static final String BUNDLE_EXTRA_SUGGESTION_ACTION_RPC = "suggestion_action_rpc"; 196 | public static final String BUNDLE_EXTRA_SUPPORT_QUERY_BUILDER = "support_query_builder"; 197 | public static final String BUNDLE_EXTRA_SUGGEST_RAW_TEXT = "suggest_raw_text"; 198 | public static final String BUNDLE_EXTRA_SUGGEST_TRUNCATE_START = "suggest_truncate_start"; 199 | 200 | /** Web data related helper methods */ 201 | public static boolean isEntity(@Nullable SearchTarget target) { 202 | return target != null && target.getExtras() != null 203 | && target.getExtras().getBoolean(BUNDLE_EXTRA_ENTITY); 204 | } 205 | 206 | public static boolean isAnswer(@Nullable SearchTarget target) { 207 | return target != null && target.getExtras() != null 208 | && target.getExtras().getBoolean(BUNDLE_EXTRA_ANSWER); 209 | } 210 | 211 | /** Whether the search target is a rich answer web result. */ 212 | public static boolean isRichAnswer(@Nullable SearchTarget target) { 213 | return target != null && isAnswer(target) 214 | && target.getLayoutType().equals(TALL_CARD_WITH_IMAGE_NO_ICON); 215 | } 216 | 217 | /** Get the crop type thumbnails should use. Returns DEFAULT if not specified. */ 218 | public static ThumbnailCropType getThumbnailCropType(@Nullable SearchTarget target) 219 | throws ArrayIndexOutOfBoundsException { 220 | Bundle extras = target == null ? Bundle.EMPTY : target.getExtras(); 221 | if (extras.isEmpty()) { 222 | return ThumbnailCropType.DEFAULT; 223 | } 224 | ThumbnailCropType cropType = ThumbnailCropType.values()[extras.getInt( 225 | BUNDLE_EXTRA_THUMBNAIL_CROP_TYPE)]; 226 | return cropType != null ? cropType : ThumbnailCropType.DEFAULT; 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.android.launcher3.icons.cache; 17 | 18 | import android.content.ComponentName; 19 | import android.content.pm.ApplicationInfo; 20 | import android.content.pm.PackageInfo; 21 | import android.content.pm.PackageManager; 22 | import android.database.Cursor; 23 | import android.database.sqlite.SQLiteException; 24 | import android.os.SystemClock; 25 | import android.os.UserHandle; 26 | import android.text.TextUtils; 27 | import android.util.ArrayMap; 28 | import android.util.Log; 29 | import android.util.SparseBooleanArray; 30 | 31 | import com.android.launcher3.icons.cache.BaseIconCache.IconDB; 32 | 33 | import java.util.Collections; 34 | import java.util.HashMap; 35 | import java.util.HashSet; 36 | import java.util.List; 37 | import java.util.Map.Entry; 38 | import java.util.Set; 39 | import java.util.Stack; 40 | 41 | /** 42 | * Utility class to handle updating the Icon cache 43 | */ 44 | public class IconCacheUpdateHandler { 45 | 46 | private static final String TAG = "IconCacheUpdateHandler"; 47 | 48 | /** 49 | * In this mode, all invalid icons are marked as to-be-deleted in {@link #mItemsToDelete}. 50 | * This mode is used for the first run. 51 | */ 52 | private static final boolean MODE_SET_INVALID_ITEMS = true; 53 | 54 | /** 55 | * In this mode, any valid icon is removed from {@link #mItemsToDelete}. This is used for all 56 | * subsequent runs, which essentially acts as set-union of all valid items. 57 | */ 58 | private static final boolean MODE_CLEAR_VALID_ITEMS = false; 59 | 60 | private static final Object ICON_UPDATE_TOKEN = new Object(); 61 | 62 | private final HashMap mPkgInfoMap; 63 | private final BaseIconCache mIconCache; 64 | 65 | private final ArrayMap> mPackagesToIgnore = new ArrayMap<>(); 66 | 67 | private final SparseBooleanArray mItemsToDelete = new SparseBooleanArray(); 68 | private boolean mFilterMode = MODE_SET_INVALID_ITEMS; 69 | 70 | IconCacheUpdateHandler(BaseIconCache cache) { 71 | mIconCache = cache; 72 | 73 | mPkgInfoMap = new HashMap<>(); 74 | 75 | // Remove all active icon update tasks. 76 | mIconCache.mWorkerHandler.removeCallbacksAndMessages(ICON_UPDATE_TOKEN); 77 | 78 | createPackageInfoMap(); 79 | } 80 | 81 | /** 82 | * Sets a package to ignore for processing 83 | */ 84 | public void addPackagesToIgnore(UserHandle userHandle, String packageName) { 85 | Set packages = mPackagesToIgnore.get(userHandle); 86 | if (packages == null) { 87 | packages = new HashSet<>(); 88 | mPackagesToIgnore.put(userHandle, packages); 89 | } 90 | packages.add(packageName); 91 | } 92 | 93 | private void createPackageInfoMap() { 94 | PackageManager pm = mIconCache.mPackageManager; 95 | for (PackageInfo info : 96 | pm.getInstalledPackages(PackageManager.MATCH_UNINSTALLED_PACKAGES)) { 97 | mPkgInfoMap.put(info.packageName, info); 98 | } 99 | } 100 | 101 | /** 102 | * Updates the persistent DB, such that only entries corresponding to {@param apps} remain in 103 | * the DB and are updated. 104 | * @return The set of packages for which icons have updated. 105 | */ 106 | public void updateIcons(List apps, CachingLogic cachingLogic, 107 | OnUpdateCallback onUpdateCallback) { 108 | // Filter the list per user 109 | HashMap> userComponentMap = new HashMap<>(); 110 | int count = apps.size(); 111 | for (int i = 0; i < count; i++) { 112 | T app = apps.get(i); 113 | UserHandle userHandle = cachingLogic.getUser(app); 114 | HashMap componentMap = userComponentMap.get(userHandle); 115 | if (componentMap == null) { 116 | componentMap = new HashMap<>(); 117 | userComponentMap.put(userHandle, componentMap); 118 | } 119 | componentMap.put(cachingLogic.getComponent(app), app); 120 | } 121 | 122 | for (Entry> entry : userComponentMap.entrySet()) { 123 | updateIconsPerUser(entry.getKey(), entry.getValue(), cachingLogic, onUpdateCallback); 124 | } 125 | 126 | // From now on, clear every valid item from the global valid map. 127 | mFilterMode = MODE_CLEAR_VALID_ITEMS; 128 | } 129 | 130 | /** 131 | * Updates the persistent DB, such that only entries corresponding to {@param apps} remain in 132 | * the DB and are updated. 133 | * @return The set of packages for which icons have updated. 134 | */ 135 | @SuppressWarnings("unchecked") 136 | private void updateIconsPerUser(UserHandle user, HashMap componentMap, 137 | CachingLogic cachingLogic, OnUpdateCallback onUpdateCallback) { 138 | Set ignorePackages = mPackagesToIgnore.get(user); 139 | if (ignorePackages == null) { 140 | ignorePackages = Collections.emptySet(); 141 | } 142 | long userSerial = mIconCache.getSerialNumberForUser(user); 143 | 144 | Stack appsToUpdate = new Stack<>(); 145 | 146 | try (Cursor c = mIconCache.mIconDb.query( 147 | new String[]{IconDB.COLUMN_ROWID, IconDB.COLUMN_COMPONENT, 148 | IconDB.COLUMN_LAST_UPDATED, IconDB.COLUMN_VERSION, 149 | IconDB.COLUMN_SYSTEM_STATE}, 150 | IconDB.COLUMN_USER + " = ? ", 151 | new String[]{Long.toString(userSerial)})) { 152 | 153 | final int indexComponent = c.getColumnIndex(IconDB.COLUMN_COMPONENT); 154 | final int indexLastUpdate = c.getColumnIndex(IconDB.COLUMN_LAST_UPDATED); 155 | final int indexVersion = c.getColumnIndex(IconDB.COLUMN_VERSION); 156 | final int rowIndex = c.getColumnIndex(IconDB.COLUMN_ROWID); 157 | final int systemStateIndex = c.getColumnIndex(IconDB.COLUMN_SYSTEM_STATE); 158 | 159 | while (c.moveToNext()) { 160 | String cn = c.getString(indexComponent); 161 | ComponentName component = ComponentName.unflattenFromString(cn); 162 | PackageInfo info = mPkgInfoMap.get(component.getPackageName()); 163 | 164 | int rowId = c.getInt(rowIndex); 165 | if (info == null) { 166 | if (!ignorePackages.contains(component.getPackageName())) { 167 | 168 | if (mFilterMode == MODE_SET_INVALID_ITEMS) { 169 | mIconCache.remove(component, user); 170 | mItemsToDelete.put(rowId, true); 171 | } 172 | } 173 | continue; 174 | } 175 | if ((info.applicationInfo.flags & ApplicationInfo.FLAG_IS_DATA_ONLY) != 0) { 176 | // Application is not present 177 | continue; 178 | } 179 | 180 | long updateTime = c.getLong(indexLastUpdate); 181 | int version = c.getInt(indexVersion); 182 | T app = componentMap.remove(component); 183 | if (version == info.versionCode 184 | && updateTime == cachingLogic.getLastUpdatedTime(app, info) 185 | && TextUtils.equals(c.getString(systemStateIndex), 186 | mIconCache.getIconSystemState(info.packageName))) { 187 | 188 | if (mFilterMode == MODE_CLEAR_VALID_ITEMS) { 189 | mItemsToDelete.put(rowId, false); 190 | } 191 | continue; 192 | } 193 | 194 | if (app == null) { 195 | if (mFilterMode == MODE_SET_INVALID_ITEMS) { 196 | mIconCache.remove(component, user); 197 | mItemsToDelete.put(rowId, true); 198 | } 199 | } else { 200 | appsToUpdate.add(app); 201 | } 202 | } 203 | } catch (SQLiteException e) { 204 | Log.d(TAG, "Error reading icon cache", e); 205 | // Continue updating whatever we have read so far 206 | } 207 | 208 | // Insert remaining apps. 209 | if (!componentMap.isEmpty() || !appsToUpdate.isEmpty()) { 210 | Stack appsToAdd = new Stack<>(); 211 | appsToAdd.addAll(componentMap.values()); 212 | new SerializedIconUpdateTask(userSerial, user, appsToAdd, appsToUpdate, cachingLogic, 213 | onUpdateCallback).scheduleNext(); 214 | } 215 | } 216 | 217 | /** 218 | * Commits all updates as part of the update handler to disk. Not more calls should be made 219 | * to this class after this. 220 | */ 221 | public void finish() { 222 | // Commit all deletes 223 | int deleteCount = 0; 224 | StringBuilder queryBuilder = new StringBuilder() 225 | .append(IconDB.COLUMN_ROWID) 226 | .append(" IN ("); 227 | 228 | int count = mItemsToDelete.size(); 229 | for (int i = 0; i < count; i++) { 230 | if (mItemsToDelete.valueAt(i)) { 231 | if (deleteCount > 0) { 232 | queryBuilder.append(", "); 233 | } 234 | queryBuilder.append(mItemsToDelete.keyAt(i)); 235 | deleteCount++; 236 | } 237 | } 238 | queryBuilder.append(')'); 239 | 240 | if (deleteCount > 0) { 241 | mIconCache.mIconDb.delete(queryBuilder.toString(), null); 242 | } 243 | } 244 | 245 | /** 246 | * A runnable that updates invalid icons and adds missing icons in the DB for the provided 247 | * LauncherActivityInfo list. Items are updated/added one at a time, so that the 248 | * worker thread doesn't get blocked. 249 | */ 250 | private class SerializedIconUpdateTask implements Runnable { 251 | private final long mUserSerial; 252 | private final UserHandle mUserHandle; 253 | private final Stack mAppsToAdd; 254 | private final Stack mAppsToUpdate; 255 | private final CachingLogic mCachingLogic; 256 | private final HashSet mUpdatedPackages = new HashSet<>(); 257 | private final OnUpdateCallback mOnUpdateCallback; 258 | 259 | SerializedIconUpdateTask(long userSerial, UserHandle userHandle, 260 | Stack appsToAdd, Stack appsToUpdate, CachingLogic cachingLogic, 261 | OnUpdateCallback onUpdateCallback) { 262 | mUserHandle = userHandle; 263 | mUserSerial = userSerial; 264 | mAppsToAdd = appsToAdd; 265 | mAppsToUpdate = appsToUpdate; 266 | mCachingLogic = cachingLogic; 267 | mOnUpdateCallback = onUpdateCallback; 268 | } 269 | 270 | @Override 271 | public void run() { 272 | if (!mAppsToUpdate.isEmpty()) { 273 | T app = mAppsToUpdate.pop(); 274 | String pkg = mCachingLogic.getComponent(app).getPackageName(); 275 | PackageInfo info = mPkgInfoMap.get(pkg); 276 | 277 | mIconCache.addIconToDBAndMemCache( 278 | app, mCachingLogic, info, mUserSerial, true /*replace existing*/); 279 | mUpdatedPackages.add(pkg); 280 | 281 | if (mAppsToUpdate.isEmpty() && !mUpdatedPackages.isEmpty()) { 282 | // No more app to update. Notify callback. 283 | mOnUpdateCallback.onPackageIconsUpdated(mUpdatedPackages, mUserHandle); 284 | } 285 | 286 | // Let it run one more time. 287 | scheduleNext(); 288 | } else if (!mAppsToAdd.isEmpty()) { 289 | T app = mAppsToAdd.pop(); 290 | PackageInfo info = mPkgInfoMap.get(mCachingLogic.getComponent(app).getPackageName()); 291 | // We do not check the mPkgInfoMap when generating the mAppsToAdd. Although every 292 | // app should have package info, this is not guaranteed by the api 293 | if (info != null) { 294 | mIconCache.addIconToDBAndMemCache(app, mCachingLogic, info, 295 | mUserSerial, false /*replace existing*/); 296 | } 297 | 298 | if (!mAppsToAdd.isEmpty()) { 299 | scheduleNext(); 300 | } 301 | } 302 | } 303 | 304 | public void scheduleNext() { 305 | mIconCache.mWorkerHandler.postAtTime(this, ICON_UPDATE_TOKEN, 306 | SystemClock.uptimeMillis() + 1); 307 | } 308 | } 309 | 310 | public interface OnUpdateCallback { 311 | 312 | void onPackageIconsUpdated(HashSet updatedPackages, UserHandle user); 313 | } 314 | } 315 | --------------------------------------------------------------------------------