summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--PermissionController/Android.bp2
-rw-r--r--PermissionController/iconloaderlib/.gitignore13
-rw-r--r--PermissionController/iconloaderlib/Android.bp52
-rw-r--r--PermissionController/iconloaderlib/AndroidManifest.xml20
-rw-r--r--PermissionController/iconloaderlib/build.gradle38
-rw-r--r--PermissionController/iconloaderlib/res/drawable-v26/adaptive_icon_drawable_wrapper.xml22
-rw-r--r--PermissionController/iconloaderlib/res/drawable/ic_instant_app_badge.xml39
-rw-r--r--PermissionController/iconloaderlib/res/values/attrs.xml23
-rw-r--r--PermissionController/iconloaderlib/res/values/colors.xml24
-rw-r--r--PermissionController/iconloaderlib/res/values/config.xml30
-rw-r--r--PermissionController/iconloaderlib/res/values/dimens.xml19
-rw-r--r--PermissionController/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java466
-rw-r--r--PermissionController/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java163
-rw-r--r--PermissionController/iconloaderlib/src/com/android/launcher3/icons/BitmapRenderer.java70
-rw-r--r--PermissionController/iconloaderlib/src/com/android/launcher3/icons/ClockDrawableWrapper.java448
-rw-r--r--PermissionController/iconloaderlib/src/com/android/launcher3/icons/ColorExtractor.java127
-rw-r--r--PermissionController/iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java143
-rw-r--r--PermissionController/iconloaderlib/src/com/android/launcher3/icons/FastBitmapDrawable.java314
-rw-r--r--PermissionController/iconloaderlib/src/com/android/launcher3/icons/FixedScaleDrawable.java53
-rw-r--r--PermissionController/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java127
-rw-r--r--PermissionController/iconloaderlib/src/com/android/launcher3/icons/IconNormalizer.java411
-rw-r--r--PermissionController/iconloaderlib/src/com/android/launcher3/icons/IconProvider.java377
-rw-r--r--PermissionController/iconloaderlib/src/com/android/launcher3/icons/PlaceHolderIconDrawable.java79
-rw-r--r--PermissionController/iconloaderlib/src/com/android/launcher3/icons/RoundDrawableWrapper.java56
-rw-r--r--PermissionController/iconloaderlib/src/com/android/launcher3/icons/ShadowGenerator.java183
-rw-r--r--PermissionController/iconloaderlib/src/com/android/launcher3/icons/ThemedIconDrawable.java296
-rw-r--r--PermissionController/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java570
-rw-r--r--PermissionController/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java65
-rw-r--r--PermissionController/iconloaderlib/src/com/android/launcher3/icons/cache/HandlerRunnable.java79
-rw-r--r--PermissionController/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java313
-rw-r--r--PermissionController/iconloaderlib/src/com/android/launcher3/util/ComponentKey.java84
-rw-r--r--PermissionController/iconloaderlib/src/com/android/launcher3/util/NoLocaleSQLiteHelper.java58
-rw-r--r--PermissionController/iconloaderlib/src/com/android/launcher3/util/SQLiteCacheHelper.java125
-rw-r--r--PermissionController/iconloaderlib/src/com/android/launcher3/util/SafeCloseable.java26
-rw-r--r--PermissionController/iconloaderlib/src_full_lib/com/android/launcher3/icons/IconFactory.java89
-rw-r--r--PermissionController/iconloaderlib/src_full_lib/com/android/launcher3/icons/SimpleIconCache.java114
-rw-r--r--PermissionController/tests/mocking/Android.bp2
37 files changed, 5118 insertions, 2 deletions
diff --git a/PermissionController/Android.bp b/PermissionController/Android.bp
index afd4658ae..c973ee8e0 100644
--- a/PermissionController/Android.bp
+++ b/PermissionController/Android.bp
@@ -92,7 +92,7 @@ android_app {
],
static_libs: [
- "iconloader",
+ "iconloader_sc_mainline_prod",
"com.google.android.material_material",
"androidx.transition_transition",
"androidx-constraintlayout_constraintlayout",
diff --git a/PermissionController/iconloaderlib/.gitignore b/PermissionController/iconloaderlib/.gitignore
new file mode 100644
index 000000000..6213826ab
--- /dev/null
+++ b/PermissionController/iconloaderlib/.gitignore
@@ -0,0 +1,13 @@
+*.iml
+.project
+.classpath
+.project.properties
+gen/
+bin/
+.idea/
+.gradle/
+local.properties
+gradle/
+build/
+gradlew*
+.DS_Store
diff --git a/PermissionController/iconloaderlib/Android.bp b/PermissionController/iconloaderlib/Android.bp
new file mode 100644
index 000000000..2bc3de435
--- /dev/null
+++ b/PermissionController/iconloaderlib/Android.bp
@@ -0,0 +1,52 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_library {
+ name: "iconloader_base_sc_mainline_prod",
+ sdk_version: "current",
+ min_sdk_version: "26",
+ static_libs: [
+ "androidx.core_core",
+ ],
+ resource_dirs: [
+ "res",
+ ],
+ srcs: [
+ "src/**/*.java",
+ ],
+}
+
+android_library {
+ name: "iconloader_sc_mainline_prod",
+ sdk_version: "system_current",
+ min_sdk_version: "26",
+ static_libs: [
+ "androidx.core_core",
+ ],
+ resource_dirs: [
+ "res",
+ ],
+ srcs: [
+ "src/**/*.java",
+ "src_full_lib/**/*.java",
+ ],
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.permission",
+ ],
+}
diff --git a/PermissionController/iconloaderlib/AndroidManifest.xml b/PermissionController/iconloaderlib/AndroidManifest.xml
new file mode 100644
index 000000000..b30258da2
--- /dev/null
+++ b/PermissionController/iconloaderlib/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.launcher3.icons">
+</manifest>
diff --git a/PermissionController/iconloaderlib/build.gradle b/PermissionController/iconloaderlib/build.gradle
new file mode 100644
index 000000000..84102758e
--- /dev/null
+++ b/PermissionController/iconloaderlib/build.gradle
@@ -0,0 +1,38 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion COMPILE_SDK
+ buildToolsVersion BUILD_TOOLS_VERSION
+
+ defaultConfig {
+ minSdkVersion 26
+ targetSdkVersion 28
+ versionCode 1
+ versionName "1.0"
+ }
+
+ sourceSets {
+ main {
+ java.srcDirs = ['src', 'src_full_lib']
+ manifest.srcFile 'AndroidManifest.xml'
+ res.srcDirs = ['res']
+ }
+ }
+
+ lintOptions {
+ abortOnError false
+ }
+
+ tasks.withType(JavaCompile) {
+ options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+dependencies {
+ implementation "androidx.core:core:${ANDROID_X_VERSION}"
+}
diff --git a/PermissionController/iconloaderlib/res/drawable-v26/adaptive_icon_drawable_wrapper.xml b/PermissionController/iconloaderlib/res/drawable-v26/adaptive_icon_drawable_wrapper.xml
new file mode 100644
index 000000000..9f13cf571
--- /dev/null
+++ b/PermissionController/iconloaderlib/res/drawable-v26/adaptive_icon_drawable_wrapper.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@color/legacy_icon_background"/>
+ <foreground>
+ <com.android.launcher3.icons.FixedScaleDrawable />
+ </foreground>
+</adaptive-icon>
diff --git a/PermissionController/iconloaderlib/res/drawable/ic_instant_app_badge.xml b/PermissionController/iconloaderlib/res/drawable/ic_instant_app_badge.xml
new file mode 100644
index 000000000..b74317e5f
--- /dev/null
+++ b/PermissionController/iconloaderlib/res/drawable/ic_instant_app_badge.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="@dimen/profile_badge_size"
+ android:height="@dimen/profile_badge_size"
+ android:viewportWidth="18"
+ android:viewportHeight="18">
+
+ <path
+ android:fillColor="@android:color/black"
+ android:strokeWidth="1"
+ android:pathData="M 9 0 C 13.9705627485 0 18 4.02943725152 18 9 C 18 13.9705627485 13.9705627485 18 9 18 C 4.02943725152 18 0 13.9705627485 0 9 C 0 4.02943725152 4.02943725152 0 9 0 Z" />
+ <path
+ android:fillColor="@android:color/white"
+ android:strokeWidth="1"
+ android:pathData="M 9 0 C 13.9705627485 0 18 4.02943725152 18 9 C 18 13.9705627485 13.9705627485 18 9 18 C 4.02943725152 18 0 13.9705627485 0 9 C 0 4.02943725152 4.02943725152 0 9 0 Z" />
+ <path
+ android:fillColor="@android:color/white"
+ android:strokeWidth="1"
+ android:pathData="M 9 0 C 13.9705627485 0 18 4.02943725152 18 9 C 18 13.9705627485 13.9705627485 18 9 18 C 4.02943725152 18 0 13.9705627485 0 9 C 0 4.02943725152 4.02943725152 0 9 0 Z" />
+ <path
+ android:fillColor="@android:color/black"
+ android:fillAlpha="0.87"
+ android:strokeWidth="1"
+ android:pathData="M 6 10.4123279 L 8.63934949 10.4123279 L 8.63934949 15.6 L 12.5577168 7.84517705 L 9.94547194 7.84517705 L 9.94547194 2 Z" />
+</vector>
diff --git a/PermissionController/iconloaderlib/res/values/attrs.xml b/PermissionController/iconloaderlib/res/values/attrs.xml
new file mode 100644
index 000000000..8f0bd2c1a
--- /dev/null
+++ b/PermissionController/iconloaderlib/res/values/attrs.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2021, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources>
+ <attr name="disabledIconAlpha" format="float" />
+ <attr name="loadingIconColor" format="color" />
+
+</resources> \ No newline at end of file
diff --git a/PermissionController/iconloaderlib/res/values/colors.xml b/PermissionController/iconloaderlib/res/values/colors.xml
new file mode 100644
index 000000000..70582c2e2
--- /dev/null
+++ b/PermissionController/iconloaderlib/res/values/colors.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2018, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources>
+ <color name="legacy_icon_background">#FFFFFF</color>
+
+ <!-- Yellow 600, used for highlighting "important" conversations in settings & notifications -->
+ <color name="important_conversation">#f9ab00</color>
+</resources>
diff --git a/PermissionController/iconloaderlib/res/values/config.xml b/PermissionController/iconloaderlib/res/values/config.xml
new file mode 100644
index 000000000..893f955c2
--- /dev/null
+++ b/PermissionController/iconloaderlib/res/values/config.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2018, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources>
+
+ <!-- Various configurations to control the simple cache implementation -->
+
+ <dimen name="default_icon_bitmap_size">56dp</dimen>
+ <bool name="simple_cache_enable_im_memory">false</bool>
+ <string name="cache_db_name" translatable="false">app_icons.db</string>
+
+ <string name="calendar_component_name" translatable="false"></string>
+ <string name="clock_component_name" translatable="false"></string>
+
+</resources> \ No newline at end of file
diff --git a/PermissionController/iconloaderlib/res/values/dimens.xml b/PermissionController/iconloaderlib/res/values/dimens.xml
new file mode 100644
index 000000000..e8c0c44f7
--- /dev/null
+++ b/PermissionController/iconloaderlib/res/values/dimens.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <dimen name="profile_badge_size">24dp</dimen>
+</resources>
diff --git a/PermissionController/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
new file mode 100644
index 000000000..9ce997587
--- /dev/null
+++ b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
@@ -0,0 +1,466 @@
+package com.android.launcher3.icons;
+
+import static android.graphics.Paint.DITHER_FLAG;
+import static android.graphics.Paint.FILTER_BITMAP_FLAG;
+
+import static com.android.launcher3.icons.ShadowGenerator.BLUR_FACTOR;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PaintFlagsDrawFilter;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.InsetDrawable;
+import android.os.Build;
+import android.os.Process;
+import android.os.UserHandle;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.icons.BitmapInfo.Extender;
+
+/**
+ * This class will be moved to androidx library. There shouldn't be any dependency outside
+ * this package.
+ */
+public class BaseIconFactory implements AutoCloseable {
+
+ private static final String TAG = "BaseIconFactory";
+ private static final int DEFAULT_WRAPPER_BACKGROUND = Color.WHITE;
+ static final boolean ATLEAST_OREO = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
+ static final boolean ATLEAST_P = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
+
+ private static final float ICON_BADGE_SCALE = 0.444f;
+
+ private final Rect mOldBounds = new Rect();
+ protected final Context mContext;
+ private final Canvas mCanvas;
+ private final PackageManager mPm;
+ private final ColorExtractor mColorExtractor;
+ private boolean mDisableColorExtractor;
+ private boolean mBadgeOnLeft = false;
+
+ protected final int mFillResIconDpi;
+ protected final int mIconBitmapSize;
+
+ private IconNormalizer mNormalizer;
+ private ShadowGenerator mShadowGenerator;
+ private final boolean mShapeDetection;
+
+ private Drawable mWrapperIcon;
+ private int mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
+ private Bitmap mUserBadgeBitmap;
+
+ private final Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+ private static final float PLACEHOLDER_TEXT_SIZE = 20f;
+ private static int PLACEHOLDER_BACKGROUND_COLOR = Color.rgb(240, 240, 240);
+
+ protected BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize,
+ boolean shapeDetection) {
+ mContext = context.getApplicationContext();
+ mShapeDetection = shapeDetection;
+ mFillResIconDpi = fillResIconDpi;
+ mIconBitmapSize = iconBitmapSize;
+
+ mPm = mContext.getPackageManager();
+ mColorExtractor = new ColorExtractor();
+
+ mCanvas = new Canvas();
+ mCanvas.setDrawFilter(new PaintFlagsDrawFilter(DITHER_FLAG, FILTER_BITMAP_FLAG));
+ mTextPaint.setTextAlign(Paint.Align.CENTER);
+ mTextPaint.setColor(PLACEHOLDER_BACKGROUND_COLOR);
+ mTextPaint.setTextSize(context.getResources().getDisplayMetrics().density *
+ PLACEHOLDER_TEXT_SIZE);
+ clear();
+ }
+
+ public BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize) {
+ this(context, fillResIconDpi, iconBitmapSize, false);
+ }
+
+ protected void clear() {
+ mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
+ mDisableColorExtractor = false;
+ mBadgeOnLeft = false;
+ }
+
+ public ShadowGenerator getShadowGenerator() {
+ if (mShadowGenerator == null) {
+ mShadowGenerator = new ShadowGenerator(mIconBitmapSize);
+ }
+ return mShadowGenerator;
+ }
+
+ public IconNormalizer getNormalizer() {
+ if (mNormalizer == null) {
+ mNormalizer = new IconNormalizer(mContext, mIconBitmapSize, mShapeDetection);
+ }
+ return mNormalizer;
+ }
+
+ @SuppressWarnings("deprecation")
+ public BitmapInfo createIconBitmap(Intent.ShortcutIconResource iconRes) {
+ try {
+ Resources resources = mPm.getResourcesForApplication(iconRes.packageName);
+ if (resources != null) {
+ final int id = resources.getIdentifier(iconRes.resourceName, null, null);
+ // do not stamp old legacy shortcuts as the app may have already forgotten about it
+ return createBadgedIconBitmap(
+ resources.getDrawableForDensity(id, mFillResIconDpi),
+ Process.myUserHandle() /* only available on primary user */,
+ false /* do not apply legacy treatment */);
+ }
+ } catch (Exception e) {
+ // Icon not found.
+ }
+ return null;
+ }
+
+ /**
+ * Create a placeholder icon using the passed in text.
+ *
+ * @param placeholder used for foreground element in the icon bitmap
+ * @param color used for the foreground text color
+ * @return
+ */
+ public BitmapInfo createIconBitmap(String placeholder, int color) {
+ if (!ATLEAST_OREO) return null;
+
+ Bitmap placeholderBitmap = Bitmap.createBitmap(mIconBitmapSize, mIconBitmapSize,
+ Bitmap.Config.ARGB_8888);
+ mTextPaint.setColor(color);
+ Canvas canvas = new Canvas(placeholderBitmap);
+ canvas.drawText(placeholder, mIconBitmapSize / 2, mIconBitmapSize * 5 / 8, mTextPaint);
+ AdaptiveIconDrawable drawable = new AdaptiveIconDrawable(
+ new ColorDrawable(PLACEHOLDER_BACKGROUND_COLOR),
+ new BitmapDrawable(mContext.getResources(), placeholderBitmap));
+ Bitmap icon = createIconBitmap(drawable, 1f);
+ return BitmapInfo.of(icon, extractColor(icon));
+ }
+
+ public BitmapInfo createIconBitmap(Bitmap icon) {
+ if (mIconBitmapSize != icon.getWidth() || mIconBitmapSize != icon.getHeight()) {
+ icon = createIconBitmap(new BitmapDrawable(mContext.getResources(), icon), 1f);
+ }
+
+ return BitmapInfo.of(icon, extractColor(icon));
+ }
+
+ /**
+ * Creates an icon from the bitmap cropped to the current device icon shape
+ */
+ public BitmapInfo createShapedIconBitmap(Bitmap icon, UserHandle user) {
+ Drawable d = new FixedSizeBitmapDrawable(icon);
+ if (ATLEAST_OREO) {
+ float inset = AdaptiveIconDrawable.getExtraInsetFraction();
+ inset = inset / (1 + 2 * inset);
+ d = new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK),
+ new InsetDrawable(d, inset, inset, inset, inset));
+ }
+ return createBadgedIconBitmap(d, user, true);
+ }
+
+ public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
+ boolean shrinkNonAdaptiveIcons) {
+ return createBadgedIconBitmap(icon, user, shrinkNonAdaptiveIcons, false, null);
+ }
+
+ public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
+ int iconAppTargetSdk) {
+ return createBadgedIconBitmap(icon, user, iconAppTargetSdk, false);
+ }
+
+ public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
+ int iconAppTargetSdk, boolean isInstantApp) {
+ return createBadgedIconBitmap(icon, user, iconAppTargetSdk, isInstantApp, null);
+ }
+
+ public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
+ int iconAppTargetSdk, boolean isInstantApp, float[] scale) {
+ boolean shrinkNonAdaptiveIcons = ATLEAST_P ||
+ (ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O);
+ return createBadgedIconBitmap(icon, user, shrinkNonAdaptiveIcons, isInstantApp, scale);
+ }
+
+ public Bitmap createScaledBitmapWithoutShadow(Drawable icon, int iconAppTargetSdk) {
+ boolean shrinkNonAdaptiveIcons = ATLEAST_P ||
+ (ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O);
+ return createScaledBitmapWithoutShadow(icon, shrinkNonAdaptiveIcons);
+ }
+
+ /**
+ * Creates bitmap using the source drawable and various parameters.
+ * The bitmap is visually normalized with other icons and has enough spacing to add shadow.
+ *
+ * @param icon source of the icon
+ * @param user info can be used for a badge
+ * @param shrinkNonAdaptiveIcons {@code true} if non adaptive icons should be treated
+ * @param isInstantApp info can be used for a badge
+ * @param scale returns the scale result from normalization
+ * @return a bitmap suitable for disaplaying as an icon at various system UIs.
+ */
+ public BitmapInfo createBadgedIconBitmap(@NonNull Drawable icon, UserHandle user,
+ boolean shrinkNonAdaptiveIcons, boolean isInstantApp, float[] scale) {
+ if (scale == null) {
+ scale = new float[1];
+ }
+ icon = normalizeAndWrapToAdaptiveIcon(icon, shrinkNonAdaptiveIcons, null, scale);
+ Bitmap bitmap = createIconBitmap(icon, scale[0]);
+ if (ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
+ mCanvas.setBitmap(bitmap);
+ getShadowGenerator().recreateIcon(Bitmap.createBitmap(bitmap), mCanvas);
+ mCanvas.setBitmap(null);
+ }
+
+ if (isInstantApp) {
+ badgeWithDrawable(bitmap, mContext.getDrawable(R.drawable.ic_instant_app_badge));
+ }
+ if (user != null) {
+ BitmapDrawable drawable = new FixedSizeBitmapDrawable(bitmap);
+ Drawable badged = mPm.getUserBadgedIcon(drawable, user);
+ if (badged instanceof BitmapDrawable) {
+ bitmap = ((BitmapDrawable) badged).getBitmap();
+ } else {
+ bitmap = createIconBitmap(badged, 1f);
+ }
+ }
+ int color = extractColor(bitmap);
+ return icon instanceof BitmapInfo.Extender
+ ? ((BitmapInfo.Extender) icon).getExtendedInfo(bitmap, color, this, scale[0], user)
+ : BitmapInfo.of(bitmap, color);
+ }
+
+ public Bitmap getUserBadgeBitmap(UserHandle user) {
+ if (mUserBadgeBitmap == null) {
+ Bitmap bitmap = Bitmap.createBitmap(
+ mIconBitmapSize, mIconBitmapSize, Bitmap.Config.ARGB_8888);
+ Drawable badgedDrawable = mPm.getUserBadgedIcon(
+ new FixedSizeBitmapDrawable(bitmap), user);
+ if (badgedDrawable instanceof BitmapDrawable) {
+ mUserBadgeBitmap = ((BitmapDrawable) badgedDrawable).getBitmap();
+ } else {
+ badgedDrawable.setBounds(0, 0, mIconBitmapSize, mIconBitmapSize);
+ mUserBadgeBitmap = BitmapRenderer.createSoftwareBitmap(
+ mIconBitmapSize, mIconBitmapSize, badgedDrawable::draw);
+ }
+ }
+ return mUserBadgeBitmap;
+ }
+
+ public Bitmap createScaledBitmapWithoutShadow(Drawable icon, boolean shrinkNonAdaptiveIcons) {
+ RectF iconBounds = new RectF();
+ float[] scale = new float[1];
+ icon = normalizeAndWrapToAdaptiveIcon(icon, shrinkNonAdaptiveIcons, iconBounds, scale);
+ return createIconBitmap(icon,
+ Math.min(scale[0], ShadowGenerator.getScaleForBounds(iconBounds)));
+ }
+
+ /**
+ * Switches badging to left/right
+ */
+ public void setBadgeOnLeft(boolean badgeOnLeft) {
+ mBadgeOnLeft = badgeOnLeft;
+ }
+
+ /**
+ * Sets the background color used for wrapped adaptive icon
+ */
+ public void setWrapperBackgroundColor(int color) {
+ mWrapperBackgroundColor = (Color.alpha(color) < 255) ? DEFAULT_WRAPPER_BACKGROUND : color;
+ }
+
+ /**
+ * Disables the dominant color extraction for all icons loaded.
+ */
+ public void disableColorExtraction() {
+ mDisableColorExtractor = true;
+ }
+
+ private Drawable normalizeAndWrapToAdaptiveIcon(@NonNull Drawable icon,
+ boolean shrinkNonAdaptiveIcons, RectF outIconBounds, float[] outScale) {
+ if (icon == null) {
+ return null;
+ }
+ float scale = 1f;
+
+ if (shrinkNonAdaptiveIcons && ATLEAST_OREO) {
+ if (mWrapperIcon == null) {
+ mWrapperIcon = mContext.getDrawable(R.drawable.adaptive_icon_drawable_wrapper)
+ .mutate();
+ }
+ AdaptiveIconDrawable dr = (AdaptiveIconDrawable) mWrapperIcon;
+ dr.setBounds(0, 0, 1, 1);
+ boolean[] outShape = new boolean[1];
+ scale = getNormalizer().getScale(icon, outIconBounds, dr.getIconMask(), outShape);
+ if (!(icon instanceof AdaptiveIconDrawable) && !outShape[0]) {
+ FixedScaleDrawable fsd = ((FixedScaleDrawable) dr.getForeground());
+ fsd.setDrawable(icon);
+ fsd.setScale(scale);
+ icon = dr;
+ scale = getNormalizer().getScale(icon, outIconBounds, null, null);
+
+ ((ColorDrawable) dr.getBackground()).setColor(mWrapperBackgroundColor);
+ }
+ } else {
+ scale = getNormalizer().getScale(icon, outIconBounds, null, null);
+ }
+
+ outScale[0] = scale;
+ return icon;
+ }
+
+ /**
+ * Adds the {@param badge} on top of {@param target} using the badge dimensions.
+ */
+ public void badgeWithDrawable(Bitmap target, Drawable badge) {
+ mCanvas.setBitmap(target);
+ badgeWithDrawable(mCanvas, badge);
+ mCanvas.setBitmap(null);
+ }
+
+ /**
+ * Adds the {@param badge} on top of {@param target} using the badge dimensions.
+ */
+ public void badgeWithDrawable(Canvas target, Drawable badge) {
+ int badgeSize = getBadgeSizeForIconSize(mIconBitmapSize);
+ if (mBadgeOnLeft) {
+ badge.setBounds(0, mIconBitmapSize - badgeSize, badgeSize, mIconBitmapSize);
+ } else {
+ badge.setBounds(mIconBitmapSize - badgeSize, mIconBitmapSize - badgeSize,
+ mIconBitmapSize, mIconBitmapSize);
+ }
+ badge.draw(target);
+ }
+
+ private Bitmap createIconBitmap(Drawable icon, float scale) {
+ return createIconBitmap(icon, scale, mIconBitmapSize);
+ }
+
+ /**
+ * @param icon drawable that should be flattened to a bitmap
+ * @param scale the scale to apply before drawing {@param icon} on the canvas
+ */
+ public Bitmap createIconBitmap(@NonNull Drawable icon, float scale, int size) {
+ Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
+ if (icon == null) {
+ return bitmap;
+ }
+ mCanvas.setBitmap(bitmap);
+ mOldBounds.set(icon.getBounds());
+
+ if (ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
+ int offset = Math.max((int) Math.ceil(BLUR_FACTOR * size),
+ Math.round(size * (1 - scale) / 2 ));
+ icon.setBounds(offset, offset, size - offset, size - offset);
+ if (icon instanceof BitmapInfo.Extender) {
+ ((Extender) icon).drawForPersistence(mCanvas);
+ } else {
+ icon.draw(mCanvas);
+ }
+ } else {
+ if (icon instanceof BitmapDrawable) {
+ BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
+ Bitmap b = bitmapDrawable.getBitmap();
+ if (bitmap != null && b.getDensity() == Bitmap.DENSITY_NONE) {
+ bitmapDrawable.setTargetDensity(mContext.getResources().getDisplayMetrics());
+ }
+ }
+ int width = size;
+ int height = size;
+
+ int intrinsicWidth = icon.getIntrinsicWidth();
+ int intrinsicHeight = icon.getIntrinsicHeight();
+ if (intrinsicWidth > 0 && intrinsicHeight > 0) {
+ // Scale the icon proportionally to the icon dimensions
+ final float ratio = (float) intrinsicWidth / intrinsicHeight;
+ if (intrinsicWidth > intrinsicHeight) {
+ height = (int) (width / ratio);
+ } else if (intrinsicHeight > intrinsicWidth) {
+ width = (int) (height * ratio);
+ }
+ }
+ final int left = (size - width) / 2;
+ final int top = (size - height) / 2;
+ icon.setBounds(left, top, left + width, top + height);
+ mCanvas.save();
+ mCanvas.scale(scale, scale, size / 2, size / 2);
+ icon.draw(mCanvas);
+ mCanvas.restore();
+
+ }
+ icon.setBounds(mOldBounds);
+ mCanvas.setBitmap(null);
+ return bitmap;
+ }
+
+ @Override
+ public void close() {
+ clear();
+ }
+
+ public BitmapInfo makeDefaultIcon(UserHandle user) {
+ return createBadgedIconBitmap(getFullResDefaultActivityIcon(mFillResIconDpi),
+ user, Build.VERSION.SDK_INT);
+ }
+
+ public static Drawable getFullResDefaultActivityIcon(int iconDpi) {
+ return Resources.getSystem().getDrawableForDensity(
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
+ ? android.R.drawable.sym_def_app_icon : android.R.mipmap.sym_def_app_icon,
+ iconDpi);
+ }
+
+ /**
+ * Badges the provided source with the badge info
+ */
+ public BitmapInfo badgeBitmap(Bitmap source, BitmapInfo badgeInfo) {
+ Bitmap icon = BitmapRenderer.createHardwareBitmap(mIconBitmapSize, mIconBitmapSize, (c) -> {
+ getShadowGenerator().recreateIcon(source, c);
+ badgeWithDrawable(c, new FixedSizeBitmapDrawable(badgeInfo.icon));
+ });
+ return BitmapInfo.of(icon, badgeInfo.color);
+ }
+
+ private int extractColor(Bitmap bitmap) {
+ return mDisableColorExtractor ? 0 : mColorExtractor.findDominantColorByHue(bitmap);
+ }
+
+ /**
+ * Returns the correct badge size given an icon size
+ */
+ public static int getBadgeSizeForIconSize(int iconSize) {
+ return (int) (ICON_BADGE_SCALE * iconSize);
+ }
+
+ /**
+ * An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size.
+ * This allows the badging to be done based on the action bitmap size rather than
+ * the scaled bitmap size.
+ */
+ private static class FixedSizeBitmapDrawable extends BitmapDrawable {
+
+ public FixedSizeBitmapDrawable(Bitmap bitmap) {
+ super(null, bitmap);
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return getBitmap().getWidth();
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return getBitmap().getWidth();
+ }
+ }
+}
diff --git a/PermissionController/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java
new file mode 100644
index 000000000..06b39b8f1
--- /dev/null
+++ b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.icons;
+
+import static com.android.launcher3.icons.GraphicsUtils.getExpectedBitmapSize;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.UserHandle;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.icons.ThemedIconDrawable.ThemedBitmapInfo;
+import com.android.launcher3.icons.cache.BaseIconCache;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+public class BitmapInfo {
+
+ public static final Bitmap LOW_RES_ICON = Bitmap.createBitmap(1, 1, Config.ALPHA_8);
+ public static final BitmapInfo LOW_RES_INFO = fromBitmap(LOW_RES_ICON);
+
+ public static final String TAG = "BitmapInfo";
+
+ protected static final byte TYPE_DEFAULT = 1;
+ protected static final byte TYPE_THEMED = 2;
+
+ public final Bitmap icon;
+ public final int color;
+
+ public BitmapInfo(Bitmap icon, int color) {
+ this.icon = icon;
+ this.color = color;
+ }
+
+ /**
+ * Ideally icon should not be null, except in cases when generating hardware bitmap failed
+ */
+ public final boolean isNullOrLowRes() {
+ return icon == null || icon == LOW_RES_ICON;
+ }
+
+ public final boolean isLowRes() {
+ return LOW_RES_ICON == icon;
+ }
+
+ /**
+ * Returns a serialized version of BitmapInfo
+ */
+ @Nullable
+ public byte[] toByteArray() {
+ if (isNullOrLowRes()) {
+ return null;
+ }
+ ByteArrayOutputStream out = new ByteArrayOutputStream(getExpectedBitmapSize(icon) + 1);
+ try {
+ out.write(TYPE_DEFAULT);
+ icon.compress(Bitmap.CompressFormat.PNG, 100, out);
+ out.flush();
+ out.close();
+ return out.toByteArray();
+ } catch (IOException e) {
+ Log.w(TAG, "Could not write bitmap");
+ return null;
+ }
+ }
+
+ /**
+ * Returns a new icon based on the theme of the context
+ */
+ public FastBitmapDrawable newThemedIcon(Context context) {
+ return newIcon(context);
+ }
+
+ /**
+ * Creates a drawable for the provided BitmapInfo
+ */
+ public FastBitmapDrawable newIcon(Context context) {
+ FastBitmapDrawable drawable = isLowRes()
+ ? new PlaceHolderIconDrawable(this, context)
+ : new FastBitmapDrawable(this);
+ drawable.mDisabledAlpha = GraphicsUtils.getFloat(context, R.attr.disabledIconAlpha, 1f);
+ return drawable;
+ }
+
+ /**
+ * Returns a BitmapInfo previously serialized using {@link #toByteArray()};
+ */
+ @NonNull
+ public static BitmapInfo fromByteArray(byte[] data, int color, UserHandle user,
+ BaseIconCache iconCache, Context context) {
+ if (data == null) {
+ return null;
+ }
+ BitmapFactory.Options decodeOptions;
+ if (BitmapRenderer.USE_HARDWARE_BITMAP && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ decodeOptions = new BitmapFactory.Options();
+ decodeOptions.inPreferredConfig = Bitmap.Config.HARDWARE;
+ } else {
+ decodeOptions = null;
+ }
+ if (data[0] == TYPE_DEFAULT) {
+ return BitmapInfo.of(
+ BitmapFactory.decodeByteArray(data, 1, data.length - 1, decodeOptions),
+ color);
+ } else if (data[0] == TYPE_THEMED) {
+ return ThemedBitmapInfo.decode(data, color, decodeOptions, user, iconCache, context);
+ } else {
+ return null;
+ }
+ }
+
+ public static BitmapInfo fromBitmap(@NonNull Bitmap bitmap) {
+ return of(bitmap, 0);
+ }
+
+ public static BitmapInfo of(@NonNull Bitmap bitmap, int color) {
+ return new BitmapInfo(bitmap, color);
+ }
+
+ /**
+ * Interface to be implemented by drawables to provide a custom BitmapInfo
+ */
+ public interface Extender {
+
+ /**
+ * Called for creating a custom BitmapInfo
+ */
+ BitmapInfo getExtendedInfo(Bitmap bitmap, int color,
+ BaseIconFactory iconFactory, float normalizationScale, UserHandle user);
+
+ /**
+ * Called to draw the UI independent of any runtime configurations like time or theme
+ */
+ void drawForPersistence(Canvas canvas);
+
+ /**
+ * Returns a new icon with theme applied
+ */
+ Drawable getThemedDrawable(Context context);
+ }
+}
diff --git a/PermissionController/iconloaderlib/src/com/android/launcher3/icons/BitmapRenderer.java b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/BitmapRenderer.java
new file mode 100644
index 000000000..5751ed95c
--- /dev/null
+++ b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/BitmapRenderer.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.icons;
+
+import android.annotation.TargetApi;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.Picture;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Build;
+import android.os.Build.VERSION_CODES;
+
+/**
+ * Interface representing a bitmap draw operation.
+ */
+public interface BitmapRenderer {
+
+ boolean USE_HARDWARE_BITMAP = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
+
+ static Bitmap createSoftwareBitmap(int width, int height, BitmapRenderer renderer) {
+ GraphicsUtils.noteNewBitmapCreated();
+ Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ renderer.draw(new Canvas(result));
+ return result;
+ }
+
+ @TargetApi(Build.VERSION_CODES.P)
+ static Bitmap createHardwareBitmap(int width, int height, BitmapRenderer renderer) {
+ if (!USE_HARDWARE_BITMAP) {
+ return createSoftwareBitmap(width, height, renderer);
+ }
+
+ GraphicsUtils.noteNewBitmapCreated();
+ Picture picture = new Picture();
+ renderer.draw(picture.beginRecording(width, height));
+ picture.endRecording();
+ return Bitmap.createBitmap(picture);
+ }
+
+ /**
+ * Returns a bitmap from subset of the source bitmap. The new bitmap may be the
+ * same object as source, or a copy may have been made.
+ */
+ static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height) {
+ if (Build.VERSION.SDK_INT >= VERSION_CODES.O && source.getConfig() == Config.HARDWARE) {
+ return createHardwareBitmap(width, height, c -> c.drawBitmap(source,
+ new Rect(x, y, x + width, y + height), new RectF(0, 0, width, height), null));
+ } else {
+ GraphicsUtils.noteNewBitmapCreated();
+ return Bitmap.createBitmap(source, x, y, width, height);
+ }
+ }
+
+ void draw(Canvas out);
+}
diff --git a/PermissionController/iconloaderlib/src/com/android/launcher3/icons/ClockDrawableWrapper.java b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/ClockDrawableWrapper.java
new file mode 100644
index 000000000..a7894c991
--- /dev/null
+++ b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/ClockDrawableWrapper.java
@@ -0,0 +1,448 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.icons;
+
+import static com.android.launcher3.icons.ThemedIconDrawable.getColors;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.Rect;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Process;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.TypedValue;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.icons.ThemedIconDrawable.ThemeData;
+
+import java.util.Calendar;
+import java.util.concurrent.TimeUnit;
+import java.util.function.IntFunction;
+
+/**
+ * Wrapper over {@link AdaptiveIconDrawable} to intercept icon flattening logic for dynamic
+ * clock icons
+ */
+@TargetApi(Build.VERSION_CODES.O)
+public class ClockDrawableWrapper extends AdaptiveIconDrawable implements BitmapInfo.Extender {
+
+ private static final String TAG = "ClockDrawableWrapper";
+
+ private static final boolean DISABLE_SECONDS = true;
+
+ // Time after which the clock icon should check for an update. The actual invalidate
+ // will only happen in case of any change.
+ public static final long TICK_MS = DISABLE_SECONDS ? TimeUnit.MINUTES.toMillis(1) : 200L;
+
+ private static final String LAUNCHER_PACKAGE = "com.android.launcher3";
+ private static final String ROUND_ICON_METADATA_KEY = LAUNCHER_PACKAGE
+ + ".LEVEL_PER_TICK_ICON_ROUND";
+ private static final String HOUR_INDEX_METADATA_KEY = LAUNCHER_PACKAGE + ".HOUR_LAYER_INDEX";
+ private static final String MINUTE_INDEX_METADATA_KEY = LAUNCHER_PACKAGE
+ + ".MINUTE_LAYER_INDEX";
+ private static final String SECOND_INDEX_METADATA_KEY = LAUNCHER_PACKAGE
+ + ".SECOND_LAYER_INDEX";
+ private static final String DEFAULT_HOUR_METADATA_KEY = LAUNCHER_PACKAGE
+ + ".DEFAULT_HOUR";
+ private static final String DEFAULT_MINUTE_METADATA_KEY = LAUNCHER_PACKAGE
+ + ".DEFAULT_MINUTE";
+ private static final String DEFAULT_SECOND_METADATA_KEY = LAUNCHER_PACKAGE
+ + ".DEFAULT_SECOND";
+
+ /* Number of levels to jump per second for the second hand */
+ private static final int LEVELS_PER_SECOND = 10;
+
+ public static final int INVALID_VALUE = -1;
+
+ private final AnimationInfo mAnimationInfo = new AnimationInfo();
+ private int mTargetSdkVersion;
+ protected ThemeData mThemeData;
+
+ public ClockDrawableWrapper(AdaptiveIconDrawable base) {
+ super(base.getBackground(), base.getForeground());
+ }
+
+ /**
+ * Loads and returns the wrapper from the provided package, or returns null
+ * if it is unable to load.
+ */
+ public static ClockDrawableWrapper forPackage(Context context, String pkg, int iconDpi) {
+ try {
+ PackageManager pm = context.getPackageManager();
+ ApplicationInfo appInfo = pm.getApplicationInfo(pkg,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA);
+ Resources res = pm.getResourcesForApplication(appInfo);
+ return forExtras(appInfo, appInfo.metaData,
+ resId -> res.getDrawableForDensity(resId, iconDpi));
+ } catch (Exception e) {
+ Log.d(TAG, "Unable to load clock drawable info", e);
+ }
+ return null;
+ }
+
+ private static ClockDrawableWrapper fromThemeData(Context context, ThemeData themeData) {
+ try {
+ TypedArray ta = themeData.mResources.obtainTypedArray(themeData.mResID);
+ int count = ta.length();
+ Bundle extras = new Bundle();
+ for (int i = 0; i < count; i += 2) {
+ TypedValue v = ta.peekValue(i + 1);
+ extras.putInt(ta.getString(i), v.type >= TypedValue.TYPE_FIRST_INT
+ && v.type <= TypedValue.TYPE_LAST_INT
+ ? v.data : v.resourceId);
+ }
+ ta.recycle();
+ ClockDrawableWrapper drawable = ClockDrawableWrapper.forExtras(
+ context.getApplicationInfo(), extras, resId -> {
+ int[] colors = getColors(context);
+ Drawable bg = new ColorDrawable(colors[0]);
+ Drawable fg = themeData.mResources.getDrawable(resId).mutate();
+ fg.setTint(colors[1]);
+ return new AdaptiveIconDrawable(bg, fg);
+ });
+ if (drawable != null) {
+ return drawable;
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error loading themed clock", e);
+ }
+ return null;
+ }
+
+ private static ClockDrawableWrapper forExtras(ApplicationInfo appInfo, Bundle metadata,
+ IntFunction<Drawable> drawableProvider) {
+ if (metadata == null) {
+ return null;
+ }
+ int drawableId = metadata.getInt(ROUND_ICON_METADATA_KEY, 0);
+ if (drawableId == 0) {
+ return null;
+ }
+
+ Drawable drawable = drawableProvider.apply(drawableId).mutate();
+ if (!(drawable instanceof AdaptiveIconDrawable)) {
+ return null;
+ }
+
+ ClockDrawableWrapper wrapper =
+ new ClockDrawableWrapper((AdaptiveIconDrawable) drawable);
+ wrapper.mTargetSdkVersion = appInfo.targetSdkVersion;
+ AnimationInfo info = wrapper.mAnimationInfo;
+
+ info.baseDrawableState = drawable.getConstantState();
+
+ info.hourLayerIndex = metadata.getInt(HOUR_INDEX_METADATA_KEY, INVALID_VALUE);
+ info.minuteLayerIndex = metadata.getInt(MINUTE_INDEX_METADATA_KEY, INVALID_VALUE);
+ info.secondLayerIndex = metadata.getInt(SECOND_INDEX_METADATA_KEY, INVALID_VALUE);
+
+ info.defaultHour = metadata.getInt(DEFAULT_HOUR_METADATA_KEY, 0);
+ info.defaultMinute = metadata.getInt(DEFAULT_MINUTE_METADATA_KEY, 0);
+ info.defaultSecond = metadata.getInt(DEFAULT_SECOND_METADATA_KEY, 0);
+
+ LayerDrawable foreground = (LayerDrawable) wrapper.getForeground();
+ int layerCount = foreground.getNumberOfLayers();
+ if (info.hourLayerIndex < 0 || info.hourLayerIndex >= layerCount) {
+ info.hourLayerIndex = INVALID_VALUE;
+ }
+ if (info.minuteLayerIndex < 0 || info.minuteLayerIndex >= layerCount) {
+ info.minuteLayerIndex = INVALID_VALUE;
+ }
+ if (info.secondLayerIndex < 0 || info.secondLayerIndex >= layerCount) {
+ info.secondLayerIndex = INVALID_VALUE;
+ } else if (DISABLE_SECONDS) {
+ foreground.setDrawable(info.secondLayerIndex, null);
+ info.secondLayerIndex = INVALID_VALUE;
+ }
+ info.applyTime(Calendar.getInstance(), foreground);
+ return wrapper;
+ }
+
+ @Override
+ public ClockBitmapInfo getExtendedInfo(Bitmap bitmap, int color,
+ BaseIconFactory iconFactory, float normalizationScale, UserHandle user) {
+ iconFactory.disableColorExtraction();
+ AdaptiveIconDrawable background = new AdaptiveIconDrawable(
+ getBackground().getConstantState().newDrawable(), null);
+ BitmapInfo bitmapInfo = iconFactory.createBadgedIconBitmap(background,
+ Process.myUserHandle(), mTargetSdkVersion, false);
+
+ return new ClockBitmapInfo(bitmap, color, normalizationScale,
+ mAnimationInfo, bitmapInfo.icon, mThemeData);
+ }
+
+ @Override
+ public void drawForPersistence(Canvas canvas) {
+ LayerDrawable foreground = (LayerDrawable) getForeground();
+ resetLevel(foreground, mAnimationInfo.hourLayerIndex);
+ resetLevel(foreground, mAnimationInfo.minuteLayerIndex);
+ resetLevel(foreground, mAnimationInfo.secondLayerIndex);
+ draw(canvas);
+ mAnimationInfo.applyTime(Calendar.getInstance(), (LayerDrawable) getForeground());
+ }
+
+ @Override
+ public Drawable getThemedDrawable(Context context) {
+ if (mThemeData != null) {
+ ClockDrawableWrapper drawable = fromThemeData(context, mThemeData);
+ return drawable == null ? this : drawable;
+ }
+ return this;
+ }
+
+ private void resetLevel(LayerDrawable drawable, int index) {
+ if (index != INVALID_VALUE) {
+ drawable.getDrawable(index).setLevel(0);
+ }
+ }
+
+ private static class AnimationInfo {
+
+ public ConstantState baseDrawableState;
+
+ public int hourLayerIndex;
+ public int minuteLayerIndex;
+ public int secondLayerIndex;
+ public int defaultHour;
+ public int defaultMinute;
+ public int defaultSecond;
+
+ boolean applyTime(Calendar time, LayerDrawable foregroundDrawable) {
+ time.setTimeInMillis(System.currentTimeMillis());
+
+ // We need to rotate by the difference from the default time if one is specified.
+ int convertedHour = (time.get(Calendar.HOUR) + (12 - defaultHour)) % 12;
+ int convertedMinute = (time.get(Calendar.MINUTE) + (60 - defaultMinute)) % 60;
+ int convertedSecond = (time.get(Calendar.SECOND) + (60 - defaultSecond)) % 60;
+
+ boolean invalidate = false;
+ if (hourLayerIndex != INVALID_VALUE) {
+ final Drawable hour = foregroundDrawable.getDrawable(hourLayerIndex);
+ if (hour.setLevel(convertedHour * 60 + time.get(Calendar.MINUTE))) {
+ invalidate = true;
+ }
+ }
+
+ if (minuteLayerIndex != INVALID_VALUE) {
+ final Drawable minute = foregroundDrawable.getDrawable(minuteLayerIndex);
+ if (minute.setLevel(time.get(Calendar.HOUR) * 60 + convertedMinute)) {
+ invalidate = true;
+ }
+ }
+
+ if (secondLayerIndex != INVALID_VALUE) {
+ final Drawable second = foregroundDrawable.getDrawable(secondLayerIndex);
+ if (second.setLevel(convertedSecond * LEVELS_PER_SECOND)) {
+ invalidate = true;
+ }
+ }
+
+ return invalidate;
+ }
+ }
+
+ static class ClockBitmapInfo extends BitmapInfo {
+
+ public final float scale;
+ public final int offset;
+ public final AnimationInfo animInfo;
+ public final Bitmap mFlattenedBackground;
+
+ public final ThemeData themeData;
+ public final ColorFilter bgFilter;
+
+ ClockBitmapInfo(Bitmap icon, int color, float scale, AnimationInfo animInfo,
+ Bitmap background, ThemeData themeData) {
+ this(icon, color, scale, animInfo, background, themeData, null);
+ }
+
+ ClockBitmapInfo(Bitmap icon, int color, float scale, AnimationInfo animInfo,
+ Bitmap background, ThemeData themeData, ColorFilter bgFilter) {
+ super(icon, color);
+ this.scale = scale;
+ this.animInfo = animInfo;
+ this.offset = (int) Math.ceil(ShadowGenerator.BLUR_FACTOR * icon.getWidth());
+ this.mFlattenedBackground = background;
+ this.themeData = themeData;
+ this.bgFilter = bgFilter;
+ }
+
+ @Override
+ public FastBitmapDrawable newThemedIcon(Context context) {
+ if (themeData != null) {
+ ClockDrawableWrapper wrapper = fromThemeData(context, themeData);
+ if (wrapper != null) {
+ int[] colors = getColors(context);
+ ColorFilter bgFilter = new PorterDuffColorFilter(colors[0], Mode.SRC_ATOP);
+ return new ClockBitmapInfo(icon, colors[1], scale,
+ wrapper.mAnimationInfo, mFlattenedBackground, themeData, bgFilter)
+ .newIcon(context);
+ }
+ }
+ return super.newThemedIcon(context);
+ }
+
+ @Override
+ public FastBitmapDrawable newIcon(Context context) {
+ ClockIconDrawable d = new ClockIconDrawable(this);
+ d.mDisabledAlpha = GraphicsUtils.getFloat(context, R.attr.disabledIconAlpha, 1f);
+ return d;
+ }
+
+ @Nullable
+ @Override
+ public byte[] toByteArray() {
+ return null;
+ }
+
+ void drawBackground(Canvas canvas, Rect bounds, Paint paint) {
+ // draw the background that is already flattened to a bitmap
+ ColorFilter oldFilter = paint.getColorFilter();
+ if (bgFilter != null) {
+ paint.setColorFilter(bgFilter);
+ }
+ canvas.drawBitmap(mFlattenedBackground, null, bounds, paint);
+ paint.setColorFilter(oldFilter);
+ }
+ }
+
+ private static class ClockIconDrawable extends FastBitmapDrawable implements Runnable {
+
+ private final Calendar mTime = Calendar.getInstance();
+
+ private final ClockBitmapInfo mInfo;
+
+ private final AdaptiveIconDrawable mFullDrawable;
+ private final LayerDrawable mForeground;
+
+ ClockIconDrawable(ClockBitmapInfo clockInfo) {
+ super(clockInfo);
+
+ mInfo = clockInfo;
+ mFullDrawable = (AdaptiveIconDrawable) mInfo.animInfo.baseDrawableState
+ .newDrawable().mutate();
+ mForeground = (LayerDrawable) mFullDrawable.getForeground();
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ super.onBoundsChange(bounds);
+ mFullDrawable.setBounds(bounds);
+ }
+
+ @Override
+ public void drawInternal(Canvas canvas, Rect bounds) {
+ if (mInfo == null) {
+ super.drawInternal(canvas, bounds);
+ return;
+ }
+ mInfo.drawBackground(canvas, bounds, mPaint);
+
+ // prepare and draw the foreground
+ mInfo.animInfo.applyTime(mTime, mForeground);
+
+ canvas.scale(mInfo.scale, mInfo.scale,
+ bounds.exactCenterX() + mInfo.offset, bounds.exactCenterY() + mInfo.offset);
+ canvas.clipPath(mFullDrawable.getIconMask());
+ mForeground.draw(canvas);
+
+ reschedule();
+ }
+
+ @Override
+ public boolean isThemed() {
+ return mInfo.bgFilter != null;
+ }
+
+ @Override
+ protected void updateFilter() {
+ super.updateFilter();
+ mFullDrawable.setColorFilter(mPaint.getColorFilter());
+ }
+
+ @Override
+ public void run() {
+ if (mInfo.animInfo.applyTime(mTime, mForeground)) {
+ invalidateSelf();
+ } else {
+ reschedule();
+ }
+ }
+
+ @Override
+ public boolean setVisible(boolean visible, boolean restart) {
+ boolean result = super.setVisible(visible, restart);
+ if (visible) {
+ reschedule();
+ } else {
+ unscheduleSelf(this);
+ }
+ return result;
+ }
+
+ private void reschedule() {
+ if (!isVisible()) {
+ return;
+ }
+
+ unscheduleSelf(this);
+ final long upTime = SystemClock.uptimeMillis();
+ final long step = TICK_MS; /* tick every 200 ms */
+ scheduleSelf(this, upTime - ((upTime % step)) + step);
+ }
+
+ @Override
+ public ConstantState getConstantState() {
+ return new ClockConstantState(mInfo, isDisabled());
+ }
+
+ private static class ClockConstantState extends FastBitmapConstantState {
+
+ private final ClockBitmapInfo mInfo;
+
+ ClockConstantState(ClockBitmapInfo info, boolean isDisabled) {
+ super(info.icon, info.color, isDisabled);
+ mInfo = info;
+ }
+
+ @Override
+ public FastBitmapDrawable newDrawable() {
+ ClockIconDrawable drawable = new ClockIconDrawable(mInfo);
+ drawable.setIsDisabled(mIsDisabled);
+ return drawable;
+ }
+ }
+ }
+}
diff --git a/PermissionController/iconloaderlib/src/com/android/launcher3/icons/ColorExtractor.java b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/ColorExtractor.java
new file mode 100644
index 000000000..87bda825c
--- /dev/null
+++ b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/ColorExtractor.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.icons;
+
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.util.SparseArray;
+import java.util.Arrays;
+
+/**
+ * Utility class for extracting colors from a bitmap.
+ */
+public class ColorExtractor {
+
+ private final int NUM_SAMPLES = 20;
+ private final float[] mTmpHsv = new float[3];
+ private final float[] mTmpHueScoreHistogram = new float[360];
+ private final int[] mTmpPixels = new int[NUM_SAMPLES];
+ private final SparseArray<Float> mTmpRgbScores = new SparseArray<>();
+
+ /**
+ * This picks a dominant color, looking for high-saturation, high-value, repeated hues.
+ * @param bitmap The bitmap to scan
+ */
+ public int findDominantColorByHue(Bitmap bitmap) {
+ return findDominantColorByHue(bitmap, NUM_SAMPLES);
+ }
+
+ /**
+ * This picks a dominant color, looking for high-saturation, high-value, repeated hues.
+ * @param bitmap The bitmap to scan
+ */
+ public int findDominantColorByHue(Bitmap bitmap, int samples) {
+ final int height = bitmap.getHeight();
+ final int width = bitmap.getWidth();
+ int sampleStride = (int) Math.sqrt((height * width) / samples);
+ if (sampleStride < 1) {
+ sampleStride = 1;
+ }
+
+ // This is an out-param, for getting the hsv values for an rgb
+ float[] hsv = mTmpHsv;
+ Arrays.fill(hsv, 0);
+
+ // First get the best hue, by creating a histogram over 360 hue buckets,
+ // where each pixel contributes a score weighted by saturation, value, and alpha.
+ float[] hueScoreHistogram = mTmpHueScoreHistogram;
+ Arrays.fill(hueScoreHistogram, 0);
+ float highScore = -1;
+ int bestHue = -1;
+
+ int[] pixels = mTmpPixels;
+ Arrays.fill(pixels, 0);
+ int pixelCount = 0;
+
+ for (int y = 0; y < height; y += sampleStride) {
+ for (int x = 0; x < width; x += sampleStride) {
+ int argb = bitmap.getPixel(x, y);
+ int alpha = 0xFF & (argb >> 24);
+ if (alpha < 0x80) {
+ // Drop mostly-transparent pixels.
+ continue;
+ }
+ // Remove the alpha channel.
+ int rgb = argb | 0xFF000000;
+ Color.colorToHSV(rgb, hsv);
+ // Bucket colors by the 360 integer hues.
+ int hue = (int) hsv[0];
+ if (hue < 0 || hue >= hueScoreHistogram.length) {
+ // Defensively avoid array bounds violations.
+ continue;
+ }
+ if (pixelCount < samples) {
+ pixels[pixelCount++] = rgb;
+ }
+ float score = hsv[1] * hsv[2];
+ hueScoreHistogram[hue] += score;
+ if (hueScoreHistogram[hue] > highScore) {
+ highScore = hueScoreHistogram[hue];
+ bestHue = hue;
+ }
+ }
+ }
+
+ SparseArray<Float> rgbScores = mTmpRgbScores;
+ rgbScores.clear();
+ int bestColor = 0xff000000;
+ highScore = -1;
+ // Go back over the RGB colors that match the winning hue,
+ // creating a histogram of weighted s*v scores, for up to 100*100 [s,v] buckets.
+ // The highest-scoring RGB color wins.
+ for (int i = 0; i < pixelCount; i++) {
+ int rgb = pixels[i];
+ Color.colorToHSV(rgb, hsv);
+ int hue = (int) hsv[0];
+ if (hue == bestHue) {
+ float s = hsv[1];
+ float v = hsv[2];
+ int bucket = (int) (s * 100) + (int) (v * 10000);
+ // Score by cumulative saturation * value.
+ float score = s * v;
+ Float oldTotal = rgbScores.get(bucket);
+ float newTotal = oldTotal == null ? score : oldTotal + score;
+ rgbScores.put(bucket, newTotal);
+ if (newTotal > highScore) {
+ highScore = newTotal;
+ // All the colors in the winning bucket are very similar. Last in wins.
+ bestColor = rgb;
+ }
+ }
+ }
+ return bestColor;
+ }
+}
diff --git a/PermissionController/iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java
new file mode 100644
index 000000000..97a0fd3ff
--- /dev/null
+++ b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.icons;
+
+import static android.graphics.Paint.ANTI_ALIAS_FLAG;
+import static android.graphics.Paint.FILTER_BITMAP_FLAG;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PathMeasure;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.Log;
+import android.view.ViewDebug;
+
+/**
+ * Used to draw a notification dot on top of an icon.
+ */
+public class DotRenderer {
+
+ private static final String TAG = "DotRenderer";
+
+ // The dot size is defined as a percentage of the app icon size.
+ private static final float SIZE_PERCENTAGE = 0.228f;
+
+ private final float mCircleRadius;
+ private final Paint mCirclePaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG);
+
+ private final Bitmap mBackgroundWithShadow;
+ private final float mBitmapOffset;
+
+ // Stores the center x and y position as a percentage (0 to 1) of the icon size
+ private final float[] mRightDotPosition;
+ private final float[] mLeftDotPosition;
+
+ public DotRenderer(int iconSizePx, Path iconShapePath, int pathSize) {
+ int size = Math.round(SIZE_PERCENTAGE * iconSizePx);
+ ShadowGenerator.Builder builder = new ShadowGenerator.Builder(Color.TRANSPARENT);
+ builder.ambientShadowAlpha = 88;
+ mBackgroundWithShadow = builder.setupBlurForSize(size).createPill(size, size);
+ mCircleRadius = builder.radius;
+
+ mBitmapOffset = -mBackgroundWithShadow.getHeight() * 0.5f; // Same as width.
+
+ // Find the points on the path that are closest to the top left and right corners.
+ mLeftDotPosition = getPathPoint(iconShapePath, pathSize, -1);
+ mRightDotPosition = getPathPoint(iconShapePath, pathSize, 1);
+ }
+
+ private static float[] getPathPoint(Path path, float size, float direction) {
+ float halfSize = size / 2;
+ // Small delta so that we don't get a zero size triangle
+ float delta = 1;
+
+ float x = halfSize + direction * halfSize;
+ Path trianglePath = new Path();
+ trianglePath.moveTo(halfSize, halfSize);
+ trianglePath.lineTo(x + delta * direction, 0);
+ trianglePath.lineTo(x, -delta);
+ trianglePath.close();
+
+ trianglePath.op(path, Path.Op.INTERSECT);
+ float[] pos = new float[2];
+ new PathMeasure(trianglePath, false).getPosTan(0, pos, null);
+
+ pos[0] = pos[0] / size;
+ pos[1] = pos[1] / size;
+ return pos;
+ }
+
+ public float[] getLeftDotPosition() {
+ return mLeftDotPosition;
+ }
+
+ public float[] getRightDotPosition() {
+ return mRightDotPosition;
+ }
+
+ /**
+ * Draw a circle on top of the canvas according to the given params.
+ */
+ public void draw(Canvas canvas, DrawParams params) {
+ if (params == null) {
+ Log.e(TAG, "Invalid null argument(s) passed in call to draw.");
+ return;
+ }
+ canvas.save();
+
+ Rect iconBounds = params.iconBounds;
+ float[] dotPosition = params.leftAlign ? mLeftDotPosition : mRightDotPosition;
+ float dotCenterX = iconBounds.left + iconBounds.width() * dotPosition[0];
+ float dotCenterY = iconBounds.top + iconBounds.height() * dotPosition[1];
+
+ // Ensure dot fits entirely in canvas clip bounds.
+ Rect canvasBounds = canvas.getClipBounds();
+ float offsetX = params.leftAlign
+ ? Math.max(0, canvasBounds.left - (dotCenterX + mBitmapOffset))
+ : Math.min(0, canvasBounds.right - (dotCenterX - mBitmapOffset));
+ float offsetY = Math.max(0, canvasBounds.top - (dotCenterY + mBitmapOffset));
+
+ // We draw the dot relative to its center.
+ canvas.translate(dotCenterX + offsetX, dotCenterY + offsetY);
+ canvas.scale(params.scale, params.scale);
+
+ mCirclePaint.setColor(Color.BLACK);
+ canvas.drawBitmap(mBackgroundWithShadow, mBitmapOffset, mBitmapOffset, mCirclePaint);
+ mCirclePaint.setColor(params.color);
+ canvas.drawCircle(0, 0, mCircleRadius, mCirclePaint);
+ canvas.restore();
+ }
+
+ public static class DrawParams {
+ /** The color (possibly based on the icon) to use for the dot. */
+ @ViewDebug.ExportedProperty(category = "notification dot", formatToHexString = true)
+ public int color;
+ /** The bounds of the icon that the dot is drawn on top of. */
+ @ViewDebug.ExportedProperty(category = "notification dot")
+ public Rect iconBounds = new Rect();
+ /** The progress of the animation, from 0 to 1. */
+ @ViewDebug.ExportedProperty(category = "notification dot")
+ public float scale;
+ /** Whether the dot should align to the top left of the icon rather than the top right. */
+ @ViewDebug.ExportedProperty(category = "notification dot")
+ public boolean leftAlign;
+ }
+}
diff --git a/PermissionController/iconloaderlib/src/com/android/launcher3/icons/FastBitmapDrawable.java b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/FastBitmapDrawable.java
new file mode 100644
index 000000000..4aa284618
--- /dev/null
+++ b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/FastBitmapDrawable.java
@@ -0,0 +1,314 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.icons;
+
+import android.animation.ObjectAnimator;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.Property;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+import androidx.annotation.Nullable;
+
+public class FastBitmapDrawable extends Drawable {
+
+ private static final Interpolator ACCEL = new AccelerateInterpolator();
+ private static final Interpolator DEACCEL = new DecelerateInterpolator();
+
+ private static final float PRESSED_SCALE = 1.1f;
+
+ private static final float DISABLED_DESATURATION = 1f;
+ private static final float DISABLED_BRIGHTNESS = 0.5f;
+
+ public static final int CLICK_FEEDBACK_DURATION = 200;
+
+ private static ColorFilter sDisabledFColorFilter;
+
+ protected final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
+ protected Bitmap mBitmap;
+ protected final int mIconColor;
+
+ @Nullable private ColorFilter mColorFilter;
+
+ private boolean mIsPressed;
+ protected boolean mIsDisabled;
+ float mDisabledAlpha = 1f;
+
+ // Animator and properties for the fast bitmap drawable's scale
+ private static final Property<FastBitmapDrawable, Float> SCALE
+ = new Property<FastBitmapDrawable, Float>(Float.TYPE, "scale") {
+ @Override
+ public Float get(FastBitmapDrawable fastBitmapDrawable) {
+ return fastBitmapDrawable.mScale;
+ }
+
+ @Override
+ public void set(FastBitmapDrawable fastBitmapDrawable, Float value) {
+ fastBitmapDrawable.mScale = value;
+ fastBitmapDrawable.invalidateSelf();
+ }
+ };
+ private ObjectAnimator mScaleAnimation;
+ private float mScale = 1;
+
+ private int mAlpha = 255;
+
+ public FastBitmapDrawable(Bitmap b) {
+ this(b, Color.TRANSPARENT);
+ }
+
+ public FastBitmapDrawable(BitmapInfo info) {
+ this(info.icon, info.color);
+ }
+
+ protected FastBitmapDrawable(Bitmap b, int iconColor) {
+ this(b, iconColor, false);
+ }
+
+ protected FastBitmapDrawable(Bitmap b, int iconColor, boolean isDisabled) {
+ mBitmap = b;
+ mIconColor = iconColor;
+ setFilterBitmap(true);
+ setIsDisabled(isDisabled);
+ }
+
+ @Override
+ public final void draw(Canvas canvas) {
+ if (mScale != 1f) {
+ int count = canvas.save();
+ Rect bounds = getBounds();
+ canvas.scale(mScale, mScale, bounds.exactCenterX(), bounds.exactCenterY());
+ drawInternal(canvas, bounds);
+ canvas.restoreToCount(count);
+ } else {
+ drawInternal(canvas, getBounds());
+ }
+ }
+
+ protected void drawInternal(Canvas canvas, Rect bounds) {
+ canvas.drawBitmap(mBitmap, null, bounds, mPaint);
+ }
+
+ /**
+ * Returns the primary icon color
+ */
+ public int getIconColor() {
+ return mIconColor;
+ }
+
+ /**
+ * Returns if this represents a themed icon
+ */
+ public boolean isThemed() {
+ return false;
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter cf) {
+ mColorFilter = cf;
+ updateFilter();
+ }
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.TRANSLUCENT;
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ if (mAlpha != alpha) {
+ mAlpha = alpha;
+ mPaint.setAlpha(alpha);
+ invalidateSelf();
+ }
+ }
+
+ @Override
+ public void setFilterBitmap(boolean filterBitmap) {
+ mPaint.setFilterBitmap(filterBitmap);
+ mPaint.setAntiAlias(filterBitmap);
+ }
+
+ @Override
+ public int getAlpha() {
+ return mAlpha;
+ }
+
+ public void resetScale() {
+ if (mScaleAnimation != null) {
+ mScaleAnimation.cancel();
+ mScaleAnimation = null;
+ }
+ mScale = 1;
+ invalidateSelf();
+ }
+
+ public float getAnimatedScale() {
+ return mScaleAnimation == null ? 1 : mScale;
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return mBitmap.getWidth();
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return mBitmap.getHeight();
+ }
+
+ @Override
+ public int getMinimumWidth() {
+ return getBounds().width();
+ }
+
+ @Override
+ public int getMinimumHeight() {
+ return getBounds().height();
+ }
+
+ @Override
+ public boolean isStateful() {
+ return true;
+ }
+
+ @Override
+ public ColorFilter getColorFilter() {
+ return mPaint.getColorFilter();
+ }
+
+ @Override
+ protected boolean onStateChange(int[] state) {
+ boolean isPressed = false;
+ for (int s : state) {
+ if (s == android.R.attr.state_pressed) {
+ isPressed = true;
+ break;
+ }
+ }
+ if (mIsPressed != isPressed) {
+ mIsPressed = isPressed;
+
+ if (mScaleAnimation != null) {
+ mScaleAnimation.cancel();
+ mScaleAnimation = null;
+ }
+
+ if (mIsPressed) {
+ // Animate when going to pressed state
+ mScaleAnimation = ObjectAnimator.ofFloat(this, SCALE, PRESSED_SCALE);
+ mScaleAnimation.setDuration(CLICK_FEEDBACK_DURATION);
+ mScaleAnimation.setInterpolator(ACCEL);
+ mScaleAnimation.start();
+ } else {
+ if (isVisible()) {
+ mScaleAnimation = ObjectAnimator.ofFloat(this, SCALE, 1f);
+ mScaleAnimation.setDuration(CLICK_FEEDBACK_DURATION);
+ mScaleAnimation.setInterpolator(DEACCEL);
+ mScaleAnimation.start();
+ } else {
+ mScale = 1f;
+ invalidateSelf();
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public void setIsDisabled(boolean isDisabled) {
+ if (mIsDisabled != isDisabled) {
+ mIsDisabled = isDisabled;
+ updateFilter();
+ }
+ }
+
+ protected boolean isDisabled() {
+ return mIsDisabled;
+ }
+
+ private ColorFilter getDisabledColorFilter() {
+ if (sDisabledFColorFilter == null) {
+ sDisabledFColorFilter = getDisabledFColorFilter(mDisabledAlpha);
+ }
+ return sDisabledFColorFilter;
+ }
+
+ /**
+ * Updates the paint to reflect the current brightness and saturation.
+ */
+ protected void updateFilter() {
+ mPaint.setColorFilter(mIsDisabled ? getDisabledColorFilter() : mColorFilter);
+ invalidateSelf();
+ }
+
+ @Override
+ public ConstantState getConstantState() {
+ return new FastBitmapConstantState(mBitmap, mIconColor, mIsDisabled);
+ }
+
+ public static ColorFilter getDisabledFColorFilter(float disabledAlpha) {
+ ColorMatrix tempBrightnessMatrix = new ColorMatrix();
+ ColorMatrix tempFilterMatrix = new ColorMatrix();
+
+ tempFilterMatrix.setSaturation(1f - DISABLED_DESATURATION);
+ float scale = 1 - DISABLED_BRIGHTNESS;
+ int brightnessI = (int) (255 * DISABLED_BRIGHTNESS);
+ float[] mat = tempBrightnessMatrix.getArray();
+ mat[0] = scale;
+ mat[6] = scale;
+ mat[12] = scale;
+ mat[4] = brightnessI;
+ mat[9] = brightnessI;
+ mat[14] = brightnessI;
+ mat[18] = disabledAlpha;
+ tempFilterMatrix.preConcat(tempBrightnessMatrix);
+ return new ColorMatrixColorFilter(tempBrightnessMatrix);
+ }
+
+ protected static class FastBitmapConstantState extends ConstantState {
+ protected final Bitmap mBitmap;
+ protected final int mIconColor;
+ protected final boolean mIsDisabled;
+
+ public FastBitmapConstantState(Bitmap bitmap, int color, boolean isDisabled) {
+ mBitmap = bitmap;
+ mIconColor = color;
+ mIsDisabled = isDisabled;
+ }
+
+ @Override
+ public FastBitmapDrawable newDrawable() {
+ return new FastBitmapDrawable(mBitmap, mIconColor, mIsDisabled);
+ }
+
+ @Override
+ public int getChangingConfigurations() {
+ return 0;
+ }
+ }
+}
diff --git a/PermissionController/iconloaderlib/src/com/android/launcher3/icons/FixedScaleDrawable.java b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/FixedScaleDrawable.java
new file mode 100644
index 000000000..516965ec2
--- /dev/null
+++ b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/FixedScaleDrawable.java
@@ -0,0 +1,53 @@
+package com.android.launcher3.icons;
+
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.graphics.Canvas;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.DrawableWrapper;
+import android.util.AttributeSet;
+
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * Extension of {@link DrawableWrapper} which scales the child drawables by a fixed amount.
+ */
+public class FixedScaleDrawable extends DrawableWrapper {
+
+ // TODO b/33553066 use the constant defined in MaskableIconDrawable
+ private static final float LEGACY_ICON_SCALE = .7f * .6667f;
+ private float mScaleX, mScaleY;
+
+ public FixedScaleDrawable() {
+ super(new ColorDrawable());
+ mScaleX = LEGACY_ICON_SCALE;
+ mScaleY = LEGACY_ICON_SCALE;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ int saveCount = canvas.save();
+ canvas.scale(mScaleX, mScaleY,
+ getBounds().exactCenterX(), getBounds().exactCenterY());
+ super.draw(canvas);
+ canvas.restoreToCount(saveCount);
+ }
+
+ @Override
+ public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) { }
+
+ @Override
+ public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) { }
+
+ public void setScale(float scale) {
+ float h = getIntrinsicHeight();
+ float w = getIntrinsicWidth();
+ mScaleX = scale * LEGACY_ICON_SCALE;
+ mScaleY = scale * LEGACY_ICON_SCALE;
+ if (h > w && w > 0) {
+ mScaleX *= w / h;
+ } else if (w > h && h > 0) {
+ mScaleY *= h / w;
+ }
+ }
+}
diff --git a/PermissionController/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java
new file mode 100644
index 000000000..17b001642
--- /dev/null
+++ b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.icons;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.RegionIterator;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.util.Log;
+
+import androidx.annotation.ColorInt;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+public class GraphicsUtils {
+
+ private static final String TAG = "GraphicsUtils";
+
+ public static Runnable sOnNewBitmapRunnable = () -> { };
+
+ /**
+ * Set the alpha component of {@code color} to be {@code alpha}. Unlike the support lib version,
+ * it bounds the alpha in valid range instead of throwing an exception to allow for safer
+ * interpolation of color animations
+ */
+ @ColorInt
+ public static int setColorAlphaBound(int color, int alpha) {
+ if (alpha < 0) {
+ alpha = 0;
+ } else if (alpha > 255) {
+ alpha = 255;
+ }
+ return (color & 0x00ffffff) | (alpha << 24);
+ }
+
+ /**
+ * Compresses the bitmap to a byte array for serialization.
+ */
+ public static byte[] flattenBitmap(Bitmap bitmap) {
+ ByteArrayOutputStream out = new ByteArrayOutputStream(getExpectedBitmapSize(bitmap));
+ try {
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
+ out.flush();
+ out.close();
+ return out.toByteArray();
+ } catch (IOException e) {
+ Log.w(TAG, "Could not write bitmap");
+ return null;
+ }
+ }
+
+ /**
+ * Try go guesstimate how much space the icon will take when serialized to avoid unnecessary
+ * allocations/copies during the write (4 bytes per pixel).
+ */
+ static int getExpectedBitmapSize(Bitmap bitmap) {
+ return bitmap.getWidth() * bitmap.getHeight() * 4;
+ }
+
+ public static int getArea(Region r) {
+ RegionIterator itr = new RegionIterator(r);
+ int area = 0;
+ Rect tempRect = new Rect();
+ while (itr.next(tempRect)) {
+ area += tempRect.width() * tempRect.height();
+ }
+ return area;
+ }
+
+ /**
+ * Utility method to track new bitmap creation
+ */
+ public static void noteNewBitmapCreated() {
+ sOnNewBitmapRunnable.run();
+ }
+
+
+ /**
+ * Returns the default path to be used by an icon
+ */
+ public static Path getShapePath(int size) {
+ AdaptiveIconDrawable drawable = new AdaptiveIconDrawable(
+ new ColorDrawable(Color.BLACK), new ColorDrawable(Color.BLACK));
+ drawable.setBounds(0, 0, size, size);
+ return new Path(drawable.getIconMask());
+ }
+
+ /**
+ * Returns the color associated with the attribute
+ */
+ public static int getAttrColor(Context context, int attr) {
+ TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
+ int colorAccent = ta.getColor(0, 0);
+ ta.recycle();
+ return colorAccent;
+ }
+
+ /**
+ * Returns the alpha corresponding to the theme attribute {@param attr}
+ */
+ public static float getFloat(Context context, int attr, float defValue) {
+ TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
+ float value = ta.getFloat(0, defValue);
+ ta.recycle();
+ return value;
+ }
+}
diff --git a/PermissionController/iconloaderlib/src/com/android/launcher3/icons/IconNormalizer.java b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/IconNormalizer.java
new file mode 100644
index 000000000..de39e79fe
--- /dev/null
+++ b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/IconNormalizer.java
@@ -0,0 +1,411 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.icons;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.util.Log;
+
+import java.nio.ByteBuffer;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+public class IconNormalizer {
+
+ private static final String TAG = "IconNormalizer";
+ private static final boolean DEBUG = false;
+ // Ratio of icon visible area to full icon size for a square shaped icon
+ private static final float MAX_SQUARE_AREA_FACTOR = 375.0f / 576;
+ // Ratio of icon visible area to full icon size for a circular shaped icon
+ private static final float MAX_CIRCLE_AREA_FACTOR = 380.0f / 576;
+
+ private static final float CIRCLE_AREA_BY_RECT = (float) Math.PI / 4;
+
+ // Slope used to calculate icon visible area to full icon size for any generic shaped icon.
+ private static final float LINEAR_SCALE_SLOPE =
+ (MAX_CIRCLE_AREA_FACTOR - MAX_SQUARE_AREA_FACTOR) / (1 - CIRCLE_AREA_BY_RECT);
+
+ private static final int MIN_VISIBLE_ALPHA = 40;
+
+ // Shape detection related constants
+ private static final float BOUND_RATIO_MARGIN = .05f;
+ private static final float PIXEL_DIFF_PERCENTAGE_THRESHOLD = 0.005f;
+ private static final float SCALE_NOT_INITIALIZED = 0;
+
+ // Ratio of the diameter of an normalized circular icon to the actual icon size.
+ public static final float ICON_VISIBLE_AREA_FACTOR = 0.92f;
+
+ private final int mMaxSize;
+ private final Bitmap mBitmap;
+ private final Canvas mCanvas;
+ private final Paint mPaintMaskShape;
+ private final Paint mPaintMaskShapeOutline;
+ private final byte[] mPixels;
+
+ private final RectF mAdaptiveIconBounds;
+ private float mAdaptiveIconScale;
+
+ private boolean mEnableShapeDetection;
+
+ // for each y, stores the position of the leftmost x and the rightmost x
+ private final float[] mLeftBorder;
+ private final float[] mRightBorder;
+ private final Rect mBounds;
+ private final Path mShapePath;
+ private final Matrix mMatrix;
+
+ /** package private **/
+ IconNormalizer(Context context, int iconBitmapSize, boolean shapeDetection) {
+ // Use twice the icon size as maximum size to avoid scaling down twice.
+ mMaxSize = iconBitmapSize * 2;
+ mBitmap = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ALPHA_8);
+ mCanvas = new Canvas(mBitmap);
+ mPixels = new byte[mMaxSize * mMaxSize];
+ mLeftBorder = new float[mMaxSize];
+ mRightBorder = new float[mMaxSize];
+ mBounds = new Rect();
+ mAdaptiveIconBounds = new RectF();
+
+ mPaintMaskShape = new Paint();
+ mPaintMaskShape.setColor(Color.RED);
+ mPaintMaskShape.setStyle(Paint.Style.FILL);
+ mPaintMaskShape.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR));
+
+ mPaintMaskShapeOutline = new Paint();
+ mPaintMaskShapeOutline.setStrokeWidth(
+ 2 * context.getResources().getDisplayMetrics().density);
+ mPaintMaskShapeOutline.setStyle(Paint.Style.STROKE);
+ mPaintMaskShapeOutline.setColor(Color.BLACK);
+ mPaintMaskShapeOutline.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+
+ mShapePath = new Path();
+ mMatrix = new Matrix();
+ mAdaptiveIconScale = SCALE_NOT_INITIALIZED;
+ mEnableShapeDetection = shapeDetection;
+ }
+
+ private static float getScale(float hullArea, float boundingArea, float fullArea) {
+ float hullByRect = hullArea / boundingArea;
+ float scaleRequired;
+ if (hullByRect < CIRCLE_AREA_BY_RECT) {
+ scaleRequired = MAX_CIRCLE_AREA_FACTOR;
+ } else {
+ scaleRequired = MAX_SQUARE_AREA_FACTOR + LINEAR_SCALE_SLOPE * (1 - hullByRect);
+ }
+
+ float areaScale = hullArea / fullArea;
+ // Use sqrt of the final ratio as the images is scaled across both width and height.
+ return areaScale > scaleRequired ? (float) Math.sqrt(scaleRequired / areaScale) : 1;
+ }
+
+ /**
+ * @param d Should be AdaptiveIconDrawable
+ * @param size Canvas size to use
+ */
+ @TargetApi(Build.VERSION_CODES.O)
+ public static float normalizeAdaptiveIcon(Drawable d, int size, @Nullable RectF outBounds) {
+ Rect tmpBounds = new Rect(d.getBounds());
+ d.setBounds(0, 0, size, size);
+
+ Path path = ((AdaptiveIconDrawable) d).getIconMask();
+ Region region = new Region();
+ region.setPath(path, new Region(0, 0, size, size));
+
+ Rect hullBounds = region.getBounds();
+ int hullArea = GraphicsUtils.getArea(region);
+
+ if (outBounds != null) {
+ float sizeF = size;
+ outBounds.set(
+ hullBounds.left / sizeF,
+ hullBounds.top / sizeF,
+ 1 - (hullBounds.right / sizeF),
+ 1 - (hullBounds.bottom / sizeF));
+ }
+ d.setBounds(tmpBounds);
+ return getScale(hullArea, hullArea, size * size);
+ }
+
+ /**
+ * Returns if the shape of the icon is same as the path.
+ * For this method to work, the shape path bounds should be in [0,1]x[0,1] bounds.
+ */
+ private boolean isShape(Path maskPath) {
+ // Condition1:
+ // If width and height of the path not close to a square, then the icon shape is
+ // not same as the mask shape.
+ float iconRatio = ((float) mBounds.width()) / mBounds.height();
+ if (Math.abs(iconRatio - 1) > BOUND_RATIO_MARGIN) {
+ if (DEBUG) {
+ Log.d(TAG, "Not same as mask shape because width != height. " + iconRatio);
+ }
+ return false;
+ }
+
+ // Condition 2:
+ // Actual icon (white) and the fitted shape (e.g., circle)(red) XOR operation
+ // should generate transparent image, if the actual icon is equivalent to the shape.
+
+ // Fit the shape within the icon's bounding box
+ mMatrix.reset();
+ mMatrix.setScale(mBounds.width(), mBounds.height());
+ mMatrix.postTranslate(mBounds.left, mBounds.top);
+ maskPath.transform(mMatrix, mShapePath);
+
+ // XOR operation
+ mCanvas.drawPath(mShapePath, mPaintMaskShape);
+
+ // DST_OUT operation around the mask path outline
+ mCanvas.drawPath(mShapePath, mPaintMaskShapeOutline);
+
+ // Check if the result is almost transparent
+ return isTransparentBitmap();
+ }
+
+ /**
+ * Used to determine if certain the bitmap is transparent.
+ */
+ private boolean isTransparentBitmap() {
+ ByteBuffer buffer = ByteBuffer.wrap(mPixels);
+ buffer.rewind();
+ mBitmap.copyPixelsToBuffer(buffer);
+
+ int y = mBounds.top;
+ // buffer position
+ int index = y * mMaxSize;
+ // buffer shift after every row, width of buffer = mMaxSize
+ int rowSizeDiff = mMaxSize - mBounds.right;
+
+ int sum = 0;
+ for (; y < mBounds.bottom; y++) {
+ index += mBounds.left;
+ for (int x = mBounds.left; x < mBounds.right; x++) {
+ if ((mPixels[index] & 0xFF) > MIN_VISIBLE_ALPHA) {
+ sum++;
+ }
+ index++;
+ }
+ index += rowSizeDiff;
+ }
+
+ float percentageDiffPixels = ((float) sum) / (mBounds.width() * mBounds.height());
+ return percentageDiffPixels < PIXEL_DIFF_PERCENTAGE_THRESHOLD;
+ }
+
+ /**
+ * Returns the amount by which the {@param d} should be scaled (in both dimensions) so that it
+ * matches the design guidelines for a launcher icon.
+ *
+ * We first calculate the convex hull of the visible portion of the icon.
+ * This hull then compared with the bounding rectangle of the hull to find how closely it
+ * resembles a circle and a square, by comparing the ratio of the areas. Note that this is not an
+ * ideal solution but it gives satisfactory result without affecting the performance.
+ *
+ * This closeness is used to determine the ratio of hull area to the full icon size.
+ * Refer {@link #MAX_CIRCLE_AREA_FACTOR} and {@link #MAX_SQUARE_AREA_FACTOR}
+ *
+ * @param outBounds optional rect to receive the fraction distance from each edge.
+ */
+ public synchronized float getScale(@NonNull Drawable d, @Nullable RectF outBounds,
+ @Nullable Path path, @Nullable boolean[] outMaskShape) {
+ if (BaseIconFactory.ATLEAST_OREO && d instanceof AdaptiveIconDrawable) {
+ if (mAdaptiveIconScale == SCALE_NOT_INITIALIZED) {
+ mAdaptiveIconScale = normalizeAdaptiveIcon(d, mMaxSize, mAdaptiveIconBounds);
+ }
+ if (outBounds != null) {
+ outBounds.set(mAdaptiveIconBounds);
+ }
+ return mAdaptiveIconScale;
+ }
+ int width = d.getIntrinsicWidth();
+ int height = d.getIntrinsicHeight();
+ if (width <= 0 || height <= 0) {
+ width = width <= 0 || width > mMaxSize ? mMaxSize : width;
+ height = height <= 0 || height > mMaxSize ? mMaxSize : height;
+ } else if (width > mMaxSize || height > mMaxSize) {
+ int max = Math.max(width, height);
+ width = mMaxSize * width / max;
+ height = mMaxSize * height / max;
+ }
+
+ mBitmap.eraseColor(Color.TRANSPARENT);
+ d.setBounds(0, 0, width, height);
+ d.draw(mCanvas);
+
+ ByteBuffer buffer = ByteBuffer.wrap(mPixels);
+ buffer.rewind();
+ mBitmap.copyPixelsToBuffer(buffer);
+
+ // Overall bounds of the visible icon.
+ int topY = -1;
+ int bottomY = -1;
+ int leftX = mMaxSize + 1;
+ int rightX = -1;
+
+ // Create border by going through all pixels one row at a time and for each row find
+ // the first and the last non-transparent pixel. Set those values to mLeftBorder and
+ // mRightBorder and use -1 if there are no visible pixel in the row.
+
+ // buffer position
+ int index = 0;
+ // buffer shift after every row, width of buffer = mMaxSize
+ int rowSizeDiff = mMaxSize - width;
+ // first and last position for any row.
+ int firstX, lastX;
+
+ for (int y = 0; y < height; y++) {
+ firstX = lastX = -1;
+ for (int x = 0; x < width; x++) {
+ if ((mPixels[index] & 0xFF) > MIN_VISIBLE_ALPHA) {
+ if (firstX == -1) {
+ firstX = x;
+ }
+ lastX = x;
+ }
+ index++;
+ }
+ index += rowSizeDiff;
+
+ mLeftBorder[y] = firstX;
+ mRightBorder[y] = lastX;
+
+ // If there is at least one visible pixel, update the overall bounds.
+ if (firstX != -1) {
+ bottomY = y;
+ if (topY == -1) {
+ topY = y;
+ }
+
+ leftX = Math.min(leftX, firstX);
+ rightX = Math.max(rightX, lastX);
+ }
+ }
+
+ if (topY == -1 || rightX == -1) {
+ // No valid pixels found. Do not scale.
+ return 1;
+ }
+
+ convertToConvexArray(mLeftBorder, 1, topY, bottomY);
+ convertToConvexArray(mRightBorder, -1, topY, bottomY);
+
+ // Area of the convex hull
+ float area = 0;
+ for (int y = 0; y < height; y++) {
+ if (mLeftBorder[y] <= -1) {
+ continue;
+ }
+ area += mRightBorder[y] - mLeftBorder[y] + 1;
+ }
+
+ mBounds.left = leftX;
+ mBounds.right = rightX;
+
+ mBounds.top = topY;
+ mBounds.bottom = bottomY;
+
+ if (outBounds != null) {
+ outBounds.set(((float) mBounds.left) / width, ((float) mBounds.top) / height,
+ 1 - ((float) mBounds.right) / width,
+ 1 - ((float) mBounds.bottom) / height);
+ }
+ if (outMaskShape != null && mEnableShapeDetection && outMaskShape.length > 0) {
+ outMaskShape[0] = isShape(path);
+ }
+ // Area of the rectangle required to fit the convex hull
+ float rectArea = (bottomY + 1 - topY) * (rightX + 1 - leftX);
+ return getScale(area, rectArea, width * height);
+ }
+
+ /**
+ * Modifies {@param xCoordinates} to represent a convex border. Fills in all missing values
+ * (except on either ends) with appropriate values.
+ * @param xCoordinates map of x coordinate per y.
+ * @param direction 1 for left border and -1 for right border.
+ * @param topY the first Y position (inclusive) with a valid value.
+ * @param bottomY the last Y position (inclusive) with a valid value.
+ */
+ private static void convertToConvexArray(
+ float[] xCoordinates, int direction, int topY, int bottomY) {
+ int total = xCoordinates.length;
+ // The tangent at each pixel.
+ float[] angles = new float[total - 1];
+
+ int first = topY; // First valid y coordinate
+ int last = -1; // Last valid y coordinate which didn't have a missing value
+
+ float lastAngle = Float.MAX_VALUE;
+
+ for (int i = topY + 1; i <= bottomY; i++) {
+ if (xCoordinates[i] <= -1) {
+ continue;
+ }
+ int start;
+
+ if (lastAngle == Float.MAX_VALUE) {
+ start = first;
+ } else {
+ float currentAngle = (xCoordinates[i] - xCoordinates[last]) / (i - last);
+ start = last;
+ // If this position creates a concave angle, keep moving up until we find a
+ // position which creates a convex angle.
+ if ((currentAngle - lastAngle) * direction < 0) {
+ while (start > first) {
+ start --;
+ currentAngle = (xCoordinates[i] - xCoordinates[start]) / (i - start);
+ if ((currentAngle - angles[start]) * direction >= 0) {
+ break;
+ }
+ }
+ }
+ }
+
+ // Reset from last check
+ lastAngle = (xCoordinates[i] - xCoordinates[start]) / (i - start);
+ // Update all the points from start.
+ for (int j = start; j < i; j++) {
+ angles[j] = lastAngle;
+ xCoordinates[j] = xCoordinates[start] + lastAngle * (j - start);
+ }
+ last = i;
+ }
+ }
+
+ /**
+ * @return The diameter of the normalized circle that fits inside of the square (size x size).
+ */
+ public static int getNormalizedCircleSize(int size) {
+ float area = size * size * MAX_CIRCLE_AREA_FACTOR;
+ return (int) Math.round(Math.sqrt((4 * area) / Math.PI));
+ }
+}
diff --git a/PermissionController/iconloaderlib/src/com/android/launcher3/icons/IconProvider.java b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/IconProvider.java
new file mode 100644
index 000000000..449c0daa5
--- /dev/null
+++ b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/IconProvider.java
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.icons;
+
+import static android.content.Intent.ACTION_DATE_CHANGED;
+import static android.content.Intent.ACTION_TIMEZONE_CHANGED;
+import static android.content.Intent.ACTION_TIME_CHANGED;
+import static android.content.res.Resources.ID_NULL;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.PatternMatcher;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.launcher3.icons.ThemedIconDrawable.ThemeData;
+import com.android.launcher3.util.SafeCloseable;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Map;
+import java.util.function.Supplier;
+
+/**
+ * Class to handle icon loading from different packages
+ */
+public class IconProvider {
+
+ private final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
+ private static final int CONFIG_ICON_MASK_RES_ID = Resources.getSystem().getIdentifier(
+ "config_icon_mask", "string", "android");
+
+ private static final String TAG_ICON = "icon";
+ private static final String ATTR_PACKAGE = "package";
+ private static final String ATTR_DRAWABLE = "drawable";
+
+ private static final String TAG = "IconProvider";
+ private static final boolean DEBUG = false;
+
+ private static final String ICON_METADATA_KEY_PREFIX = ".dynamic_icons";
+
+ private static final String SYSTEM_STATE_SEPARATOR = " ";
+ private static final String THEMED_ICON_MAP_FILE = "grayscale_icon_map";
+
+ private static final Map<String, ThemeData> DISABLED_MAP = Collections.emptyMap();
+
+ private Map<String, ThemeData> mThemedIconMap;
+
+ private final Context mContext;
+ private final ComponentName mCalendar;
+ private final ComponentName mClock;
+
+ static final int ICON_TYPE_DEFAULT = 0;
+ static final int ICON_TYPE_CALENDAR = 1;
+ static final int ICON_TYPE_CLOCK = 2;
+
+ public IconProvider(Context context) {
+ this(context, false);
+ }
+
+ public IconProvider(Context context, boolean supportsIconTheme) {
+ mContext = context;
+ mCalendar = parseComponentOrNull(context, R.string.calendar_component_name);
+ mClock = parseComponentOrNull(context, R.string.clock_component_name);
+ if (!supportsIconTheme) {
+ // Initialize an empty map if theming is not supported
+ mThemedIconMap = DISABLED_MAP;
+ }
+ }
+
+ /**
+ * Enables or disables icon theme support
+ */
+ public void setIconThemeSupported(boolean isSupported) {
+ mThemedIconMap = isSupported ? null : DISABLED_MAP;
+ }
+
+ /**
+ * Adds any modification to the provided systemState for dynamic icons. This system state
+ * is used by caches to check for icon invalidation.
+ */
+ public String getSystemStateForPackage(String systemState, String packageName) {
+ if (mCalendar != null && mCalendar.getPackageName().equals(packageName)) {
+ return systemState + SYSTEM_STATE_SEPARATOR + getDay();
+ } else {
+ return systemState;
+ }
+ }
+
+ /**
+ * Loads the icon for the provided LauncherActivityInfo
+ */
+ public Drawable getIcon(LauncherActivityInfo info, int iconDpi) {
+ return getIconWithOverrides(info.getApplicationInfo().packageName, info.getUser(), iconDpi,
+ () -> info.getIcon(iconDpi));
+ }
+
+ /**
+ * Loads the icon for the provided activity info
+ */
+ public Drawable getIcon(ActivityInfo info) {
+ return getIcon(info, mContext.getResources().getConfiguration().densityDpi);
+ }
+
+ /**
+ * Loads the icon for the provided activity info
+ */
+ public Drawable getIcon(ActivityInfo info, int iconDpi) {
+ return getIconWithOverrides(info.applicationInfo.packageName,
+ UserHandle.getUserHandleForUid(info.applicationInfo.uid),
+ iconDpi, () -> loadActivityInfoIcon(info, iconDpi));
+ }
+
+ private Drawable getIconWithOverrides(String packageName, UserHandle user, int iconDpi,
+ Supplier<Drawable> fallback) {
+ Drawable icon = null;
+
+ int iconType = ICON_TYPE_DEFAULT;
+ if (mCalendar != null && mCalendar.getPackageName().equals(packageName)) {
+ icon = loadCalendarDrawable(iconDpi);
+ iconType = ICON_TYPE_CALENDAR;
+ } else if (mClock != null
+ && mClock.getPackageName().equals(packageName)
+ && Process.myUserHandle().equals(user)) {
+ icon = loadClockDrawable(iconDpi);
+ iconType = ICON_TYPE_CLOCK;
+ }
+ if (icon == null) {
+ icon = fallback.get();
+ iconType = ICON_TYPE_DEFAULT;
+ }
+
+ ThemeData td = getThemedIconMap().get(packageName);
+ return td != null ? td.wrapDrawable(icon, iconType) : icon;
+ }
+
+ private Drawable loadActivityInfoIcon(ActivityInfo ai, int density) {
+ final int iconRes = ai.getIconResource();
+ Drawable icon = null;
+ // Get the preferred density icon from the app's resources
+ if (density != 0 && iconRes != 0) {
+ try {
+ final Resources resources = mContext.getPackageManager()
+ .getResourcesForApplication(ai.applicationInfo);
+ icon = resources.getDrawableForDensity(iconRes, density);
+ } catch (NameNotFoundException | Resources.NotFoundException exc) { }
+ }
+ // Get the default density icon
+ if (icon == null) {
+ icon = ai.loadIcon(mContext.getPackageManager());
+ }
+ return icon;
+ }
+
+ private Map<String, ThemeData> getThemedIconMap() {
+ if (mThemedIconMap != null) {
+ return mThemedIconMap;
+ }
+ ArrayMap<String, ThemeData> map = new ArrayMap<>();
+ try {
+ Resources res = mContext.getResources();
+ int resID = res.getIdentifier(THEMED_ICON_MAP_FILE, "xml", mContext.getPackageName());
+ if (resID != 0) {
+ XmlResourceParser parser = res.getXml(resID);
+ final int depth = parser.getDepth();
+
+ int type;
+
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT);
+
+ while (((type = parser.next()) != XmlPullParser.END_TAG ||
+ parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+ if (TAG_ICON.equals(parser.getName())) {
+ String pkg = parser.getAttributeValue(null, ATTR_PACKAGE);
+ int iconId = parser.getAttributeResourceValue(null, ATTR_DRAWABLE, 0);
+ if (iconId != 0 && !TextUtils.isEmpty(pkg)) {
+ map.put(pkg, new ThemeData(res, iconId));
+ }
+ }
+ }
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to parse icon map", e);
+ }
+ mThemedIconMap = map;
+ return mThemedIconMap;
+ }
+
+ private Drawable loadCalendarDrawable(int iconDpi) {
+ PackageManager pm = mContext.getPackageManager();
+ try {
+ final Bundle metadata = pm.getActivityInfo(
+ mCalendar,
+ PackageManager.GET_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA)
+ .metaData;
+ final Resources resources = pm.getResourcesForApplication(mCalendar.getPackageName());
+ final int id = getDynamicIconId(metadata, resources);
+ if (id != ID_NULL) {
+ if (DEBUG) Log.d(TAG, "Got icon #" + id);
+ return resources.getDrawableForDensity(id, iconDpi, null /* theme */);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ if (DEBUG) {
+ Log.d(TAG, "Could not get activityinfo or resources for package: "
+ + mCalendar.getPackageName());
+ }
+ }
+ return null;
+ }
+
+ private Drawable loadClockDrawable(int iconDpi) {
+ return ClockDrawableWrapper.forPackage(mContext, mClock.getPackageName(), iconDpi);
+ }
+
+ /**
+ * @param metadata metadata of the default activity of Calendar
+ * @param resources from the Calendar package
+ * @return the resource id for today's Calendar icon; 0 if resources cannot be found.
+ */
+ private int getDynamicIconId(Bundle metadata, Resources resources) {
+ if (metadata == null) {
+ return ID_NULL;
+ }
+ String key = mCalendar.getPackageName() + ICON_METADATA_KEY_PREFIX;
+ final int arrayId = metadata.getInt(key, ID_NULL);
+ if (arrayId == ID_NULL) {
+ return ID_NULL;
+ }
+ try {
+ return resources.obtainTypedArray(arrayId).getResourceId(getDay(), ID_NULL);
+ } catch (Resources.NotFoundException e) {
+ if (DEBUG) {
+ Log.d(TAG, "package defines '" + key + "' but corresponding array not found");
+ }
+ return ID_NULL;
+ }
+ }
+
+ /**
+ * @return Today's day of the month, zero-indexed.
+ */
+ static int getDay() {
+ return Calendar.getInstance().get(Calendar.DAY_OF_MONTH) - 1;
+ }
+
+ private static ComponentName parseComponentOrNull(Context context, int resId) {
+ String cn = context.getString(resId);
+ return TextUtils.isEmpty(cn) ? null : ComponentName.unflattenFromString(cn);
+ }
+
+ /**
+ * Returns a string representation of the current system icon state
+ */
+ public String getSystemIconState() {
+ return (CONFIG_ICON_MASK_RES_ID == ID_NULL
+ ? "" : mContext.getResources().getString(CONFIG_ICON_MASK_RES_ID))
+ + (mThemedIconMap == DISABLED_MAP ? ",no-theme" : ",with-theme");
+ }
+
+ /**
+ * Registers a callback to listen for various system dependent icon changes.
+ */
+ public SafeCloseable registerIconChangeListener(IconChangeListener listener, Handler handler) {
+ return new IconChangeReceiver(listener, handler);
+ }
+
+ private class IconChangeReceiver extends BroadcastReceiver implements SafeCloseable {
+
+ private final IconChangeListener mCallback;
+ private String mIconState;
+
+ IconChangeReceiver(IconChangeListener callback, Handler handler) {
+ mCallback = callback;
+ mIconState = getSystemIconState();
+
+
+ IntentFilter packageFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
+ packageFilter.addDataScheme("package");
+ packageFilter.addDataSchemeSpecificPart("android", PatternMatcher.PATTERN_LITERAL);
+ mContext.registerReceiver(this, packageFilter, null, handler);
+
+ if (mCalendar != null || mClock != null) {
+ final IntentFilter filter = new IntentFilter(ACTION_TIMEZONE_CHANGED);
+ if (mCalendar != null) {
+ filter.addAction(Intent.ACTION_TIME_CHANGED);
+ filter.addAction(ACTION_DATE_CHANGED);
+ }
+ mContext.registerReceiver(this, filter, null, handler);
+ }
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ switch (intent.getAction()) {
+ case ACTION_TIMEZONE_CHANGED:
+ if (mClock != null) {
+ mCallback.onAppIconChanged(mClock.getPackageName(), Process.myUserHandle());
+ }
+ // follow through
+ case ACTION_DATE_CHANGED:
+ case ACTION_TIME_CHANGED:
+ if (mCalendar != null) {
+ for (UserHandle user
+ : context.getSystemService(UserManager.class).getUserProfiles()) {
+ mCallback.onAppIconChanged(mCalendar.getPackageName(), user);
+ }
+ }
+ break;
+ case ACTION_OVERLAY_CHANGED: {
+ String newState = getSystemIconState();
+ if (!mIconState.equals(newState)) {
+ mIconState = newState;
+ mCallback.onSystemIconStateChanged(mIconState);
+ }
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void close() {
+ mContext.unregisterReceiver(this);
+ }
+ }
+
+ /**
+ * Listener for receiving icon changes
+ */
+ public interface IconChangeListener {
+
+ /**
+ * Called when the icon for a particular app changes
+ */
+ void onAppIconChanged(String packageName, UserHandle user);
+
+ /**
+ * Called when the global icon state changed, which can typically affect all icons
+ */
+ void onSystemIconStateChanged(String iconState);
+ }
+}
diff --git a/PermissionController/iconloaderlib/src/com/android/launcher3/icons/PlaceHolderIconDrawable.java b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/PlaceHolderIconDrawable.java
new file mode 100644
index 000000000..5f3343e31
--- /dev/null
+++ b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/PlaceHolderIconDrawable.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.icons;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Path;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+
+import androidx.core.graphics.ColorUtils;
+
+/**
+ * Subclass which draws a placeholder icon when the actual icon is not yet loaded
+ */
+public class PlaceHolderIconDrawable extends FastBitmapDrawable {
+
+ // Path in [0, 100] bounds.
+ private final Path mProgressPath;
+
+ public PlaceHolderIconDrawable(BitmapInfo info, Context context) {
+ super(info);
+
+ mProgressPath = GraphicsUtils.getShapePath(100);
+ mPaint.setColor(ColorUtils.compositeColors(
+ GraphicsUtils.getAttrColor(context, R.attr.loadingIconColor), info.color));
+ }
+
+ @Override
+ protected void drawInternal(Canvas canvas, Rect bounds) {
+ int saveCount = canvas.save();
+ canvas.translate(bounds.left, bounds.top);
+ canvas.scale(bounds.width() / 100f, bounds.height() / 100f);
+ canvas.drawPath(mProgressPath, mPaint);
+ canvas.restoreToCount(saveCount);
+ }
+
+ /** Updates this placeholder to {@code newIcon} with animation. */
+ public void animateIconUpdate(Drawable newIcon) {
+ int placeholderColor = mPaint.getColor();
+ int originalAlpha = Color.alpha(placeholderColor);
+
+ ValueAnimator iconUpdateAnimation = ValueAnimator.ofInt(originalAlpha, 0);
+ iconUpdateAnimation.setDuration(375);
+ iconUpdateAnimation.addUpdateListener(valueAnimator -> {
+ int newAlpha = (int) valueAnimator.getAnimatedValue();
+ int newColor = ColorUtils.setAlphaComponent(placeholderColor, newAlpha);
+
+ newIcon.setColorFilter(new PorterDuffColorFilter(newColor, PorterDuff.Mode.SRC_ATOP));
+ });
+ iconUpdateAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ newIcon.setColorFilter(null);
+ }
+ });
+ iconUpdateAnimation.start();
+ }
+
+}
diff --git a/PermissionController/iconloaderlib/src/com/android/launcher3/icons/RoundDrawableWrapper.java b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/RoundDrawableWrapper.java
new file mode 100644
index 000000000..e569c1ea0
--- /dev/null
+++ b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/RoundDrawableWrapper.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.icons;
+
+import android.graphics.Canvas;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.DrawableWrapper;
+
+/**
+ * A drawable which clips rounded corner around a child drawable
+ */
+public class RoundDrawableWrapper extends DrawableWrapper {
+
+ private final RectF mTempRect = new RectF();
+ private final Path mClipPath = new Path();
+ private final float mRoundedCornersRadius;
+
+ public RoundDrawableWrapper(Drawable dr, float radius) {
+ super(dr);
+ mRoundedCornersRadius = radius;
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ mTempRect.set(getBounds());
+ mClipPath.reset();
+ mClipPath.addRoundRect(mTempRect, mRoundedCornersRadius,
+ mRoundedCornersRadius, Path.Direction.CCW);
+ super.onBoundsChange(bounds);
+ }
+
+ @Override
+ public final void draw(Canvas canvas) {
+ int saveCount = canvas.save();
+ canvas.clipPath(mClipPath);
+ super.draw(canvas);
+ canvas.restoreToCount(saveCount);
+ }
+}
diff --git a/PermissionController/iconloaderlib/src/com/android/launcher3/icons/ShadowGenerator.java b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/ShadowGenerator.java
new file mode 100644
index 000000000..e24f353ad
--- /dev/null
+++ b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/ShadowGenerator.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.icons;
+
+import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
+
+import android.graphics.Bitmap;
+import android.graphics.BlurMaskFilter;
+import android.graphics.BlurMaskFilter.Blur;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.RectF;
+
+/**
+ * Utility class to add shadows to bitmaps.
+ */
+public class ShadowGenerator {
+
+ public static final boolean ENABLE_SHADOWS = true;
+
+ public static final float BLUR_FACTOR = 1.5f/48;
+
+ // Percent of actual icon size
+ public static final float KEY_SHADOW_DISTANCE = 1f/48;
+ private static final int KEY_SHADOW_ALPHA = 10;
+ // Percent of actual icon size
+ private static final float HALF_DISTANCE = 0.5f;
+ private static final int AMBIENT_SHADOW_ALPHA = 7;
+
+ private final int mIconSize;
+
+ private final Paint mBlurPaint;
+ private final Paint mDrawPaint;
+ private final BlurMaskFilter mDefaultBlurMaskFilter;
+
+ public ShadowGenerator(int iconSize) {
+ mIconSize = iconSize;
+ mBlurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+ mDrawPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+ mDefaultBlurMaskFilter = new BlurMaskFilter(mIconSize * BLUR_FACTOR, Blur.NORMAL);
+ }
+
+ public synchronized void recreateIcon(Bitmap icon, Canvas out) {
+ recreateIcon(icon, mDefaultBlurMaskFilter, AMBIENT_SHADOW_ALPHA, KEY_SHADOW_ALPHA, out);
+ }
+
+ public synchronized void recreateIcon(Bitmap icon, BlurMaskFilter blurMaskFilter,
+ int ambientAlpha, int keyAlpha, Canvas out) {
+ if (ENABLE_SHADOWS) {
+ int[] offset = new int[2];
+ mBlurPaint.setMaskFilter(blurMaskFilter);
+ Bitmap shadow = icon.extractAlpha(mBlurPaint, offset);
+
+ // Draw ambient shadow
+ mDrawPaint.setAlpha(ambientAlpha);
+ out.drawBitmap(shadow, offset[0], offset[1], mDrawPaint);
+
+ // Draw key shadow
+ mDrawPaint.setAlpha(keyAlpha);
+ out.drawBitmap(shadow, offset[0], offset[1] + KEY_SHADOW_DISTANCE * mIconSize,
+ mDrawPaint);
+ }
+
+ // Draw the icon
+ mDrawPaint.setAlpha(255);
+ out.drawBitmap(icon, 0, 0, mDrawPaint);
+ }
+
+ /**
+ * Returns the minimum amount by which an icon with {@param bounds} should be scaled
+ * so that the shadows do not get clipped.
+ */
+ public static float getScaleForBounds(RectF bounds) {
+ float scale = 1;
+
+ if (ENABLE_SHADOWS) {
+ // For top, left & right, we need same space.
+ float minSide = Math.min(Math.min(bounds.left, bounds.right), bounds.top);
+ if (minSide < BLUR_FACTOR) {
+ scale = (HALF_DISTANCE - BLUR_FACTOR) / (HALF_DISTANCE - minSide);
+ }
+
+ float bottomSpace = BLUR_FACTOR + KEY_SHADOW_DISTANCE;
+ if (bounds.bottom < bottomSpace) {
+ scale = Math.min(scale,
+ (HALF_DISTANCE - bottomSpace) / (HALF_DISTANCE - bounds.bottom));
+ }
+ }
+ return scale;
+ }
+
+ public static class Builder {
+
+ public final RectF bounds = new RectF();
+ public final int color;
+
+ public int ambientShadowAlpha = AMBIENT_SHADOW_ALPHA;
+
+ public float shadowBlur;
+
+ public float keyShadowDistance;
+ public int keyShadowAlpha = KEY_SHADOW_ALPHA;
+ public float radius;
+
+ public Builder(int color) {
+ this.color = color;
+ }
+
+ public Builder setupBlurForSize(int height) {
+ if (ENABLE_SHADOWS) {
+ shadowBlur = height * 1f / 24;
+ keyShadowDistance = height * 1f / 16;
+ } else {
+ shadowBlur = 0;
+ keyShadowDistance = 0;
+ }
+ return this;
+ }
+
+ public Bitmap createPill(int width, int height) {
+ return createPill(width, height, height / 2f);
+ }
+
+ public Bitmap createPill(int width, int height, float r) {
+ radius = r;
+
+ int centerX = Math.round(width / 2f + shadowBlur);
+ int centerY = Math.round(radius + shadowBlur + keyShadowDistance);
+ int center = Math.max(centerX, centerY);
+ bounds.set(0, 0, width, height);
+ bounds.offsetTo(center - width / 2f, center - height / 2f);
+
+ int size = center * 2;
+ return BitmapRenderer.createHardwareBitmap(size, size, this::drawShadow);
+ }
+
+ public void drawShadow(Canvas c) {
+ Paint p = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+ p.setColor(color);
+
+ if (ENABLE_SHADOWS) {
+ // Key shadow
+ p.setShadowLayer(shadowBlur, 0, keyShadowDistance,
+ setColorAlphaBound(Color.BLACK, keyShadowAlpha));
+ c.drawRoundRect(bounds, radius, radius, p);
+
+ // Ambient shadow
+ p.setShadowLayer(shadowBlur, 0, 0,
+ setColorAlphaBound(Color.BLACK, ambientShadowAlpha));
+ c.drawRoundRect(bounds, radius, radius, p);
+ }
+
+ if (Color.alpha(color) < 255) {
+ // Clear any content inside the pill-rect for translucent fill.
+ p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+ p.clearShadowLayer();
+ p.setColor(Color.BLACK);
+ c.drawRoundRect(bounds, radius, radius, p);
+
+ p.setXfermode(null);
+ p.setColor(color);
+ c.drawRoundRect(bounds, radius, radius, p);
+ }
+ }
+ }
+}
diff --git a/PermissionController/iconloaderlib/src/com/android/launcher3/icons/ThemedIconDrawable.java b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/ThemedIconDrawable.java
new file mode 100644
index 000000000..b2e554b7b
--- /dev/null
+++ b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/ThemedIconDrawable.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.icons;
+
+import static android.content.res.Configuration.UI_MODE_NIGHT_MASK;
+import static android.content.res.Configuration.UI_MODE_NIGHT_YES;
+import static android.content.res.Resources.ID_NULL;
+
+import static com.android.launcher3.icons.GraphicsUtils.getExpectedBitmapSize;
+import static com.android.launcher3.icons.IconProvider.ICON_TYPE_CALENDAR;
+import static com.android.launcher3.icons.IconProvider.ICON_TYPE_CLOCK;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.InsetDrawable;
+import android.os.Process;
+import android.os.UserHandle;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.icons.BitmapInfo.Extender;
+import com.android.launcher3.icons.cache.BaseIconCache;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+/**
+ * Class to handle monochrome themed app icons
+ */
+@SuppressWarnings("NewApi")
+public class ThemedIconDrawable extends FastBitmapDrawable {
+
+ public static final String TAG = "ThemedIconDrawable";
+
+ final ThemedBitmapInfo bitmapInfo;
+ final int colorFg, colorBg;
+
+ // The foreground/monochrome icon for the app
+ private final Drawable mMonochromeIcon;
+ private final AdaptiveIconDrawable mBgWrapper;
+ private final Rect mBadgeBounds;
+
+ protected ThemedIconDrawable(ThemedConstantState constantState) {
+ super(constantState.mBitmap, constantState.colorFg, constantState.mIsDisabled);
+ bitmapInfo = constantState.bitmapInfo;
+ colorBg = constantState.colorBg;
+ colorFg = constantState.colorFg;
+
+ mMonochromeIcon = bitmapInfo.mThemeData.loadMonochromeDrawable(colorFg);
+ mBgWrapper = new AdaptiveIconDrawable(new ColorDrawable(colorBg), null);
+ mBadgeBounds = bitmapInfo.mUserBadge == null ? null :
+ new Rect(0, 0, bitmapInfo.mUserBadge.getWidth(), bitmapInfo.mUserBadge.getHeight());
+
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ super.onBoundsChange(bounds);
+ mBgWrapper.setBounds(bounds);
+ mMonochromeIcon.setBounds(bounds);
+ }
+
+ @Override
+ protected void drawInternal(Canvas canvas, Rect bounds) {
+ int count = canvas.save();
+ canvas.scale(bitmapInfo.mNormalizationScale, bitmapInfo.mNormalizationScale,
+ bounds.exactCenterX(), bounds.exactCenterY());
+ mPaint.setColor(colorBg);
+ canvas.drawPath(mBgWrapper.getIconMask(), mPaint);
+ mMonochromeIcon.draw(canvas);
+ canvas.restoreToCount(count);
+ if (mBadgeBounds != null) {
+ canvas.drawBitmap(bitmapInfo.mUserBadge, mBadgeBounds, getBounds(), mPaint);
+ }
+ }
+
+ @Override
+ public boolean isThemed() {
+ return true;
+ }
+
+ @Override
+ public ConstantState getConstantState() {
+ return new ThemedConstantState(bitmapInfo, colorBg, colorFg, mIsDisabled);
+ }
+
+ static class ThemedConstantState extends FastBitmapConstantState {
+
+ final ThemedBitmapInfo bitmapInfo;
+ final int colorFg, colorBg;
+
+ public ThemedConstantState(ThemedBitmapInfo bitmapInfo,
+ int colorBg, int colorFg, boolean isDisabled) {
+ super(bitmapInfo.icon, bitmapInfo.color, isDisabled);
+ this.bitmapInfo = bitmapInfo;
+ this.colorBg = colorBg;
+ this.colorFg = colorFg;
+ }
+
+ @Override
+ public FastBitmapDrawable newDrawable() {
+ return new ThemedIconDrawable(this);
+ }
+ }
+
+ public static class ThemedBitmapInfo extends BitmapInfo {
+
+ final ThemeData mThemeData;
+ final float mNormalizationScale;
+ final Bitmap mUserBadge;
+
+ public ThemedBitmapInfo(Bitmap icon, int color, ThemeData themeData,
+ float normalizationScale, Bitmap userBadge) {
+ super(icon, color);
+ mThemeData = themeData;
+ mNormalizationScale = normalizationScale;
+ mUserBadge = userBadge;
+ }
+
+ @Override
+ public FastBitmapDrawable newThemedIcon(Context context) {
+ int[] colors = getColors(context);
+ FastBitmapDrawable drawable = new ThemedConstantState(this, colors[0], colors[1], false)
+ .newDrawable();
+ drawable.mDisabledAlpha = GraphicsUtils.getFloat(context, R.attr.disabledIconAlpha, 1f);
+ return drawable;
+ }
+
+ @Nullable
+ public byte[] toByteArray() {
+ if (isNullOrLowRes()) {
+ return null;
+ }
+ String resName = mThemeData.mResources.getResourceName(mThemeData.mResID);
+ ByteArrayOutputStream out = new ByteArrayOutputStream(
+ getExpectedBitmapSize(icon) + 3 + resName.length());
+ try {
+ DataOutputStream dos = new DataOutputStream(out);
+ dos.writeByte(TYPE_THEMED);
+ dos.writeFloat(mNormalizationScale);
+ dos.writeUTF(resName);
+ icon.compress(Bitmap.CompressFormat.PNG, 100, dos);
+
+ dos.flush();
+ dos.close();
+ return out.toByteArray();
+ } catch (IOException e) {
+ Log.w(TAG, "Could not write bitmap");
+ return null;
+ }
+ }
+
+ static ThemedBitmapInfo decode(byte[] data, int color,
+ BitmapFactory.Options decodeOptions, UserHandle user, BaseIconCache iconCache,
+ Context context) {
+ try (DataInputStream dis = new DataInputStream(new ByteArrayInputStream(data))) {
+ dis.readByte(); // type
+ float normalizationScale = dis.readFloat();
+
+ String resName = dis.readUTF();
+ int resId = context.getResources()
+ .getIdentifier(resName, "drawable", context.getPackageName());
+ if (resId == ID_NULL) {
+ return null;
+ }
+
+ Bitmap userBadgeBitmap = null;
+ if (!Process.myUserHandle().equals(user)) {
+ try (BaseIconFactory iconFactory = iconCache.getIconFactory()) {
+ userBadgeBitmap = iconFactory.getUserBadgeBitmap(user);
+ }
+ }
+
+ ThemeData themeData = new ThemeData(context.getResources(), resId);
+ Bitmap icon = BitmapFactory.decodeStream(dis, null, decodeOptions);
+ return new ThemedBitmapInfo(icon, color, themeData, normalizationScale,
+ userBadgeBitmap);
+ } catch (IOException e) {
+ return null;
+ }
+ }
+ }
+
+ public static class ThemeData {
+
+ final Resources mResources;
+ final int mResID;
+
+ public ThemeData(Resources resources, int resID) {
+ mResources = resources;
+ mResID = resID;
+ }
+
+ Drawable loadMonochromeDrawable(int accentColor) {
+ Drawable d = mResources.getDrawable(mResID).mutate();
+ d.setTint(accentColor);
+ d = new InsetDrawable(d, .2f);
+ return d;
+ }
+
+ public Drawable wrapDrawable(Drawable original, int iconType) {
+ if (!(original instanceof AdaptiveIconDrawable)) {
+ return original;
+ }
+ AdaptiveIconDrawable aid = (AdaptiveIconDrawable) original;
+ String resourceType = mResources.getResourceTypeName(mResID);
+ if (iconType == ICON_TYPE_CALENDAR && "array".equals(resourceType)) {
+ TypedArray ta = mResources.obtainTypedArray(mResID);
+ int id = ta.getResourceId(IconProvider.getDay(), ID_NULL);
+ ta.recycle();
+ return id == ID_NULL ? original
+ : new ThemedAdaptiveIcon(aid, new ThemeData(mResources, id));
+ } else if (iconType == ICON_TYPE_CLOCK && "array".equals(resourceType)) {
+ ((ClockDrawableWrapper) original).mThemeData = this;
+ return original;
+ } else if ("drawable".equals(resourceType)) {
+ return new ThemedAdaptiveIcon(aid, this);
+ } else {
+ return original;
+ }
+ }
+ }
+
+ static class ThemedAdaptiveIcon extends AdaptiveIconDrawable implements Extender {
+
+ protected final ThemeData mThemeData;
+
+ public ThemedAdaptiveIcon(AdaptiveIconDrawable parent, ThemeData themeData) {
+ super(parent.getBackground(), parent.getForeground());
+ mThemeData = themeData;
+ }
+
+ @Override
+ public BitmapInfo getExtendedInfo(Bitmap bitmap, int color, BaseIconFactory iconFactory,
+ float normalizationScale, UserHandle user) {
+ Bitmap userBadge = Process.myUserHandle().equals(user)
+ ? null : iconFactory.getUserBadgeBitmap(user);
+ return new ThemedBitmapInfo(bitmap, color, mThemeData, normalizationScale, userBadge);
+ }
+
+ @Override
+ public void drawForPersistence(Canvas canvas) {
+ draw(canvas);
+ }
+
+ @Override
+ public Drawable getThemedDrawable(Context context) {
+ int[] colors = getColors(context);
+ Drawable bg = new ColorDrawable(colors[0]);
+ float inset = getExtraInsetFraction() / (1 + 2 * getExtraInsetFraction());
+ Drawable fg = new InsetDrawable(mThemeData.loadMonochromeDrawable(colors[1]), inset);
+ return new AdaptiveIconDrawable(bg, fg);
+ }
+ }
+
+ /**
+ * Get an int array representing background and foreground colors for themed icons
+ */
+ public static int[] getColors(Context context) {
+ Resources res = context.getResources();
+ int[] colors = new int[2];
+ if ((res.getConfiguration().uiMode & UI_MODE_NIGHT_MASK) == UI_MODE_NIGHT_YES) {
+ colors[0] = res.getColor(android.R.color.system_neutral1_800);
+ colors[1] = res.getColor(android.R.color.system_accent1_100);
+ } else {
+ colors[0] = res.getColor(android.R.color.system_accent1_100);
+ colors[1] = res.getColor(android.R.color.system_neutral2_700);
+ }
+ return colors;
+ }
+}
diff --git a/PermissionController/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java
new file mode 100644
index 000000000..d685737c4
--- /dev/null
+++ b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java
@@ -0,0 +1,570 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.icons.cache;
+
+import static com.android.launcher3.icons.BaseIconFactory.getFullResDefaultActivityIcon;
+import static com.android.launcher3.icons.BitmapInfo.LOW_RES_ICON;
+import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
+
+import android.content.ComponentName;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Handler;
+import android.os.LocaleList;
+import android.os.Looper;
+import android.os.Process;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.launcher3.icons.BaseIconFactory;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.SQLiteCacheHelper;
+
+import java.util.AbstractMap;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Supplier;
+
+public abstract class BaseIconCache {
+
+ private static final String TAG = "BaseIconCache";
+ private static final boolean DEBUG = false;
+
+ private static final int INITIAL_ICON_CACHE_CAPACITY = 50;
+
+ // Empty class name is used for storing package default entry.
+ public static final String EMPTY_CLASS_NAME = ".";
+
+ public static class CacheEntry {
+
+ @NonNull
+ public BitmapInfo bitmap = BitmapInfo.LOW_RES_INFO;
+ public CharSequence title = "";
+ public CharSequence contentDescription = "";
+ }
+
+ private final HashMap<UserHandle, BitmapInfo> mDefaultIcons = new HashMap<>();
+
+ protected final Context mContext;
+ protected final PackageManager mPackageManager;
+
+ private final Map<ComponentKey, CacheEntry> mCache;
+ protected final Handler mWorkerHandler;
+
+ protected int mIconDpi;
+ protected IconDB mIconDb;
+ protected LocaleList mLocaleList = LocaleList.getEmptyLocaleList();
+ protected String mSystemState = "";
+
+ private final String mDbFileName;
+ private final Looper mBgLooper;
+
+ public BaseIconCache(Context context, String dbFileName, Looper bgLooper,
+ int iconDpi, int iconPixelSize, boolean inMemoryCache) {
+ mContext = context;
+ mDbFileName = dbFileName;
+ mPackageManager = context.getPackageManager();
+ mBgLooper = bgLooper;
+ mWorkerHandler = new Handler(mBgLooper);
+
+ if (inMemoryCache) {
+ mCache = new HashMap<>(INITIAL_ICON_CACHE_CAPACITY);
+ } else {
+ // Use a dummy cache
+ mCache = new AbstractMap<ComponentKey, CacheEntry>() {
+ @Override
+ public Set<Entry<ComponentKey, CacheEntry>> entrySet() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public CacheEntry put(ComponentKey key, CacheEntry value) {
+ return value;
+ }
+ };
+ }
+
+ updateSystemState();
+ mIconDpi = iconDpi;
+ mIconDb = new IconDB(context, dbFileName, iconPixelSize);
+ }
+
+ /**
+ * Returns the persistable serial number for {@param user}. Subclass should implement proper
+ * caching strategy to avoid making binder call every time.
+ */
+ protected abstract long getSerialNumberForUser(UserHandle user);
+
+ /**
+ * Return true if the given app is an instant app and should be badged appropriately.
+ */
+ protected abstract boolean isInstantApp(ApplicationInfo info);
+
+ /**
+ * Opens and returns an icon factory. The factory is recycled by the caller.
+ */
+ public abstract BaseIconFactory getIconFactory();
+
+ public void updateIconParams(int iconDpi, int iconPixelSize) {
+ mWorkerHandler.post(() -> updateIconParamsBg(iconDpi, iconPixelSize));
+ }
+
+ private synchronized void updateIconParamsBg(int iconDpi, int iconPixelSize) {
+ mIconDpi = iconDpi;
+ mDefaultIcons.clear();
+ mIconDb.clear();
+ mIconDb.close();
+ mIconDb = new IconDB(mContext, mDbFileName, iconPixelSize);
+ mCache.clear();
+ }
+
+ private Drawable getFullResIcon(Resources resources, int iconId) {
+ if (resources != null && iconId != 0) {
+ try {
+ return resources.getDrawableForDensity(iconId, mIconDpi);
+ } catch (Resources.NotFoundException e) { }
+ }
+ return getFullResDefaultActivityIcon(mIconDpi);
+ }
+
+ public Drawable getFullResIcon(String packageName, int iconId) {
+ try {
+ return getFullResIcon(mPackageManager.getResourcesForApplication(packageName), iconId);
+ } catch (PackageManager.NameNotFoundException e) { }
+ return getFullResDefaultActivityIcon(mIconDpi);
+ }
+
+ public Drawable getFullResIcon(ActivityInfo info) {
+ try {
+ return getFullResIcon(mPackageManager.getResourcesForApplication(info.applicationInfo),
+ info.getIconResource());
+ } catch (PackageManager.NameNotFoundException e) { }
+ return getFullResDefaultActivityIcon(mIconDpi);
+ }
+
+ private BitmapInfo makeDefaultIcon(UserHandle user) {
+ try (BaseIconFactory li = getIconFactory()) {
+ return li.makeDefaultIcon(user);
+ }
+ }
+
+ /**
+ * Remove any records for the supplied ComponentName.
+ */
+ public synchronized void remove(ComponentName componentName, UserHandle user) {
+ mCache.remove(new ComponentKey(componentName, user));
+ }
+
+ /**
+ * Remove any records for the supplied package name from memory.
+ */
+ private void removeFromMemCacheLocked(String packageName, UserHandle user) {
+ HashSet<ComponentKey> forDeletion = new HashSet<>();
+ for (ComponentKey key: mCache.keySet()) {
+ if (key.componentName.getPackageName().equals(packageName)
+ && key.user.equals(user)) {
+ forDeletion.add(key);
+ }
+ }
+ for (ComponentKey condemned: forDeletion) {
+ mCache.remove(condemned);
+ }
+ }
+
+ /**
+ * Removes the entries related to the given package in memory and persistent DB.
+ */
+ public synchronized void removeIconsForPkg(String packageName, UserHandle user) {
+ removeFromMemCacheLocked(packageName, user);
+ long userSerial = getSerialNumberForUser(user);
+ mIconDb.delete(
+ IconDB.COLUMN_COMPONENT + " LIKE ? AND " + IconDB.COLUMN_USER + " = ?",
+ new String[]{packageName + "/%", Long.toString(userSerial)});
+ }
+
+ public IconCacheUpdateHandler getUpdateHandler() {
+ updateSystemState();
+ return new IconCacheUpdateHandler(this);
+ }
+
+ /**
+ * Refreshes the system state definition used to check the validity of the cache. It
+ * incorporates all the properties that can affect the cache like the list of enabled locale
+ * and system-version.
+ */
+ private void updateSystemState() {
+ mLocaleList = mContext.getResources().getConfiguration().getLocales();
+ mSystemState = mLocaleList.toLanguageTags() + "," + Build.VERSION.SDK_INT;
+ }
+
+ protected String getIconSystemState(String packageName) {
+ return mSystemState;
+ }
+
+ /**
+ * Adds an entry into the DB and the in-memory cache.
+ * @param replaceExisting if true, it will recreate the bitmap even if it already exists in
+ * the memory. This is useful then the previous bitmap was created using
+ * old data.
+ */
+ @VisibleForTesting
+ public synchronized <T> void addIconToDBAndMemCache(T object, CachingLogic<T> cachingLogic,
+ PackageInfo info, long userSerial, boolean replaceExisting) {
+ UserHandle user = cachingLogic.getUser(object);
+ ComponentName componentName = cachingLogic.getComponent(object);
+
+ final ComponentKey key = new ComponentKey(componentName, user);
+ CacheEntry entry = null;
+ if (!replaceExisting) {
+ entry = mCache.get(key);
+ // We can't reuse the entry if the high-res icon is not present.
+ if (entry == null || entry.bitmap.isNullOrLowRes()) {
+ entry = null;
+ }
+ }
+ if (entry == null) {
+ entry = new CacheEntry();
+ entry.bitmap = cachingLogic.loadIcon(mContext, object);
+ }
+ // Icon can't be loaded from cachingLogic, which implies alternative icon was loaded
+ // (e.g. fallback icon, default icon). So we drop here since there's no point in caching
+ // an empty entry.
+ if (entry.bitmap.isNullOrLowRes()) return;
+ entry.title = cachingLogic.getLabel(object);
+ entry.contentDescription = mPackageManager.getUserBadgedLabel(entry.title, user);
+ if (cachingLogic.addToMemCache()) mCache.put(key, entry);
+
+ ContentValues values = newContentValues(entry.bitmap, entry.title.toString(),
+ componentName.getPackageName(), cachingLogic.getKeywords(object, mLocaleList));
+ addIconToDB(values, componentName, info, userSerial,
+ cachingLogic.getLastUpdatedTime(object, info));
+ }
+
+ /**
+ * Updates {@param values} to contain versioning information and adds it to the DB.
+ * @param values {@link ContentValues} containing icon & title
+ */
+ private void addIconToDB(ContentValues values, ComponentName key,
+ PackageInfo info, long userSerial, long lastUpdateTime) {
+ values.put(IconDB.COLUMN_COMPONENT, key.flattenToString());
+ values.put(IconDB.COLUMN_USER, userSerial);
+ values.put(IconDB.COLUMN_LAST_UPDATED, lastUpdateTime);
+ values.put(IconDB.COLUMN_VERSION, info.versionCode);
+ mIconDb.insertOrReplace(values);
+ }
+
+ public synchronized BitmapInfo getDefaultIcon(UserHandle user) {
+ if (!mDefaultIcons.containsKey(user)) {
+ mDefaultIcons.put(user, makeDefaultIcon(user));
+ }
+ return mDefaultIcons.get(user);
+ }
+
+ public boolean isDefaultIcon(BitmapInfo icon, UserHandle user) {
+ return getDefaultIcon(user).icon == icon.icon;
+ }
+
+ /**
+ * Retrieves the entry from the cache. If the entry is not present, it creates a new entry.
+ * This method is not thread safe, it must be called from a synchronized method.
+ */
+ protected <T> CacheEntry cacheLocked(
+ @NonNull ComponentName componentName, @NonNull UserHandle user,
+ @NonNull Supplier<T> infoProvider, @NonNull CachingLogic<T> cachingLogic,
+ boolean usePackageIcon, boolean useLowResIcon) {
+ assertWorkerThread();
+ ComponentKey cacheKey = new ComponentKey(componentName, user);
+ CacheEntry entry = mCache.get(cacheKey);
+ if (entry == null || (entry.bitmap.isLowRes() && !useLowResIcon)) {
+ entry = new CacheEntry();
+ if (cachingLogic.addToMemCache()) {
+ mCache.put(cacheKey, entry);
+ }
+
+ // Check the DB first.
+ T object = null;
+ boolean providerFetchedOnce = false;
+
+ if (!getEntryFromDB(cacheKey, entry, useLowResIcon)) {
+ object = infoProvider.get();
+ providerFetchedOnce = true;
+
+ if (object != null) {
+ entry.bitmap = cachingLogic.loadIcon(mContext, object);
+ } else {
+ if (usePackageIcon) {
+ CacheEntry packageEntry = getEntryForPackageLocked(
+ componentName.getPackageName(), user, false);
+ if (packageEntry != null) {
+ if (DEBUG) Log.d(TAG, "using package default icon for " +
+ componentName.toShortString());
+ entry.bitmap = packageEntry.bitmap;
+ entry.title = packageEntry.title;
+ entry.contentDescription = packageEntry.contentDescription;
+ }
+ }
+ if (entry.bitmap == null) {
+ if (DEBUG) Log.d(TAG, "using default icon for " +
+ componentName.toShortString());
+ entry.bitmap = getDefaultIcon(user);
+ }
+ }
+ }
+
+ if (TextUtils.isEmpty(entry.title)) {
+ if (object == null && !providerFetchedOnce) {
+ object = infoProvider.get();
+ providerFetchedOnce = true;
+ }
+ if (object != null) {
+ entry.title = cachingLogic.getLabel(object);
+ entry.contentDescription = mPackageManager.getUserBadgedLabel(
+ cachingLogic.getDescription(object, entry.title), user);
+ }
+ }
+ }
+ return entry;
+ }
+
+ public synchronized void clear() {
+ assertWorkerThread();
+ mIconDb.clear();
+ }
+
+ /**
+ * Adds a default package entry in the cache. This entry is not persisted and will be removed
+ * when the cache is flushed.
+ */
+ protected synchronized void cachePackageInstallInfo(String packageName, UserHandle user,
+ Bitmap icon, CharSequence title) {
+ removeFromMemCacheLocked(packageName, user);
+
+ ComponentKey cacheKey = getPackageKey(packageName, user);
+ CacheEntry entry = mCache.get(cacheKey);
+
+ // For icon caching, do not go through DB. Just update the in-memory entry.
+ if (entry == null) {
+ entry = new CacheEntry();
+ }
+ if (!TextUtils.isEmpty(title)) {
+ entry.title = title;
+ }
+ if (icon != null) {
+ BaseIconFactory li = getIconFactory();
+ entry.bitmap = li.createShapedIconBitmap(icon, user);
+ li.close();
+ }
+ if (!TextUtils.isEmpty(title) && entry.bitmap.icon != null) {
+ mCache.put(cacheKey, entry);
+ }
+ }
+
+ private static ComponentKey getPackageKey(String packageName, UserHandle user) {
+ ComponentName cn = new ComponentName(packageName, packageName + EMPTY_CLASS_NAME);
+ return new ComponentKey(cn, user);
+ }
+
+ /**
+ * Gets an entry for the package, which can be used as a fallback entry for various components.
+ * This method is not thread safe, it must be called from a synchronized method.
+ */
+ protected CacheEntry getEntryForPackageLocked(String packageName, UserHandle user,
+ boolean useLowResIcon) {
+ assertWorkerThread();
+ ComponentKey cacheKey = getPackageKey(packageName, user);
+ CacheEntry entry = mCache.get(cacheKey);
+
+ if (entry == null || (entry.bitmap.isLowRes() && !useLowResIcon)) {
+ entry = new CacheEntry();
+ boolean entryUpdated = true;
+
+ // Check the DB first.
+ if (!getEntryFromDB(cacheKey, entry, useLowResIcon)) {
+ try {
+ int flags = Process.myUserHandle().equals(user) ? 0 :
+ PackageManager.GET_UNINSTALLED_PACKAGES;
+ PackageInfo info = mPackageManager.getPackageInfo(packageName, flags);
+ ApplicationInfo appInfo = info.applicationInfo;
+ if (appInfo == null) {
+ throw new NameNotFoundException("ApplicationInfo is null");
+ }
+
+ BaseIconFactory li = getIconFactory();
+ // Load the full res icon for the application, but if useLowResIcon is set, then
+ // only keep the low resolution icon instead of the larger full-sized icon
+ BitmapInfo iconInfo = li.createBadgedIconBitmap(
+ appInfo.loadIcon(mPackageManager), user, appInfo.targetSdkVersion,
+ isInstantApp(appInfo));
+ li.close();
+
+ entry.title = appInfo.loadLabel(mPackageManager);
+ entry.contentDescription = mPackageManager.getUserBadgedLabel(entry.title, user);
+ entry.bitmap = BitmapInfo.of(
+ useLowResIcon ? LOW_RES_ICON : iconInfo.icon, iconInfo.color);
+
+ // Add the icon in the DB here, since these do not get written during
+ // package updates.
+ ContentValues values = newContentValues(
+ iconInfo, entry.title.toString(), packageName, null);
+ addIconToDB(values, cacheKey.componentName, info, getSerialNumberForUser(user),
+ info.lastUpdateTime);
+
+ } catch (NameNotFoundException e) {
+ if (DEBUG) Log.d(TAG, "Application not installed " + packageName);
+ entryUpdated = false;
+ }
+ }
+
+ // Only add a filled-out entry to the cache
+ if (entryUpdated) {
+ mCache.put(cacheKey, entry);
+ }
+ }
+ return entry;
+ }
+
+ protected boolean getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes) {
+ Cursor c = null;
+ try {
+ c = mIconDb.query(
+ lowRes ? IconDB.COLUMNS_LOW_RES : IconDB.COLUMNS_HIGH_RES,
+ IconDB.COLUMN_COMPONENT + " = ? AND " + IconDB.COLUMN_USER + " = ?",
+ new String[]{
+ cacheKey.componentName.flattenToString(),
+ Long.toString(getSerialNumberForUser(cacheKey.user))});
+ if (c.moveToNext()) {
+ // Set the alpha to be 255, so that we never have a wrong color
+ entry.bitmap = BitmapInfo.of(LOW_RES_ICON, setColorAlphaBound(c.getInt(0), 255));
+ entry.title = c.getString(1);
+ if (entry.title == null) {
+ entry.title = "";
+ entry.contentDescription = "";
+ } else {
+ entry.contentDescription = mPackageManager.getUserBadgedLabel(
+ entry.title, cacheKey.user);
+ }
+
+ if (!lowRes) {
+ try {
+ entry.bitmap = BitmapInfo.fromByteArray(
+ c.getBlob(2), entry.bitmap.color, cacheKey.user, this, mContext);
+ } catch (Exception e) {
+ return false;
+ }
+ }
+ return entry.bitmap != null;
+ }
+ } catch (SQLiteException e) {
+ Log.d(TAG, "Error reading icon cache", e);
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns a cursor for an arbitrary query to the cache db
+ */
+ public synchronized Cursor queryCacheDb(String[] columns, String selection,
+ String[] selectionArgs) {
+ return mIconDb.query(columns, selection, selectionArgs);
+ }
+
+ /**
+ * Cache class to store the actual entries on disk
+ */
+ public static final class IconDB extends SQLiteCacheHelper {
+ private static final int RELEASE_VERSION = 31;
+
+ public static final String TABLE_NAME = "icons";
+ public static final String COLUMN_ROWID = "rowid";
+ public static final String COLUMN_COMPONENT = "componentName";
+ public static final String COLUMN_USER = "profileId";
+ public static final String COLUMN_LAST_UPDATED = "lastUpdated";
+ public static final String COLUMN_VERSION = "version";
+ public static final String COLUMN_ICON = "icon";
+ public static final String COLUMN_ICON_COLOR = "icon_color";
+ public static final String COLUMN_LABEL = "label";
+ public static final String COLUMN_SYSTEM_STATE = "system_state";
+ public static final String COLUMN_KEYWORDS = "keywords";
+
+ public static final String[] COLUMNS_HIGH_RES = new String[] {
+ IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL, IconDB.COLUMN_ICON };
+ public static final String[] COLUMNS_LOW_RES = new String[] {
+ IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL };
+
+ public IconDB(Context context, String dbFileName, int iconPixelSize) {
+ super(context, dbFileName, (RELEASE_VERSION << 16) + iconPixelSize, TABLE_NAME);
+ }
+
+ @Override
+ protected void onCreateTable(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " ("
+ + COLUMN_COMPONENT + " TEXT NOT NULL, "
+ + COLUMN_USER + " INTEGER NOT NULL, "
+ + COLUMN_LAST_UPDATED + " INTEGER NOT NULL DEFAULT 0, "
+ + COLUMN_VERSION + " INTEGER NOT NULL DEFAULT 0, "
+ + COLUMN_ICON + " BLOB, "
+ + COLUMN_ICON_COLOR + " INTEGER NOT NULL DEFAULT 0, "
+ + COLUMN_LABEL + " TEXT, "
+ + COLUMN_SYSTEM_STATE + " TEXT, "
+ + COLUMN_KEYWORDS + " TEXT, "
+ + "PRIMARY KEY (" + COLUMN_COMPONENT + ", " + COLUMN_USER + ") "
+ + ");");
+ }
+ }
+
+ private ContentValues newContentValues(BitmapInfo bitmapInfo, String label,
+ String packageName, @Nullable String keywords) {
+ ContentValues values = new ContentValues();
+ values.put(IconDB.COLUMN_ICON, bitmapInfo.toByteArray());
+ values.put(IconDB.COLUMN_ICON_COLOR, bitmapInfo.color);
+
+ values.put(IconDB.COLUMN_LABEL, label);
+ values.put(IconDB.COLUMN_SYSTEM_STATE, getIconSystemState(packageName));
+ values.put(IconDB.COLUMN_KEYWORDS, keywords);
+ return values;
+ }
+
+ private void assertWorkerThread() {
+ if (Looper.myLooper() != mBgLooper) {
+ throw new IllegalStateException("Cache accessed on wrong thread " + Looper.myLooper());
+ }
+ }
+}
diff --git a/PermissionController/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java
new file mode 100644
index 000000000..c12e9dcc1
--- /dev/null
+++ b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.icons.cache;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.os.LocaleList;
+import android.os.UserHandle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.icons.BitmapInfo;
+
+public interface CachingLogic<T> {
+
+ ComponentName getComponent(T object);
+
+ UserHandle getUser(T object);
+
+ CharSequence getLabel(T object);
+
+ default CharSequence getDescription(T object, CharSequence fallback) {
+ return fallback;
+ }
+
+ @NonNull
+ BitmapInfo loadIcon(Context context, T object);
+
+ /**
+ * Provides a option list of keywords to associate with this object
+ */
+ @Nullable
+ default String getKeywords(T object, LocaleList localeList) {
+ return null;
+ }
+
+ /**
+ * Returns the timestamp the entry was last updated in cache.
+ */
+ default long getLastUpdatedTime(T object, PackageInfo info) {
+ return info.lastUpdateTime;
+ }
+
+ /**
+ * Returns true the object should be added to mem cache; otherwise returns false.
+ */
+ default boolean addToMemCache() {
+ return true;
+ }
+}
diff --git a/PermissionController/iconloaderlib/src/com/android/launcher3/icons/cache/HandlerRunnable.java b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/cache/HandlerRunnable.java
new file mode 100644
index 000000000..3dfb3840f
--- /dev/null
+++ b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/cache/HandlerRunnable.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.icons.cache;
+
+import android.os.Handler;
+
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * A runnable that can be posted to a {@link Handler} which can be canceled.
+ */
+public class HandlerRunnable<T> implements Runnable {
+
+ private final Handler mWorkerHandler;
+ private final Supplier<T> mTask;
+
+ private final Executor mCallbackExecutor;
+ private final Consumer<T> mCallback;
+ private final Runnable mEndRunnable;
+
+ private boolean mEnded = false;
+ private boolean mCanceled = false;
+
+ public HandlerRunnable(Handler workerHandler, Supplier<T> task, Executor callbackExecutor,
+ Consumer<T> callback) {
+ this(workerHandler, task, callbackExecutor, callback, () -> { });
+ }
+
+ public HandlerRunnable(Handler workerHandler, Supplier<T> task, Executor callbackExecutor,
+ Consumer<T> callback, Runnable endRunnable) {
+ mWorkerHandler = workerHandler;
+ mTask = task;
+ mCallbackExecutor = callbackExecutor;
+ mCallback = callback;
+ mEndRunnable = endRunnable;
+ }
+
+ /**
+ * Cancels this runnable from being run, only if it has not already run.
+ */
+ public void cancel() {
+ mWorkerHandler.removeCallbacks(this);
+ mCanceled = true;
+ mCallbackExecutor.execute(this::onEnd);
+ }
+
+ @Override
+ public void run() {
+ T value = mTask.get();
+ mCallbackExecutor.execute(() -> {
+ if (!mCanceled) {
+ mCallback.accept(value);
+ }
+ onEnd();
+ });
+ }
+
+ private void onEnd() {
+ if (!mEnded) {
+ mEnded = true;
+ mEndRunnable.run();
+ }
+ }
+}
diff --git a/PermissionController/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java
new file mode 100644
index 000000000..9e1ad7b7b
--- /dev/null
+++ b/PermissionController/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.icons.cache;
+
+import android.content.ComponentName;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.SparseBooleanArray;
+
+import com.android.launcher3.icons.cache.BaseIconCache.IconDB;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.Stack;
+
+/**
+ * Utility class to handle updating the Icon cache
+ */
+public class IconCacheUpdateHandler {
+
+ private static final String TAG = "IconCacheUpdateHandler";
+
+ /**
+ * In this mode, all invalid icons are marked as to-be-deleted in {@link #mItemsToDelete}.
+ * This mode is used for the first run.
+ */
+ private static final boolean MODE_SET_INVALID_ITEMS = true;
+
+ /**
+ * In this mode, any valid icon is removed from {@link #mItemsToDelete}. This is used for all
+ * subsequent runs, which essentially acts as set-union of all valid items.
+ */
+ private static final boolean MODE_CLEAR_VALID_ITEMS = false;
+
+ private static final Object ICON_UPDATE_TOKEN = new Object();
+
+ private final HashMap<String, PackageInfo> mPkgInfoMap;
+ private final BaseIconCache mIconCache;
+
+ private final ArrayMap<UserHandle, Set<String>> mPackagesToIgnore = new ArrayMap<>();
+
+ private final SparseBooleanArray mItemsToDelete = new SparseBooleanArray();
+ private boolean mFilterMode = MODE_SET_INVALID_ITEMS;
+
+ IconCacheUpdateHandler(BaseIconCache cache) {
+ mIconCache = cache;
+
+ mPkgInfoMap = new HashMap<>();
+
+ // Remove all active icon update tasks.
+ mIconCache.mWorkerHandler.removeCallbacksAndMessages(ICON_UPDATE_TOKEN);
+
+ createPackageInfoMap();
+ }
+
+ /**
+ * Sets a package to ignore for processing
+ */
+ public void addPackagesToIgnore(UserHandle userHandle, String packageName) {
+ Set<String> packages = mPackagesToIgnore.get(userHandle);
+ if (packages == null) {
+ packages = new HashSet<>();
+ mPackagesToIgnore.put(userHandle, packages);
+ }
+ packages.add(packageName);
+ }
+
+ private void createPackageInfoMap() {
+ PackageManager pm = mIconCache.mPackageManager;
+ for (PackageInfo info :
+ pm.getInstalledPackages(PackageManager.MATCH_UNINSTALLED_PACKAGES)) {
+ mPkgInfoMap.put(info.packageName, info);
+ }
+ }
+
+ /**
+ * Updates the persistent DB, such that only entries corresponding to {@param apps} remain in
+ * the DB and are updated.
+ * @return The set of packages for which icons have updated.
+ */
+ public <T> void updateIcons(List<T> apps, CachingLogic<T> cachingLogic,
+ OnUpdateCallback onUpdateCallback) {
+ // Filter the list per user
+ HashMap<UserHandle, HashMap<ComponentName, T>> userComponentMap = new HashMap<>();
+ int count = apps.size();
+ for (int i = 0; i < count; i++) {
+ T app = apps.get(i);
+ UserHandle userHandle = cachingLogic.getUser(app);
+ HashMap<ComponentName, T> componentMap = userComponentMap.get(userHandle);
+ if (componentMap == null) {
+ componentMap = new HashMap<>();
+ userComponentMap.put(userHandle, componentMap);
+ }
+ componentMap.put(cachingLogic.getComponent(app), app);
+ }
+
+ for (Entry<UserHandle, HashMap<ComponentName, T>> entry : userComponentMap.entrySet()) {
+ updateIconsPerUser(entry.getKey(), entry.getValue(), cachingLogic, onUpdateCallback);
+ }
+
+ // From now on, clear every valid item from the global valid map.
+ mFilterMode = MODE_CLEAR_VALID_ITEMS;
+ }
+
+ /**
+ * Updates the persistent DB, such that only entries corresponding to {@param apps} remain in
+ * the DB and are updated.
+ * @return The set of packages for which icons have updated.
+ */
+ @SuppressWarnings("unchecked")
+ private <T> void updateIconsPerUser(UserHandle user, HashMap<ComponentName, T> componentMap,
+ CachingLogic<T> cachingLogic, OnUpdateCallback onUpdateCallback) {
+ Set<String> ignorePackages = mPackagesToIgnore.get(user);
+ if (ignorePackages == null) {
+ ignorePackages = Collections.emptySet();
+ }
+ long userSerial = mIconCache.getSerialNumberForUser(user);
+
+ Stack<T> appsToUpdate = new Stack<>();
+
+ try (Cursor c = mIconCache.mIconDb.query(
+ new String[]{IconDB.COLUMN_ROWID, IconDB.COLUMN_COMPONENT,
+ IconDB.COLUMN_LAST_UPDATED, IconDB.COLUMN_VERSION,
+ IconDB.COLUMN_SYSTEM_STATE},
+ IconDB.COLUMN_USER + " = ? ",
+ new String[]{Long.toString(userSerial)})) {
+
+ final int indexComponent = c.getColumnIndex(IconDB.COLUMN_COMPONENT);
+ final int indexLastUpdate = c.getColumnIndex(IconDB.COLUMN_LAST_UPDATED);
+ final int indexVersion = c.getColumnIndex(IconDB.COLUMN_VERSION);
+ final int rowIndex = c.getColumnIndex(IconDB.COLUMN_ROWID);
+ final int systemStateIndex = c.getColumnIndex(IconDB.COLUMN_SYSTEM_STATE);
+
+ while (c.moveToNext()) {
+ String cn = c.getString(indexComponent);
+ ComponentName component = ComponentName.unflattenFromString(cn);
+ PackageInfo info = mPkgInfoMap.get(component.getPackageName());
+
+ int rowId = c.getInt(rowIndex);
+ if (info == null) {
+ if (!ignorePackages.contains(component.getPackageName())) {
+
+ if (mFilterMode == MODE_SET_INVALID_ITEMS) {
+ mIconCache.remove(component, user);
+ mItemsToDelete.put(rowId, true);
+ }
+ }
+ continue;
+ }
+ if ((info.applicationInfo.flags & ApplicationInfo.FLAG_IS_DATA_ONLY) != 0) {
+ // Application is not present
+ continue;
+ }
+
+ long updateTime = c.getLong(indexLastUpdate);
+ int version = c.getInt(indexVersion);
+ T app = componentMap.remove(component);
+ if (version == info.versionCode && updateTime == info.lastUpdateTime
+ && TextUtils.equals(c.getString(systemStateIndex),
+ mIconCache.getIconSystemState(info.packageName))) {
+
+ if (mFilterMode == MODE_CLEAR_VALID_ITEMS) {
+ mItemsToDelete.put(rowId, false);
+ }
+ continue;
+ }
+
+ if (app == null) {
+ if (mFilterMode == MODE_SET_INVALID_ITEMS) {
+ mIconCache.remove(component, user);
+ mItemsToDelete.put(rowId, true);
+ }
+ } else {
+ appsToUpdate.add(app);
+ }
+ }
+ } catch (SQLiteException e) {
+ Log.d(TAG, "Error reading icon cache", e);
+ // Continue updating whatever we have read so far
+ }
+
+ // Insert remaining apps.
+ if (!componentMap.isEmpty() || !appsToUpdate.isEmpty()) {
+ Stack<T> appsToAdd = new Stack<>();
+ appsToAdd.addAll(componentMap.values());
+ new SerializedIconUpdateTask(userSerial, user, appsToAdd, appsToUpdate, cachingLogic,
+ onUpdateCallback).scheduleNext();
+ }
+ }
+
+ /**
+ * Commits all updates as part of the update handler to disk. Not more calls should be made
+ * to this class after this.
+ */
+ public void finish() {
+ // Commit all deletes
+ int deleteCount = 0;
+ StringBuilder queryBuilder = new StringBuilder()
+ .append(IconDB.COLUMN_ROWID)
+ .append(" IN (");
+
+ int count = mItemsToDelete.size();
+ for (int i = 0; i < count; i++) {
+ if (mItemsToDelete.valueAt(i)) {
+ if (deleteCount > 0) {
+ queryBuilder.append(", ");
+ }
+ queryBuilder.append(mItemsToDelete.keyAt(i));
+ deleteCount++;
+ }
+ }
+ queryBuilder.append(')');
+
+ if (deleteCount > 0) {
+ mIconCache.mIconDb.delete(queryBuilder.toString(), null);
+ }
+ }
+
+ /**
+ * A runnable that updates invalid icons and adds missing icons in the DB for the provided
+ * LauncherActivityInfo list. Items are updated/added one at a time, so that the
+ * worker thread doesn't get blocked.
+ */
+ private class SerializedIconUpdateTask<T> implements Runnable {
+ private final long mUserSerial;
+ private final UserHandle mUserHandle;
+ private final Stack<T> mAppsToAdd;
+ private final Stack<T> mAppsToUpdate;
+ private final CachingLogic<T> mCachingLogic;
+ private final HashSet<String> mUpdatedPackages = new HashSet<>();
+ private final OnUpdateCallback mOnUpdateCallback;
+
+ SerializedIconUpdateTask(long userSerial, UserHandle userHandle,
+ Stack<T> appsToAdd, Stack<T> appsToUpdate, CachingLogic<T> cachingLogic,
+ OnUpdateCallback onUpdateCallback) {
+ mUserHandle = userHandle;
+ mUserSerial = userSerial;
+ mAppsToAdd = appsToAdd;
+ mAppsToUpdate = appsToUpdate;
+ mCachingLogic = cachingLogic;
+ mOnUpdateCallback = onUpdateCallback;
+ }
+
+ @Override
+ public void run() {
+ if (!mAppsToUpdate.isEmpty()) {
+ T app = mAppsToUpdate.pop();
+ String pkg = mCachingLogic.getComponent(app).getPackageName();
+ PackageInfo info = mPkgInfoMap.get(pkg);
+
+ mIconCache.addIconToDBAndMemCache(
+ app, mCachingLogic, info, mUserSerial, true /*replace existing*/);
+ mUpdatedPackages.add(pkg);
+
+ if (mAppsToUpdate.isEmpty() && !mUpdatedPackages.isEmpty()) {
+ // No more app to update. Notify callback.
+ mOnUpdateCallback.onPackageIconsUpdated(mUpdatedPackages, mUserHandle);
+ }
+
+ // Let it run one more time.
+ scheduleNext();
+ } else if (!mAppsToAdd.isEmpty()) {
+ T app = mAppsToAdd.pop();
+ PackageInfo info = mPkgInfoMap.get(mCachingLogic.getComponent(app).getPackageName());
+ // We do not check the mPkgInfoMap when generating the mAppsToAdd. Although every
+ // app should have package info, this is not guaranteed by the api
+ if (info != null) {
+ mIconCache.addIconToDBAndMemCache(app, mCachingLogic, info,
+ mUserSerial, false /*replace existing*/);
+ }
+
+ if (!mAppsToAdd.isEmpty()) {
+ scheduleNext();
+ }
+ }
+ }
+
+ public void scheduleNext() {
+ mIconCache.mWorkerHandler.postAtTime(this, ICON_UPDATE_TOKEN,
+ SystemClock.uptimeMillis() + 1);
+ }
+ }
+
+ public interface OnUpdateCallback {
+
+ void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandle user);
+ }
+}
diff --git a/PermissionController/iconloaderlib/src/com/android/launcher3/util/ComponentKey.java b/PermissionController/iconloaderlib/src/com/android/launcher3/util/ComponentKey.java
new file mode 100644
index 000000000..71451031d
--- /dev/null
+++ b/PermissionController/iconloaderlib/src/com/android/launcher3/util/ComponentKey.java
@@ -0,0 +1,84 @@
+package com.android.launcher3.util;
+
+/**
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import android.content.ComponentName;
+import android.os.UserHandle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.Arrays;
+
+public class ComponentKey {
+
+ public final ComponentName componentName;
+ public final UserHandle user;
+
+ private final int mHashCode;
+
+ public ComponentKey(ComponentName componentName, UserHandle user) {
+ if (componentName == null || user == null) {
+ throw new NullPointerException();
+ }
+ this.componentName = componentName;
+ this.user = user;
+ mHashCode = Arrays.hashCode(new Object[] {componentName, user});
+
+ }
+
+ @Override
+ public int hashCode() {
+ return mHashCode;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ ComponentKey other = (ComponentKey) o;
+ return other.componentName.equals(componentName) && other.user.equals(user);
+ }
+
+ /**
+ * Encodes a component key as a string of the form [flattenedComponentString#userId].
+ */
+ @Override
+ public String toString() {
+ return componentName.flattenToString() + "#" + user.hashCode();
+ }
+
+ /**
+ * Parses and returns ComponentKey objected from string representation
+ * Returns null if string is not properly formatted
+ */
+ @Nullable
+ public static ComponentKey fromString(@NonNull String str) {
+ int sep = str.indexOf('#');
+ if (sep < 0 || (sep + 1) >= str.length()) {
+ return null;
+ }
+ ComponentName componentName = ComponentName.unflattenFromString(str.substring(0, sep));
+ if (componentName == null) {
+ return null;
+ }
+ try {
+ return new ComponentKey(componentName,
+ UserHandle.getUserHandleForUid(Integer.parseInt(str.substring(sep + 1))));
+ } catch (NumberFormatException ex) {
+ return null;
+ }
+ }
+} \ No newline at end of file
diff --git a/PermissionController/iconloaderlib/src/com/android/launcher3/util/NoLocaleSQLiteHelper.java b/PermissionController/iconloaderlib/src/com/android/launcher3/util/NoLocaleSQLiteHelper.java
new file mode 100644
index 000000000..fe864a284
--- /dev/null
+++ b/PermissionController/iconloaderlib/src/com/android/launcher3/util/NoLocaleSQLiteHelper.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util;
+
+import static android.database.sqlite.SQLiteDatabase.NO_LOCALIZED_COLLATORS;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.database.DatabaseErrorHandler;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteDatabase.CursorFactory;
+import android.database.sqlite.SQLiteDatabase.OpenParams;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.os.Build;
+
+/**
+ * Extension of {@link SQLiteOpenHelper} which avoids creating default locale table by
+ * A context wrapper which creates databases without support for localized collators.
+ */
+public abstract class NoLocaleSQLiteHelper extends SQLiteOpenHelper {
+
+ private static final boolean ATLEAST_P =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
+
+ public NoLocaleSQLiteHelper(Context context, String name, int version) {
+ super(ATLEAST_P ? context : new NoLocalContext(context), name, null, version);
+ if (ATLEAST_P) {
+ setOpenParams(new OpenParams.Builder().addOpenFlags(NO_LOCALIZED_COLLATORS).build());
+ }
+ }
+
+ private static class NoLocalContext extends ContextWrapper {
+ public NoLocalContext(Context base) {
+ super(base);
+ }
+
+ @Override
+ public SQLiteDatabase openOrCreateDatabase(
+ String name, int mode, CursorFactory factory, DatabaseErrorHandler errorHandler) {
+ return super.openOrCreateDatabase(
+ name, mode | Context.MODE_NO_LOCALIZED_COLLATORS, factory, errorHandler);
+ }
+ }
+}
diff --git a/PermissionController/iconloaderlib/src/com/android/launcher3/util/SQLiteCacheHelper.java b/PermissionController/iconloaderlib/src/com/android/launcher3/util/SQLiteCacheHelper.java
new file mode 100644
index 000000000..49de4bd1b
--- /dev/null
+++ b/PermissionController/iconloaderlib/src/com/android/launcher3/util/SQLiteCacheHelper.java
@@ -0,0 +1,125 @@
+package com.android.launcher3.util;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
+import android.database.sqlite.SQLiteFullException;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Log;
+
+/**
+ * An extension of {@link SQLiteOpenHelper} with utility methods for a single table cache DB.
+ * Any exception during write operations are ignored, and any version change causes a DB reset.
+ */
+public abstract class SQLiteCacheHelper {
+ private static final String TAG = "SQLiteCacheHelper";
+
+ private static final boolean IN_MEMORY_CACHE = false;
+
+ private final String mTableName;
+ private final MySQLiteOpenHelper mOpenHelper;
+
+ private boolean mIgnoreWrites;
+
+ public SQLiteCacheHelper(Context context, String name, int version, String tableName) {
+ if (IN_MEMORY_CACHE) {
+ name = null;
+ }
+ mTableName = tableName;
+ mOpenHelper = new MySQLiteOpenHelper(context, name, version);
+
+ mIgnoreWrites = false;
+ }
+
+ /**
+ * @see SQLiteDatabase#delete(String, String, String[])
+ */
+ public void delete(String whereClause, String[] whereArgs) {
+ if (mIgnoreWrites) {
+ return;
+ }
+ try {
+ mOpenHelper.getWritableDatabase().delete(mTableName, whereClause, whereArgs);
+ } catch (SQLiteFullException e) {
+ onDiskFull(e);
+ } catch (SQLiteException e) {
+ Log.d(TAG, "Ignoring sqlite exception", e);
+ }
+ }
+
+ /**
+ * @see SQLiteDatabase#insertWithOnConflict(String, String, ContentValues, int)
+ */
+ public void insertOrReplace(ContentValues values) {
+ if (mIgnoreWrites) {
+ return;
+ }
+ try {
+ mOpenHelper.getWritableDatabase().insertWithOnConflict(
+ mTableName, null, values, SQLiteDatabase.CONFLICT_REPLACE);
+ } catch (SQLiteFullException e) {
+ onDiskFull(e);
+ } catch (SQLiteException e) {
+ Log.d(TAG, "Ignoring sqlite exception", e);
+ }
+ }
+
+ private void onDiskFull(SQLiteFullException e) {
+ Log.e(TAG, "Disk full, all write operations will be ignored", e);
+ mIgnoreWrites = true;
+ }
+
+ /**
+ * @see SQLiteDatabase#query(String, String[], String, String[], String, String, String)
+ */
+ public Cursor query(String[] columns, String selection, String[] selectionArgs) {
+ return mOpenHelper.getReadableDatabase().query(
+ mTableName, columns, selection, selectionArgs, null, null, null);
+ }
+
+ public void clear() {
+ mOpenHelper.clearDB(mOpenHelper.getWritableDatabase());
+ }
+
+ public void close() {
+ mOpenHelper.close();
+ }
+
+ protected abstract void onCreateTable(SQLiteDatabase db);
+
+ /**
+ * A private inner class to prevent direct DB access.
+ */
+ private class MySQLiteOpenHelper extends NoLocaleSQLiteHelper {
+
+ public MySQLiteOpenHelper(Context context, String name, int version) {
+ super(context, name, version);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ onCreateTable(db);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ if (oldVersion != newVersion) {
+ clearDB(db);
+ }
+ }
+
+ @Override
+ public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ if (oldVersion != newVersion) {
+ clearDB(db);
+ }
+ }
+
+ private void clearDB(SQLiteDatabase db) {
+ db.execSQL("DROP TABLE IF EXISTS " + mTableName);
+ onCreate(db);
+ }
+ }
+}
diff --git a/PermissionController/iconloaderlib/src/com/android/launcher3/util/SafeCloseable.java b/PermissionController/iconloaderlib/src/com/android/launcher3/util/SafeCloseable.java
new file mode 100644
index 000000000..ba8ee04d2
--- /dev/null
+++ b/PermissionController/iconloaderlib/src/com/android/launcher3/util/SafeCloseable.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util;
+
+/**
+ * Extension of closeable which does not throw an exception
+ */
+public interface SafeCloseable extends AutoCloseable {
+
+ @Override
+ void close();
+}
diff --git a/PermissionController/iconloaderlib/src_full_lib/com/android/launcher3/icons/IconFactory.java b/PermissionController/iconloaderlib/src_full_lib/com/android/launcher3/icons/IconFactory.java
new file mode 100644
index 000000000..48f11fde3
--- /dev/null
+++ b/PermissionController/iconloaderlib/src_full_lib/com/android/launcher3/icons/IconFactory.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.icons;
+
+import android.content.Context;
+
+/**
+ * Wrapper class to provide access to {@link BaseIconFactory} and also to provide pool of this class
+ * that are threadsafe.
+ */
+public class IconFactory extends BaseIconFactory {
+
+ private static final Object sPoolSync = new Object();
+ private static IconFactory sPool;
+ private static int sPoolId = 0;
+
+ /**
+ * Return a new Message instance from the global pool. Allows us to
+ * avoid allocating new objects in many cases.
+ */
+ public static IconFactory obtain(Context context) {
+ int poolId;
+ synchronized (sPoolSync) {
+ if (sPool != null) {
+ IconFactory m = sPool;
+ sPool = m.next;
+ m.next = null;
+ return m;
+ }
+ poolId = sPoolId;
+ }
+
+ return new IconFactory(context,
+ context.getResources().getConfiguration().densityDpi,
+ context.getResources().getDimensionPixelSize(R.dimen.default_icon_bitmap_size),
+ poolId);
+ }
+
+ public static void clearPool() {
+ synchronized (sPoolSync) {
+ sPool = null;
+ sPoolId++;
+ }
+ }
+
+ private final int mPoolId;
+
+ private IconFactory next;
+
+ private IconFactory(Context context, int fillResIconDpi, int iconBitmapSize, int poolId) {
+ super(context, fillResIconDpi, iconBitmapSize);
+ mPoolId = poolId;
+ }
+
+ /**
+ * Recycles a LauncherIcons that may be in-use.
+ */
+ public void recycle() {
+ synchronized (sPoolSync) {
+ if (sPoolId != mPoolId) {
+ return;
+ }
+ // Clear any temporary state variables
+ clear();
+
+ next = sPool;
+ sPool = this;
+ }
+ }
+
+ @Override
+ public void close() {
+ recycle();
+ }
+}
diff --git a/PermissionController/iconloaderlib/src_full_lib/com/android/launcher3/icons/SimpleIconCache.java b/PermissionController/iconloaderlib/src_full_lib/com/android/launcher3/icons/SimpleIconCache.java
new file mode 100644
index 000000000..cc4ad7b0b
--- /dev/null
+++ b/PermissionController/iconloaderlib/src_full_lib/com/android/launcher3/icons/SimpleIconCache.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.icons;
+
+import static android.content.Intent.ACTION_MANAGED_PROFILE_ADDED;
+import static android.content.Intent.ACTION_MANAGED_PROFILE_REMOVED;
+
+import android.annotation.TargetApi;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.SparseLongArray;
+
+import com.android.launcher3.icons.cache.BaseIconCache;
+
+/**
+ * Wrapper class to provide access to {@link BaseIconFactory} and also to provide pool of this class
+ * that are threadsafe.
+ */
+@TargetApi(Build.VERSION_CODES.P)
+public class SimpleIconCache extends BaseIconCache {
+
+ private static SimpleIconCache sIconCache = null;
+ private static final Object CACHE_LOCK = new Object();
+
+ private final SparseLongArray mUserSerialMap = new SparseLongArray(2);
+ private final UserManager mUserManager;
+
+ public SimpleIconCache(Context context, String dbFileName, Looper bgLooper, int iconDpi,
+ int iconPixelSize, boolean inMemoryCache) {
+ super(context, dbFileName, bgLooper, iconDpi, iconPixelSize, inMemoryCache);
+ mUserManager = context.getSystemService(UserManager.class);
+
+ // Listen for user cache changes.
+ IntentFilter filter = new IntentFilter(ACTION_MANAGED_PROFILE_ADDED);
+ filter.addAction(ACTION_MANAGED_PROFILE_REMOVED);
+ context.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ resetUserCache();
+ }
+ }, filter, null, new Handler(bgLooper), 0);
+ }
+
+ @Override
+ protected long getSerialNumberForUser(UserHandle user) {
+ synchronized (mUserSerialMap) {
+ int index = mUserSerialMap.indexOfKey(user.getIdentifier());
+ if (index >= 0) {
+ return mUserSerialMap.valueAt(index);
+ }
+ long serial = mUserManager.getSerialNumberForUser(user);
+ mUserSerialMap.put(user.getIdentifier(), serial);
+ return serial;
+ }
+ }
+
+ private void resetUserCache() {
+ synchronized (mUserSerialMap) {
+ mUserSerialMap.clear();
+ }
+ }
+
+ @Override
+ protected boolean isInstantApp(ApplicationInfo info) {
+ return info.isInstantApp();
+ }
+
+ @Override
+ public BaseIconFactory getIconFactory() {
+ return IconFactory.obtain(mContext);
+ }
+
+ public static SimpleIconCache getIconCache(Context context) {
+ synchronized (CACHE_LOCK) {
+ if (sIconCache != null) {
+ return sIconCache;
+ }
+ boolean inMemoryCache =
+ context.getResources().getBoolean(R.bool.simple_cache_enable_im_memory);
+ String dbFileName = context.getString(R.string.cache_db_name);
+
+ HandlerThread bgThread = new HandlerThread("simple-icon-cache");
+ bgThread.start();
+
+ sIconCache = new SimpleIconCache(context.getApplicationContext(), dbFileName,
+ bgThread.getLooper(), context.getResources().getConfiguration().densityDpi,
+ context.getResources().getDimensionPixelSize(R.dimen.default_icon_bitmap_size),
+ inMemoryCache);
+ return sIconCache;
+ }
+ }
+}
diff --git a/PermissionController/tests/mocking/Android.bp b/PermissionController/tests/mocking/Android.bp
index 89d5626e9..20566764f 100644
--- a/PermissionController/tests/mocking/Android.bp
+++ b/PermissionController/tests/mocking/Android.bp
@@ -49,7 +49,7 @@ android_test {
],
static_libs: [
- "iconloader",
+ "iconloader_sc_mainline_prod",
"com.google.android.material_material",
"androidx.transition_transition",
"androidx-constraintlayout_constraintlayout",