diff --git a/Android.bp b/Android.bp
index 95d63f2744c..dbd84eb946e 100644
--- a/Android.bp
+++ b/Android.bp
@@ -458,6 +458,7 @@ android_library {
"launcher-executors-module",
"libGoogleFeed",
"SettingsLib",
+
],
manifest: "AndroidManifest-common.xml",
sdk_version: "current",
@@ -482,6 +483,8 @@ android_app {
static_libs: [
"Launcher3ResLib",
+ "androidx.room_room-runtime",
+ "androidx.room_room-ktx",
],
srcs: [
":launcher-src",
@@ -498,7 +501,10 @@ android_app {
sdk_version: "current",
min_sdk_version: min_launcher3_sdk_version,
target_sdk_version: "current",
- plugins: ["dagger2-compiler"],
+ plugins: [
+ "androidx.room_room-compiler-plugin",
+ "dagger2-compiler",
+ ],
certificate: "platform",
privileged: true,
system_ext_specific: true,
@@ -582,6 +588,7 @@ android_library {
static_libs: [
"QuickstepResLib",
"androidx.room_room-runtime",
+ "androidx.room_room-ktx",
],
plugins: [
"androidx.room_room-compiler-plugin",
@@ -624,10 +631,16 @@ android_library {
static_libs: [
"SystemUI-statsd",
"QuickstepResLib",
+ "androidx.room_room-runtime",
+ "androidx.room_room-ktx",
+ "axquicklook-client",
],
manifest: "quickstep/AndroidManifest.xml",
platform_apis: true,
- plugins: ["dagger2-compiler"],
+ plugins: [
+ "androidx.room_room-compiler-plugin",
+ "dagger2-compiler",
+ ],
min_sdk_version: "current",
// TODO(b/319712088): re-enable use_resource_processor
use_resource_processor: false,
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index ad598818e1f..cc9cca46de1 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -47,6 +47,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 946a454462c..7c54deb39ae 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -650,3 +650,10 @@ flag {
description: "Enables mouse based workflows for interacting with launcher"
bug: "388897603"
}
+
+flag {
+ name: "app_launch_blur"
+ namespace: "launcher"
+ description: "Enable background blur at app launch"
+ bug: "379905951"
+}
diff --git a/crowdin.yml b/crowdin.yml
new file mode 100644
index 00000000000..d650cbe4fd4
--- /dev/null
+++ b/crowdin.yml
@@ -0,0 +1,3 @@
+files:
+ - source: /res/values/alpha_strings.xml
+ translation: /res/values-%android_code%/%original_file_name%
diff --git a/go/quickstep/src/com/android/launcher3/BuildConfig.java b/go/quickstep/src/com/android/launcher3/BuildConfig.java
index 53ccbd30c76..48a083ac436 100644
--- a/go/quickstep/src/com/android/launcher3/BuildConfig.java
+++ b/go/quickstep/src/com/android/launcher3/BuildConfig.java
@@ -24,7 +24,7 @@ public final class BuildConfig {
* Flag to state if the QSB is on the first screen and placed on the top,
* this can be overwritten in other launchers with a different value, if needed.
*/
- public static final boolean QSB_ON_FIRST_SCREEN = true;
+ public static final boolean USE_QUICKSPACE_VIEW = true;
/**
* Flag to control various developer centric features
diff --git a/privapp_whitelist_com.android.launcher3-ext.xml b/privapp_whitelist_com.android.launcher3-ext.xml
index 2101a8c328a..14253824cbb 100644
--- a/privapp_whitelist_com.android.launcher3-ext.xml
+++ b/privapp_whitelist_com.android.launcher3-ext.xml
@@ -19,5 +19,14 @@
+
+
+
+
+
+
+
+
+
diff --git a/proguard.flags b/proguard.flags
index ebf177a9d79..f272875a362 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -51,4 +51,16 @@
# Don't warn about those in case this app is linking against an older
# platform version. We know about them, and they are safe.
-dontwarn android.support.**
--dontwarn org.apache.http.**
\ No newline at end of file
+-dontwarn org.apache.http.**
+
+-keep class com.android.launcher3.icons.** {
+ *;
+}
+
+-keep class com.android.launcher3.data.** {
+ *;
+}
+
+-keep class com.android.launcher3.lineage.trust.** {
+ *;
+}
diff --git a/quickstep/res/drawable/bg_overview_actions_button.xml b/quickstep/res/drawable/bg_overview_actions_button.xml
new file mode 100644
index 00000000000..51e17a86d18
--- /dev/null
+++ b/quickstep/res/drawable/bg_overview_actions_button.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ -
+
+
+
+
+
+
diff --git a/quickstep/res/drawable/ic_clear_all.xml b/quickstep/res/drawable/ic_clear_all.xml
new file mode 100644
index 00000000000..00e8fd42a31
--- /dev/null
+++ b/quickstep/res/drawable/ic_clear_all.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/quickstep/res/drawable/ic_kill_app.xml b/quickstep/res/drawable/ic_kill_app.xml
new file mode 100644
index 00000000000..bb7036a6ac6
--- /dev/null
+++ b/quickstep/res/drawable/ic_kill_app.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
diff --git a/quickstep/res/drawable/ic_lens.xml b/quickstep/res/drawable/ic_lens.xml
new file mode 100644
index 00000000000..e372e83526c
--- /dev/null
+++ b/quickstep/res/drawable/ic_lens.xml
@@ -0,0 +1,5 @@
+
+
+
+
\ No newline at end of file
diff --git a/quickstep/res/drawable/ic_lock_recent.xml b/quickstep/res/drawable/ic_lock_recent.xml
new file mode 100644
index 00000000000..f084b733404
--- /dev/null
+++ b/quickstep/res/drawable/ic_lock_recent.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
diff --git a/quickstep/res/drawable/ic_screenshot.xml b/quickstep/res/drawable/ic_screenshot.xml
index 9ee6c44011d..093ea9fd3e3 100644
--- a/quickstep/res/drawable/ic_screenshot.xml
+++ b/quickstep/res/drawable/ic_screenshot.xml
@@ -12,21 +12,9 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
-
-
-
-
-
\ No newline at end of file
+
+
+
+
+
+
diff --git a/quickstep/res/drawable/ic_unlock_recent.xml b/quickstep/res/drawable/ic_unlock_recent.xml
new file mode 100644
index 00000000000..56a26360629
--- /dev/null
+++ b/quickstep/res/drawable/ic_unlock_recent.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/quickstep/res/drawable/recents_lock_state.xml b/quickstep/res/drawable/recents_lock_state.xml
new file mode 100644
index 00000000000..4b08079adc0
--- /dev/null
+++ b/quickstep/res/drawable/recents_lock_state.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/quickstep/res/drawable/task_menu_item_bg.xml b/quickstep/res/drawable/task_menu_item_bg.xml
index 588fe9ec25a..c95c2ed8308 100644
--- a/quickstep/res/drawable/task_menu_item_bg.xml
+++ b/quickstep/res/drawable/task_menu_item_bg.xml
@@ -15,7 +15,12 @@
limitations under the License.
-->
-
-
-
-
+
+ -
+
+
+
+
+
+
diff --git a/quickstep/res/layout/fallback_recents_activity.xml b/quickstep/res/layout/fallback_recents_activity.xml
index f0ea09c1afe..4adbb282d3d 100644
--- a/quickstep/res/layout/fallback_recents_activity.xml
+++ b/quickstep/res/layout/fallback_recents_activity.xml
@@ -52,5 +52,8 @@
android:id="@+id/overview_actions_view"
layout="@layout/overview_actions_container" />
+
+
diff --git a/quickstep/res/layout/meminfo.xml b/quickstep/res/layout/meminfo.xml
new file mode 100644
index 00000000000..0aa9ba9994b
--- /dev/null
+++ b/quickstep/res/layout/meminfo.xml
@@ -0,0 +1,9 @@
+
+
diff --git a/quickstep/res/layout/overview_actions_container.xml b/quickstep/res/layout/overview_actions_container.xml
index ce54028256e..b4b731b77df 100644
--- a/quickstep/res/layout/overview_actions_container.xml
+++ b/quickstep/res/layout/overview_actions_container.xml
@@ -34,17 +34,46 @@
android:id="@+id/action_screenshot"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_marginHorizontal="6dp"
android:drawableStart="@drawable/ic_screenshot"
- android:text="@string/action_screenshot" />
+ android:text="@string/recents_screenshot_title"
+ android:visibility="gone" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/quickstep/res/layout/overview_panel.xml b/quickstep/res/layout/overview_panel.xml
index 01d675f3717..22e784715cb 100644
--- a/quickstep/res/layout/overview_panel.xml
+++ b/quickstep/res/layout/overview_panel.xml
@@ -29,4 +29,7 @@
android:id="@+id/overview_actions_view"
layout="@layout/overview_actions_container" />
+
+
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index e12efc33133..8dfe5954c32 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -63,4 +63,21 @@
M61.554,32.22C58.4,27.364 55.245,22.502 52.091,17.646C50.045,14.494 47.963,11.299 45.196,8.796C42.43,6.287 38.846,4.514 35.171,4.745C31.945,4.951 28.954,6.686 26.593,8.952C24.232,11.218 22.398,14.008 20.594,16.773C15.715,24.237 10.829,31.702 5.95,39.173C3.632,42.718 1.252,46.419 0.611,50.645C-0.164,55.75 1.924,60.95 5.629,64.345C9.504,67.896 15.781,67.609 20.497,66.635C25.667,65.568 30.722,63.558 35.995,63.565C40.511,63.565 44.875,65.05 49.271,66.13C53.659,67.203 58.375,67.865 62.613,66.267C67.873,64.289 71.651,58.665 71.548,52.892C71.451,47.624 68.503,42.918 65.664,38.536C64.296,36.433 62.928,34.324 61.56,32.22H61.554Z
+
+
+ 300
+
+
+
+ - image/png
+ - image/jpeg
+ - image/webp
+ - image/gif
+ - image/bmp
+ - image/x-ico
+ - image/vnd.wap.wbmp
+ - image/heif
+ - image/heic
+ - image/avif
+
diff --git a/quickstep/res/values/cr_dimens.xml b/quickstep/res/values/cr_dimens.xml
new file mode 100644
index 00000000000..43ae6e85356
--- /dev/null
+++ b/quickstep/res/values/cr_dimens.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+ 72dp
+ 140dp
+ 108dp
+ 300dp
+ 35dp
+ 75dp
+
+
+ 1dp
+ 2dp
+ 3dp
+ 5dp
+
diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml
index 2e4567a055d..6db106cf571 100644
--- a/quickstep/res/values/styles.xml
+++ b/quickstep/res/values/styles.xml
@@ -253,6 +253,7 @@
- 4dp
- @dimen/overview_actions_button_horizontal_padding
+ - 0dp
- 14sp
- @color/overview_button
- @color/overview_button
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 072bdcf3d3b..3b79c8e278a 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -49,6 +49,7 @@
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_APP_TRANSITIONS;
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS;
import static com.android.launcher3.BaseActivity.PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION;
+import static com.android.launcher3.Flags.appLaunchBlur;
import static com.android.launcher3.Flags.refactorTaskbarUiState;
import static com.android.launcher3.Flags.syncAppLaunchWithTaskbarStash;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
@@ -207,13 +208,13 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
*/
public static final int STATUS_BAR_TRANSITION_PRE_DELAY = 96;
- public static final long APP_LAUNCH_DURATION = 500;
+ public static final long APP_LAUNCH_DURATION = 480;
- private static final long APP_LAUNCH_ALPHA_DURATION = 50;
+ private static final long APP_LAUNCH_ALPHA_DURATION = 125;
private static final long APP_LAUNCH_ALPHA_START_DELAY = 25;
public static final int ANIMATION_NAV_FADE_IN_DURATION = 266;
- public static final int ANIMATION_NAV_FADE_OUT_DURATION = 133;
+ public static final int ANIMATION_NAV_FADE_OUT_DURATION = 160;
public static final long ANIMATION_DELAY_NAV_FADE_IN =
APP_LAUNCH_DURATION - ANIMATION_NAV_FADE_IN_DURATION;
public static final Interpolator NAV_FADE_IN_INTERPOLATOR =
@@ -223,7 +224,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
public static final int RECENTS_LAUNCH_DURATION = 336;
private static final int LAUNCHER_RESUME_START_DELAY = 100;
- private static final int CLOSING_TRANSITION_DURATION_MS = 250;
+ private static final int CLOSING_TRANSITION_DURATION_MS = 280;
public static final int SPLIT_LAUNCH_DURATION = 370;
public static final int SPLIT_DIVIDER_ANIM_DURATION = 100;
@@ -240,7 +241,10 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
private static final int MAX_NUM_TASKS = 5;
// Cross-fade duration between App Widget and App when launching from widget.
- private static final int WIDGET_CROSSFADE_DURATION_MILLIS = 125;
+ private static final int WIDGET_CROSSFADE_DURATION_MILLIS = 180;
+
+ private static final float MAX_SCRIM_ALPHA_DARK = 0.8f;
+ private static final float MAX_SCRIM_ALPHA_LIGHT = 0.2f;
protected final QuickstepLauncher mLauncher;
protected final DragLayer mDragLayer;
@@ -250,6 +254,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
private final float mClosingWindowTransY;
private final float mClosingFreeformWindowTransY;
private final float mMaxShadowRadius;
+ private final int mMaxBlurRadius;
private final StartingWindowListener mStartingWindowListener =
new StartingWindowListener(this);
@@ -322,12 +327,13 @@ protected boolean removeEldestEntry(Entry> entry
mSystemUiProxy.setStartingWindowListener(mStartingWindowListener);
}
- mOpeningXInterpolator = AnimationUtils.loadInterpolator(
- launcher, R.interpolator.app_open_x);
- mOpeningInterpolator = AnimationUtils.loadInterpolator(
- launcher, R.interpolator.emphasized_interpolator);
+ mOpeningXInterpolator = new PathInterpolator(0.2f, 0f, 0f, 1f);
+ mOpeningInterpolator = new PathInterpolator(0.2f, 0f, 0f, 1f);
mCoordinateTransfer = new RemoteAnimationCoordinateTransfer(mLauncher);
mLatencyTracker = LatencyTracker.getInstance(launcher);
+
+ mMaxBlurRadius = launcher.getResources().getDimensionPixelSize(
+ R.dimen.max_depth_blur_radius_enhanced);
}
@Override
@@ -396,7 +402,9 @@ public void run() {
IRemoteCallback endCallback = completeRunnableListCallback(onEndCallback, mLauncher);
options.setOnAnimationAbortListener(endCallback);
options.setOnAnimationFinishedListener(endCallback);
- options.setLaunchCookie(StableViewInfo.toLaunchCookie(itemInfo));
+ if (itemInfo != null) {
+ options.setLaunchCookie(StableViewInfo.toLaunchCookie(itemInfo));
+ }
// Prepare taskbar for animation synchronization. This needs to happen here before any
// app transition is created.
@@ -641,13 +649,16 @@ private Pair getLauncherContentAnimator(boolean isAppOpen
ObjectAnimator alpha = ObjectAnimator.ofFloat(appsView, View.ALPHA, alphas);
alpha.setDuration(CONTENT_ALPHA_DURATION);
alpha.setInterpolator(LINEAR);
- appsView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
- alpha.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- appsView.setLayerType(View.LAYER_TYPE_NONE, null);
- }
- });
+
+ if (!mDeviceProfile.getDeviceProperties().isTablet()) {
+ appsView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ alpha.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ appsView.setLayerType(View.LAYER_TYPE_NONE, null);
+ }
+ });
+ }
if (!skipAllAppsScale) {
SCALE_PROPERTY.set(appsView, scales[0]);
@@ -772,8 +783,11 @@ private Animator getOpeningWindowAnimators(View v,
wallpaperTargets, nonAppTargets, MODE_OPENING);
int rotationChange = getRotationChange(appTargets);
Rect windowTargetBounds = getWindowTargetBounds(appTargets, rotationChange);
+ final int displayWidth = mDeviceProfile.getDeviceProperties().getWidthPx();
+ final int displayHeight = mDeviceProfile.getDeviceProperties().getHeightPx();
final int[] bottomInsetPos = new int[]{
mSystemUiProxy.getHomeVisibilityState().getNavbarInsetPosition()};
+ final int[] cachedNavbarInset = new int[]{bottomInsetPos[0]};
final RemoteAnimationTarget target = openingTargets.getFirstAppTarget();
final boolean cropToInset = shouldCropToInset(target);
if (cropToInset) {
@@ -792,6 +806,7 @@ private Animator getOpeningWindowAnimators(View v,
true /* isOpening */);
Rect crop = new Rect();
Matrix matrix = new Matrix();
+ Rect closingTargetCrop = new Rect();
SurfaceTransactionApplier surfaceApplier =
new SurfaceTransactionApplier(floatingView);
@@ -813,6 +828,9 @@ private Animator getOpeningWindowAnimators(View v,
hasSplashScreen = false;
}
+ final int windowIconSize = ResourceUtils.getDimenByName("starting_surface_icon_size",
+ mLauncher.getResources(), 108);
+
AnimOpenProperties prop = new AnimOpenProperties(mLauncher.getResources(), mDeviceProfile,
windowTargetBounds, launcherIconBounds, v, dragLayerBounds[0], dragLayerBounds[1],
hasSplashScreen, floatingView.isDifferentFromAppIcon());
@@ -827,6 +845,9 @@ private Animator getOpeningWindowAnimators(View v,
RectF tmpRectF = new RectF();
Point tmpPos = new Point();
+ final SurfaceControl scrimLayer = addScrimLayer(surfaceApplier, openingTargets);
+ final float scrimAlpha = getScrimAlpha();
+
AnimatorSet animatorSet = new AnimatorSet();
ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
appAnimator.setDuration(APP_LAUNCH_DURATION);
@@ -853,6 +874,9 @@ public void onAnimationEnd(Animator animation) {
if (taskbarInteractor != null) {
taskbarInteractor.showEduOnAppLaunch();
}
+ if (appLaunchBlur()) {
+ resetScrim(surfaceApplier, scrimLayer);
+ }
openingTargets.release();
}
@@ -893,8 +917,9 @@ private boolean newShouldShowEduOnAppLaunch() {
clampToDuration(LINEAR, APP_LAUNCH_ALPHA_START_DELAY, APP_LAUNCH_ALPHA_DURATION,
APP_LAUNCH_DURATION));
+ final Interpolator cornerInterpolator = new PathInterpolator(0.2f, 0f, 0.2f, 1f);
FloatProp mWindowRadius = new FloatProp(initialWindowRadius,
- getWindowCornerRadius(mLauncher), mOpeningInterpolator);
+ getWindowCornerRadius(mLauncher), cornerInterpolator);
FloatProp mShadowRadius = new FloatProp(0, finalShadowRadius,
mOpeningInterpolator);
@@ -908,19 +933,22 @@ private boolean newShouldShowEduOnAppLaunch() {
mOpeningInterpolator);
FloatProp mNavFadeOut = new FloatProp(1f, 0f, clampToDuration(
- NAV_FADE_OUT_INTERPOLATOR, 0, ANIMATION_NAV_FADE_OUT_DURATION,
+ NAV_FADE_OUT_INTERPOLATOR, 20, ANIMATION_NAV_FADE_OUT_DURATION,
APP_LAUNCH_DURATION));
FloatProp mNavFadeIn = new FloatProp(0f, 1f, clampToDuration(
NAV_FADE_IN_INTERPOLATOR, ANIMATION_DELAY_NAV_FADE_IN,
ANIMATION_NAV_FADE_IN_DURATION, APP_LAUNCH_DURATION));
+ FloatProp mBlurRadius = new FloatProp(0f, mMaxBlurRadius, DECELERATE_1_5);
+ FloatProp mBlurScrimAlpha = new FloatProp(0f, scrimAlpha, DECELERATE_1_5);
+
@Override
public void onUpdate(float percent, boolean initOnly) {
- if (cropToInset && bottomInsetPos[0] != mSystemUiProxy.getHomeVisibilityState()
- .getNavbarInsetPosition()) {
- final RemoteAnimationTarget target = openingTargets.getFirstAppTarget();
- bottomInsetPos[0] = mSystemUiProxy.getHomeVisibilityState()
+ if (cropToInset && bottomInsetPos[0] != cachedNavbarInset[0]) {
+ cachedNavbarInset[0] = mSystemUiProxy.getHomeVisibilityState()
.getNavbarInsetPosition();
+ bottomInsetPos[0] = cachedNavbarInset[0];
+ final RemoteAnimationTarget target = openingTargets.getFirstAppTarget();
final Rect bounds = target != null
? target.screenSpaceBounds : windowTargetBounds;
// Animate to above the taskbar.
@@ -928,25 +956,19 @@ public void onUpdate(float percent, boolean initOnly) {
windowTargetBounds.bottom = bottomLevel;
final int endHeight = bottomLevel - bounds.top;
- AnimOpenProperties prop = new AnimOpenProperties(mLauncher.getResources(),
- mDeviceProfile, windowTargetBounds, launcherIconBounds, v,
- dragLayerBounds[0], dragLayerBounds[1], hasSplashScreen,
- floatingView.isDifferentFromAppIcon());
- mCropRectCenterY = new FloatProp(prop.cropCenterYStart, prop.cropCenterYEnd,
- mOpeningInterpolator);
- mCropRectHeight = new FloatProp(prop.cropHeightStart, prop.cropHeightEnd,
- mOpeningInterpolator);
- mDy = new FloatProp(0, prop.dY, mOpeningInterpolator);
- mIconScaleToFitScreen = new FloatProp(prop.initialAppIconScale,
- prop.finalAppIconScale, mOpeningInterpolator);
+ float smallestSize = Math.min(windowTargetBounds.height(),
+ windowTargetBounds.width());
+ float maxIconScale = Math.max(smallestSize / launcherIconBounds.width(),
+ smallestSize / launcherIconBounds.height());
+ float newDY = windowTargetBounds.centerY() - dragLayerBounds[1]
+ - launcherIconBounds.centerY();
float interpolatedPercent = mOpeningInterpolator.getInterpolation(percent);
- mCropRectHeight.value = Utilities.mapRange(interpolatedPercent,
- prop.cropHeightStart, prop.cropHeightEnd);
- mCropRectCenterY.value = Utilities.mapRange(interpolatedPercent,
- prop.cropCenterYStart, prop.cropCenterYEnd);
- mDy.value = Utilities.mapRange(interpolatedPercent, 0, prop.dY);
- mIconScaleToFitScreen.value = Utilities.mapRange(interpolatedPercent,
- prop.initialAppIconScale, prop.finalAppIconScale);
+ mCropRectCenterY.value = windowTargetBounds.centerY();
+ mCropRectHeight.value = windowIconSize
+ + interpolatedPercent * (windowTargetBounds.height() - windowIconSize);
+ mDy.value = interpolatedPercent * newDY;
+ mIconScaleToFitScreen.value = prop.initialAppIconScale
+ + interpolatedPercent * (maxIconScale - prop.initialAppIconScale);
}
// Calculate the size of the scaled icon.
@@ -962,8 +984,8 @@ public void onUpdate(float percent, boolean initOnly) {
final int windowCropWidth = crop.width();
final int windowCropHeight = crop.height();
if (rotationChange != 0) {
- Utilities.rotateBounds(crop, mDeviceProfile.getDeviceProperties().getWidthPx(),
- mDeviceProfile.getDeviceProperties().getHeightPx(), rotationChange);
+ Utilities.rotateBounds(crop, displayWidth,
+ displayHeight, rotationChange);
}
// Scale the size of the icon to match the size of the window crop.
@@ -1010,14 +1032,14 @@ public void onUpdate(float percent, boolean initOnly) {
matrix.setScale(scale, scale);
if (rotationChange == 1) {
matrix.postTranslate(windowTransY0,
- mDeviceProfile.getDeviceProperties().getWidthPx() - (windowTransX0 + scaledCropWidth));
+ displayWidth - (windowTransX0 + scaledCropWidth));
} else if (rotationChange == 2) {
matrix.postTranslate(
- mDeviceProfile.getDeviceProperties().getWidthPx() - (windowTransX0 + scaledCropWidth),
- mDeviceProfile.getDeviceProperties().getHeightPx() - (windowTransY0 + scaledCropHeight));
+ displayWidth - (windowTransX0 + scaledCropWidth),
+ displayHeight - (windowTransY0 + scaledCropHeight));
} else if (rotationChange == 3) {
matrix.postTranslate(
- mDeviceProfile.getDeviceProperties().getHeightPx() - (windowTransY0 + scaledCropHeight),
+ displayHeight - (windowTransY0 + scaledCropHeight),
windowTransX0);
} else {
matrix.postTranslate(windowTransX0, windowTransY0);
@@ -1036,20 +1058,20 @@ public void onUpdate(float percent, boolean initOnly) {
} else {
tmpPos.set(target.position.x, target.position.y);
}
- final Rect crop = new Rect(target.screenSpaceBounds);
- crop.offsetTo(0, 0);
+ closingTargetCrop.set(target.screenSpaceBounds);
+ closingTargetCrop.offsetTo(0, 0);
if ((rotationChange % 2) == 1) {
- int tmp = crop.right;
- crop.right = crop.bottom;
- crop.bottom = tmp;
+ int tmp = closingTargetCrop.right;
+ closingTargetCrop.right = closingTargetCrop.bottom;
+ closingTargetCrop.bottom = tmp;
tmp = tmpPos.x;
tmpPos.x = tmpPos.y;
tmpPos.y = tmp;
}
matrix.setTranslate(tmpPos.x, tmpPos.y);
builder.setMatrix(matrix)
- .setWindowCrop(crop)
+ .setWindowCrop(closingTargetCrop)
.setAlpha(1f);
}
}
@@ -1067,6 +1089,13 @@ public void onUpdate(float percent, boolean initOnly) {
navBuilder.setAlpha(mNavFadeOut.value);
}
}
+
+ if (appLaunchBlur() && scrimLayer != null && scrimLayer.isValid()) {
+ SurfaceProperties builder = transaction.forSurface(scrimLayer);
+ builder.setAlpha(mBlurScrimAlpha.value);
+ builder.setBackgroundBlurRadius((int) mBlurRadius.value);
+ }
+
surfaceApplier.scheduleApply(transaction);
}
};
@@ -1125,6 +1154,8 @@ private Animator getOpeningWindowAnimatorsForWidget(LauncherAppWidgetHostView v,
openingTargets.addReleaseCheck(surfaceApplier);
RemoteAnimationTarget navBarTarget = openingTargets.getNavBarRemoteAnimationTarget();
+ final SurfaceControl scrimLayer = addScrimLayer(surfaceApplier, openingTargets);
+ final float scrimAlpha = getScrimAlpha();
AnimatorSet animatorSet = new AnimatorSet();
ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
@@ -1134,6 +1165,9 @@ private Animator getOpeningWindowAnimatorsForWidget(LauncherAppWidgetHostView v,
appAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
+ if (appLaunchBlur()) {
+ resetScrim(surfaceApplier, scrimLayer);
+ }
openingTargets.release();
}
});
@@ -1142,7 +1176,7 @@ public void onAnimationEnd(Animator animation) {
appAnimator.addUpdateListener(new MultiValueUpdateListener() {
float mAppWindowScale = 1;
final FloatProp mWidgetForegroundAlpha = new FloatProp(1, 0, clampToDuration(
- LINEAR, 0, WIDGET_CROSSFADE_DURATION_MILLIS / 2, APP_LAUNCH_DURATION));
+ DECELERATE_1_5, 0, WIDGET_CROSSFADE_DURATION_MILLIS / 2, APP_LAUNCH_DURATION));
final FloatProp mWidgetFallbackBackgroundAlpha = new FloatProp(0, 1,
clampToDuration(LINEAR, 0, 75, APP_LAUNCH_DURATION));
@@ -1151,8 +1185,9 @@ public void onAnimationEnd(Animator animation) {
WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* delay */,
WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* duration */,
APP_LAUNCH_DURATION));
+ final Interpolator cornerInterpolator = new PathInterpolator(0.2f, 0f, 0.2f, 1f);
final FloatProp mWindowRadius = new FloatProp(initialWindowRadius, finalWindowRadius,
- mOpeningInterpolator);
+ cornerInterpolator);
final FloatProp mCornerRadiusProgress = new FloatProp(0, 1, mOpeningInterpolator);
// Window & widget background positioning bounds
@@ -1213,6 +1248,13 @@ public void onUpdate(float percent, boolean initOnly) {
navBuilder.setAlpha(mNavFadeOut.value);
}
}
+
+ if (appLaunchBlur() && scrimLayer != null && scrimLayer.isValid()) {
+ SurfaceProperties builder = transaction.forSurface(scrimLayer);
+ builder.setAlpha(percent * scrimAlpha);
+ builder.setBackgroundBlurRadius((int) (percent * mMaxBlurRadius));
+ }
+
surfaceApplier.scheduleApply(transaction);
}
});
@@ -1227,6 +1269,59 @@ public void onUpdate(float percent, boolean initOnly) {
return animatorSet;
}
+ private SurfaceControl addScrimLayer(SurfaceTransactionApplier applier,
+ RemoteAnimationTargets targets) {
+ if (!appLaunchBlur()) {
+ return null;
+ }
+
+ RemoteAnimationTarget launcherTarget = null;
+ for (final RemoteAnimationTarget target : targets.unfilteredApps) {
+ if (MODE_CLOSING == target.mode) {
+ launcherTarget = target;
+ break;
+ }
+ }
+
+ SurfaceControl parent = launcherTarget != null ? launcherTarget.leash : null;
+ if (parent == null || !parent.isValid()) {
+ // Parent surface is not ready at the moment. Retry later.
+ return null;
+ }
+ SurfaceControl scrimLayer = new SurfaceControl.Builder()
+ .setName("App launch background scrim")
+ .setCallsite("AppLaunchAnimationRunner")
+ .setEffectLayer()
+ .setOpaque(false)
+ .setHidden(true)
+ .build();
+ final float[] colorComponents = new float[] { 0f, 0f, 0f };
+
+ SurfaceTransaction transaction = new SurfaceTransaction();
+ SurfaceProperties builder = transaction.forSurface(scrimLayer);
+ builder
+ .setColor(colorComponents)
+ .setAlpha(0)
+ .reparent(launcherTarget.leash)
+ .setShow()
+ // Ensure the scrim layer occludes wallpaper
+ .setLayer(1000);
+ applier.scheduleApply(transaction);
+ return scrimLayer;
+ }
+
+ private float getScrimAlpha() {
+ return Utilities.isDarkTheme(mLauncher) ? MAX_SCRIM_ALPHA_DARK : MAX_SCRIM_ALPHA_LIGHT;
+ }
+
+ private void resetScrim(SurfaceTransactionApplier applier, SurfaceControl scrimLayer) {
+ if (scrimLayer != null && scrimLayer.isValid()) {
+ SurfaceTransaction surfaceTransaction = new SurfaceTransaction();
+ surfaceTransaction.getTransaction().remove(scrimLayer);
+ applier.scheduleApply(surfaceTransaction);
+ }
+ }
+
/** Returns animator that controls depth/blur of the background during app/widget opening. */
private Animator getBackgroundAnimator() {
if (Flags.allAppsBlur()) {
@@ -1430,6 +1525,7 @@ protected void unregisterRemoteTransitions() {
mMoveDisplayTransition = null;
}
if (mBackAnimationController != null) {
+ mBackAnimationController.cleanupForDestroy();
mBackAnimationController.unregisterBackCallbacks();
mBackAnimationController.unregisterComponentCallbacks();
mBackAnimationController = null;
@@ -1668,14 +1764,18 @@ private Animator getFallbackClosingWindowAnimators(RemoteAnimationTarget[] appTa
Matrix matrix = new Matrix();
Point tmpPos = new Point();
Rect tmpRect = new Rect();
+ Rect fallbackCrop = new Rect();
+ SurfaceTransaction transaction = new SurfaceTransaction();
ValueAnimator closingAnimator = ValueAnimator.ofFloat(0, 1);
int duration = CLOSING_TRANSITION_DURATION_MS;
float windowCornerRadius = getWindowCornerRadius(mLauncher);
float startShadowRadius = areAllTargetsTranslucent(appTargets) ? 0 : mMaxShadowRadius;
closingAnimator.setDuration(duration);
boolean isFreeform = isFreeformAnimation(appTargets);
- float translateY = isFreeform ? mClosingFreeformWindowTransY : mClosingWindowTransY;
- float endScale = isFreeform ? 0.95f : 1f;
+ float translateY = isFreeform
+ ? mClosingFreeformWindowTransY
+ : mClosingWindowTransY * 1.0f;
+ float endScale = isFreeform ? 0.95f : 0.97f;
Interpolator alphaInterpolator = isFreeform
? clampToDuration(LINEAR, 0, 100, duration)
: clampToDuration(LINEAR, 25, 125, duration);
@@ -1687,7 +1787,6 @@ private Animator getFallbackClosingWindowAnimators(RemoteAnimationTarget[] appTa
@Override
public void onUpdate(float percent, boolean initOnly) {
- SurfaceTransaction transaction = new SurfaceTransaction();
for (int i = appTargets.length - 1; i >= 0; i--) {
RemoteAnimationTarget target = appTargets[i];
SurfaceProperties builder = transaction.forSurface(target.leash);
@@ -1698,14 +1797,14 @@ public void onUpdate(float percent, boolean initOnly) {
tmpPos.set(target.position.x, target.position.y);
}
- final Rect crop = new Rect(target.localBounds);
- crop.offsetTo(0, 0);
+ fallbackCrop.set(target.localBounds);
+ fallbackCrop.offsetTo(0, 0);
if (target.mode == MODE_CLOSING) {
tmpRect.set(target.screenSpaceBounds);
if ((rotationChange % 2) != 0) {
- final int right = crop.right;
- crop.right = crop.bottom;
- crop.bottom = right;
+ final int right = fallbackCrop.right;
+ fallbackCrop.right = fallbackCrop.bottom;
+ fallbackCrop.bottom = right;
}
matrix.setScale(mScale.value, mScale.value,
tmpRect.centerX(),
@@ -1713,14 +1812,14 @@ public void onUpdate(float percent, boolean initOnly) {
matrix.postTranslate(0, mDy.value);
matrix.postTranslate(tmpPos.x, tmpPos.y);
builder.setMatrix(matrix)
- .setWindowCrop(crop)
+ .setWindowCrop(fallbackCrop)
.setAlpha(mAlpha.value)
.setCornerRadius(windowCornerRadius)
.setShadowRadius(mShadowRadius.value);
} else if (target.mode == MODE_OPENING) {
matrix.setTranslate(tmpPos.x, tmpPos.y);
builder.setMatrix(matrix)
- .setWindowCrop(crop)
+ .setWindowCrop(fallbackCrop)
.setAlpha(1f);
}
}
@@ -1844,7 +1943,9 @@ public void onAnimationFinished() {
boolean playWorkspaceReveal = true;
boolean skipAllAppsScale = false;
- if (!playFallBackAnimation) {
+ if (mLauncher.isInState(OVERVIEW)) {
+ playWorkspaceReveal = false;
+ } else if (!playFallBackAnimation) {
rectFSpringAnim = getClosingWindowAnimators(
anim, appTargets, launcherView, new PointF(), startRect,
startWindowCornerRadius);
@@ -2313,7 +2414,7 @@ void transferRectToTargetCoordinate(RemoteAnimationTarget target, RectF currentR
final DeviceProfile profile = mLauncher.getDeviceProfile();
final int rotation = profile.getDeviceProperties().getRotationHint();
final int widthPx = profile.getDeviceProperties().getWidthPx();
- final int heightPx = profile.getDeviceProperties().getWidthPx();
+ final int heightPx = profile.getDeviceProperties().getHeightPx();
final int rotationDelta = toLauncher
? android.util.RotationUtils.deltaRotation(taskRotation, rotation)
@@ -2349,7 +2450,9 @@ private class SpringAnimRunner implements RectFSpringAnim.OnUpdateListener {
private final Rect mWindowStartBounds = new Rect();
private final Rect mWindowOriginalBounds = new Rect();
+ private float mLastCornerRadius = -1f;
private final Rect mTmpRect = new Rect();
+ private final SurfaceTransaction mTransaction = new SurfaceTransaction();
/**
* Constructor for SpringAnimRunner
@@ -2391,16 +2494,19 @@ private class SpringAnimRunner implements RectFSpringAnim.OnUpdateListener {
}
}
+ private static final Interpolator CORNER_LEAD_INTERPOLATOR =
+ new PathInterpolator(0.05f, 0f, 0.1f, 1f);
+
public float getCornerRadius(float progress) {
- return Utilities.mapRange(progress, mStartRadius, mEndRadius);
+ float curvedProgress = CORNER_LEAD_INTERPOLATOR.getInterpolation(progress);
+ return mStartRadius + curvedProgress * (mEndRadius - mStartRadius);
}
@Override
public void onUpdate(RectF currentRectF, float progress) {
- SurfaceTransaction transaction = new SurfaceTransaction();
for (int i = mAppTargets.length - 1; i >= 0; i--) {
RemoteAnimationTarget target = mAppTargets[i];
- SurfaceProperties builder = transaction.forSurface(target.leash);
+ SurfaceProperties builder = mTransaction.forSurface(target.leash);
if (target.localBounds != null) {
mTmpPos.set(target.localBounds.left, target.localBounds.top);
@@ -2438,15 +2544,20 @@ public void onUpdate(RectF currentRectF, float progress) {
builder.setMatrix(mMatrix)
.setWindowCrop(mTmpRect)
- .setAlpha(getWindowAlpha(progress))
- .setCornerRadius(getCornerRadius(progress) / scale);
+ .setAlpha(getWindowAlpha(progress));
+
+ float cornerRadius = getCornerRadius(progress) / scale;
+ if (Math.abs(mLastCornerRadius - cornerRadius) >= 4f || progress >= 1f) {
+ builder.setCornerRadius(cornerRadius);
+ mLastCornerRadius = cornerRadius;
+ }
} else if (target.mode == MODE_OPENING) {
mMatrix.setTranslate(mTmpPos.x, mTmpPos.y);
builder.setMatrix(mMatrix)
.setAlpha(1f);
}
}
- mSurfaceApplier.scheduleApply(transaction);
+ mSurfaceApplier.scheduleApply(mTransaction);
}
protected float getWindowAlpha(float progress) {
diff --git a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
index 05e1bf4d307..c9faa954d06 100644
--- a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -35,6 +35,7 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.Flags;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.FloatingHeaderRow;
@@ -42,6 +43,7 @@
import com.android.launcher3.anim.AlphaUpdateListener;
import com.android.launcher3.keyboard.FocusIndicatorHelper;
import com.android.launcher3.keyboard.FocusIndicatorHelper.SimpleFocusIndicatorHelper;
+import com.android.launcher3.lineage.trust.db.TrustDatabaseHelper;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -148,7 +150,7 @@ public int getExpectedHeight() {
int totalHeight = iconHeight + iconPadding + textHeight + mVerticalPadding * 2;
// Prediction row height will be 4dp bigger than the regular apps in A-Z list when two line
// is not enabled. Otherwise, the extra height will increase by just the textHeight.
- int extraHeight = deviceProfile.inv.enableTwoLinesInAllApps
+ int extraHeight = LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE.get(getContext())
? (textHeight + mTopRowExtraHeight) : mTopRowExtraHeight;
totalHeight += extraHeight;
return getVisibility() == GONE ? 0 : totalHeight + getPaddingTop() + getPaddingBottom();
@@ -189,10 +191,15 @@ public void setPredictedApps(List items) {
applyPredictedApps(items);
}
+ private boolean isAppHidden(ItemInfo itemInfo) {
+ TrustDatabaseHelper instance = TrustDatabaseHelper.getInstance(getContext());
+ return instance != null && instance.isPackageHidden(itemInfo.getTargetPackage());
+ }
+
private void applyPredictedApps(List items) {
mPredictedApps.clear();
mPredictedApps.addAll(items.stream()
- .filter(itemInfo -> itemInfo instanceof WorkspaceItemInfo)
+ .filter(itemInfo -> itemInfo instanceof WorkspaceItemInfo && !isAppHidden(itemInfo))
.map(itemInfo -> (WorkspaceItemInfo) itemInfo).collect(Collectors.toList()));
applyPredictionApps();
}
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
index 7c53360ea46..7a95c9102a6 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
@@ -146,7 +146,7 @@ void showEdu() {
return v != null && v.getTag() != null && ((ItemInfo) v.getTag()).container
!= LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
});
- boolean canMigrateToFirstPage = cellLayout.makeSpaceForHotseatMigration(false);
+ boolean canMigrateToFirstPage = cellLayout != null && cellLayout.makeSpaceForHotseatMigration(false);
if (requiresMigration && canMigrateToFirstPage) {
showDialog();
} else {
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index 4f301828476..647a077b20f 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -27,6 +27,8 @@
import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
import static com.android.launcher3.model.PredictionHelper.getBundleForHotseatPredictions;
import static com.android.launcher3.model.PredictionHelper.getBundleForWidgetPredictions;
+import static com.android.launcher3.util.DisplayController.CHANGE_OVERLAYS;
+import static com.android.launcher3.util.DisplayController.CHANGE_UI_MODE;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.app.StatsManager;
@@ -47,6 +49,7 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.lineage.trust.db.TrustDatabaseHelper;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.InstanceIdSequence;
@@ -54,8 +57,12 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.PredictedContainerInfo;
import com.android.launcher3.model.data.WorkspaceData;
+import com.android.launcher3.LauncherAppState;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.IntSparseArrayMap;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
+import com.android.launcher3.util.DisplayController.Info;
import com.android.quickstep.logging.SettingsChangeLogger;
import com.android.quickstep.logging.StatsLogCompatManager;
import com.android.quickstep.util.ContextualSearchStateManager;
@@ -98,6 +105,7 @@ public class QuickstepModelDelegate extends ModelDelegate {
private final StatsManager mStatsManager;
protected boolean mActive = false;
+ private DisplayInfoChangeListener mDisplayInfoChangeListener;
@Inject
public QuickstepModelDelegate(@ApplicationContext Context context,
@@ -188,6 +196,19 @@ public void modelLoadComplete() {
prefs.put(LAST_SNAPSHOT_TIME_MILLIS, now);
}
+ mDisplayInfoChangeListener = new DisplayInfoChangeListener() {
+ @Override
+ public void onDisplayInfoChanged(Context context, Info info, int flags) {
+ if ((flags & CHANGE_UI_MODE) != 0 || (flags & CHANGE_OVERLAYS) != 0) {
+ Log.d(TAG, "onDisplayInfoChanged " + flags);
+ MODEL_EXECUTOR.execute(() ->
+ LauncherAppState.getInstance(mContext).getIconCache().clearDb());
+ mModel.forceReload();
+ }
+ }
+ };
+ DisplayController.INSTANCE.get(mContext).addChangeListener(mDisplayInfoChangeListener);
+
registerSnapshotLoggingCallback();
}
@@ -247,8 +268,8 @@ private static CollectionInfo getContainer(
"Item info: %s found with invalid container: %s",
info,
containerInfo));
+ return null;
}
- // Allow crash to help debug b/173838775
return (CollectionInfo) containerInfo;
}
return null;
@@ -276,6 +297,10 @@ public void destroy() {
Log.e(TAG, "Failed to unregister snapshot logging callback with StatsManager", e);
}
}
+ if (mDisplayInfoChangeListener != null) {
+ DisplayController.INSTANCE.get(mContext).removeChangeListener(mDisplayInfoChangeListener);
+ mDisplayInfoChangeListener = null;
+ }
destroyPredictors();
}
@@ -297,7 +322,8 @@ private void recreatePredictors() {
mAllPredictionAppsState.registerPredictor(mContext,
new AppPredictionContext.Builder(mContext)
.setUiSurface("home")
- .setPredictedTargetCount(mIDP.numDatabaseAllAppsColumns)
+ .setPredictedTargetCount(mIDP.numDatabaseAllAppsColumns +
+ TrustDatabaseHelper.getInstance(mContext).getTotalPackageHidden())
.build(),
mModel,
PredictionUpdateTask::new);
@@ -330,7 +356,8 @@ private void registerHotseatPredictor(Context context) {
mHotseatPredictionState.registerPredictor(context,
new AppPredictionContext.Builder(context)
.setUiSurface("hotseat")
- .setPredictedTargetCount(mIDP.numDatabaseHotseatIcons)
+ .setPredictedTargetCount(mIDP.numDatabaseHotseatIcons +
+ TrustDatabaseHelper.getInstance(mContext).getTotalPackageHidden())
.setExtras(getBundleForHotseatPredictions(context, mDataModel))
.build(),
mModel, PredictionUpdateTask::new);
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index d70f858ca62..2d8dcdc311c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -16,6 +16,7 @@
package com.android.launcher3.taskbar;
import static android.view.View.AccessibilityDelegate;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
@@ -264,8 +265,6 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
private final Runnable mAutoDim = () -> mTaskbarTransitions.setAutoDim(true);
- private final SettingsCache.OnChangeListener mButtonOrderListener;
-
private final boolean mIsUserUnlocked;
public NavbarButtonsViewController(TaskbarActivityContext context,
@@ -295,9 +294,6 @@ public NavbarButtonsViewController(TaskbarActivityContext context,
|| SUWTheme.equals(GLIF_EXPRESSIVE_LIGHT_THEME);
mIsUserUnlocked = LockedUserState.get(context).isUserUnlocked();
-
- mButtonOrderListener = isEnabled -> getLayoutterForCurrentState().layoutButtons(
- mContext, isA11yButtonPersistent());
}
/**
@@ -314,17 +310,11 @@ protected void setupController() {
DeviceProfile deviceProfile = mContext.getDeviceProfile();
Resources resources = mContext.getResources();
- int setupSize = mControllers.taskbarActivityContext.getSetupWindowSize();
Point p = DimensionUtils.getTaskbarPhoneDimensions(deviceProfile, resources, isPhoneMode,
mContext.isGestureNav());
ViewGroup.LayoutParams navButtonsViewLayoutParams = mNavButtonsView.getLayoutParams();
navButtonsViewLayoutParams.width = p.x;
- if (!mContext.isUserSetupComplete()) {
- // Setup mode in phone mode uses gesture nav.
- navButtonsViewLayoutParams.height = setupSize;
- } else {
- navButtonsViewLayoutParams.height = p.y;
- }
+ navButtonsViewLayoutParams.height = p.y;
mNavButtonsView.setLayoutParams(navButtonsViewLayoutParams);
mIsImeRenderingNavButtons = mContext.imeDrawsImeNavBar();
@@ -530,11 +520,6 @@ private void initButtons(ViewGroup navContainer, ViewGroup endContainer,
mSpace.setOnClickListener(view -> navButtonController.onButtonClick(BUTTON_SPACE, view));
mSpace.setOnLongClickListener(view ->
navButtonController.onButtonLongClick(BUTTON_SPACE, view));
-
- if (android.view.accessibility.Flags.navbarFlipOrderOption()) {
- SettingsCache.INSTANCE.get(mContext).register(
- mButtonOrderChangedUri, mButtonOrderListener);
- }
}
private void notifyRecentsButtonPosition() {
@@ -1047,6 +1032,12 @@ private ImageView addButton(ViewGroup parent, @IdRes int id, @LayoutRes int layo
ImageView buttonView = (ImageView) mContext.getLayoutInflater()
.inflate(layoutId, parent, false);
buttonView.setId(id);
+ if (mContext.isPhoneButtonNavMode()) {
+ ViewGroup.LayoutParams navButtonParams =
+ (ViewGroup.LayoutParams) buttonView.getLayoutParams();
+ navButtonParams.width = MATCH_PARENT;
+ navButtonParams.height = MATCH_PARENT;
+ }
parent.addView(buttonView);
mAllButtons.add(buttonView);
return buttonView;
@@ -1079,8 +1070,6 @@ private void handleSetupUi() {
navButtonsLayoutParams.setMarginEnd(0);
navButtonsLayoutParams.gravity = Gravity.START;
- mControllers.taskbarActivityContext.setTaskbarWindowSize(
- mControllers.taskbarActivityContext.getSetupWindowSize());
// If SUW is on a large screen device that is landscape (or has a square aspect
// ratio) the back button has to be placed accordingly
@@ -1089,20 +1078,10 @@ private void handleSetupUi() {
&& deviceProfile.getDeviceProperties().getAspectRatio() < SQUARE_ASPECT_RATIO_UPPER_BOUND)) {
navButtonsLayoutParams.setMarginStart(
resources.getDimensionPixelSize(R.dimen.taskbar_back_button_suw_start_margin));
- navButtonsViewLayoutParams.bottomMargin = resources.getDimensionPixelSize(
- R.dimen.taskbar_back_button_suw_bottom_margin);
- navButtonsLayoutParams.height = resources.getDimensionPixelSize(
- R.dimen.taskbar_back_button_suw_height);
} else {
int phoneOrPortraitSetupMargin = resources.getDimensionPixelSize(
R.dimen.taskbar_contextual_button_suw_margin);
navButtonsLayoutParams.setMarginStart(phoneOrPortraitSetupMargin);
- navButtonsLayoutParams.bottomMargin = !deviceProfile.getDeviceProperties().isLandscape()
- ? 0
- : phoneOrPortraitSetupMargin - (resources.getDimensionPixelSize(
- R.dimen.taskbar_nav_buttons_size) / 2);
- navButtonsViewLayoutParams.height = resources.getDimensionPixelSize(
- R.dimen.taskbar_contextual_button_suw_height);
}
mNavButtonsView.setLayoutParams(navButtonsViewLayoutParams);
mNavButtonContainer.setLayoutParams(navButtonsLayoutParams);
@@ -1155,9 +1134,6 @@ public void onDestroy() {
mFloatingRotationButton.hide();
mFloatingRotationButton = null;
}
- SettingsCache.INSTANCE.get(mContext).unregister(
- mButtonOrderChangedUri, mButtonOrderListener);
-
moveNavButtonsBackToTaskbarWindow();
mNavButtonContainer.removeAllViews();
mEndContextualContainer.removeAllViews();
diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
index caa23ac9a67..2f83bf2d787 100644
--- a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
@@ -21,6 +21,9 @@
import static com.android.launcher3.EncryptionType.ENCRYPTED;
import static com.android.launcher3.LauncherPrefs.nonRestorableItem;
import static com.android.launcher3.taskbar.TaskbarDesktopExperienceFlags.enableAutoStashConnectedDisplayTaskbar;
+import static com.android.launcher3.taskbar.TaskbarManagerImpl.GESTURE_NAVBAR_HEIGHT_MODE_URI;
+import static com.android.launcher3.taskbar.TaskbarManagerImpl.GESTURE_NAVBAR_LENGTH_MODE_URI;
+import static com.android.launcher3.taskbar.TaskbarManagerImpl.NAVIGATION_BAR_HINT_URI;
import static com.android.launcher3.taskbar.Utilities.getShapedTaskbarRadius;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
@@ -47,6 +50,7 @@
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.MultiValueAlpha;
+import com.android.launcher3.util.SettingsCache;
import com.android.quickstep.NavHandle;
import com.android.quickstep.TopTaskTracker;
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
@@ -87,7 +91,7 @@ public class StashedHandleViewController implements TaskbarControllers.LoggableT
private final LauncherPrefs mPrefs;
private final StashedHandleView mStashedHandleView;
private int mStashedHandleWidth;
- private final int mStashedHandleHeight;
+ private int mStashedHandleHeight;
@Nullable
private RegionSamplingHelper mRegionSamplingHelper;
private final MultiValueAlpha mTaskbarStashedHandleAlpha;
@@ -126,27 +130,64 @@ public StashedHandleViewController(TaskbarActivityContext activity,
mTaskbarStashedHandleAlpha.setUpdateVisibility(true);
mStashedHandleView.updateHandleColor(
mPrefs.get(STASHED_HANDLE_REGION_IS_DARK), false /* animate */);
- final Resources resources = mActivity.getResources();
- mStashedHandleHeight = resources.getDimensionPixelSize(
- R.dimen.taskbar_stashed_handle_height);
}
public void init(TaskbarControllers controllers) {
mControllers = controllers;
DeviceProfile deviceProfile = mActivity.getDeviceProfile();
Resources resources = mActivity.getResources();
+
+ int handleHeightMode = SettingsCache.INSTANCE.get(mActivity)
+ .getIntValue(GESTURE_NAVBAR_HEIGHT_MODE_URI, 3);
+ if (handleHeightMode == 0) {
+ mStashedHandleHeight =
+ resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_height_smallest);
+ } else if (handleHeightMode == 1) {
+ mStashedHandleHeight =
+ resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_height_smaller);
+ } else if (handleHeightMode == 2) {
+ mStashedHandleHeight =
+ resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_height_small);
+ } else if (handleHeightMode == 4) {
+ mStashedHandleHeight =
+ resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_height_tall);
+ } else {
+ mStashedHandleHeight =
+ resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_height);
+ }
+
+ int handleWidthMode = SettingsCache.INSTANCE.get(mActivity)
+ .getIntValue(GESTURE_NAVBAR_LENGTH_MODE_URI, 1);
if (mActivity.isPhoneGestureNavMode() || mActivity.isTinyTaskbar()
|| mActivity.isBubbleBarOnPhone()) {
mTaskbarSize = resources.getDimensionPixelSize(R.dimen.taskbar_phone_size);
- mStashedHandleWidth =
+ if (handleWidthMode == 0) {
+ mStashedHandleWidth =
+ resources.getDimensionPixelSize(R.dimen.taskbar_stashed_small_screen_short);
+ } else if (handleWidthMode == 2) {
+ mStashedHandleWidth =
+ resources.getDimensionPixelSize(R.dimen.taskbar_stashed_small_screen_long);
+ } else {
+ mStashedHandleWidth =
resources.getDimensionPixelSize(R.dimen.taskbar_stashed_small_screen);
+ }
} else {
mTaskbarSize = deviceProfile.getTaskbarProfile().getHeight();
- mStashedHandleWidth = resources
+ if (handleWidthMode == 0) {
+ mStashedHandleWidth = resources
+ .getDimensionPixelSize(R.dimen.taskbar_stashed_handle_width_short);
+ } else if (handleWidthMode == 2) {
+ mStashedHandleWidth = resources
+ .getDimensionPixelSize(R.dimen.taskbar_stashed_handle_width_long);
+ } else {
+ mStashedHandleWidth = resources
.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_width);
+ }
}
int taskbarBottomMargin = deviceProfile.getTaskbarProfile().getBottomMargin();
- mStashedHandleView.getLayoutParams().height = mTaskbarSize + taskbarBottomMargin;
+ mStashedHandleView.getLayoutParams().height =
+ SettingsCache.INSTANCE.get(mActivity).getValue(NAVIGATION_BAR_HINT_URI)
+ ? mTaskbarSize + taskbarBottomMargin : 0;
mTaskbarStashedHandleAlpha.get(ALPHA_INDEX_STASHED).setValue(
mActivity.isPhoneGestureNavMode() ? 1 : 0);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 6d4a5122a7a..d1df34f9cbc 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -40,6 +40,9 @@
import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING;
import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_FULLSCREEN;
import static com.android.launcher3.taskbar.TaskbarDesktopExperienceFlags.enableAutoStashConnectedDisplayTaskbar;
+import static com.android.launcher3.taskbar.TaskbarManagerImpl.ENABLE_TASKBAR_URI;
+import static com.android.launcher3.taskbar.TaskbarManagerImpl.NAVBAR_IME_SPACE_URI;
+import static com.android.launcher3.taskbar.TaskbarManagerImpl.NAVIGATION_BAR_HINT_URI;
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_SECONDARY_LAUNCHER_ON_CD;
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_STASHED_IN_APP_AUTO;
import static com.android.launcher3.taskbar.TaskbarStashController.SHOULD_BUBBLES_FOLLOW_DEFAULT_VALUE;
@@ -51,7 +54,6 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
import static com.android.wm.shell.Flags.enableBubbleBar;
import static com.android.wm.shell.Flags.enableBubbleBarOnPhones;
-import static com.android.wm.shell.Flags.enableTinyTaskbar;
import static java.lang.invoke.MethodHandles.Lookup.PROTECTED;
@@ -309,8 +311,6 @@ public TaskbarActivityContext(int displayId, Context windowContext,
mDeviceProfile.inv.numRows,
mDeviceProfile.inv.numColumns);
- mImeDrawsImeNavBar = getBoolByName(IME_DRAWS_IME_NAV_BAR_RES_NAME, getResources(), false)
- && isPrimaryDisplay();
mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
() -> getPackageManager().isSafeMode());
@@ -558,6 +558,9 @@ private void applyDeviceProfile(DeviceProfile originDeviceProfile) {
mNavMode = getNavigationMode();
SettingsCache settingsCache = SettingsCache.INSTANCE.get(this);
+ mImeDrawsImeNavBar = getBoolByName(IME_DRAWS_IME_NAV_BAR_RES_NAME, getResources(), false)
+ && isPrimaryDisplay()
+ && settingsCache.getValue(NAVBAR_IME_SPACE_URI);
mIsUserSetupComplete = settingsCache.getValue(Secure.getUriFor(Secure.USER_SETUP_COMPLETE));
mIsNavBarKidsMode = settingsCache.getValue(Secure.getUriFor(Secure.NAV_BAR_KIDS_MODE));
mIsNavBarForceVisible = mIsNavBarKidsMode;
@@ -643,6 +646,11 @@ public AnimatorSet onDestroyAnimation(int duration) {
* single window for taskbar and navbar.
*/
public boolean isPhoneMode() {
+ if (!mDeviceProfile.getDeviceProperties().isPhone() &&
+ !(SettingsCache.INSTANCE.get(this).getIntValue(ENABLE_TASKBAR_URI,
+ mDeviceProfile.getDeviceProperties().isTablet() ? 1 : 0) != 0)) {
+ return true;
+ }
return mDeviceProfile.getDeviceProperties().isPhone() && !mDeviceProfile.isTaskbarPresent;
}
@@ -671,7 +679,7 @@ public boolean drawsTaskbarBackground() {
/** Returns {@code true} iff a tiny version of taskbar is shown on phone. */
public boolean isTinyTaskbar() {
- return enableTinyTaskbar() && mDeviceProfile.getDeviceProperties().isPhone() && mDeviceProfile.isTaskbarPresent;
+ return mDeviceProfile.getDeviceProperties().isPhone() && mDeviceProfile.isTaskbarPresent;
}
public boolean isBubbleBarOnPhone() {
@@ -871,9 +879,6 @@ private void populatePhoneButtonNavModeWindowLayoutParams(int rot,
public void onConfigurationChanged(@Config int configChanges) {
mControllers.onConfigurationChanged(configChanges);
- if (!mIsUserSetupComplete) {
- setTaskbarWindowSize(getSetupWindowSize());
- }
}
public boolean isThreeButtonNav() {
@@ -1467,11 +1472,9 @@ public int getDefaultTaskbarWindowSize() {
if (isPhoneMode()) {
return isThreeButtonNav() ?
resources.getDimensionPixelSize(R.dimen.taskbar_phone_size) :
- resources.getDimensionPixelSize(R.dimen.taskbar_stashed_size);
- }
-
- if (!isUserSetupComplete()) {
- return getSetupWindowSize();
+ SettingsCache.INSTANCE.get(this).getValue(NAVIGATION_BAR_HINT_URI) ?
+ resources.getDimensionPixelSize(R.dimen.taskbar_stashed_size) :
+ 0;
}
int bubbleBarTop = mControllers.bubbleControllers.map(bubbleControllers ->
@@ -1514,10 +1517,6 @@ public int getDefaultTaskbarWindowSize() {
return Math.max(taskbarWindowSize, bubbleBarTop);
}
- public int getSetupWindowSize() {
- return getResources().getDimensionPixelSize(R.dimen.taskbar_suw_frame);
- }
-
public TaskbarProfile getTransientTaskbarProfile() {
return mTransientTaskbarProfile;
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
index dc14829d3ad..6c12159a48d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
@@ -29,9 +29,11 @@ import com.android.launcher3.Utilities
import com.android.launcher3.Utilities.mapRange
import com.android.launcher3.Utilities.mapToRange
import com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound
+import com.android.launcher3.taskbar.TaskbarManagerImpl.NAVIGATION_BAR_HINT_URI
import com.android.launcher3.taskbar.TaskbarPinningController.Companion.PINNING_PERSISTENT
import com.android.launcher3.taskbar.TaskbarPinningController.Companion.PINNING_TRANSIENT
import com.android.launcher3.taskbar.Utilities.getShapedTaskbarRadius
+import com.android.launcher3.util.SettingsCache
import kotlin.math.min
/** Helps draw the taskbar background, made up of a rectangle plus two inverted rounded corners. */
@@ -79,7 +81,11 @@ class TaskbarBackgroundRenderer(private val context: TaskbarActivityContext) {
context.resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_width)
private val stashedHandleHeight =
- context.resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_height)
+ if (SettingsCache.INSTANCE.get(context).getValue(NAVIGATION_BAR_HINT_URI)) {
+ context.resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_height)
+ } else {
+ 0
+ }
init {
paint.color = context.getColor(R.color.taskbar_background)
@@ -212,6 +218,7 @@ class TaskbarBackgroundRenderer(private val context: TaskbarActivityContext) {
if (isAnimatingPinning) maxPersistentTaskbarHeight else stashedHandleHeight.toFloat()
val newBackgroundHeight =
mapRange(progress, backgroundHeightWhileAnimating, maxTransientTaskbarHeight)
+ if (newBackgroundHeight == 0f) return
val fullWidth = transientBackgroundBounds.width()
val animationWidth = context.currentTaskbarWidth
val backgroundWidthWhileAnimating =
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInteractor.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInteractor.kt
index 5ca5581a194..afaf475698c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInteractor.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInteractor.kt
@@ -189,10 +189,15 @@ class TaskbarInteractor(private val taskbarUIController: TaskbarUIController) {
fun postOnRootViewDraw(callback: Runnable, callbackExecutor: Executor): Boolean {
val rootView = taskbarUIController.rootView
return if (rootView != null) {
- executor.execute {
- ViewUtils.postFrameDrawn(rootView) { callbackExecutor.execute(callback) }
+ if (rootView.height == 0) {
+ // When view height is 0, we don't have to wait for anything.
+ false
+ } else {
+ executor.execute {
+ ViewUtils.postFrameDrawn(rootView) { callbackExecutor.execute(callback) }
+ }
+ true
}
- true
} else {
false
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManagerImpl.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManagerImpl.java
index 3f8b1069877..f7e611b010a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManagerImpl.java
@@ -41,6 +41,7 @@
import static com.android.launcher3.util.DisplayController.CHANGE_SHOW_LOCKED_TASKBAR;
import static com.android.launcher3.util.DisplayController.CHANGE_TASKBAR_PINNING;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
import static com.android.launcher3.util.SimpleBroadcastReceiver.actionsFilter;
@@ -64,6 +65,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.RemoteException;
import android.os.Trace;
import android.provider.Settings;
import android.util.ArraySet;
@@ -71,9 +73,12 @@
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.view.Display;
+import android.view.IWindowManager;
import android.view.MotionEvent;
import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
import android.widget.FrameLayout;
+import android.widget.Toast;
import android.window.DesktopExperienceFlags;
import androidx.annotation.NonNull;
@@ -92,6 +97,7 @@
import com.android.launcher3.LauncherPrefChangeListener;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.statehandlers.DesktopVisibilityController;
@@ -125,7 +131,9 @@
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.StringJoiner;
@@ -166,6 +174,30 @@ public class TaskbarManagerImpl implements DisplayDecorationListener {
private static final Uri NAV_BAR_KIDS_MODE = Settings.Secure.getUriFor(
Settings.Secure.NAV_BAR_KIDS_MODE);
+ public static final Uri ENABLE_TASKBAR_URI = Settings.System.getUriFor(
+ Settings.System.ENABLE_TASKBAR);
+
+ public static final Uri NAVIGATION_BAR_HINT_URI = Settings.System.getUriFor(
+ Settings.System.NAVIGATION_BAR_HINT);
+
+ public static final Uri FORCE_SHOW_NAVBAR_URI = Settings.System.getUriFor(
+ Settings.System.FORCE_SHOW_NAVBAR);
+
+ public static final Uri NAVIGATIONBAR_KEY_ORDER_URI = Settings.Secure.getUriFor(
+ Settings.Secure.NAVIGATIONBAR_KEY_ORDER);
+
+ public static final Uri NAV_BAR_LAYOUT_URI = Settings.Secure.getUriFor(
+ Settings.Secure.NAVBAR_LAYOUT_MODE);
+
+ public static final Uri GESTURE_NAVBAR_LENGTH_MODE_URI = Settings.System.getUriFor(
+ Settings.System.GESTURE_NAVBAR_LENGTH_MODE);
+
+ public static final Uri GESTURE_NAVBAR_HEIGHT_MODE_URI = Settings.System.getUriFor(
+ Settings.System.GESTURE_NAVBAR_HEIGHT_MODE);
+
+ public static final Uri NAVBAR_IME_SPACE_URI = Settings.Secure.getUriFor(
+ Settings.Secure.NAVBAR_IME_SPACE);
+
public static final LooperExecutor TASKBAR_UI_THREAD =
new LooperExecutor("TASKBAR_UI_THREAD", THREAD_PRIORITY_FOREGROUND);
@@ -322,6 +354,8 @@ public void onDisplayInfoChanged(Context context, DisplayController.Info info, i
recreateTaskbars();
};
+ private final SettingsCache.OnChangeListener mOnTaskBarChangeListener;
+
private final DesktopVisibilityController.TaskbarDesktopModeListener
mTaskbarDesktopModeListener =
new DesktopVisibilityController.TaskbarDesktopModeListener() {
@@ -447,6 +481,11 @@ public TaskbarManagerImpl(
mNavCallbacks = navCallbacks;
mDisplaysWithDecorationsRepositoryCompat = displaysWithDecorationsRepositoryCompat;
+ mOnTaskBarChangeListener = c -> {
+ Toast.makeText(mBaseContext, R.string.restarting_launcher_changes, Toast.LENGTH_SHORT).show();
+ Utilities.restart();
+ };
+
// Set up primary display.
debugPrimaryTaskbar("TaskbarManager constructor");
mDisplayManager = mBaseContext.getSystemService(DisplayManager.class);
@@ -464,6 +503,22 @@ public TaskbarManagerImpl(
.register(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
SettingsCache.INSTANCE.get(mPrimaryWindowContext)
.register(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
+ SettingsCache.INSTANCE.get(mPrimaryWindowContext)
+ .register(ENABLE_TASKBAR_URI, mOnTaskBarChangeListener);
+ SettingsCache.INSTANCE.get(mPrimaryWindowContext)
+ .register(NAVIGATION_BAR_HINT_URI, mOnTaskBarChangeListener);
+ SettingsCache.INSTANCE.get(mPrimaryWindowContext)
+ .register(FORCE_SHOW_NAVBAR_URI, mOnTaskBarChangeListener);
+ SettingsCache.INSTANCE.get(mPrimaryWindowContext)
+ .register(NAVIGATIONBAR_KEY_ORDER_URI, mOnTaskBarChangeListener);
+ SettingsCache.INSTANCE.get(mPrimaryWindowContext)
+ .register(NAV_BAR_LAYOUT_URI, mOnTaskBarChangeListener);
+ SettingsCache.INSTANCE.get(mPrimaryWindowContext)
+ .register(GESTURE_NAVBAR_LENGTH_MODE_URI, mOnTaskBarChangeListener);
+ SettingsCache.INSTANCE.get(mPrimaryWindowContext)
+ .register(GESTURE_NAVBAR_HEIGHT_MODE_URI, mOnTaskBarChangeListener);
+ SettingsCache.INSTANCE.get(mPrimaryWindowContext)
+ .register(NAVBAR_IME_SPACE_URI, mOnTaskBarChangeListener);
if (DesktopExperienceFlags.ENABLE_SYS_DECORS_CALLBACKS_VIA_WM.isTrue()
&& DesktopExperienceFlags.ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT.isTrue()) {
displaysWithDecorationsRepositoryCompat
@@ -769,12 +824,29 @@ private TaskbarUIController createTaskbarUIControllerForRecentsViewContainer(
* we fully want to destroy existing taskbars and create all desired new ones.
* In other case (folding/unfolding) we don't need to remove and add window.
*/
- public synchronized void recreateTaskbars() {
- for (int i = 0; i < mWindowContexts.size(); i++) {
- int displayId = mWindowContexts.keyAt(i);
- debugTaskbarManager("recreateTaskbars", displayId);
- recreateTaskbarForDisplay(displayId, 0);
- }
+ public void recreateTaskbars() {
+ THREAD_POOL_EXECUTOR.execute(() -> {
+ List displayIds;
+ synchronized (mWindowContexts) {
+ displayIds = new ArrayList<>();
+ for (int i = 0; i < mWindowContexts.size(); i++) {
+ displayIds.add(mWindowContexts.keyAt(i));
+ }
+ }
+ for (int displayId : displayIds) {
+ recreateTaskbarForDisplayAsync(displayId);
+ }
+ });
+ }
+
+ private void recreateTaskbarForDisplayAsync(int displayId) {
+ DeviceProfile dp = getDeviceProfile(displayId);
+ boolean displayExists = getDisplay(displayId) != null;
+ MAIN_EXECUTOR.post(() -> {
+ if (dp != null && displayExists) {
+ recreateTaskbarForDisplay(displayId, 0);
+ }
+ });
}
private void attachPinningSharedPreferenceChangeListener(Context context) {
@@ -1195,6 +1267,22 @@ public void destroy() {
.unregister(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
SettingsCache.INSTANCE.get(mPrimaryWindowContext)
.unregister(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
+ SettingsCache.INSTANCE.get(mPrimaryWindowContext)
+ .unregister(ENABLE_TASKBAR_URI, mOnTaskBarChangeListener);
+ SettingsCache.INSTANCE.get(mPrimaryWindowContext)
+ .unregister(NAVIGATION_BAR_HINT_URI, mOnTaskBarChangeListener);
+ SettingsCache.INSTANCE.get(mPrimaryWindowContext)
+ .unregister(FORCE_SHOW_NAVBAR_URI, mOnTaskBarChangeListener);
+ SettingsCache.INSTANCE.get(mPrimaryWindowContext)
+ .unregister(NAVIGATIONBAR_KEY_ORDER_URI, mOnTaskBarChangeListener);
+ SettingsCache.INSTANCE.get(mPrimaryWindowContext)
+ .unregister(NAV_BAR_LAYOUT_URI, mOnTaskBarChangeListener);
+ SettingsCache.INSTANCE.get(mPrimaryWindowContext)
+ .unregister(GESTURE_NAVBAR_LENGTH_MODE_URI, mOnTaskBarChangeListener);
+ SettingsCache.INSTANCE.get(mPrimaryWindowContext)
+ .unregister(GESTURE_NAVBAR_HEIGHT_MODE_URI, mOnTaskBarChangeListener);
+ SettingsCache.INSTANCE.get(mPrimaryWindowContext)
+ .unregister(NAVBAR_IME_SPACE_URI, mOnTaskBarChangeListener);
if (DesktopExperienceFlags.ENABLE_SYS_DECORS_CALLBACKS_VIA_WM.isTrue()
&& DesktopExperienceFlags.ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT.isTrue()) {
mDisplaysWithDecorationsRepositoryCompat.unregisterDisplayDecorationListener(this);
@@ -1234,7 +1322,19 @@ public void dumpLogs(String prefix, PrintWriter pw) {
}
}
+ boolean hasNavigationBar() {
+ try {
+ IWindowManager windowManager = WindowManagerGlobal.getWindowManagerService();
+ return windowManager.hasNavigationBar(Display.DEFAULT_DISPLAY);
+ } catch (RemoteException e) {
+ return true;
+ }
+ }
+
private void addTaskbarRootViewToWindow(@NonNull TaskbarActivityContext taskbar) {
+ if (!hasNavigationBar()) {
+ return;
+ }
int displayId = taskbar.getDisplayId();
debugTaskbarManager("addTaskbarRootViewToWindow:", displayId);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
index ec385b0da0e..2a95833e337 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
@@ -155,13 +155,13 @@ public void onButtonClick(@TaskbarButton int buttonType, View view) {
logEvent(LAUNCHER_TASKBAR_HOME_BUTTON_TAP);
mSystemUiProxy.updateContextualEduStats(/* isTrackpadGesture= */ false,
GestureType.HOME);
- navigateHome();
+ mSystemUiProxy.onKeyEvent(KeyEvent.KEYCODE_HOME, mDisplayId);
break;
case BUTTON_RECENTS:
logEvent(LAUNCHER_TASKBAR_OVERVIEW_BUTTON_TAP);
mSystemUiProxy.updateContextualEduStats(/* isTrackpadGesture= */ false,
GestureType.OVERVIEW);
- navigateToOverview();
+ mSystemUiProxy.onKeyEvent(KeyEvent.KEYCODE_APP_SWITCH, mDisplayId);
break;
case BUTTON_IME_SWITCH:
logEvent(LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_TAP);
@@ -198,7 +198,7 @@ public boolean onButtonLongClick(@TaskbarButton int buttonType, View view) {
switch (buttonType) {
case BUTTON_HOME:
logEvent(LAUNCHER_TASKBAR_HOME_BUTTON_LONGPRESS);
- onLongPressHome(view);
+ mSystemUiProxy.onLongPressKeyEvent(KeyEvent.KEYCODE_HOME, mDisplayId);
view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
return true;
@@ -213,6 +213,8 @@ public boolean onButtonLongClick(@TaskbarButton int buttonType, View view) {
if (backRecentsLongpress(buttonType)) {
view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+ } else {
+ mSystemUiProxy.onLongPressKeyEvent(KeyEvent.KEYCODE_BACK, mDisplayId);
}
return true;
case BUTTON_RECENTS:
@@ -220,6 +222,8 @@ public boolean onButtonLongClick(@TaskbarButton int buttonType, View view) {
if (backRecentsLongpress(buttonType)) {
view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+ } else {
+ mSystemUiProxy.onLongPressKeyEvent(KeyEvent.KEYCODE_APP_SWITCH, mDisplayId);
}
return true;
case BUTTON_IME_SWITCH:
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index f4e67d5cd5b..781072feef8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -30,6 +30,7 @@
import static com.android.launcher3.statehandlers.DesktopVisibilityController.INACTIVE_DESK_ID;
import static com.android.launcher3.taskbar.TaskbarActivityContext.ENABLE_TASKBAR_BEHIND_SHADE;
import static com.android.launcher3.taskbar.TaskbarDesktopExperienceFlags.enableAutoStashConnectedDisplayTaskbar;
+import static com.android.launcher3.taskbar.TaskbarManagerImpl.NAVIGATION_BAR_HINT_URI;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.FlagDebugUtils.appendFlag;
import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
@@ -45,6 +46,7 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
+import android.app.PendingIntent;
import android.app.RemoteAction;
import android.graphics.drawable.Icon;
import android.os.SystemClock;
@@ -70,6 +72,7 @@
import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
+import com.android.launcher3.util.SettingsCache;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TopTaskTracker;
import com.android.quickstep.util.SystemUiFlagUtils;
@@ -331,14 +334,14 @@ public TaskbarStashController(TaskbarActivityContext activity, TaskbarUiState ta
if (mActivity.isPhoneMode()) {
mUnstashedHeight = mActivity.getResources().getDimensionPixelSize(
R.dimen.taskbar_phone_size);
- mStashedHeight = mActivity.getResources().getDimensionPixelSize(
- R.dimen.taskbar_stashed_size);
+ mStashedHeight = SettingsCache.INSTANCE.get(mActivity).getValue(NAVIGATION_BAR_HINT_URI)
+ ? mActivity.getResources().getDimensionPixelSize(R.dimen.taskbar_stashed_size)
+ : 0;
} else {
mUnstashedHeight = mActivity.getDeviceProfile().getTaskbarProfile().getHeight();
- mStashedHeight = mActivity
- .getDeviceProfile()
- .getTaskbarProfile()
- .getStashedTaskbarHeight();
+ mStashedHeight = SettingsCache.INSTANCE.get(mActivity).getValue(NAVIGATION_BAR_HINT_URI)
+ ? mActivity.getDeviceProfile().getTaskbarProfile().getStashedTaskbarHeight()
+ : 0;
}
updateIsTaskbarStashed(mIsStashed);
@@ -1217,6 +1220,7 @@ private void onIsStashedChanged(boolean isStashed) {
mControllers.stashedHandleViewController.onIsStashedChanged(
isStashed && supportsVisualStashing());
mControllers.taskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged();
+ mControllers.taskbarViewController.setTaskbarViewVisible(!isStashed);
});
updateIsTaskbarStashed(isStashed);
}
@@ -1446,17 +1450,25 @@ public void setUpTaskbarSystemAction(boolean visible) {
return;
}
- if (!mIsTaskbarSystemActionRegistered) {
- RemoteAction taskbarRemoteAction = new RemoteAction(
- Icon.createWithResource(mActivity, R.drawable.ic_info_no_shadow),
- mActivity.getString(R.string.taskbar_a11y_title),
- mActivity.getString(R.string.taskbar_a11y_title),
- mTaskbarSharedState.taskbarSystemActionPendingIntent);
+ if (mIsTaskbarSystemActionRegistered) {
+ return;
+ }
- mAccessibilityManager.registerSystemAction(taskbarRemoteAction,
- SYSTEM_ACTION_ID_TASKBAR);
- mIsTaskbarSystemActionRegistered = true;
+ PendingIntent action = mTaskbarSharedState.taskbarSystemActionPendingIntent;
+ if (action == null) {
+ Log.w(TAG, "Taskbar system action PendingIntent is null, skipping registration");
+ return;
}
+
+ RemoteAction taskbarRemoteAction = new RemoteAction(
+ Icon.createWithResource(mActivity, R.drawable.ic_info_no_shadow),
+ mActivity.getString(R.string.taskbar_a11y_title),
+ mActivity.getString(R.string.taskbar_a11y_title),
+ action
+ );
+
+ mAccessibilityManager.registerSystemAction(taskbarRemoteAction, SYSTEM_ACTION_ID_TASKBAR);
+ mIsTaskbarSystemActionRegistered = true;
});
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 09ff7322bbd..d01845b2a68 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -52,6 +52,7 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Flags;
import com.android.launcher3.Insettable;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.apppairs.AppPairIcon;
@@ -254,7 +255,14 @@ public TaskbarView(@NonNull Context context, @Nullable AttributeSet attrs, int d
}
// TODO: Disable touch events on QSB otherwise it can crash.
- mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false);
+ if (Utilities.showQSB(context)) {
+ int layoutRes = LauncherPrefs.DOCK_SEARCH_PIXEL_STYLE.get(context)
+ ? R.layout.search_container_hotseat_pixel
+ : R.layout.search_container_hotseat;
+ mQsb = LayoutInflater.from(context).inflate(layoutRes, this, false);
+ } else {
+ mQsb = LayoutInflater.from(context).inflate(R.layout.empty_view, this, false);
+ }
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 224eb310e67..b51b16df6d7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -437,6 +437,10 @@ public boolean areIconsVisible() {
return mTaskbarView.areIconsVisible();
}
+ public void setTaskbarViewVisible(boolean visible) {
+ mTaskbarView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+ }
+
public MultiPropertyFactory getTaskbarIconAlpha() {
return mTaskbarIconAlpha;
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
index 254ba5173b4..b71b809d228 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
@@ -27,6 +27,7 @@
import android.animation.Animator;
import android.content.Context;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
@@ -41,16 +42,19 @@
import android.window.OnBackInvokedDispatcher;
import androidx.annotation.Nullable;
+import androidx.core.graphics.ColorUtils;
import com.android.app.animation.Interpolators;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Flags;
import com.android.launcher3.Insettable;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.taskbar.allapps.TaskbarAllAppsViewController.TaskbarAllAppsCallbacks;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
+import com.android.launcher3.util.BerryBlackTheme;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.AbstractSlideInView;
@@ -273,14 +277,21 @@ protected void onLayout(boolean changed, int l, int t, int r, int b) {
@Override
protected int getScrimColor(Context context) {
- if (!mActivityContext.getDeviceProfile().shouldShowAllAppsOnSheet()) {
+ int scrimColor;
+ if (!mActivityContext.getDeviceProfile().shouldShowAllAppsOnSheet(mActivityContext)) {
// Always use an opaque scrim if there's no sheet.
- return context.getResources().getColor(R.color.materialColorSurfaceDim);
+ scrimColor = context.getResources().getColor(R.color.materialColorSurfaceDim);
} else if (!Flags.allAppsBlur()) {
// If there's a sheet but no blur, use the old scrim color.
- return context.getResources().getColor(R.color.widgets_picker_scrim);
+ scrimColor = context.getResources().getColor(R.color.widgets_picker_scrim);
+ } else {
+ scrimColor = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
+ }
+ if (BerryBlackTheme.isActive(context)) {
+ scrimColor = Color.BLACK;
}
- return Themes.getAttrColor(context, R.attr.allAppsScrimColor);
+ return ColorUtils.setAlphaComponent(
+ scrimColor, LauncherPrefs.APP_DRAWER_OPACITY.get(context) * 255 / 100);
}
@Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
index 2a0c2163437..922ccdf9a0d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
@@ -19,6 +19,8 @@
import static android.view.View.VISIBLE;
import static com.android.launcher3.Flags.refactorTaskbarUiState;
+import static com.android.launcher3.taskbar.TaskbarManagerImpl.GESTURE_NAVBAR_HEIGHT_MODE_URI;
+import static com.android.launcher3.taskbar.TaskbarManagerImpl.GESTURE_NAVBAR_LENGTH_MODE_URI;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -42,6 +44,7 @@
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.MultiValueAlpha;
+import com.android.launcher3.util.SettingsCache;
import com.android.wm.shell.shared.animation.PhysicsAnimator;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
import com.android.wm.shell.shared.handles.RegionSamplingHelper;
@@ -99,10 +102,38 @@ public void init(BubbleControllers bubbleControllers) {
DeviceProfile deviceProfile = mActivity.getDeviceProfile();
Resources resources = mActivity.getResources();
- mStashedHandleHeight = resources.getDimensionPixelSize(
- R.dimen.bubblebar_stashed_handle_height);
- mStashedHandleWidth = resources.getDimensionPixelSize(
+
+ int handleHeightMode = SettingsCache.INSTANCE.get(mActivity)
+ .getIntValue(GESTURE_NAVBAR_HEIGHT_MODE_URI, 3);
+ if (handleHeightMode == 0) {
+ mStashedHandleHeight =
+ resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_height_smallest);
+ } else if (handleHeightMode == 1) {
+ mStashedHandleHeight =
+ resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_height_smaller);
+ } else if (handleHeightMode == 2) {
+ mStashedHandleHeight =
+ resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_height_small);
+ } else if (handleHeightMode == 4) {
+ mStashedHandleHeight =
+ resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_height_tall);
+ } else {
+ mStashedHandleHeight =
+ resources.getDimensionPixelSize(R.dimen.bubblebar_stashed_handle_height);
+ }
+
+ int handleWidthMode = SettingsCache.INSTANCE.get(mActivity)
+ .getIntValue(GESTURE_NAVBAR_LENGTH_MODE_URI, 1);
+ if (handleWidthMode == 0) {
+ mStashedHandleWidth = resources.getDimensionPixelSize(
+ R.dimen.bubblebar_stashed_handle_width_short);
+ } else if (handleWidthMode == 2) {
+ mStashedHandleWidth = resources.getDimensionPixelSize(
+ R.dimen.bubblebar_stashed_handle_width_long);
+ } else {
+ mStashedHandleWidth = resources.getDimensionPixelSize(
R.dimen.bubblebar_stashed_handle_width);
+ }
int barSize = resources.getDimensionPixelSize(R.dimen.bubblebar_size);
// Use the max translation for bubble bar whether it is on the home screen or in app.
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
index fc3d6e69484..c542d8a0a0c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
@@ -95,9 +95,6 @@ abstract class AbstractNavButtonLayoutter(
val phoneOrPortraitSetupMargin =
resources.getDimensionPixelSize(R.dimen.taskbar_contextual_button_suw_margin)
nearestTouchFrameLayoutParams.marginStart = phoneOrPortraitSetupMargin
- nearestTouchFrameLayoutParams.bottomMargin = 0
- nearestTouchFrameLayoutParams.height =
- resources.getDimensionPixelSize(R.dimen.taskbar_contextual_button_suw_height)
}
open fun repositionContextualContainer(
@@ -122,7 +119,6 @@ abstract class AbstractNavButtonLayoutter(
/** For ordered layouts, this determines if the order of buttons should be flipped. */
open fun shouldFlipButtonOrder(): Boolean {
val isFlipEnabledBySetting =
- android.view.accessibility.Flags.navbarFlipOrderOption() &&
SettingsCache.INSTANCE.get(navButtonContainer.context)
.getValue(NAVBAR_KEY_ORDER_URI)
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
index 2b089b8d9f7..ffb8b5d402e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
@@ -26,6 +26,8 @@ import android.widget.LinearLayout
import android.widget.Space
import com.android.launcher3.R
import com.android.launcher3.taskbar.TaskbarActivityContext
+import com.android.launcher3.taskbar.TaskbarManagerImpl.NAV_BAR_LAYOUT_URI
+import com.android.launcher3.util.SettingsCache
open class PhoneLandscapeNavLayoutter(
resources: Resources,
@@ -49,6 +51,7 @@ open class PhoneLandscapeNavLayoutter(
override val orientation = LinearLayout.VERTICAL
override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
+ val layoutMode = SettingsCache.INSTANCE.get(homeButton!!.context).getIntValue(NAV_BAR_LAYOUT_URI, 0)
val totalHeight = context.deviceProfile.deviceProperties.heightPx
val homeButtonHeight =
resources.getDimensionPixelSize(R.dimen.taskbar_phone_home_button_size)
@@ -62,13 +65,17 @@ open class PhoneLandscapeNavLayoutter(
val sideButtonHeight = contextualButtonHeight * 2
val navButtonContainerHeight = contentWidth - contextualButtonHeight * 2
+ val bottomFactor = when (layoutMode) {
+ 2 -> 0.4f // left
+ 3 -> 1.6f // right
+ else -> 1f // normal & compact
+ }
+ val marginBase = contextualButtonHeight + contentPadding + roundedCornerContentMargin
val navContainerParams =
FrameLayout.LayoutParams(MATCH_PARENT, navButtonContainerHeight.toInt())
navContainerParams.apply {
- topMargin =
- (contextualButtonHeight + contentPadding + roundedCornerContentMargin).toInt()
- bottomMargin =
- (contextualButtonHeight + contentPadding + roundedCornerContentMargin).toInt()
+ topMargin = marginBase.toInt()
+ bottomMargin = (bottomFactor * marginBase.toFloat()).toInt()
marginEnd = 0
marginStart = 0
}
@@ -82,6 +89,7 @@ open class PhoneLandscapeNavLayoutter(
// Add the spaces in between the nav buttons
val spaceInBetween =
(navButtonContainerHeight - homeButtonHeight - sideButtonHeight * 2) / 2.0f
+ val spaceInBetweenDiv = if (layoutMode == 0) 1 else 4
for (i in 0 until navButtonContainer.childCount) {
val navButton = navButtonContainer.getChildAt(i)
val buttonLayoutParams = navButton.layoutParams as LinearLayout.LayoutParams
@@ -99,8 +107,8 @@ open class PhoneLandscapeNavLayoutter(
}
else -> {
// other buttons
- buttonLayoutParams.topMargin = margin
- buttonLayoutParams.bottomMargin = margin
+ buttonLayoutParams.topMargin = (margin / spaceInBetweenDiv).toInt()
+ buttonLayoutParams.bottomMargin = (margin / spaceInBetweenDiv).toInt()
buttonLayoutParams.height = homeButtonHeight
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
index 5f8475e8d2b..b560b7b5d63 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
@@ -26,6 +26,8 @@ import android.widget.LinearLayout
import android.widget.Space
import com.android.launcher3.R
import com.android.launcher3.taskbar.TaskbarActivityContext
+import com.android.launcher3.taskbar.TaskbarManagerImpl.NAV_BAR_LAYOUT_URI
+import com.android.launcher3.util.SettingsCache
class PhonePortraitNavLayoutter(
resources: Resources,
@@ -47,6 +49,7 @@ class PhonePortraitNavLayoutter(
) {
override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
+ val layoutMode = SettingsCache.INSTANCE.get(homeButton!!.context).getIntValue(NAV_BAR_LAYOUT_URI, 0)
val totalWidth = context.deviceProfile.deviceProperties.widthPx
val homeButtonWidth =
resources.getDimensionPixelSize(R.dimen.taskbar_phone_home_button_size)
@@ -60,6 +63,12 @@ class PhonePortraitNavLayoutter(
val sideButtonWidth = contextualButtonWidth * 2
val navButtonContainerWidth = contentWidth - contextualButtonWidth * 2
+ val startFactor = when (layoutMode) {
+ 2 -> 0.4f // left
+ 3 -> 1.6f // right
+ else -> 1f // normal & compact
+ }
+ val marginBase = contextualButtonWidth + contentPadding + roundedCornerContentMargin
val navContainerParams =
FrameLayout.LayoutParams(
navButtonContainerWidth.toInt(),
@@ -68,10 +77,8 @@ class PhonePortraitNavLayoutter(
navContainerParams.apply {
topMargin = 0
bottomMargin = 0
- marginEnd =
- (contextualButtonWidth + contentPadding + roundedCornerContentMargin).toInt()
- marginStart =
- (contextualButtonWidth + contentPadding + roundedCornerContentMargin).toInt()
+ marginEnd = marginBase.toInt()
+ marginStart = (startFactor * marginBase.toFloat()).toInt()
}
// Ensure order of buttons is correct
@@ -83,6 +90,7 @@ class PhonePortraitNavLayoutter(
// Add the spaces in between the nav buttons
val spaceInBetween =
(navButtonContainerWidth - homeButtonWidth - sideButtonWidth * 2) / 2.0f
+ val spaceInBetweenDiv = if (layoutMode == 0) 1 else 4
for (i in 0 until navButtonContainer.childCount) {
val navButton = navButtonContainer.getChildAt(i)
val buttonLayoutParams = navButton.layoutParams as LinearLayout.LayoutParams
@@ -100,8 +108,8 @@ class PhonePortraitNavLayoutter(
}
else -> {
// other buttons
- buttonLayoutParams.marginStart = margin
- buttonLayoutParams.marginEnd = margin
+ buttonLayoutParams.marginStart = (margin / spaceInBetweenDiv).toInt()
+ buttonLayoutParams.marginEnd = (margin / spaceInBetweenDiv).toInt()
buttonLayoutParams.width = homeButtonWidth
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
index 50606ea1186..0d0264dc8ee 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
@@ -73,7 +73,6 @@ class SetupNavLayoutter(
navButtonsLayoutParams.marginEnd = 0
navButtonsLayoutParams.gravity = Gravity.START or Gravity.CENTER_VERTICAL
- context.setTaskbarWindowSize(context.setupWindowSize)
// If SUW is on a large screen device that is landscape (or has a square aspect
// ratio) the back button has to be placed accordingly
@@ -84,10 +83,6 @@ class SetupNavLayoutter(
) {
navButtonsLayoutParams.marginStart =
resources.getDimensionPixelSize(R.dimen.taskbar_back_button_suw_start_margin)
- navButtonsOverallViewGroupLayoutParams.bottomMargin =
- resources.getDimensionPixelSize(R.dimen.taskbar_back_button_suw_bottom_margin)
- navButtonsLayoutParams.height =
- resources.getDimensionPixelSize(R.dimen.taskbar_back_button_suw_height)
} else {
adjustForSetupInPhoneMode(navButtonsOverallViewGroupLayoutParams, deviceProfile)
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
index 15170763159..c114fd4ec48 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
@@ -26,6 +26,9 @@ import android.widget.LinearLayout
import android.widget.Space
import com.android.launcher3.R
import com.android.launcher3.taskbar.TaskbarActivityContext
+import com.android.launcher3.taskbar.TaskbarManagerImpl.NAV_BAR_LAYOUT_URI
+import com.android.launcher3.taskbar.TaskbarManagerImpl.NAVIGATIONBAR_KEY_ORDER_URI
+import com.android.launcher3.util.SettingsCache
/** Layoutter for rendering task bar in large screen, both in 3-button and gesture nav mode. */
class TaskbarNavLayoutter(
@@ -48,6 +51,8 @@ class TaskbarNavLayoutter(
) {
override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
+ val layoutMode = SettingsCache.INSTANCE.get(homeButton!!.context).getIntValue(NAV_BAR_LAYOUT_URI, 0)
+
// Add spacing after the end of the last nav button
var navMarginEnd =
resources.getDimension(context.deviceProfile.inv.inlineNavButtonsEndSpacing).toInt()
@@ -66,6 +71,11 @@ class TaskbarNavLayoutter(
navMarginEnd += resources.getDimensionPixelSize(R.dimen.taskbar_hotseat_nav_spacing) / 2
}
+ val endFactor = when (layoutMode) {
+ 2 -> 0.4f // left
+ 3 -> 1.6f // right
+ else -> 1f // normal & compact
+ }
val navButtonParams =
FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
@@ -73,13 +83,28 @@ class TaskbarNavLayoutter(
)
navButtonParams.apply {
gravity = Gravity.END or Gravity.CENTER_VERTICAL
- marginEnd = navMarginEnd
+ marginEnd = (endFactor * navMarginEnd.toFloat()).toInt()
}
navButtonContainer.orientation = LinearLayout.HORIZONTAL
navButtonContainer.layoutParams = navButtonParams
+ if (backButton != null && homeButton != null && recentsButton != null) {
+ navButtonContainer.removeAllViews()
+
+ if (SettingsCache.INSTANCE.get(context).getIntValue(NAVIGATIONBAR_KEY_ORDER_URI, 0) == 1) {
+ navButtonContainer.addView(recentsButton)
+ navButtonContainer.addView(homeButton)
+ navButtonContainer.addView(backButton)
+ } else {
+ navButtonContainer.addView(backButton)
+ navButtonContainer.addView(homeButton)
+ navButtonContainer.addView(recentsButton)
+ }
+ }
+
// Add the spaces in between the nav buttons
val spaceInBetween = resources.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween)
+ val spaceInBetweenDiv = if (layoutMode == 0) 1 else 4
for (i in 0 until navButtonContainer.childCount) {
val navButton = navButtonContainer.getChildAt(i)
val buttonLayoutParams = navButton.layoutParams as LinearLayout.LayoutParams
@@ -92,8 +117,8 @@ class TaskbarNavLayoutter(
buttonLayoutParams.marginStart = spaceInBetween / 2
}
else -> {
- buttonLayoutParams.marginStart = spaceInBetween / 2
- buttonLayoutParams.marginEnd = spaceInBetween / 2
+ buttonLayoutParams.marginStart = (spaceInBetween / 2) / spaceInBetweenDiv
+ buttonLayoutParams.marginEnd = (spaceInBetween / 2) / spaceInBetweenDiv
}
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 15267f7e5da..6e1ebe064b7 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -57,9 +57,13 @@
import static com.android.launcher3.popup.SystemShortcut.APP_INFO;
import static com.android.launcher3.popup.SystemShortcut.BUBBLE_SHORTCUT;
import static com.android.launcher3.popup.SystemShortcut.DONT_SUGGEST_APP;
+import static com.android.launcher3.popup.SystemShortcut.FLOATING;
import static com.android.launcher3.popup.SystemShortcut.INSTALL;
+import static com.android.launcher3.popup.SystemShortcut.KILL_APP;
import static com.android.launcher3.popup.SystemShortcut.PRIVATE_PROFILE_INSTALL;
import static com.android.launcher3.popup.SystemShortcut.REMOVE;
+import static com.android.launcher3.popup.SystemShortcut.RENAME_APP;
+import static com.android.launcher3.popup.SystemShortcut.UNINSTALL;
import static com.android.launcher3.popup.SystemShortcut.UNINSTALL_APP;
import static com.android.launcher3.popup.SystemShortcut.WIDGETS;
import static com.android.launcher3.taskbar.LauncherTaskbarUIController.ALL_APPS_PAGE_PROGRESS_INDEX;
@@ -104,6 +108,7 @@
import android.util.Log;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
+import android.view.MotionEvent;
import android.view.View;
import android.widget.AnalogClock;
import android.widget.TextClock;
@@ -118,6 +123,8 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
+import androidx.core.view.WindowInsetsCompat;
+import androidx.core.view.WindowInsetsControllerCompat;
import com.android.app.viewcapture.ViewCaptureFactory;
import com.android.launcher3.AbstractFloatingView;
@@ -143,6 +150,7 @@
import com.android.launcher3.apppairs.AppPairIcon;
import com.android.launcher3.appprediction.PredictionRowView;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.data.AppDatabase;
import com.android.launcher3.desktop.DesktopRecentsTransitionController;
import com.android.launcher3.hybridhotseat.HotseatPredictionController;
import com.android.launcher3.logging.InstanceId;
@@ -159,6 +167,7 @@
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
import com.android.launcher3.statemanager.StateManager.StateHandler;
+import com.android.launcher3.statemanager.StateManager.StateListener;
import com.android.launcher3.taskbar.TaskbarInteractor;
import com.android.launcher3.taskbar.TaskbarManager;
import com.android.launcher3.taskbar.TaskbarUiState;
@@ -221,6 +230,7 @@
import com.android.quickstep.util.unfold.LauncherUnfoldTransitionController;
import com.android.quickstep.util.unfold.ProxyUnfoldTransitionProvider;
import com.android.quickstep.views.FloatingTaskView;
+import com.android.quickstep.views.MemInfoView;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.RecentsViewContainer;
@@ -316,6 +326,8 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
private boolean mShouldUpdateSuspensions;
+ private MemInfoView mMemInfoView;
+
private final TaskViewRecentsTouchContext mTaskViewRecentsTouchContext =
new TaskViewRecentsTouchContext() {
@Override
@@ -335,6 +347,22 @@ public void onUserControlledAnimationCreated(
}
};
+ private StateListener noStatusBarStateListener = new StateManager.StateListener() {
+ @Override
+ public void onStateTransitionStart(LauncherState toState) {
+ if (toState == OVERVIEW) {
+ getWindow().getDecorView().getWindowInsetsController().show(WindowInsetsCompat.Type.statusBars());
+ }
+ }
+
+ @Override
+ public void onStateTransitionComplete(LauncherState finalState) {
+ if (finalState != OVERVIEW) {
+ getWindow().getDecorView().getWindowInsetsController().hide(WindowInsetsCompat.Type.statusBars());
+ }
+ }
+ };
+
@Override
protected LauncherOverlayManager getDefaultOverlay() {
return new OverlayCallbackImpl(this);
@@ -350,6 +378,7 @@ protected void setupViews() {
mDepthController.setSurfaceTransactionApplier(getRootView());
mActionsView = findViewById(R.id.overview_actions_view);
+ mMemInfoView = findViewById(R.id.meminfo);
RecentsView, LauncherState> overviewPanel = getOverviewPanel();
SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.get(this);
mSplitSelectStateController =
@@ -363,13 +392,15 @@ protected void setupViews() {
getDepthController(), DesktopState.getInstance(this));
}
overviewPanel.init(mActionsView, mSplitSelectStateController,
- mDesktopRecentsTransitionController);
+ mDesktopRecentsTransitionController, mMemInfoView);
mSplitWithKeyboardShortcutController = new SplitWithKeyboardShortcutController(
this, mSplitSelectStateController);
mSplitToWorkspaceController = new SplitToWorkspaceController(this,
mSplitSelectStateController);
mActionsView.updateDimension(getDeviceProfile(), overviewPanel.getLastComputedTaskSize());
mActionsView.updateVerticalMargin(DisplayController.getNavigationMode(this));
+ mMemInfoView.setDp(getDeviceProfile());
+ mMemInfoView.updateVerticalMargin(DisplayController.getNavigationMode(this));
mAppTransitionManager = buildAppTransitionManager();
mAppTransitionManager.registerRemoteAnimations();
@@ -556,7 +587,8 @@ public View.OnClickListener getItemOnClickListener() {
public Stream getSupportedShortcuts(ItemInfo itemInfo) {
// Order matters as it affects order of appearance in popup container
List shortcuts = new ArrayList(Arrays.asList(
- APP_INFO, WellbeingModel.SHORTCUT_FACTORY, mHotseatPredictionController));
+ APP_INFO, WellbeingModel.SHORTCUT_FACTORY, mHotseatPredictionController,
+ RENAME_APP));
int container = itemInfo.container;
if (canPinAppWithContextMenu()
&& DisplayController.showDesktopTaskbarForFreeformDisplay(this)
@@ -566,8 +598,11 @@ public Stream getSupportedShortcuts(ItemInfo itemInfo) {
}
shortcuts.addAll(getSplitShortcuts());
+ shortcuts.add(UNINSTALL);
shortcuts.add(WellbeingModel.PAUSE_APPS);
+ shortcuts.add(FLOATING);
shortcuts.add(WIDGETS);
+ shortcuts.add(KILL_APP);
shortcuts.add(INSTALL);
// TODO(b/444744861): Update private space apps to have its own container.
boolean isPinnable = itemInfo instanceof ItemInfoWithIcon info
@@ -695,6 +730,9 @@ public void bindWorkspaceComponentsRemoved(Predicate matcher) {
@Override
public void onDestroy() {
+ if (!LauncherPrefs.SHOW_STATUS_BAR.get(this)) {
+ getStateManager().removeStateListener(noStatusBarStateListener);
+ }
if (mAppTransitionManager != null) {
mAppTransitionManager.onActivityDestroyed();
}
@@ -853,10 +891,15 @@ protected void onCreate(Bundle savedInstanceState) {
mViewCapture = ViewCaptureFactory.getInstance(this).startCapture(getWindow());
getWindow().addPrivateFlags(PRIVATE_FLAG_OPTIMIZE_MEASURE);
QuickstepOnboardingPrefs.setup(this);
+ if (!LauncherPrefs.SHOW_STATUS_BAR.get(this)) {
+ getStateManager().addStateListener(noStatusBarStateListener);
+ }
View.setTraceLayoutSteps(TRACE_LAYOUTS);
View.setTracedRequestLayoutClassClass(TRACE_RELAYOUT_CLASS);
OverviewComponentObserver.INSTANCE.get(this)
.addOverviewChangeListener(mOverviewChangeListener);
+
+ AppDatabase.INSTANCE.get(this).checkpointSync();
}
@Override
@@ -1335,11 +1378,21 @@ public void dismissSplitSelection(StatsLogManager.LauncherEvent splitDismissEven
.playPlaceholderDismissAnim(this, splitDismissEvent);
}
+ @Override
+ public void onSleepEvent(MotionEvent ev) {
+ super.onSleepEvent(ev);
+ SystemUiProxy.INSTANCE.get(this).onSleepEvent(ev);
+ }
+
@Override
public OverviewActionsView> getActionsView() {
return mActionsView;
}
+ public MemInfoView getMemInfoView () {
+ return mMemInfoView;
+ }
+
@Override
protected void closeOpenViews(boolean animate) {
super.closeOpenViews(animate);
@@ -1449,6 +1502,9 @@ public void onDisplayInfoChanged(Context context, DisplayController.Info info, i
if (mActionsView != null) {
mActionsView.updateVerticalMargin(info.getNavigationMode());
}
+ if (mMemInfoView != null) {
+ mMemInfoView.updateVerticalMargin(info.getNavigationMode());
+ }
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt
index 87d2666858f..1f67f2d9d5a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt
@@ -39,6 +39,7 @@ import com.android.launcher3.util.OverviewReleaseFlags.enableGridOnlyOverview
import com.android.quickstep.util.AnimUtils
import com.android.quickstep.views.AddDesktopButton
import com.android.quickstep.views.ClearAllButton
+import com.android.quickstep.views.MemInfoView
import com.android.quickstep.views.RecentsView
import com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET
import com.android.quickstep.views.RecentsView.CONTENT_ALPHA
@@ -313,6 +314,14 @@ class RecentsViewStateController(private val launcher: QuickstepLauncher) :
LINEAR,
)
}
+ val memInfoAlpha =
+ if (state.areElementsVisible(launcherUiState, LauncherState.MEMINFO)) 1f else 0f
+ propertySetter.setFloat(
+ launcher.memInfoView,
+ MemInfoView.STATE_CTRL_ALPHA,
+ memInfoAlpha,
+ config.getInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, LINEAR),
+ )
}
private fun getOverviewInterpolator(fromState: LauncherState, toState: LauncherState) =
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index 649f5c71871..9ea75f5198a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -21,13 +21,17 @@
import android.content.Context;
import android.graphics.Color;
+import androidx.core.graphics.ColorUtils;
+
import com.android.internal.jank.Cuj;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Flags;
import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherUiState;
import com.android.launcher3.R;
+import com.android.launcher3.util.BerryBlackTheme;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.ScrimColors;
@@ -121,7 +125,7 @@ public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
@Override
public ScaleAndTranslation getHotseatScaleAndTranslation(Launcher launcher) {
- if (launcher.getDeviceProfile().shouldShowAllAppsOnSheet()) {
+ if (launcher.getDeviceProfile().shouldShowAllAppsOnSheet(launcher)) {
return getWorkspaceScaleAndTranslation(launcher);
} else {
ScaleAndTranslation overviewScaleAndTranslation = LauncherState.OVERVIEW
@@ -136,7 +140,7 @@ public ScaleAndTranslation getHotseatScaleAndTranslation(Launcher launcher) {
@Override
protected
float getDepthUnchecked(DEVICE_PROFILE_CONTEXT context) {
- if (context.getDeviceProfile().shouldShowAllAppsOnSheet()) {
+ if (context.getDeviceProfile().shouldShowAllAppsOnSheet(context)) {
return context.getDeviceProfile().getBottomSheetProfile().getBottomSheetDepth();
} else {
// The scrim fades in at approximately 50% of the swipe gesture.
@@ -203,7 +207,12 @@ public boolean shouldFloatingSearchBarUsePillWhenUnfocused(Launcher launcher) {
@Override
public ScrimColors getWorkspaceScrimColor(Launcher launcher) {
int backgroundColor;
- if (!launcher.getDeviceProfile().shouldShowAllAppsOnSheet()) {
+ int scrimColor;
+ if (LauncherPrefs.APP_DRAWER_CUSTOM_COLOR_ENABLED.get(launcher)) {
+ backgroundColor = com.android.launcher3.Utilities.isDarkTheme(launcher)
+ ? LauncherPrefs.APP_DRAWER_CUSTOM_COLOR_DARK.get(launcher)
+ : LauncherPrefs.APP_DRAWER_CUSTOM_COLOR_LIGHT.get(launcher);
+ } else if (!launcher.getDeviceProfile().shouldShowAllAppsOnSheet(launcher)) {
// Always use an opaque scrim if there's no sheet.
backgroundColor = launcher.getResources().getColor(R.color.materialColorSurfaceDim);
} else if (!Flags.allAppsBlur()) {
@@ -212,6 +221,11 @@ public ScrimColors getWorkspaceScrimColor(Launcher launcher) {
} else {
backgroundColor = Themes.getAttrColor(launcher, R.attr.allAppsScrimColor);
}
- return new ScrimColors(backgroundColor, /* foregroundColor */ Color.TRANSPARENT);
+ if (BerryBlackTheme.isActive(launcher)) {
+ backgroundColor = Color.BLACK;
+ }
+ scrimColor = ColorUtils.setAlphaComponent(
+ backgroundColor, LauncherPrefs.APP_DRAWER_OPACITY.get(launcher) * 255 / 100);
+ return new ScrimColors(scrimColor, /* foregroundColor */ Color.TRANSPARENT);
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index c02af3ef437..e2f7a9e3940 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -80,6 +80,7 @@ public int getVisibleElements(LauncherUiState launcherUiState) {
return super.getVisibleElements(launcherUiState)
& ~OVERVIEW_ACTIONS
& ~CLEAR_ALL_BUTTON
+ & ~MEMINFO
& ~VERTICAL_SWIPE_INDICATOR
& ~ADD_DESK_BUTTON;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
index bd17d17379a..f2596661a93 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
@@ -20,6 +20,7 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherUiState;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.views.ActivityContext;
@@ -45,7 +46,12 @@ public int getTransitionDuration(ActivityContext launcher, boolean isToState) {
@Override
public int getVisibleElements(LauncherUiState launcherUiState) {
- return OVERVIEW_ACTIONS;
+ int elements = OVERVIEW_ACTIONS;
+ DeviceProfile dp = launcherUiState.getDeviceProfileRef().getValue();
+ if (LauncherPrefs.RECENTS_MEMINFO.get(dp.getContext())) {
+ elements |= MEMINFO;
+ }
+ return elements;
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 6191afd20e4..e5fc37640a2 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -21,6 +21,7 @@
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
import android.content.Context;
+import android.graphics.Color;
import android.graphics.Rect;
import android.os.SystemProperties;
@@ -28,6 +29,7 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherUiState;
import com.android.launcher3.R;
@@ -116,7 +118,7 @@ public float getPageAlpha(int pageIndex) {
@Override
public int getVisibleElements(LauncherUiState launcherUiState) {
- int elements = CLEAR_ALL_BUTTON | OVERVIEW_ACTIONS | ADD_DESK_BUTTON;
+ int elements = OVERVIEW_ACTIONS | ADD_DESK_BUTTON;
boolean showFloatingSearch;
DeviceProfile dp = launcherUiState.getDeviceProfileRef().getValue();
if (dp.getDeviceProperties().isPhone()) {
@@ -129,9 +131,16 @@ public int getVisibleElements(LauncherUiState launcherUiState) {
if (showFloatingSearch) {
elements |= FLOATING_SEARCH_BAR;
}
+ if (dp.getDeviceProperties().isTablet() ||
+ !LauncherPrefs.RECENTS_CLEAR_ALL.get(dp.getContext())) {
+ elements |= CLEAR_ALL_BUTTON;
+ }
if (launcherUiState.isSplitSelectActiveRef().getValue()) {
elements &= ~CLEAR_ALL_BUTTON & ~ADD_DESK_BUTTON;
}
+ if (LauncherPrefs.RECENTS_MEMINFO.get(dp.getContext())) {
+ elements |= MEMINFO;
+ }
return elements;
}
@@ -163,11 +172,12 @@ public boolean isTaskbarAlignedWithHotseat() {
@Override
public ScrimColors getWorkspaceScrimColor(Launcher launcher) {
+ int scrimColor = ColorUtils.setAlphaComponent(
+ Themes.getAttrColor(launcher, R.attr.overviewScrimColor),
+ LauncherPrefs.RECENTS_OPACITY.get(launcher) * 255 / 100);
return new ScrimColors(
- /* backgroundColor */ Themes.getAttrColor(launcher, R.attr.overviewScrimColor),
- /* foregroundColor */ ColorUtils.compositeColors(
- Themes.getAttrColor(launcher, R.attr.overviewScrimForegroundPrimary),
- Themes.getAttrColor(launcher, R.attr.overviewScrimForegroundSecondary)));
+ /* backgroundColor */ scrimColor,
+ /* foregroundColor */ Color.TRANSPARENT);
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index 8fb1dd0092b..2e3d9d034db 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -61,20 +61,21 @@ protected void applyOverviewToHomeAnimConfig(
isThreeButton);
Workspace> workspace = getContainer().getWorkspace();
// Start from a higher workspace scale, but only if we're invisible so we don't jump.
- boolean isWorkspaceVisible = workspace.getVisibility() == VISIBLE;
- if (isWorkspaceVisible) {
+ boolean isWorkspaceVisible = workspace != null && workspace.getVisibility() == VISIBLE;
+ if (isWorkspaceVisible && workspace != null) {
CellLayout currentChild = (CellLayout) workspace.getChildAt(
workspace.getCurrentPage());
- isWorkspaceVisible = currentChild.getVisibility() == VISIBLE
+ isWorkspaceVisible = currentChild != null && currentChild.getVisibility() == VISIBLE
&& currentChild.getShortcutsAndWidgets().getAlpha() > 0;
}
- if (!isWorkspaceVisible) {
+ if (!isWorkspaceVisible && workspace != null) {
workspace.setScaleX(WORKSPACE_PREPARE_SCALE);
workspace.setScaleY(WORKSPACE_PREPARE_SCALE);
}
Hotseat hotseat = getContainer().getHotseat();
- boolean isHotseatVisible = hotseat.getVisibility() == VISIBLE && hotseat.getAlpha() > 0;
- if (!isHotseatVisible) {
+ boolean isHotseatVisible = hotseat != null && hotseat.getVisibility() == VISIBLE
+ && hotseat.getAlpha() > 0;
+ if (!isHotseatVisible && hotseat != null) {
hotseat.setScaleX(WORKSPACE_PREPARE_SCALE);
hotseat.setScaleY(WORKSPACE_PREPARE_SCALE);
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index 0125066f2d8..c190d093770 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -157,7 +157,7 @@ private void initCurrentAnimation() {
AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_TASK_MENU);
} else if (mStartState == ALL_APPS) {
AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
- if (mLauncher.getDeviceProfile().shouldShowAllAppsOnSheet()) {
+ if (mLauncher.getDeviceProfile().shouldShowAllAppsOnSheet(mLauncher)) {
allAppsController.setShouldScaleHeader(true);
builder.addAnimatedFloat(allAppsController.getAllAppScale(), 1f,
PREDICTIVE_BACK_MIN_SCALE, PULLBACK_INTERPOLATOR);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewLaunchTouchController.kt b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewLaunchTouchController.kt
index 6225c47835d..6efddae8b46 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewLaunchTouchController.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewLaunchTouchController.kt
@@ -19,25 +19,26 @@ package com.android.launcher3.uioverrides.touchcontrollers
import android.content.Context
import android.graphics.Rect
import android.view.MotionEvent
-import com.android.app.animation.Interpolators.ZOOM_IN
import com.android.launcher3.AbstractFloatingView
-import com.android.launcher3.LauncherAnimUtils
import com.android.launcher3.Utilities.EDGE_NAV_BAR
import com.android.launcher3.Utilities.boundToRange
import com.android.launcher3.Utilities.debugLog
import com.android.launcher3.Utilities.isRtl
-import com.android.launcher3.anim.AnimatorPlaybackController
-import com.android.launcher3.touch.BaseSwipeDetector
import com.android.launcher3.touch.SingleAxisSwipeDetector
import com.android.launcher3.util.DisplayController
-import com.android.launcher3.util.FlingBlockCheck
import com.android.launcher3.util.TouchController
+import com.android.quickstep.LockedTaskManager
+import com.android.quickstep.views.RecentsDismissUtils
import com.android.quickstep.views.RecentsView
import com.android.quickstep.views.RecentsViewContainer
import com.android.quickstep.views.TaskView
+import com.google.android.msdl.data.model.MSDLToken
+import com.android.launcher3.util.MSDLPlayerWrapper
import kotlin.math.abs
+import kotlin.math.ceil
+import kotlin.math.roundToInt
-/** Touch controller which handles dragging task view cards for launch. */
+/** Touch controller which handles dragging task view cards for lock/unlock. */
class TaskViewLaunchTouchController(
private val container: CONTAINER,
private val taskViewRecentsTouchContext: TaskViewRecentsTouchContext,
@@ -45,7 +46,6 @@ class TaskViewLaunchTouchController(
CONTAINER : Context,
CONTAINER : RecentsViewContainer {
private val tempRect = Rect()
- private val flingBlockCheck = FlingBlockCheck()
private val recentsView: RecentsView<*, *> = container.getOverviewPanel()
private val detector: SingleAxisSwipeDetector =
SingleAxisSwipeDetector(
@@ -57,12 +57,13 @@ CONTAINER : RecentsViewContainer {
private val downDirection = recentsView.pagedOrientationHandler.getDownDirection(isRtl)
private var taskBeingDragged: TaskView? = null
- private var launchEndDisplacement: Float = 0f
- private var playbackController: AnimatorPlaybackController? = null
+ private var maxLockDisplacement: Float = 0f
private var verticalFactor: Int = 0
private var canInterceptTouch = false
+ private var wasLockedBeforeDrag = false
+ private var hasLockThresholdHapticRun = false
- private fun canTaskLaunchTaskView(taskView: TaskView?) =
+ private fun canTaskLockTaskView(taskView: TaskView?) =
taskView != null &&
taskView === recentsView.currentPageTaskView &&
DisplayController.getNavigationMode(container).hasGestures &&
@@ -93,7 +94,7 @@ CONTAINER : RecentsViewContainer {
false
}
- // Do not allow launch while recents is scrolling.
+ // Do not allow lock while recents is scrolling.
!recentsView.scroller.isFinished -> {
debugLog(TAG, "Not intercepting touch, recents scrolling.")
false
@@ -109,8 +110,7 @@ CONTAINER : RecentsViewContainer {
override fun onControllerInterceptTouchEvent(ev: MotionEvent): Boolean {
if (
- (ev.action == MotionEvent.ACTION_UP || ev.action == MotionEvent.ACTION_CANCEL) &&
- playbackController == null
+ (ev.action == MotionEvent.ACTION_UP || ev.action == MotionEvent.ACTION_CANCEL)
) {
clearState()
}
@@ -148,8 +148,8 @@ CONTAINER : RecentsViewContainer {
verticalFactor =
recentsView.pagedOrientationHandler.getTaskDragDisplacementFactor(isRtl)
}
- if (!canTaskLaunchTaskView(taskBeingDragged)) {
- debugLog(TAG, "Not intercepting touch, task cannot be launched.")
+ if (!canTaskLockTaskView(taskBeingDragged)) {
+ debugLog(TAG, "Not intercepting touch, task cannot be locked.")
return false
}
detector.setDetectableScrollConditions(downDirection, /* ignoreSlop= */ false)
@@ -158,74 +158,136 @@ CONTAINER : RecentsViewContainer {
override fun onDragStart(start: Boolean, startDisplacement: Float) {
val taskBeingDragged = taskBeingDragged ?: return
- debugLog(TAG, "Handling touch event.")
+ debugLog(TAG, "Handling lock touch event.")
val secondaryLayerDimension: Int =
recentsView.pagedOrientationHandler.getSecondaryDimension(container.getDragLayer())
- val maxDuration = 2L * secondaryLayerDimension
- recentsView.clearPendingAnimation()
- val pendingAnimation =
- recentsView.createTaskLaunchAnimation(taskBeingDragged, maxDuration, ZOOM_IN)
- // Since the thumbnail is what is filling the screen, based the end displacement on it.
taskBeingDragged.getThumbnailBounds(tempRect, /* relativeToDragLayer= */ true)
- launchEndDisplacement =
- recentsView.pagedOrientationHandler
- .getTaskLaunchLength(secondaryLayerDimension, tempRect)
- .toFloat() * verticalFactor
- playbackController =
- pendingAnimation.createPlaybackController()?.apply {
- taskViewRecentsTouchContext.onUserControlledAnimationCreated(this)
- dispatchOnStart()
- }
+ maxLockDisplacement = ceil(
+ recentsView.pagedOrientationHandler.getTaskDismissLength(
+ secondaryLayerDimension, tempRect
+ ) * LOCK_DISPLACEMENT_FRACTION
+ ).toFloat() * verticalFactor
+
+ taskBeingDragged.translationZ = 0.1f
+
+ wasLockedBeforeDrag = taskBeingDragged.isLocked
+ hasLockThresholdHapticRun = false
+
+ showLockPill(wasLockedBeforeDrag)
}
override fun onDrag(displacement: Float): Boolean {
- playbackController?.setPlayFraction(
- boundToRange(displacement / launchEndDisplacement, 0f, 1f)
+ val taskBeingDragged = taskBeingDragged ?: return false
+ val boundedDisplacement = boundToRange(
+ abs(displacement),
+ 0f,
+ abs(maxLockDisplacement)
+ ) * verticalFactor
+ taskBeingDragged.secondaryDismissTranslationProperty.setValue(
+ taskBeingDragged, boundedDisplacement
)
+ if (taskBeingDragged.isRunningTask && recentsView.enableDrawingLiveTile) {
+ recentsView.runActionOnRemoteHandles { remoteTargetHandle ->
+ remoteTargetHandle.taskViewSimulator.taskSecondaryTranslation.value =
+ boundedDisplacement
+ }
+ recentsView.redrawLiveTile()
+ }
+ playLockThresholdHaptic(displacement)
return true
}
+ private fun playLockThresholdHaptic(displacement: Float) {
+ val lockThreshold = (LOCK_THRESHOLD_FRACTION * maxLockDisplacement)
+ val lockThresholdAbs = abs(lockThreshold)
+ val displacementAbs = abs(displacement)
+ val inHapticRange =
+ displacementAbs >= (lockThresholdAbs - LOCK_THRESHOLD_HAPTIC_RANGE) &&
+ displacementAbs <= (lockThresholdAbs + LOCK_THRESHOLD_HAPTIC_RANGE)
+ if (!inHapticRange) {
+ hasLockThresholdHapticRun = false
+ } else if (!hasLockThresholdHapticRun) {
+ MSDLPlayerWrapper.INSTANCE.get(recentsView.context)
+ .playToken(MSDLToken.SWIPE_THRESHOLD_INDICATOR)
+ hasLockThresholdHapticRun = true
+ }
+ }
+
override fun onDragEnd(velocity: Float) {
- val playbackController = playbackController ?: return
-
- val isBeyondLaunchThreshold =
- abs(playbackController.progressFraction) > abs(LAUNCH_THRESHOLD_FRACTION)
- val velocityIsNegative = !recentsView.pagedOrientationHandler.isGoingUp(velocity, isRtl)
- val isFlingingTowardsLaunch = detector.isFling(velocity) && velocityIsNegative
- val isFlingingTowardsRestState = detector.isFling(velocity) && !velocityIsNegative
- val isLaunching =
- isFlingingTowardsLaunch || (isBeyondLaunchThreshold && !isFlingingTowardsRestState)
-
- val progress = playbackController.progressFraction
- var animationDuration =
- BaseSwipeDetector.calculateDuration(
+ val taskBeingDragged = taskBeingDragged ?: return
+ val currentDisplacement =
+ taskBeingDragged.secondaryDismissTranslationProperty.get(taskBeingDragged)
+ val isBeyondLockThreshold =
+ abs(currentDisplacement) > abs(LOCK_THRESHOLD_FRACTION * maxLockDisplacement)
+
+ if (isBeyondLockThreshold) {
+ val packageName = taskBeingDragged.firstTask?.key?.packageName
+ if (packageName != null) {
+ LockedTaskManager.getInstance(container).setPackageLocked(
+ packageName, !wasLockedBeforeDrag
+ )
+ taskBeingDragged.updateLockState(packageName)
+ }
+ }
+
+ hideLockPill()
+
+ val dismissLength = abs(maxLockDisplacement).roundToInt()
+ recentsView.runTaskDismissSettlingSpringAnimation(
+ taskBeingDragged,
+ false,
+ RecentsDismissUtils.DismissedTaskData(
velocity,
- if (isLaunching) (1 - progress) else progress,
- )
- if (detector.isFling(velocity) && flingBlockCheck.isBlocked && !isLaunching) {
- animationDuration *= LauncherAnimUtils.blockedFlingDurationFactor(velocity).toLong()
+ dismissLength,
+ 0f,
+ (LOCK_THRESHOLD_FRACTION * dismissLength).roundToInt(),
+ ),
+ false,
+ false,
+ )?.addEndListener {
+ if (taskBeingDragged.isRunningTask) {
+ recentsView.runActionOnRemoteHandles { remoteTargetHandle ->
+ remoteTargetHandle.taskViewSimulator.taskSecondaryTranslation.value = 0f
+ }
+ recentsView.redrawLiveTile()
+ }
+ taskBeingDragged.translationZ = 0f
+ taskBeingDragged.isBeingDismissed = false
}
+ }
- playbackController.setEndAction(this::clearState)
- playbackController.startWithVelocity(
- container,
- isLaunching,
- velocity,
- launchEndDisplacement,
- animationDuration,
- )
+ private fun showLockPill(isCurrentlyLocked: Boolean) {
+ val actionsView = container.actionsView ?: return
+ actionsView.showLockPill(isCurrentlyLocked)
+ }
+
+ private fun hideLockPill() {
+ val actionsView = container.actionsView ?: return
+ actionsView.hideLockPill()
}
private fun clearState() {
detector.finishedScrolling()
detector.setDetectableScrollConditions(0, false)
+ taskBeingDragged?.let {
+ it.secondaryDismissTranslationProperty.setValue(it, 0f)
+ if (it.isRunningTask) {
+ recentsView.runActionOnRemoteHandles { remoteTargetHandle ->
+ remoteTargetHandle.taskViewSimulator.taskSecondaryTranslation.value = 0f
+ }
+ recentsView.redrawLiveTile()
+ }
+ it.translationZ = 0f
+ }
+ hideLockPill()
taskBeingDragged = null
- playbackController = null
}
companion object {
private const val TAG = "TaskViewLaunchTouchController"
- private const val LAUNCH_THRESHOLD_FRACTION: Float = 0.5f
+ private const val LOCK_DISPLACEMENT_FRACTION = 0.4f
+ private const val LOCK_THRESHOLD_FRACTION = 0.5f
+ private const val LOCK_THRESHOLD_HAPTIC_RANGE = 10f
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchControllerDeprecated.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchControllerDeprecated.java
index b57eba547f5..2d3b98466d5 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchControllerDeprecated.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchControllerDeprecated.java
@@ -27,7 +27,6 @@
import android.os.VibrationEffect;
import android.view.MotionEvent;
import android.view.animation.Interpolator;
-
import com.android.app.animation.Interpolators;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.LauncherAnimUtils;
@@ -42,6 +41,7 @@
import com.android.launcher3.util.TouchController;
import com.android.launcher3.util.VibratorWrapper;
import com.android.launcher3.views.BaseDragLayer;
+import com.android.quickstep.LockedTaskManager;
import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.util.VibrationConstants;
import com.android.quickstep.views.RecentsView;
@@ -91,6 +91,7 @@ public class TaskViewTouchControllerDeprecated<
private Float mOverrideVelocity = null;
private TaskView mTaskBeingDragged;
+ private float mTaskDragStartTranslationZ = 0f;
private boolean mIsDismissHapticRunning = false;
@@ -130,6 +131,39 @@ private boolean canInterceptTouch(MotionEvent ev) {
return mTaskViewRecentsTouchContext.isRecentsInteractive();
}
+ /**
+ * Returns the topmost TaskView under the event (prefers larger Z, then later draw order).
+ * This prevents dragging the wrong card when task views overlap.
+ */
+ private TaskView findTopMostTaskUnderEvent(MotionEvent ev) {
+ if (mTaskViewRecentsTouchContext.isRecentsModal()) {
+ return null;
+ }
+
+ final BaseDragLayer dragLayer = mContainer.getDragLayer();
+ TaskView best = null;
+ float bestZ = Float.NEGATIVE_INFINITY;
+ int bestIndex = Integer.MIN_VALUE;
+ final float eps = 1e-4f;
+
+ for (TaskView taskView : mRecentsView.getTaskViews()) {
+ if (!mRecentsView.isTaskViewVisible(taskView)) continue;
+ if (!dragLayer.isEventOverView(taskView, ev)) continue;
+
+ final float z = taskView.getZ();
+ final int index = mRecentsView.indexOfChild(taskView);
+
+ if (best == null
+ || z > bestZ + eps
+ || (Math.abs(z - bestZ) <= eps && index > bestIndex)) {
+ best = taskView;
+ bestZ = z;
+ bestIndex = index;
+ }
+ }
+ return best;
+ }
+
@Override
public void onAnimationCancel(Animator animation) {
if (mCurrentAnimation != null && animation == mCurrentAnimation.getTarget()) {
@@ -158,37 +192,28 @@ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
directionsToDetectScroll = DIRECTION_BOTH;
ignoreSlopWhenSettling = true;
} else {
- mTaskBeingDragged = null;
-
- for (TaskView taskView : mRecentsView.getTaskViews()) {
- if (mRecentsView.isTaskViewVisible(taskView) && mContainer.getDragLayer()
- .isEventOverView(taskView, ev)) {
- // Disable swiping up and down if the task overlay is modal.
- if (mTaskViewRecentsTouchContext.isRecentsModal()) {
- mTaskBeingDragged = null;
- break;
- }
- mTaskBeingDragged = taskView;
- int upDirection = mRecentsView.getPagedOrientationHandler()
- .getUpDirection(mIsRtl);
-
- // The task can be dragged up to dismiss it
- mAllowGoingUp = true;
-
- // The task can be dragged down to open it if:
- // - It's the current page
- // - We support gestures to enter overview
- // - It's the focused task if in grid view
- // - The task is snapped
- mAllowGoingDown = taskView == mRecentsView.getCurrentPageTaskView()
- && DisplayController.getNavigationMode(mContainer).hasGestures
- && (!mRecentsView.showAsGrid() || mTaskBeingDragged.isLargeTile())
- && mRecentsView.isTaskInExpectedScrollPosition(taskView);
-
- directionsToDetectScroll = mAllowGoingDown ? DIRECTION_BOTH : upDirection;
- break;
- }
+ mTaskBeingDragged = findTopMostTaskUnderEvent(ev);
+
+ if (mTaskBeingDragged != null) {
+ int upDirection = mRecentsView.getPagedOrientationHandler()
+ .getUpDirection(mIsRtl);
+
+ // The task can be dragged up to dismiss it.
+ mAllowGoingUp = true;
+
+ // The task can be dragged down to open it if:
+ // - It's the current page
+ // - We support gestures to enter overview
+ // - It's the focused task if in grid view
+ // - The task is snapped
+ mAllowGoingDown = mTaskBeingDragged == mRecentsView.getCurrentPageTaskView()
+ && DisplayController.getNavigationMode(mContainer).hasGestures
+ && (!mRecentsView.showAsGrid() || mTaskBeingDragged.isLargeTile())
+ && mRecentsView.isTaskInExpectedScrollPosition(mTaskBeingDragged);
+
+ directionsToDetectScroll = mAllowGoingDown ? DIRECTION_BOTH : upDirection;
}
+
if (mTaskBeingDragged == null) {
mNoIntercept = true;
debugLog(TAG, "Not intercepting touch, no task to drag.");
@@ -237,27 +262,43 @@ private void reInitAnimationController(boolean goingUp) {
long maxDuration = 2 * secondaryLayerDimension;
int verticalFactor = orientationHandler.getTaskDragDisplacementFactor(mIsRtl);
int secondaryTaskDimension = orientationHandler.getSecondaryDimension(mTaskBeingDragged);
- // The interpolator controlling the most prominent visual movement. We use this to determine
- // whether we passed SUCCESS_TRANSITION_PROGRESS.
+
final Interpolator currentInterpolator;
PendingAnimation pa;
+
if (goingUp) {
+ // Build the full dismiss animation up front so the user's drag drives both
+ // the dismissed card AND the gap-fill translations on the remaining cards in
+ // lockstep via setPlayFraction(). This is the AOSP path; the previous
+ // "simple animation only on the dragged card during drag, swap to full
+ // animation on lift" approach (commit de4afeb88c) caused a discontinuity on
+ // lift because the dragged card's secondary translation and the other cards'
+ // dismiss translations jumped from their simple-animation values to the full
+ // animation's values at the swap point. Building the full animation from the
+ // start keeps the gesture continuous and reversible (cancel just plays the
+ // animation back to progress=0).
currentInterpolator = Interpolators.LINEAR;
pa = new PendingAnimation(maxDuration);
mRecentsView.createTaskDismissAnimation(pa, mTaskBeingDragged,
true /* animateTaskView */, true /* removeTask */, maxDuration,
- false /* dismissingForSplitSelection*/, null /* gridEndData */);
+ false /* dismissingForSplitSelection */, null /* gridEndData */);
mEndDisplacement = -secondaryTaskDimension;
} else {
- currentInterpolator = Interpolators.ZOOM_IN;
- pa = mRecentsView.createTaskLaunchAnimation(
- mTaskBeingDragged, maxDuration, currentInterpolator);
+ // Drag-down uses translation with secondaryDismissTranslationProperty for lock
+ currentInterpolator = Interpolators.LINEAR;
+ pa = new PendingAnimation(maxDuration);
- // Since the thumbnail is what is filling the screen, based the end displacement on it.
- mTaskBeingDragged.getThumbnailBounds(mTempRect, /*relativeToDragLayer=*/true);
- mEndDisplacement = secondaryLayerDimension - mTempRect.bottom;
+ // Translate down by 40% of task height using the dismiss property (for neighbor settle)
+ float maxLockDisplacement = -secondaryTaskDimension * 0.4f * verticalFactor;
+ pa.setFloat(mTaskBeingDragged,
+ mTaskBeingDragged.getSecondaryDismissTranslationProperty(),
+ maxLockDisplacement,
+ Interpolators.LINEAR);
+
+ mEndDisplacement = maxLockDisplacement;
}
+
mEndDisplacement *= verticalFactor;
mCurrentAnimation = pa.createPlaybackController();
@@ -275,8 +316,20 @@ public void onDragStart(boolean start, float startDisplacement) {
if (!mDraggingEnabled) return;
debugLog(TAG, "Handling touch.");
+ // Bring the dragged task above neighbors during the gesture; restore in clearState().
+ if (mTaskBeingDragged != null) {
+ mTaskDragStartTranslationZ = mTaskBeingDragged.getTranslationZ();
+ mTaskBeingDragged.setTranslationZ(Math.max(mTaskDragStartTranslationZ, 0.1f));
+ }
+
RecentsPagedOrientationHandler orientationHandler =
mRecentsView.getPagedOrientationHandler();
+ boolean isGoingDown = !orientationHandler.isGoingUp(startDisplacement, mIsRtl);
+ if (isGoingDown && mTaskBeingDragged != null && mTaskBeingDragged.isRunningTask()
+ && mRecentsView.getEnableDrawingLiveTile()) {
+ mRecentsView.setEnableDrawingLiveTile(false);
+ }
+
if (mCurrentAnimation == null) {
reInitAnimationController(orientationHandler.isGoingUp(startDisplacement, mIsRtl));
mDisplacementShift = 0;
@@ -297,6 +350,7 @@ public boolean onDrag(float displacement) {
float totalDisplacement = displacement + mDisplacementShift;
boolean isGoingUp = totalDisplacement == 0 ? mCurrentAnimationIsGoingUp :
orientationHandler.isGoingUp(totalDisplacement, mIsRtl);
+
if (isGoingUp != mCurrentAnimationIsGoingUp) {
reInitAnimationController(isGoingUp);
mFlingBlockCheck.blockFling();
@@ -304,34 +358,9 @@ public boolean onDrag(float displacement) {
mFlingBlockCheck.onEvent();
}
- if (isGoingUp) {
- if (mCurrentAnimation.getProgressFraction() < ANIMATION_PROGRESS_FRACTION_MIDPOINT) {
- // Halve the value when dismissing, as we are animating the drag across the full
- // length for only the first half of the progress
- mCurrentAnimation.setPlayFraction(
- Utilities.boundToRange(totalDisplacement * mProgressMultiplier / 2, 0, 1));
- } else {
- // Set mOverrideVelocity to control task dismiss velocity in onDragEnd
- int velocityDimenId = R.dimen.default_task_dismiss_drag_velocity;
- if (mRecentsView.showAsGrid()) {
- if (mTaskBeingDragged.isLargeTile()) {
- velocityDimenId =
- R.dimen.default_task_dismiss_drag_velocity_grid_focus_task;
- } else {
- velocityDimenId = R.dimen.default_task_dismiss_drag_velocity_grid;
- }
- }
- mOverrideVelocity = -mTaskBeingDragged.getResources().getDimension(velocityDimenId);
-
- // Once halfway through task dismissal interpolation, switch from reversible
- // dragging-task animation to playing the remaining task translation animations,
- // while this is in progress disable dragging.
- mDraggingEnabled = false;
- }
- } else {
- mCurrentAnimation.setPlayFraction(
- Utilities.boundToRange(totalDisplacement * mProgressMultiplier, 0, 1));
- }
+ // Drag-up matches drag-down: always track finger across the full range.
+ mCurrentAnimation.setPlayFraction(
+ Utilities.boundToRange(totalDisplacement * mProgressMultiplier, 0, 1));
return true;
}
@@ -363,6 +392,37 @@ public void onDragEnd(float velocity) {
} else {
goingToEnd = interpolatedProgress > SUCCESS_TRANSITION_PROGRESS;
}
+
+ // No swap-on-lift: reInitAnimationController already built the full dismiss
+ // animation, so the controller plays continuously from `progress` to either 0
+ // (cancel) or 1 (success) without any discontinuity.
+
+ if (!mCurrentAnimationIsGoingUp && goingToEnd) {
+ // Swipe-down complete: toggle lock state
+ String packageName = mTaskBeingDragged.getFirstTask() != null
+ ? mTaskBeingDragged.getFirstTask().key.getPackageName() : null;
+ if (packageName != null) {
+ LockedTaskManager ltm = LockedTaskManager.getInstance(mContainer);
+ boolean wasLocked = mTaskBeingDragged.isLocked();
+ ltm.setPackageLocked(packageName, !wasLocked);
+ mTaskBeingDragged.updateLockState(packageName);
+ }
+ if (!mIsDismissHapticRunning) {
+ VibratorWrapper.INSTANCE.get(mContainer).vibrate(
+ TASK_DISMISS_VIBRATION_PRIMITIVE,
+ TASK_DISMISS_VIBRATION_PRIMITIVE_SCALE,
+ TASK_DISMISS_VIBRATION_FALLBACK);
+ mIsDismissHapticRunning = true;
+ }
+ // Animate back to original position (the animation already handles the reverse)
+ long lockAnimDuration = BaseSwipeDetector.calculateDuration(velocity, progress);
+ mCurrentAnimation.setEndAction(this::clearState);
+ mCurrentAnimation.startWithVelocity(mContainer, false, Math.abs(velocity),
+ mEndDisplacement, lockAnimDuration);
+ mDraggingEnabled = true;
+ return;
+ }
+
long animationDuration = BaseSwipeDetector.calculateDuration(
velocity, goingToEnd ? (1 - progress) : progress);
if (blockedFling && !goingToEnd) {
@@ -390,6 +450,22 @@ private void clearState() {
mDetector.finishedScrolling();
mDetector.setDetectableScrollConditions(0, false);
mDraggingEnabled = true;
+
+ // Restore Z if we modified it during drag.
+ if (mTaskBeingDragged != null) {
+ mTaskBeingDragged.setTranslationZ(mTaskDragStartTranslationZ);
+ // Restore live tile if it was suppressed
+ if (mTaskBeingDragged.isRunningTask()
+ && !mRecentsView.getEnableDrawingLiveTile()) {
+ mRecentsView.setEnableDrawingLiveTile(true);
+ mRecentsView.runActionOnRemoteHandles(remoteTargetHandle ->
+ remoteTargetHandle.getTaskViewSimulator()
+ .taskSecondaryTranslation.updateValue(0f));
+ mRecentsView.redrawLiveTile();
+ }
+ }
+ mTaskDragStartTranslationZ = 0f;
+
mTaskBeingDragged = null;
mCurrentAnimation = null;
mIsDismissHapticRunning = false;
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index cac4a95ded0..29fcd001f08 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -2719,9 +2719,11 @@ private boolean isKeyboardTaskFocusPending() {
}
private void onRecentsViewScroll() {
- if (moveWindowWithRecentsScroll()) {
- onCurrentShiftUpdated();
+ if (!moveWindowWithRecentsScroll()) return;
+ if (mRecentsView != null && mRecentsView.isOverlapStyleActive()) {
+ return;
}
+ onCurrentShiftUpdated();
}
protected void startNewTask(@Nullable TaskView taskToLaunch, Consumer resultCallback) {
diff --git a/quickstep/src/com/android/quickstep/AllAppsActionManager.kt b/quickstep/src/com/android/quickstep/AllAppsActionManager.kt
index c36e5404a0a..868198f8b01 100644
--- a/quickstep/src/com/android/quickstep/AllAppsActionManager.kt
+++ b/quickstep/src/com/android/quickstep/AllAppsActionManager.kt
@@ -23,6 +23,7 @@ import android.content.Context
import android.graphics.drawable.Icon
import android.provider.Settings
import android.provider.Settings.Secure.USER_SETUP_COMPLETE
+import android.util.Log
import android.view.accessibility.AccessibilityManager
import com.android.launcher3.R
import com.android.launcher3.util.SettingsCache
@@ -49,6 +50,8 @@ class AllAppsActionManager(
private val onSettingsChangeListener = OnChangeListener { v -> isUserSetupComplete = v }
+ private var isDestroyed = false
+
init {
SettingsCache.INSTANCE[context].register(USER_SETUP_COMPLETE_URI, onSettingsChangeListener)
}
@@ -94,6 +97,11 @@ class AllAppsActionManager(
private fun updateSystemAction() {
synchronized(this) {
+ if (isDestroyed) {
+ Log.w(TAG, "updateSystemAction called when destroyed", Throwable())
+ return
+ }
+
val isInSetupFlow = isSetupUiVisible || !isUserSetupComplete
val shouldRegisterAction =
(isHomeAndOverviewSame || isTaskbarPresent) && !isInSetupFlow && isUserUnlocked
@@ -101,27 +109,36 @@ class AllAppsActionManager(
isActionRegistered = shouldRegisterAction
bgExecutor.execute {
- val accessibilityManager =
- context.getSystemService(AccessibilityManager::class.java) ?: return@execute
- if (shouldRegisterAction) {
- val allAppsPendingIntent = createAllAppsPendingIntent()
- accessibilityManager.registerSystemAction(
- RemoteAction(
- Icon.createWithResource(context, R.drawable.ic_apps),
- context.getString(R.string.all_apps_label),
- context.getString(R.string.all_apps_label),
- allAppsPendingIntent,
- ),
- GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS,
- )
- quickstepKeyGestureEventsManager.registerAllAppsKeyGestureEvent(
- allAppsPendingIntent
- )
- } else {
- accessibilityManager.unregisterSystemAction(
- GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS
- )
- quickstepKeyGestureEventsManager.unregisterAllAppsKeyGestureEvent()
+ synchronized(this@AllAppsActionManager) {
+ if (isDestroyed) {
+ isActionRegistered = false
+ Log.w(TAG, "updateSystemAction bgExecutor task running " +
+ "when destroyed; shouldRegisterAction $shouldRegisterAction")
+ return@execute
+ }
+
+ val accessibilityManager =
+ context.getSystemService(AccessibilityManager::class.java) ?: return@execute
+ if (shouldRegisterAction) {
+ val allAppsPendingIntent = createAllAppsPendingIntent()
+ accessibilityManager.registerSystemAction(
+ RemoteAction(
+ Icon.createWithResource(context, R.drawable.ic_apps),
+ context.getString(R.string.all_apps_label),
+ context.getString(R.string.all_apps_label),
+ allAppsPendingIntent,
+ ),
+ GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS,
+ )
+ quickstepKeyGestureEventsManager.registerAllAppsKeyGestureEvent(
+ allAppsPendingIntent
+ )
+ } else {
+ accessibilityManager.unregisterSystemAction(
+ GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS
+ )
+ quickstepKeyGestureEventsManager.unregisterAllAppsKeyGestureEvent()
+ }
}
}
}
@@ -129,6 +146,7 @@ class AllAppsActionManager(
fun onDestroy() {
synchronized(this) {
+ isDestroyed = true
isActionRegistered = false
context
.getSystemService(AccessibilityManager::class.java)
@@ -150,4 +168,8 @@ class AllAppsActionManager(
pw.println("\tisActionRegistered=$isActionRegistered")
pw.println("\tisUserUnlocked=$isUserUnlocked")
}
+
+ companion object {
+ private const val TAG = "AllAppsActionManager"
+ }
}
diff --git a/quickstep/src/com/android/quickstep/DeviceConfigWrapper.kt b/quickstep/src/com/android/quickstep/DeviceConfigWrapper.kt
index 9d7f7042288..df2742d696d 100644
--- a/quickstep/src/com/android/quickstep/DeviceConfigWrapper.kt
+++ b/quickstep/src/com/android/quickstep/DeviceConfigWrapper.kt
@@ -83,12 +83,12 @@ class DeviceConfigWrapper private constructor(propReader: PropReader) {
"Percentage of gesture slop region to trigger the extended long press duration."
)
- val animateLpnh = propReader.get("ANIMATE_LPNH", false, "Animates navbar when long pressing")
+ val animateLpnh = propReader.get("ANIMATE_LPNH", true, "Animates navbar when long pressing")
val shrinkNavHandleOnPress =
propReader.get(
"SHRINK_NAV_HANDLE_ON_PRESS",
- false,
+ true,
"Shrinks navbar when long pressing if ANIMATE_LPNH is enabled"
)
diff --git a/quickstep/src/com/android/quickstep/ImageActionsApi.java b/quickstep/src/com/android/quickstep/ImageActionsApi.java
index 227380664c8..4288e0bd110 100644
--- a/quickstep/src/com/android/quickstep/ImageActionsApi.java
+++ b/quickstep/src/com/android/quickstep/ImageActionsApi.java
@@ -31,6 +31,7 @@
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.Log;
+import android.view.View;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
@@ -108,6 +109,11 @@ public void startShareActivity(Rect crop) {
ImageActionUtils.startShareActivity(mContext, mBitmapSupplier, crop, null, TAG);
}
+ @UiThread
+ public void startLensActivity() {
+ ImageActionUtils.startLensActivity(mContext, mBitmapSupplier, null, TAG);
+ }
+
/**
* @param screenshot to be saved to the media store.
* @param screenshotBounds the location of where the bitmap was laid out on the screen in
diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
index 76b2ede6f53..7b4626d807d 100644
--- a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
+++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
@@ -130,6 +130,9 @@ public class LauncherBackAnimationController {
private int mMaxBlurRadius;
private int mLastBlurRadius = 0;
+ private boolean mCallbacksRegistered;
+ private boolean mBackRegistered;
+
private final ComponentCallbacks mComponentCallbacks = new ComponentCallbacks() {
@Override
public void onConfigurationChanged(Configuration newConfig) {
@@ -155,10 +158,12 @@ public LauncherBackAnimationController(
* @param handler Handler to the thread to run the animations on.
*/
public void registerBackCallbacks(Handler handler) {
+ if (mBackRegistered) return;
mBackCallback = new OnBackInvokedCallbackStub(handler, mProgressAnimator,
mProgressInterpolator, this);
SystemUiProxy.INSTANCE.get(mLauncher).setBackToLauncherCallback(mBackCallback,
new RemoteAnimationRunnerStub(this, handler));
+ mBackRegistered = true;
}
private static class OnBackInvokedCallbackStub extends IOnBackInvokedCallback.Stub {
@@ -264,11 +269,9 @@ private RemoteAnimationRunnerStub(LauncherBackAnimationController controller,
public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
IRemoteAnimationFinishedCallback finishedCallback) {
- LauncherBackAnimationController controller = mControllerRef.get();
- if (controller == null) {
- return;
- }
final Runnable r = () -> {
+ LauncherBackAnimationController controller = mControllerRef.get();
+ if (controller == null) return;
for (final RemoteAnimationTarget target : apps) {
if (MODE_CLOSING == target.mode) {
controller.mBackTarget = target;
@@ -305,11 +308,29 @@ private void onCancelFinished() {
/** Unregisters the back to launcher callback in shell. */
public void unregisterBackCallbacks() {
+ if (!mBackRegistered) return;
if (mBackCallback != null) {
SystemUiProxy.INSTANCE.get(mLauncher).clearBackToLauncherCallback(mBackCallback);
}
mProgressAnimator.reset();
mBackCallback = null;
+ mBackRegistered = false;
+ }
+
+ public void cleanupForDestroy() {
+ if (mScrimAlphaAnimator != null) {
+ mScrimAlphaAnimator.cancel();
+ mScrimAlphaAnimator = null;
+ }
+ if (mScrimLayer != null) {
+ removeScrimLayer();
+ }
+ if (mBackTarget != null && mBackTarget.leash.isValid())
+ mBackTarget.leash.release();
+ if (mLauncherTarget != null && mLauncherTarget.leash.isValid())
+ mLauncherTarget.leash.release();
+ mBackTarget = null;
+ mLauncherTarget = null;
}
private void initBackMotion(BackMotionEvent backEvent) {
@@ -608,17 +629,20 @@ private void loadResources() {
* Called when launcher is destroyed. Unregisters component callbacks to avoid memory leaks.
*/
public void unregisterComponentCallbacks() {
+ if (!mCallbacksRegistered) return;
mLauncher.unregisterComponentCallbacks(mComponentCallbacks);
+ mCallbacksRegistered = false;
}
/**
* Registers component callbacks with the launcher to receive configuration change events.
*/
public void registerComponentCallbacks() {
+ if (mCallbacksRegistered) return;
mLauncher.registerComponentCallbacks(mComponentCallbacks);
+ mCallbacksRegistered = true;
}
-
private void resetScrim() {
removeScrimLayer();
mScrimAlpha = 0;
diff --git a/quickstep/src/com/android/quickstep/LauncherProcessImageListener.kt b/quickstep/src/com/android/quickstep/LauncherProcessImageListener.kt
new file mode 100644
index 00000000000..75f5cf1ba07
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/LauncherProcessImageListener.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep
+
+import android.graphics.ImageDecoder
+import android.graphics.ImageDecoder.ImageInfo
+import android.graphics.ImageDecoder.Source
+import android.util.Size
+import androidx.annotation.VisibleForTesting
+import java.io.IOException
+import java.util.Locale
+import kotlin.math.max
+import kotlin.math.sqrt
+
+/**
+ * The default image decoder listener for the Launcher process.
+ *
+ * This listener is used to downscale images that are larger than a certain memory limit.
+ */
+class LauncherProcessImageListener(
+ /*
+ * The maximum size in bytes of an image that will be decoded. If the image is
+ * larger than this, it will be downscaled, retaining the aspect ratio, until its size
+ * is no larger than this value.
+ */
+ val maxMemoryBytes: Long,
+
+ /*
+ * The set of mime types that are allowed to be decoded. If the image's mime type is
+ * not in this set, a RuntimeException will be thrown.
+ */
+ val allowedMimeTypes: Set,
+) : ImageDecoder.OnHeaderDecodedListener {
+
+ override fun onHeaderDecoded(decoder: ImageDecoder, info: ImageInfo, source: Source) {
+ onHeaderDecoded(AndroidImageDecoderWrapper(decoder, info))
+ }
+
+ @VisibleForTesting
+ fun onHeaderDecoded(decoder: ImageDecoderWrapper) {
+ // First check that the image is a supported type based on our allowlist.
+ val mimeType = decoder.mimeType
+ if (mimeType == null || mimeType.lowercase(Locale.US) !in allowedMimeTypes) {
+ throw IOException("Image mime type ($mimeType) is not allowed.")
+ }
+
+ // Remote size may have returned a giant image, so we need to defensively limit it
+ // to something reasonable.
+ val size = getPreferredSize(decoder.size.width, decoder.size.height, maxMemoryBytes)
+ if (size != null) {
+ decoder.setTargetSize(size.width, size.height)
+ }
+ }
+
+ /** Abstraction for [ImageDecoder] to allow testing. */
+ interface ImageDecoderWrapper {
+ /** The size of the image in pixels. */
+ val size: Size
+
+ /** The mime type of the image. */
+ val mimeType: String?
+
+ /** Sets the target size for the image. */
+ fun setTargetSize(width: Int, height: Int)
+ }
+
+ /**
+ * A wrapper around [ImageDecoder] that implements [ImageDecoderWrapper].
+ *
+ * @param decoder The [ImageDecoder] to wrap.
+ * @param info The [ImageInfo] of the image to decode.
+ */
+ private class AndroidImageDecoderWrapper(
+ private val decoder: ImageDecoder,
+ private val info: ImageInfo,
+ ) : ImageDecoderWrapper {
+ override val size: Size
+ get() = info.size
+
+ override val mimeType: String?
+ get() = info.mimeType
+
+ override fun setTargetSize(width: Int, height: Int) {
+ decoder.setTargetSize(width, height)
+ }
+ }
+
+ companion object {
+ // Assume worst-case (RGBA_F16) to safely fit in memory.
+ private const val BYTES_PER_PIXEL = 8
+
+ /**
+ * Returns the preferred size to use for the given image size and max memory.
+ *
+ * If the image is larger than the max memory, this will return a size that will result in
+ * the image size being no larger than the max memory. If the image is smaller than the max
+ * memory, this will return null.
+ *
+ * @param width The width of the image in pixels.
+ * @param height The height of the image in pixels.
+ * @param maxMemoryBytes The maximum size in bytes of the image.
+ * @return The preferred size to use for the image, or null if no downscaling is needed.
+ */
+ fun getPreferredSize(width: Int, height: Int, maxMemoryBytes: Long): Size? {
+ // estimated size is in memory as bytes
+ val estimatedSize = width.toLong() * height.toLong() * BYTES_PER_PIXEL
+
+ // If the image is larger than the max memory, we need to downscale it.
+ if (estimatedSize > maxMemoryBytes) {
+ // Ratio of the estimated size to the max memory.
+ val maxPixels = maxMemoryBytes / BYTES_PER_PIXEL
+ // Derive the scale factor to apply to the image to get the desired size.
+ val scale = sqrt(maxPixels.toDouble() / (width.toLong() * height.toLong()))
+ // Ensure at least 1 pixel dimensions
+ // follow image decoder rounding behavior
+ val targetWidth = max(1, (width * scale + 0.5).toInt())
+ val targetHeight = max(1, (height * scale + 0.5).toInt())
+ return Size(targetWidth, targetHeight)
+ }
+ return null
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/LockedTaskManager.kt b/quickstep/src/com/android/quickstep/LockedTaskManager.kt
new file mode 100644
index 00000000000..f3f3d635d2a
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/LockedTaskManager.kt
@@ -0,0 +1,47 @@
+package com.android.quickstep
+
+import android.content.Context
+import android.os.UserHandle
+import android.provider.Settings
+
+class LockedTaskManager private constructor(context: Context) {
+
+ private val contentResolver = context.applicationContext.contentResolver
+
+ fun getLockedPackages(): Set {
+ val locked = Settings.System.getStringForUser(
+ contentResolver,
+ Settings.System.RECENTS_LOCKED_TASKS,
+ UserHandle.USER_CURRENT
+ ) ?: return emptySet()
+ return locked.split(",").filter { it.isNotBlank() }.toSet()
+ }
+
+ fun isPackageLocked(packageName: String): Boolean {
+ return packageName in getLockedPackages()
+ }
+
+ fun setPackageLocked(packageName: String, locked: Boolean) {
+ val tasks = getLockedPackages().toMutableSet()
+ if (locked) tasks.add(packageName) else tasks.remove(packageName)
+ Settings.System.putStringForUser(
+ contentResolver,
+ Settings.System.RECENTS_LOCKED_TASKS,
+ tasks.joinToString(","),
+ UserHandle.USER_CURRENT
+ )
+ }
+
+ companion object {
+ @JvmStatic
+ private var sInstance: LockedTaskManager? = null
+
+ @JvmStatic
+ fun getInstance(context: Context): LockedTaskManager {
+ if (sInstance == null) {
+ sInstance = LockedTaskManager(context.applicationContext)
+ }
+ return sInstance!!
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
index 7fb68574375..36e8866b1cc 100644
--- a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
+++ b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
@@ -21,10 +21,12 @@
import android.os.Trace;
import android.os.UserManager;
import android.util.Log;
+import android.graphics.ImageDecoder;
import android.view.ThreadedRenderer;
import com.android.launcher3.BuildConfig;
import com.android.launcher3.MainProcessInitializer;
+import com.android.launcher3.R;
import com.android.quickstep.util.QuickstepProtoLogGroup;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
@@ -96,5 +98,19 @@ protected void init(Context context) {
}
QuickstepProtoLogGroup.initProtoLog();
+
+ setupImageDecoder(context);
+ }
+
+ private void setupImageDecoder(Context context) {
+ // Limit the max memory usage.
+ int maxMemoryMb = context.getResources().getInteger(R.integer.max_launcher_memory_mb);
+ long maxMemoryBytes = maxMemoryMb * 1024L * 1024L;
+ // Get the allowed mime types from the resources.
+ Set allowedMimeTypes = Set.of(context.getResources().getStringArray(
+ R.array.allowed_image_mime_types));
+ ImageDecoder.setDefaultProcessListener(
+ new LauncherProcessImageListener(maxMemoryBytes, allowedMimeTypes));
}
+
}
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index bb377f3c6bc..2924523b0cd 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -42,6 +42,7 @@
import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.util.DaggerSingletonTracker;
import com.android.launcher3.util.LooperExecutor;
+import com.android.quickstep.util.AxAppLockerHelper;
import com.android.quickstep.util.DesktopTask;
import com.android.quickstep.util.ExternalDisplaysKt;
import com.android.quickstep.util.GroupTask;
@@ -435,13 +436,13 @@ public boolean get(int key) {
final TaskInfo taskInfo1 = rawTask.getBaseGroupedTask().getTaskInfo1();
final Task.TaskKey task1Key = createTaskKey(taskInfo1);
final Task task1 = Task.from(task1Key, taskInfo1,
- tmpLockedUsers.get(task1Key.userId) /* isLocked */);
+ isAppLocked(task1Key, tmpLockedUsers) /* isLocked */);
if (rawTask.isBaseType(TYPE_SPLIT)) {
final TaskInfo taskInfo2 = rawTask.getBaseGroupedTask().getTaskInfo2();
final Task.TaskKey task2Key = createTaskKey(taskInfo2);
final Task task2 = Task.from(task2Key, taskInfo2,
- tmpLockedUsers.get(task2Key.userId) /* isLocked */);
+ isAppLocked(task2Key, tmpLockedUsers) /* isLocked */);
allTasks.add(new SplitTask(task1, task2,
rawTask.getBaseGroupedTask().getSplitBounds()));
} else {
@@ -454,7 +455,7 @@ public boolean get(int key) {
Task task1 = loadKeysOnly
? new Task(task1Key)
: Task.from(task1Key, taskInfo1,
- tmpLockedUsers.get(task1Key.userId) /* isLocked */);
+ isAppLocked(task1Key, tmpLockedUsers) /* isLocked */);
Task task2 = null;
if (taskInfo2 != null) {
// Is split task
@@ -462,7 +463,7 @@ public boolean get(int key) {
task2 = loadKeysOnly
? new Task(task2Key)
: Task.from(task2Key, taskInfo2,
- tmpLockedUsers.get(task2Key.userId) /* isLocked */);
+ isAppLocked(task2Key, tmpLockedUsers) /* isLocked */);
} else {
// Is fullscreen task
if (isFirstVisibleTaskFound) {
@@ -490,6 +491,11 @@ public boolean get(int key) {
return allTasks;
}
+ private boolean isAppLocked(Task.TaskKey key, SparseBooleanArray users) {
+ return AxAppLockerHelper.get().isAppLocked(key.getPackageName())
+ || (users != null && users.get(key.userId));
+ }
+
private Task createTask(TaskInfo taskInfo, Set minimizedTaskIds) {
Task.TaskKey key = createTaskKey(taskInfo);
Task task = Task.from(key, taskInfo, false);
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 6cf7962e885..9cd4eab8b9f 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -74,6 +74,7 @@
import com.android.launcher3.taskbar.TaskbarManager;
import com.android.launcher3.util.ActivityOptionsWrapper;
import com.android.launcher3.util.ContextTracker;
+import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.util.Themes;
@@ -86,6 +87,7 @@
import com.android.quickstep.util.RecentsAtomicAnimationFactory;
import com.android.quickstep.util.SplitSelectStateController;
import com.android.quickstep.util.TISBindHelper;
+import com.android.quickstep.views.MemInfoView;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.RecentsViewContainer;
@@ -119,6 +121,7 @@ public final class RecentsActivity extends StatefulActivity implem
private OverviewActionsView> mActionsView;
private TISBindHelper mTISBindHelper;
private @Nullable TaskbarInteractor mTaskbarInteractor;
+ private MemInfoView mMemInfoView;
private StateManager mStateManager;
@@ -149,6 +152,7 @@ protected void setupViews() {
mScrimView = rootView.findViewById(R.id.scrim_view);
mFallbackRecentsView = rootView.findViewById(R.id.overview_panel);
mActionsView = rootView.findViewById(R.id.overview_actions_view);
+ mMemInfoView = rootView.findViewById(R.id.meminfo);
if (DesktopModeStatus.canEnterDesktopMode(this)) {
mDesktopRecentsTransitionController = new DesktopRecentsTransitionController(
@@ -157,7 +161,10 @@ protected void setupViews() {
);
}
mFallbackRecentsView.init(mActionsView, mSplitSelectStateController,
- mDesktopRecentsTransitionController);
+ mDesktopRecentsTransitionController, mMemInfoView);
+
+ mMemInfoView.setDp(mDeviceProfile);
+ mMemInfoView.updateVerticalMargin(DisplayController.getNavigationMode(this));
setContentView(rootView);
rootView.getSysUiScrim().getSysUIProgress().updateValue(0);
@@ -254,6 +261,11 @@ public OverviewActionsView> getActionsView() {
return mActionsView;
}
+ @Override
+ public MemInfoView getMemInfoView() {
+ return mMemInfoView;
+ }
+
@Override
public void returnToHomescreen() {
// TODO(b/137318995) This should go home, but doing so removes freeform windows
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 7791c8d1a83..4da1646928f 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -128,7 +128,7 @@ public RecentsModel(@ApplicationContext Context context,
) {
// Lazily inject the ThemeManager and access themeManager once the device is
// unlocked. See b/393248495 for details.
- this(context, new IconProvider(context), systemUiProxy, topTaskTracker,
+ this(context, IconProvider.INSTANCE.get(context), systemUiProxy, topTaskTracker,
displayController, lockedUserState, themeManagerLazy, tracker, dispatcherProvider,
uiExecutor, iconChangeTracker);
}
diff --git a/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
index 9011150f3fd..57edd82326d 100644
--- a/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
+++ b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
@@ -20,12 +20,10 @@
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import android.os.Bundle;
-import android.util.Log;
import android.view.RemoteAnimationTarget;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.concurrent.CopyOnWriteArrayList;
/**
@@ -33,8 +31,6 @@
*/
public class RemoteAnimationTargets {
- private static final String TAG = "RemoteAnimationTargets";
-
private final CopyOnWriteArrayList mReleaseChecks = new CopyOnWriteArrayList<>();
public final RemoteAnimationTarget[] unfilteredApps;
@@ -83,8 +79,6 @@ public RemoteAnimationTarget findTask(int taskId) {
return target;
}
}
- Log.e(TAG, "taskId: " + taskId + " not found. apps contains: "
- + Arrays.stream(apps).map(target -> target.taskId).toList());
return null;
}
diff --git a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
index 743204b5c9e..bd91d285f50 100644
--- a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
+++ b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
@@ -21,7 +21,6 @@
import android.app.WindowConfiguration;
import android.content.Context;
-import android.util.Log;
import android.view.RemoteAnimationTarget;
import android.window.TransitionInfo;
@@ -164,8 +163,6 @@ public RemoteTargetHandle[] assignTargetsForSplitScreen(RemoteAnimationTargets t
}
boolean containsSplitTargets = mSplitBounds != null;
- Log.d(TAG, "containsSplitTargets? " + containsSplitTargets + " handleLength: " +
- mRemoteTargetHandles.length + " appsLength: " + targets.apps.length);
if (mRemoteTargetHandles.length == 1) {
resizeRemoteTargetHandles(targets);
@@ -313,7 +310,6 @@ private void resizeRemoteTargetHandles(RemoteAnimationTargets targets) {
int handleCount = (int) Arrays.stream(targets.apps)
.filter(app -> app.mode == targets.targetMode)
.count();
- Log.d(TAG, "appCount: " + handleCount + " handleLength: " + mRemoteTargetHandles.length);
if (handleCount < mRemoteTargetHandles.length) {
reduceRemoteTargetHandles(handleCount);
}
@@ -329,7 +325,6 @@ private void resizeRemoteTargetHandles(RemoteAnimationTargets targets) {
* This value should be non-negative and less than the current size of the handle list.
*/
private void reduceRemoteTargetHandles(int handleCount) {
- Log.d(TAG, "Reduce handles, count: " + handleCount);
RemoteTargetHandle[] newHandles = new RemoteTargetHandle[(int) handleCount];
System.arraycopy(mRemoteTargetHandles, 0/*src*/, newHandles, 0/*dst*/, (int) handleCount);
mRemoteTargetHandles = newHandles;
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.kt b/quickstep/src/com/android/quickstep/SystemUiProxy.kt
index 0c44a0dc53a..3dd1ca0cff9 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.kt
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.kt
@@ -267,6 +267,13 @@ class SystemUiProxy @Inject constructor(
systemUiProxy?.onKeyEvent(keycode, displayId)
}
+ fun onLongPressKeyEvent(keycode: Int, displayId: Int) =
+ executeWithErrorLog({
+ "Failed call onLongPressKeyEvent ${KeyEvent.keyCodeToString(keycode)}"
+ }) {
+ systemUiProxy?.onLongPressKeyEvent(keycode, displayId)
+ }
+
fun onImeSwitcherPressed() =
executeWithErrorLog({ "Failed call onImeSwitcherPressed" }) {
systemUiProxy?.onImeSwitcherPressed()
@@ -412,6 +419,14 @@ class SystemUiProxy @Inject constructor(
}
}
+ @MainThread
+ fun onSleepEvent(event: MotionEvent) {
+ Preconditions.assertUIThread()
+ executeWithErrorLog({ "Failed call onSleepEvent with arg: $event" }) {
+ systemUiProxy?.onSleepEvent(event)
+ }
+ }
+
fun onStatusBarTrackpadEvent(event: MotionEvent) =
executeWithErrorLog({ "Failed call onStatusBarTrackpadEvent with arg: $event" }) {
systemUiProxy?.onStatusBarTrackpadEvent(event)
diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.kt b/quickstep/src/com/android/quickstep/TaskIconCache.kt
index 10346ef7a3f..412633ddcfd 100644
--- a/quickstep/src/com/android/quickstep/TaskIconCache.kt
+++ b/quickstep/src/com/android/quickstep/TaskIconCache.kt
@@ -17,6 +17,8 @@ package com.android.quickstep
import android.app.ActivityManager
import android.content.Context
+import android.content.SharedPreferences
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.os.UserHandle
@@ -25,8 +27,10 @@ import androidx.annotation.WorkerThread
import androidx.core.graphics.drawable.toDrawable
import com.android.launcher3.Flags.enableRefactorTaskThumbnail
import com.android.launcher3.Flags.enableTaskbarRecentsThemedIcons
+import com.android.launcher3.LauncherPrefs
import com.android.launcher3.R
import com.android.launcher3.Utilities
+import com.android.launcher3.customization.IconDatabase.KEY_ICON_PACK
import com.android.launcher3.icons.BaseIconFactory
import com.android.launcher3.icons.BaseIconFactory.IconOptions
import com.android.launcher3.icons.BitmapInfo
@@ -56,9 +60,11 @@ class TaskIconCache(
private val context: Context,
private val bgExecutor: Executor,
private val iconProvider: IconProvider,
- displayController: DisplayController,
+ private val displayController: DisplayController,
val dispatcherProvider: DispatcherProvider,
-) : TaskIconDataSource, DisplayInfoChangeListener {
+) : TaskIconDataSource, DisplayController.DisplayInfoChangeListener,
+ SharedPreferences.OnSharedPreferenceChangeListener, AutoCloseable {
+
private val recentsIconCacheSize = context.resources.getInteger(R.integer.recentsIconCacheSize)
private var iconCache: TaskKeyLruCache? = null
// TODO: b/431811298 - Make non-null when flag is cleaned up.
@@ -86,6 +92,13 @@ class TaskIconCache(
// TODO (b/397205964): this will need to be updated when we support caches for different
// displays.
displayController.addChangeListener(this)
+ LauncherPrefs.getPrefs(context).registerOnSharedPreferenceChangeListener(this)
+ }
+
+ override fun close() {
+ displayController.removeChangeListener(this)
+ LauncherPrefs.getPrefs(context).unregisterOnSharedPreferenceChangeListener(this)
+ bgExecutor.execute { resetFactory() }
}
override fun onDisplayInfoChanged(context: Context, info: DisplayController.Info, flags: Int) {
@@ -94,6 +107,12 @@ class TaskIconCache(
}
}
+ override fun onSharedPreferenceChanged(prefs: SharedPreferences, key: String?) {
+ if (key == KEY_ICON_PACK) {
+ clearCache()
+ }
+ }
+
// TODO(b/387496731): Add ensureActive() calls if they show performance benefit
override suspend fun getIcon(task: Task): TaskCacheEntry {
task.icon?.let { icon ->
@@ -397,6 +416,10 @@ class TaskIconCache(
_iconFactory = null
bitmapInfoCache?.evictAll()
iconCache?.evictAll()
+ synchronized(defaultIcons) {
+ defaultIcons.clear()
+ defaultIconBase = null
+ }
}
data class TaskCacheEntry(
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index e0586c5de3d..7d334763284 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -34,6 +34,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
+import android.widget.Toast;
import com.android.launcher3.R;
import com.android.launcher3.model.data.ItemInfo;
@@ -124,8 +125,11 @@ public void clearAllActiveState() { }
private static final TaskShortcutFactory[] PER_TASK_MENU_OPTIONS = new TaskShortcutFactory[]{
TaskShortcutFactory.APP_INFO,
TaskShortcutFactory.SPLIT_SELECT,
+ TaskShortcutFactory.UNINSTALL,
+ TaskShortcutFactory.LOCK_APP,
TaskShortcutFactory.PIN,
TaskShortcutFactory.INSTALL,
+ TaskShortcutFactory.FLOATING,
TaskShortcutFactory.FREE_FORM,
DesktopSystemShortcut.Companion.createFactory(),
ExternalDisplaySystemShortcut.Companion.createFactory(),
@@ -270,6 +274,20 @@ protected void saveAppPair() {
.saveAppPair(taskView);
}
+ private void clearAllTasks() {
+ final RecentsView recentsView = mTaskContainer.getTaskView().getRecentsView();
+ recentsView.dismissAllTasks();
+ }
+
+ private void launchLens() {
+ final RecentsView recentsView =
+ mTaskContainer.getTaskView().getRecentsView();
+ if (recentsView != null) {
+ recentsView.startHome();
+ mImageApi.startLensActivity();
+ }
+ }
+
/**
* Called when the overlay is no longer used.
*/
@@ -424,17 +442,49 @@ public OverlayUICallbacksImpl(boolean isAllowedByPolicy, Task task) {
}
@SuppressLint("NewApi")
+ @Override
public void onScreenshot() {
endLiveTileMode(() -> saveScreenshot(mTask));
}
+ @Override
public void onSplit() {
endLiveTileMode(TaskOverlay.this::enterSplitSelect);
}
+ @Override
public void onSaveAppPair() {
endLiveTileMode(TaskOverlay.this::saveAppPair);
}
+
+ @Override
+ public void onClearAllTasksRequested() {
+ endLiveTileMode(TaskOverlay.this::clearAllTasks);
+ }
+
+ @Override
+ public void onLens() {
+ if (mIsAllowedByPolicy) {
+ endLiveTileMode(TaskOverlay.this::launchLens);
+ } else {
+ showBlockedByPolicyMessage();
+ }
+ }
+
+ @Override
+ public void onLockTaskRequested() {
+ if (mTask != null && mTask.getTopComponent() != null) {
+ String packageName = mTask.getTopComponent().getPackageName();
+ LockedTaskManager ltm = LockedTaskManager.getInstance(mApplicationContext);
+ boolean wasLocked = ltm.isPackageLocked(packageName);
+ ltm.setPackageLocked(packageName, !wasLocked);
+ getTaskView().updateLockState(packageName);
+ getActionsView().updateLockIcon(!wasLocked);
+ Toast.makeText(mApplicationContext,
+ wasLocked ? R.string.unlock_app : R.string.lock_app,
+ Toast.LENGTH_SHORT).show();
+ }
+ }
}
}
@@ -451,5 +501,11 @@ public interface OverlayUICallbacks {
/** User wants to save an app pair with current group of apps. */
void onSaveAppPair();
+
+ void onClearAllTasksRequested();
+
+ void onLens();
+
+ void onLockTaskRequested();
}
}
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index 3c5633eaa1d..12f73c3413c 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -26,12 +26,16 @@
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
import android.app.ActivityOptions;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
@@ -39,16 +43,20 @@
import android.view.WindowManagerGlobal;
import android.window.DesktopExperienceFlags;
import android.window.SplashScreen;
+import android.widget.Toast;
import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
+import com.android.launcher3.SecondaryDropTarget;
+import com.android.launcher3.Utilities;
import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
import com.android.launcher3.model.WellbeingModel;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.popup.SystemShortcut.AppInfo;
import com.android.launcher3.util.InstantAppResolver;
+import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
import com.android.launcher3.views.ActivityContext;
@@ -65,6 +73,8 @@
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+import java.util.Arrays;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
@@ -295,6 +305,30 @@ private ActivityOptions makeLaunchOptions(RecentsViewContainer container) {
}
}
+ class FloatingTaskShortcut extends SystemShortcut {
+ private final TaskView mTaskView;
+
+ public FloatingTaskShortcut(RecentsViewContainer container, TaskContainer taskContainer) {
+ super(R.drawable.picture_in_picture_mobile_24px, R.string.recent_task_option_freeform,
+ container, taskContainer.getItemInfo(), taskContainer.getTaskView());
+ mTaskView = taskContainer.getTaskView();
+ }
+
+ @Override
+ public void onClick(View view) {
+ dismissTaskMenuView();
+ Task task = mTaskView.getFirstTask();
+ RecentsView rv = mTarget.getOverviewPanel();
+ rv.switchToScreenshot(() -> {
+ rv.finishRecentsAnimation(true /* toRecents */, false /* shouldPip */, () -> {
+ mTarget.returnToHomescreen();
+ rv.getHandler().post(() -> Utilities.startLmoFreeform(view.getContext(),
+ task.getTopComponent(), task.key.userId, task.key.id));
+ });
+ });
+ }
+ }
+
/**
* Does NOT add split options in the following scenarios:
* * 1. Taskbar is not present AND aren't at least 2 tasks in overview to show split options for
@@ -379,6 +413,28 @@ public boolean showForGroupedTask() {
}
};
+ TaskShortcutFactory UNINSTALL = new TaskShortcutFactory() {
+ @Override
+ public List getShortcuts(RecentsViewContainer container,
+ TaskContainer taskContainer) {
+ return PackageManagerHelper.isSystemApp(container.asContext(),
+ taskContainer.getTask().getTopComponent().getPackageName()) ? null :
+ Collections.singletonList(new SystemShortcut.UnInstall(container,
+ taskContainer.getItemInfo(), taskContainer.getTaskView()));
+ }
+ };
+
+ TaskShortcutFactory LOCK_APP = new TaskShortcutFactory() {
+ @Override
+ public List getShortcuts(RecentsViewContainer container,
+ TaskContainer taskContainer) {
+ String packageName = taskContainer.getTask().getTopComponent().getPackageName();
+ Context context = taskContainer.getTaskView().getContext();
+ return Collections.singletonList(new LockAppSystemShortcut(
+ context, container, taskContainer, packageName));
+ }
+ };
+
TaskShortcutFactory FREE_FORM = new TaskShortcutFactory() {
@Override
public List getShortcuts(RecentsViewContainer container,
@@ -398,10 +454,20 @@ public List getShortcuts(RecentsViewContainer container,
}
private boolean isAvailable(RecentsViewContainer container) {
- return Settings.Global.getInt(
- container.asContext().getContentResolver(),
- Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0
- && !DesktopModeStatus.canEnterDesktopMode(container.asContext());
+ return false;
+ }
+ };
+
+ TaskShortcutFactory FLOATING = new TaskShortcutFactory() {
+ @Override
+ public List getShortcuts(RecentsViewContainer container,
+ TaskContainer taskContainer) {
+ final Task task = taskContainer.getTask();
+ if (!task.isDockable) {
+ return null;
+ }
+
+ return Collections.singletonList(new FloatingTaskShortcut(container, taskContainer));
}
};
@@ -528,4 +594,38 @@ public List getShortcuts(RecentsViewContainer container,
return createSingletonShortcutList(modalStateSystemShortcut);
}
};
+
+ class LockAppSystemShortcut extends SystemShortcut {
+ private static final String TAG = "LockAppSystemShortcut";
+ private final Task mTask;
+ private final String mPackageName;
+ private Context mContext;
+
+ public LockAppSystemShortcut(Context context, RecentsViewContainer target, TaskContainer taskContainer, String packageName) {
+ super(R.drawable.ic_lock_recent, R.string.action_lock,
+ target, taskContainer.getItemInfo(), taskContainer.getTaskView());
+ mTask = taskContainer.getTask();
+ mPackageName = packageName;
+ mContext = context;
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (mPackageName != null && mTask != null) {
+ LockedTaskManager ltm = LockedTaskManager.getInstance(mContext);
+ boolean wasLocked = ltm.isPackageLocked(mPackageName);
+ ltm.setPackageLocked(mPackageName, !wasLocked);
+ TaskView taskView = (TaskView) mOriginalView;
+ taskView.updateLockState(mPackageName);
+ // Keep the overview actions lock button in sync when it reflects this task.
+ RecentsView recentsView = taskView.getRecentsView();
+ if (recentsView != null && recentsView.getCurrentPageTaskView() == taskView) {
+ mTarget.getActionsView().updateLockIcon(!wasLocked);
+ }
+ Toast.makeText(mContext,
+ wasLocked ? R.string.unlock_app : R.string.lock_app,
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/TaskViewShortFactory.kt b/quickstep/src/com/android/quickstep/TaskViewShortFactory.kt
index fed21e5e27a..52c46167356 100644
--- a/quickstep/src/com/android/quickstep/TaskViewShortFactory.kt
+++ b/quickstep/src/com/android/quickstep/TaskViewShortFactory.kt
@@ -16,7 +16,12 @@
package com.android.quickstep
+import android.app.ActivityManagerNative
+import android.app.IActivityManager
+import android.os.RemoteException
+import android.os.UserHandle
import android.view.View
+import android.widget.Toast
import com.android.launcher3.R
import com.android.launcher3.logging.StatsLogManager.LauncherEvent
import com.android.launcher3.popup.SystemShortcut
@@ -59,6 +64,23 @@ interface TaskViewShortFactory {
val recentsView = taskView.recentsView ?: return
dismissTaskMenuView()
recentsView.dismissTaskView(taskView, true, true)
+ val pkg = taskView.itemInfo.targetComponent?.packageName
+ if (!pkg.isNullOrEmpty()) {
+ try {
+ val iam: IActivityManager = ActivityManagerNative.getDefault()
+ iam.forceStopPackage(pkg, UserHandle.USER_CURRENT)
+
+ Toast.makeText(
+ mTarget.asContext(),
+ R.string.recents_app_killed,
+ Toast.LENGTH_SHORT
+ ).show()
+ } catch (e: RemoteException) {
+ // Ignore
+ } catch (t: Throwable) {
+ // Ignore
+ }
+ }
mTarget.statsLogManager
.logger()
.withItemInfo(taskView.itemInfo)
@@ -88,7 +110,7 @@ interface TaskViewShortFactory {
return listOf>(
RemoveTaskSystemShortcut(
R.drawable.ic_remove_task_option,
- R.string.recent_task_option_remove_task,
+ R.string.recent_task_option_kill_app,
container,
taskView,
)
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 1cebd811f9d..77a50341918 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -897,7 +897,11 @@ public void onUserUnlocked() {
mActionCornerHandler = LauncherComponentProvider.get(
this).getActionCornerHandlerFactory().create(mOverviewCommandHelper);
mUserUnlocked = true;
- mInputConsumer.registerInputConsumer();
+ try {
+ mInputConsumer.registerInputConsumer();
+ } catch (Exception e) {
+ Log.e(TAG, "Failure registering InputConsumer", e);
+ }
mDeviceStateRepository.forEach(/* createIfAbsent= */ true, deviceState ->
onSystemUiFlagsChanged(deviceState.getSysuiStateFlags(),
deviceState.getDisplayId()));
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index d2f76464a0b..2073d2a0f3b 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -55,6 +55,7 @@
import com.android.quickstep.views.ClearAllButton;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.RecentsViewContainer;
+import com.android.quickstep.views.MemInfoView;
/**
* State controller for fallback recents activity
@@ -154,6 +155,9 @@ private void setProperties(RecentsState state, StateAnimationConfig config,
setter.setFloat(mRecentsView, taskViewsFloat.first, isSplitSelectionState(state)
? mRecentsView.getSplitSelectTranslation() : 0, LINEAR);
setter.setFloat(mRecentsView, taskViewsFloat.second, 0, LINEAR);
+ float memInfoAlpha = state.hasMemInfoView() ? 1 : 0;
+ setter.setFloat(mRecentsViewContainer.getMemInfoView(), MemInfoView.STATE_CTRL_ALPHA,
+ memInfoAlpha, LINEAR);
}
private Interpolator getOverviewInterpolator(RecentsState toState) {
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 9caef911d3d..7ed63a2470a 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -45,6 +45,7 @@
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.SingleTask;
import com.android.quickstep.util.SplitSelectStateController;
+import com.android.quickstep.views.MemInfoView;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.RecentsViewContainer;
@@ -78,8 +79,9 @@ public FallbackRecentsView(Context context, AttributeSet attrs, int defStyleAttr
@Override
public void init(OverviewActionsView actionsView, SplitSelectStateController splitController,
- @Nullable DesktopRecentsTransitionController desktopRecentsTransitionController) {
- super.init(actionsView, splitController, desktopRecentsTransitionController);
+ @Nullable DesktopRecentsTransitionController desktopRecentsTransitionController,
+ MemInfoView memInfoView) {
+ super.init(actionsView, splitController, desktopRecentsTransitionController, memInfoView);
if (mContainer instanceof RecentsWindowManager) {
// These will be set during the state transition to DEFAULT
return;
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsState.kt b/quickstep/src/com/android/quickstep/fallback/RecentsState.kt
index 51008c24f69..6ca14f51a6d 100644
--- a/quickstep/src/com/android/quickstep/fallback/RecentsState.kt
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsState.kt
@@ -15,12 +15,15 @@
*/
package com.android.quickstep.fallback
+import androidx.core.graphics.ColorUtils
+
import android.content.Context
import android.graphics.Color
import android.graphics.Rect
import androidx.annotation.FloatRange
import com.android.app.animation.Interpolators
import com.android.launcher3.DeviceProfile
+import com.android.launcher3.LauncherPrefs
import com.android.launcher3.Flags
import com.android.launcher3.LauncherState
import com.android.launcher3.LauncherState.FLAG_CLOSE_POPUPS
@@ -83,11 +86,16 @@ open class RecentsState(@JvmField val ordinal: Int, private val mFlags: Int) :
/** For this state, whether live tile should be shown. */
fun hasLiveTile() = hasFlag(FLAG_LIVE_TILE)
+ /** For this state, whether meminfoview should be shown. */
+ fun hasMemInfoView() = hasFlag(FLAG_MEMINFO)
+
/** For this state, what color scrim should be drawn behind overview. */
fun getScrimColor(context: Context) =
ScrimColors(
/* backgroundColor= */ if (hasFlag(FLAG_SCRIM))
- Themes.getAttrColor(context, R.attr.overviewScrimColor)
+ ColorUtils.setAlphaComponent(
+ Themes.getAttrColor(context, R.attr.overviewScrimColor),
+ LauncherPrefs.RECENTS_OPACITY.get(context) * 255 / 100)
else Color.TRANSPARENT,
/* foregroundColor= */ Color.TRANSPARENT,
)
@@ -252,6 +260,7 @@ open class RecentsState(@JvmField val ordinal: Int, private val mFlags: Int) :
private val FLAG_TASK_THUMBNAIL_SPLASH = BaseState.getFlag(8)
private val FLAG_ADD_DESK_BUTTON = BaseState.getFlag(9)
private val FLAG_SHOW_EXPLODED_DESKTOP_VIEW = BaseState.getFlag(10)
+ private val FLAG_MEMINFO = BaseState.getFlag(11)
private const val PREDICTIVE_BACK_DURATION = 1000L
private const val PREDICTIVE_BACK_MAX_RECENTS_SCALE_LAUNCH = 1.1f
@@ -281,7 +290,8 @@ open class RecentsState(@JvmField val ordinal: Int, private val mFlags: Int) :
FLAG_LIVE_TILE or
FLAG_RECENTS_VIEW_VISIBLE or
FLAG_ADD_DESK_BUTTON or
- FLAG_SHOW_EXPLODED_DESKTOP_VIEW),
+ FLAG_SHOW_EXPLODED_DESKTOP_VIEW or
+ FLAG_MEMINFO),
)
@JvmField
val MODAL_TASK: RecentsState =
@@ -294,7 +304,8 @@ open class RecentsState(@JvmField val ordinal: Int, private val mFlags: Int) :
FLAG_SCRIM or
FLAG_LIVE_TILE or
FLAG_RECENTS_VIEW_VISIBLE or
- FLAG_SHOW_EXPLODED_DESKTOP_VIEW),
+ FLAG_SHOW_EXPLODED_DESKTOP_VIEW or
+ FLAG_MEMINFO),
)
@JvmField
val BACKGROUND_APP: RecentsState =
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
index 365014d3312..d4ada7dc5c0 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
@@ -34,16 +34,23 @@
import android.content.Context;
import android.content.res.Resources;
import android.graphics.PointF;
+import android.hardware.input.InputManager;
+import android.hardware.input.InputManagerGlobal;
import android.os.Bundle;
import android.os.SystemClock;
+import android.view.Display;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.HapticFeedbackConstants;
+import android.view.InputDevice;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import com.android.app.animation.Interpolators;
import com.android.launcher3.R;
+import com.android.launcher3.util.window.WindowManagerProxy;
import com.android.quickstep.BaseContainerInterface;
import com.android.quickstep.GestureState;
import com.android.quickstep.InputConsumer;
@@ -237,6 +244,9 @@ private void updateAssistantProgress() {
}
private void startAssistantInternal() {
+ sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_HOME, KeyEvent.FLAG_LONG_SWIPE);
+ sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HOME, KeyEvent.FLAG_LONG_SWIPE);
+
RecentsViewContainer container = mContainerInterface.getCreatedContainer();
if (container != null) {
container.getRootView().performHapticFeedback(
@@ -244,10 +254,6 @@ private void startAssistantInternal() {
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
}
- Bundle args = new Bundle();
- args.putInt(OPA_BUNDLE_TRIGGER, OPA_BUNDLE_TRIGGER_DIAG_SWIPE_GESTURE);
- args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_GESTURE);
- SystemUiProxy.INSTANCE.get(mContext).startAssistant(args);
mLaunchedAssistant = true;
}
@@ -263,6 +269,19 @@ private boolean isValidAssistantGestureAngle(float deltaX, float deltaY) {
return (angle > mAngleThreshold && angle < 90);
}
+ private boolean sendEvent(int action, int code, int flags) {
+ long when = SystemClock.uptimeMillis();
+ final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */,
+ 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
+ flags | KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
+ InputDevice.SOURCE_KEYBOARD);
+
+ int displayId = WindowManagerProxy.INSTANCE.get(mContext).getDisplayId(mContext);
+ ev.setDisplayId(displayId);
+ return InputManagerGlobal.getInstance()
+ .injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
+ }
+
private class AssistantGestureListener extends SimpleOnGestureListener {
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
index b6928ae67b1..d48657c02ec 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
@@ -28,6 +28,7 @@ import android.util.Log
import android.view.View
import android.view.ViewOutlineProvider
import android.widget.FrameLayout
+import android.widget.ImageView
import androidx.annotation.ColorInt
import androidx.core.view.isInvisible
import com.android.launcher3.Flags.enableRefactorTaskContentView
@@ -40,7 +41,9 @@ import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.SnapshotSplash
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
+import com.android.quickstep.util.AxAppLockerHelper
import com.android.quickstep.views.FixedSizeImageView
+import com.android.systemui.shared.recents.model.Task
class TaskThumbnailView : FrameLayout, ViewPool.Reusable {
private val scrimView: View by lazy { findViewById(R.id.task_thumbnail_scrim) }
@@ -56,6 +59,10 @@ class TaskThumbnailView : FrameLayout, ViewPool.Reusable {
private var uiState: TaskThumbnailUiState = Uninitialized
+ private var task: Task? = null
+ private var lastMatrix: Matrix? = null
+ private var overlaid = false
+
/**
* Sets the outline bounds of the view. Default to use view's bound as outline when set to null.
*/
@@ -119,11 +126,76 @@ class TaskThumbnailView : FrameLayout, ViewPool.Reusable {
resetViews()
}
+ fun bind(tsk: Task) {
+ task = tsk
+ }
+
+ private fun getOverlayIcon(): Int = when {
+ isTopAppLocked() -> R.drawable.nt_recent_app_locked_icon
+ else -> R.drawable.nt_recent_camera_locked_icon
+ }
+
+ private fun getOverlayColor(): Int {
+ return context.getColor(R.color.recent_app_locked_bg_color)
+ }
+
+ fun getIconColor(): Int {
+ return context.getColor(R.color.recent_app_locked_icon_color)
+ }
+
+ private fun isCameraSnapshot(): Boolean {
+ val packageName = task?.topComponent?.packageName ?: return false
+ return packageName.contains("camera", ignoreCase = true) ||
+ packageName.contains("aperture", ignoreCase = true)
+ }
+
+ private fun isTopAppLocked(): Boolean {
+ val packageName = task?.key?.packageName ?: return false
+ return AxAppLockerHelper.get().isAppLocked(packageName)
+ }
+
+ fun drawOverlayThumbnail() {
+ thumbnailView.setImageResource(getOverlayIcon())
+ thumbnailView.scaleType = ImageView.ScaleType.CENTER
+ thumbnailView.isInvisible = false
+ drawBackground(getOverlayColor())
+ }
+
+ fun canOverlay(): Boolean {
+ return isTopAppLocked() || isCameraSnapshot()
+ }
+
fun setState(state: TaskThumbnailUiState, taskId: Int? = null) {
- if (uiState == state) return
+ if (uiState == state) {
+ // Even if state matches, we might need to redraw if overlay conditions changed
+ // but normally bind() calls this if flags change.
+ // However, we should check if we are currently verifying overlay vs actual state
+ if (canOverlay()) {
+ // If we should overlay, ensure we are overlaying
+ // Proceed to redraw logic
+ } else {
+ return
+ }
+ }
+
logDebug("taskId: $taskId - uiState changed from: $uiState to: $state")
uiState = state
resetViews()
+
+ if (canOverlay()) {
+ drawOverlayThumbnail()
+ overlaid = true
+ return
+ } else {
+ if (overlaid) {
+ thumbnailView.scaleType = ImageView.ScaleType.MATRIX
+ if (state is SnapshotSplash && lastMatrix != null) {
+ thumbnailView.imageMatrix = lastMatrix
+ }
+ overlaid = false
+ }
+ }
+
when (state) {
is Uninitialized -> {}
is LiveTile -> drawLiveWindow()
@@ -236,6 +308,7 @@ class TaskThumbnailView : FrameLayout, ViewPool.Reusable {
fun setImageMatrix(matrix: Matrix) {
if (uiState is SnapshotSplash) {
thumbnailView.imageMatrix = matrix
+ lastMatrix = matrix
}
}
diff --git a/quickstep/src/com/android/quickstep/util/AxAppLockerHelper.kt b/quickstep/src/com/android/quickstep/util/AxAppLockerHelper.kt
new file mode 100644
index 00000000000..cee03168f13
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/AxAppLockerHelper.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2025 AxionOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util
+
+import android.app.AxSandboxManager.AppLockState
+import android.content.Context
+import android.os.IBinder
+import android.os.RemoteException
+import android.os.ServiceManager
+import android.util.Log
+import com.android.internal.app.IAppLockStateListener
+import com.android.internal.app.IAxSandboxManager
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.atomic.AtomicReference
+
+class AxAppLockerHelper private constructor() {
+
+ companion object {
+ private const val TAG = "AxAppLockerHelper"
+
+ @Volatile
+ private var instance: AxAppLockerHelper? = null
+
+ @JvmStatic
+ fun get(): AxAppLockerHelper {
+ return instance ?: synchronized(this) {
+ instance ?: AxAppLockerHelper().also { instance = it }
+ }
+ }
+ }
+
+ private val sandboxManagerRef = AtomicReference()
+ private val appLockCache = ConcurrentHashMap()
+ private var listenerRegistered = false
+
+ private val listener = object : IAppLockStateListener.Stub() {
+ override fun onAppLockStateChanged(packageName: String, locked: Boolean) {
+ if (packageName.isBlank()) return
+ appLockCache[packageName] = locked
+ }
+ }
+
+ private val deathRecipient = IBinder.DeathRecipient {
+ sandboxManagerRef.set(null)
+ listenerRegistered = false
+ appLockCache.clear()
+ }
+
+ private fun getSandboxManager(): IAxSandboxManager? {
+ var manager = sandboxManagerRef.get()
+ if (manager == null) {
+ val binder = ServiceManager.getService(Context.AX_SANDBOX_SERVICE) ?: return null
+ try {
+ binder.linkToDeath(deathRecipient, 0)
+ manager = IAxSandboxManager.Stub.asInterface(binder)
+ sandboxManagerRef.set(manager)
+ registerListener(manager)
+ } catch (e: RemoteException) {
+ Log.w(TAG, "Failed to link to death or register listener", e)
+ }
+ }
+ return manager
+ }
+
+ private fun registerListener(manager: IAxSandboxManager) {
+ if (!listenerRegistered) {
+ try {
+ manager.registerAppLockStateListener(listener)
+ listenerRegistered = true
+ } catch (e: RemoteException) {
+ Log.w(TAG, "Failed to register listener", e)
+ }
+ }
+ }
+
+ fun isAppLocked(packageName: String): Boolean {
+ if (packageName.isBlank()) return false
+
+ val cached = appLockCache[packageName]
+ if (cached != null) return cached
+
+ val manager = getSandboxManager() ?: return false
+ return try {
+ val locked = AppLockState.fromOrdinal(manager.getAppLockState(packageName))
+ .hasAppLock()
+ appLockCache[packageName] = locked
+ locked
+ } catch (e: RemoteException) {
+ Log.w(TAG, "RemoteException in isAppLocked: ${e.message}")
+ false
+ }
+ }
+
+ fun isAppLockedWithoutCache(packageName: String): Boolean {
+ return try {
+ val ordinal = getSandboxManager()?.getAppLockState(packageName) ?: return false
+ AppLockState.fromOrdinal(ordinal).hasAppLock()
+ } catch (e: RemoteException) {
+ Log.w(TAG, "RemoteException in isAppLockedWithoutCache: ${e.message}")
+ false
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/BaseDepthController.java b/quickstep/src/com/android/quickstep/util/BaseDepthController.java
index c9bca79ea1f..59fdba047bf 100644
--- a/quickstep/src/com/android/quickstep/util/BaseDepthController.java
+++ b/quickstep/src/com/android/quickstep/util/BaseDepthController.java
@@ -39,6 +39,7 @@
import com.android.app.animation.Interpolators;
import com.android.launcher3.Flags;
import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@@ -46,6 +47,7 @@
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
+import com.android.launcher3.util.Executors;
import com.android.systemui.shared.system.BlurUtils;
/**
@@ -89,6 +91,8 @@ public Float get(BaseDepthController depthController) {
protected final WallpaperManager mWallpaperManager;
protected boolean mCrossWindowBlursEnabled;
+ private float mLastWallpaperZoom = -1f;
+
/**
* Ratio from 0 to 1, where 0 is fully zoomed out, and 1 is zoomed in.
*
@@ -132,11 +136,8 @@ public BaseDepthController(QuickstepLauncher activity) {
if (Flags.allAppsBlur() || enableOverviewBackgroundWallpaperBlur()) {
mCrossWindowBlursEnabled =
CrossWindowBlurListeners.getInstance().isCrossWindowBlurEnabled();
- mMaxBlurRadius = activity.getResources().getDimensionPixelSize(
- R.dimen.max_depth_blur_radius_enhanced);
- } else {
- mMaxBlurRadius = activity.getResources().getInteger(R.integer.max_depth_blur_radius);
}
+ mMaxBlurRadius = LauncherPrefs.BLUR_DEPTH.get(mLauncher);
mWallpaperManager = activity.getSystemService(WallpaperManager.class);
MultiPropertyFactory depthProperty =
@@ -203,7 +204,22 @@ private void applyDepthAndBlur(@Nullable SurfaceTransaction surfaceTransaction,
float depth = mDepth;
IBinder windowToken = mLauncher.getRootView().getWindowToken();
if (windowToken != null) {
- mWallpaperManager.setWallpaperZoomOut(windowToken, depth);
+ float zoom = LauncherPrefs.ALLOW_WALLPAPER_ZOOMING.get(mLauncher)
+ ? depth
+ : 0f;
+
+ if (Math.abs(mLastWallpaperZoom - zoom) >= 0.015f
+ || (zoom == 0f && mLastWallpaperZoom != 0f)
+ || (zoom == 1f && mLastWallpaperZoom != 1f)) {
+
+ final float finalZoom = zoom;
+
+ Executors.UI_HELPER_EXECUTOR.execute(() -> {
+ mWallpaperManager.setWallpaperZoomOut(windowToken, finalZoom);
+ });
+
+ mLastWallpaperZoom = zoom;
+ }
}
if (!BlurUtils.supportsBlursOnWindows()) {
@@ -232,7 +248,7 @@ private void applyDepthAndBlur(@Nullable SurfaceTransaction surfaceTransaction,
int newBlur = mCrossWindowBlursEnabled && !hasOpaqueBg && !mPauseBlurs ? (int) (blurAmount
* mMaxBlurRadius) : 0;
int delta = Math.abs(newBlur - previousBlur);
- if (skipSimilarBlur && delta < Utilities.dpToPx(1) && newBlur != 0 && previousBlur != 0
+ if (skipSimilarBlur && delta < Utilities.dpToPx(2) && newBlur != 0 && previousBlur != 0
&& blurAmount != 1f) {
Log.d(TAG, "Skipping small blur delta. newBlur: " + newBlur + " previousBlur: "
+ previousBlur + " delta: " + delta + " surface: " + blurSurface);
@@ -342,8 +358,8 @@ private void setDepth(float depth) {
depthF = depth;
} else {
// Round out the depth to dedupe frequent, non-perceptable updates
- int depthI = (int) (depth * 256);
- depthF = depthI / 256f;
+ int depthI = (int) (depth * 128);
+ depthF = depthI / 128f;
}
if (Float.compare(mDepth, depthF) == 0) {
return;
@@ -374,7 +390,7 @@ public void setBaseSurfaceOverride(@Nullable SurfaceControl baseSurfaceOverride,
private @Nullable SurfaceTransaction setupBlurSurface() {
SurfaceTransaction surfaceTransaction = null;
- if (mBaseSurface != null && mBaseSurfaceOverride != null) {
+ if (mBaseSurface != null && mBaseSurface.isValid() && mBaseSurfaceOverride != null) {
surfaceTransaction = new SurfaceTransaction();
surfaceTransaction.forSurface(mBaseSurface).setBackgroundBlurRadius(0).setOpaque(false);
if (mBlurSurface == null) {
diff --git a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
index 64e46d8d57c..41b219cb6fb 100644
--- a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
+++ b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
@@ -51,6 +51,7 @@
import com.android.internal.app.ChooserActivity;
import com.android.internal.util.ScreenshotRequest;
import com.android.launcher3.BuildConfig;
+import com.android.launcher3.Utilities;
import com.android.quickstep.SystemUiProxy;
import com.android.systemui.shared.recents.model.Task;
@@ -160,6 +161,33 @@ public static void startShareActivity(Context context, Supplier bitmapSu
});
}
+ @WorkerThread
+ public static void startLensActivity(Context context, Supplier bitmapSupplier,
+ Rect crop, String tag) {
+ UI_HELPER_EXECUTOR.execute(() -> {
+ Bitmap bitmap = bitmapSupplier.get();
+ if (bitmap == null) {
+ Log.e(tag, "No snapshot available, not starting lens.");
+ return;
+ }
+ Intent intent = new Intent();
+ Uri uri = getImageUri(bitmap, crop, context, tag);
+ ClipData clipdata = new ClipData(new ClipDescription("content",
+ new String[]{"image/png"}),
+ new ClipData.Item(uri));
+ intent.setAction(Intent.ACTION_SEND)
+ .setComponent(
+ new ComponentName(Utilities.GSA_PACKAGE, Utilities.LENS_SHARE_ACTIVITY))
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ .addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
+ .addFlags(FLAG_GRANT_READ_URI_PERMISSION)
+ .setType("image/png")
+ .putExtra(Intent.EXTRA_STREAM, uri)
+ .setClipData(clipdata);
+ context.startActivity(intent);
+ });
+ }
+
/**
* Starts activity based on given intent created from image uri.
*/
diff --git a/quickstep/src/com/android/quickstep/util/MultiValueUpdateListener.java b/quickstep/src/com/android/quickstep/util/MultiValueUpdateListener.java
index 72fc2a67b5b..9bc1b90df46 100644
--- a/quickstep/src/com/android/quickstep/util/MultiValueUpdateListener.java
+++ b/quickstep/src/com/android/quickstep/util/MultiValueUpdateListener.java
@@ -18,8 +18,6 @@
import android.animation.ValueAnimator;
import android.view.animation.Interpolator;
-import com.android.launcher3.Utilities;
-
import java.util.ArrayList;
/**
@@ -37,7 +35,7 @@ public final void onAnimationUpdate(ValueAnimator animator) {
for (int i = mAllProperties.size() - 1; i >= 0; i--) {
FloatProp prop = mAllProperties.get(i);
float interpolatedPercent = prop.mInterpolator.getInterpolation(percent);
- prop.value = Utilities.mapRange(interpolatedPercent, prop.mStart, prop.mEnd);
+ prop.value = prop.mStart + interpolatedPercent * (prop.mEnd - prop.mStart);
}
onUpdate(percent, false /* initOnly */);
}
diff --git a/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java b/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
index 492c8019acf..30347e71f9d 100644
--- a/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
+++ b/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
@@ -20,6 +20,7 @@
import android.animation.Animator;
import android.animation.AnimatorSet;
+import android.os.Handler;
import android.util.Log;
import androidx.annotation.Nullable;
@@ -49,6 +50,8 @@ public class OverviewToHomeAnim {
// Only run mOnReachedHome when both of these are true.
private boolean mIsHomeStaggeredAnimFinished;
private boolean mIsOverviewHidden;
+ private boolean mAnimationStarted = false;
+ private Handler mHandler = new Handler();
public OverviewToHomeAnim(Launcher launcher, Runnable onReachedHome,
@Nullable BiConsumer splitCancelConsumer) {
@@ -66,6 +69,23 @@ public void animateWithVelocity(float velocity) {
LauncherState startState = stateManager.getState();
if (startState != OVERVIEW) {
Log.e(TAG, "animateFromOverviewToHome: unexpected start state " + startState);
+ if (startState == NORMAL) {
+ Log.d(TAG, "Already at NORMAL state, completing animation immediately");
+ mIsHomeStaggeredAnimFinished = true;
+ mIsOverviewHidden = true;
+ maybeOverviewToHomeAnimComplete();
+ return;
+ }
+
+ mAnimationStarted = true;
+
+ Log.w(TAG, "Force transitioning from " + startState + " to NORMAL");
+ stateManager.goToState(NORMAL, false);
+ mIsHomeStaggeredAnimFinished = true;
+ mIsOverviewHidden = true;
+ maybeOverviewToHomeAnimComplete();
+ return;
+
}
AnimatorSet anim = new AnimatorSet();
@@ -99,8 +119,29 @@ public void onAnimationSuccess(Animator animator) {
mIsOverviewHidden = true;
maybeOverviewToHomeAnimComplete();
}
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ Log.w(TAG, "State animation cancelled, forcing completion");
+ mIsOverviewHidden = true;
+ maybeOverviewToHomeAnimComplete();
+ }
});
+ final long ANIMATION_TIMEOUT = config.duration + 1000; // duration + 1 second safety
+ mHandler.postDelayed(() -> {
+ if (mAnimationStarted && (!mIsHomeStaggeredAnimFinished || !mIsOverviewHidden)) {
+ Log.e(TAG, "Animation timeout reached! Force completing animation");
+ Log.e(TAG, " mIsHomeStaggeredAnimFinished: " + mIsHomeStaggeredAnimFinished);
+ Log.e(TAG, " mIsOverviewHidden: " + mIsOverviewHidden);
+ Log.e(TAG, " current state: " + stateManager.getState());
+
+ mIsHomeStaggeredAnimFinished = true;
+ mIsOverviewHidden = true;
+ maybeOverviewToHomeAnimComplete();
+ }
+ }, ANIMATION_TIMEOUT);
+
if (mSplitCancelConsumer != null) {
// Clear split state when swiping to home
mSplitCancelConsumer.accept(anim, config.duration);
@@ -111,8 +152,14 @@ public void onAnimationSuccess(Animator animator) {
}
private void maybeOverviewToHomeAnimComplete() {
+ mHandler.removeCallbacksAndMessages(null);
if (mIsHomeStaggeredAnimFinished && mIsOverviewHidden) {
- mOnReachedHome.run();
+ mAnimationStarted = false;
+ mHandler.post(() -> {
+ if (mOnReachedHome != null) {
+ mOnReachedHome.run();
+ }
+ });
}
}
}
diff --git a/quickstep/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.kt b/quickstep/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.kt
index 7de827e1f5b..c5cf7d7979c 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.kt
+++ b/quickstep/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.kt
@@ -34,6 +34,7 @@ import com.android.app.animation.Interpolators.LINEAR
import com.android.app.animation.Interpolators.OVERSHOOT_0_75
import com.android.app.animation.Interpolators.OVERSHOOT_1_2
import com.android.app.animation.Interpolators.clampToProgress
+import com.android.launcher3.LauncherAppState
import com.android.launcher3.LauncherState.ALL_APPS
import com.android.launcher3.LauncherState.HINT_STATE
import com.android.launcher3.LauncherState.HINT_STATE_TWO_BUTTON
@@ -195,6 +196,9 @@ CONTAINER : StatefulContainer {
val isPinnedTaskbar = TaskbarFeatureEvaluator.INSTANCE.get(container).isPinned
val isThreeButton =
(DisplayController.getNavigationMode(container) == NavigationMode.THREE_BUTTONS)
+ if (toState == NORMAL) {
+ LauncherAppState.INSTANCE.executeIfCreated(LauncherAppState::checkIfRestartNeeded)
+ }
if ((fromState == OVERVIEW || fromState == OVERVIEW_SPLIT_SELECT) && toState == NORMAL) {
applyOverviewToHomeAnimConfig(
fromState,
diff --git a/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java b/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java
index 993968d4f24..476c3efe8c7 100644
--- a/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java
+++ b/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java
@@ -91,6 +91,20 @@ public void setValue(RectFSpringAnim object, float value) {
}
};
+ // Cached spring constants to avoid per-start resource lookups
+ private static volatile boolean sConstantsLoaded;
+ private static long sCachedMinVelocityXPxPerS;
+ private static long sCachedMaxVelocityXPxPerS;
+ private static long sCachedMinVelocityYPxPerS;
+ private static long sCachedMaxVelocityYPxPerS;
+ private static float sCachedFallOffFactor;
+ private static float sCachedStiffnessX;
+ private static float sCachedDampingX;
+ private static float sCachedStiffnessY;
+ private static float sCachedDampingY;
+ private static float sCachedStiffnessZ;
+ private static float sCachedDampingZ;
+
private final RectF mStartRect;
private final RectF mTargetRect;
private final RectF mCurrentRect = new RectF();
@@ -197,12 +211,31 @@ public void addAnimatorListener(Animator.AnimatorListener animatorListener) {
mAnimatorListeners.add(animatorListener);
}
+ private static synchronized void ensureConstantsLoaded(Context context) {
+ if (sConstantsLoaded) return;
+ ResourceProvider rp = DynamicResource.provider(context);
+ sCachedMinVelocityXPxPerS = rp.getInt(R.dimen.swipe_up_min_velocity_x_px_per_s);
+ sCachedMaxVelocityXPxPerS = rp.getInt(R.dimen.swipe_up_max_velocity_x_px_per_s);
+ sCachedMinVelocityYPxPerS = rp.getInt(R.dimen.swipe_up_min_velocity_y_px_per_s);
+ sCachedMaxVelocityYPxPerS = rp.getInt(R.dimen.swipe_up_max_velocity_y_px_per_s);
+ sCachedFallOffFactor = rp.getFloat(R.dimen.swipe_up_max_velocity_fall_off_factor);
+ sCachedStiffnessX = rp.getFloat(R.dimen.swipe_up_rect_x_stiffness);
+ sCachedDampingX = rp.getFloat(R.dimen.swipe_up_rect_x_damping_ratio);
+ sCachedStiffnessY = rp.getFloat(R.dimen.swipe_up_rect_y_stiffness);
+ sCachedDampingY = rp.getFloat(R.dimen.swipe_up_rect_y_damping_ratio);
+ sCachedStiffnessZ = rp.getFloat(R.dimen.swipe_up_rect_scale_stiffness_v2);
+ sCachedDampingZ = rp.getFloat(R.dimen.swipe_up_rect_scale_damping_ratio_v2);
+ sConstantsLoaded = true;
+ }
+
/**
* Starts the fling/spring animation.
* @param context The activity context.
* @param velocityPxPerMs Velocity of swipe in px/ms.
*/
public void start(Context context, @Nullable DeviceProfile profile, PointF velocityPxPerMs) {
+ ensureConstantsLoaded(context);
+
// Only tell caller that we ended if both x and y animations have ended.
OnAnimationEndListener onXEndListener = ((animation, canceled, centerX, velocityX) -> {
mRectXAnimEnded = true;
@@ -221,54 +254,43 @@ public void start(Context context, @Nullable DeviceProfile profile, PointF veloc
float endY = getTrackedYFromRect(mTargetRect);
float minVisibleChange = Math.abs(1f / mStartRect.height());
- ResourceProvider rp = DynamicResource.provider(context);
- long minVelocityXPxPerS = rp.getInt(R.dimen.swipe_up_min_velocity_x_px_per_s);
- long maxVelocityXPxPerS = rp.getInt(R.dimen.swipe_up_max_velocity_x_px_per_s);
- long minVelocityYPxPerS = rp.getInt(R.dimen.swipe_up_min_velocity_y_px_per_s);
- long maxVelocityYPxPerS = rp.getInt(R.dimen.swipe_up_max_velocity_y_px_per_s);
- float fallOffFactor = rp.getFloat(R.dimen.swipe_up_max_velocity_fall_off_factor);
-
// We want the actual initial velocity to never dip below the minimum, and to taper off
// once it's above the soft cap so that we can prevent the window from flying off
// screen, while maintaining a natural feel.
xVelocityPxPerS = adjustVelocity(
- xVelocityPxPerS, minVelocityXPxPerS, maxVelocityXPxPerS, fallOffFactor);
+ xVelocityPxPerS, sCachedMinVelocityXPxPerS, sCachedMaxVelocityXPxPerS,
+ sCachedFallOffFactor);
yVelocityPxPerS = adjustVelocity(
- yVelocityPxPerS, minVelocityYPxPerS, maxVelocityYPxPerS, fallOffFactor);
+ yVelocityPxPerS, sCachedMinVelocityYPxPerS, sCachedMaxVelocityYPxPerS,
+ sCachedFallOffFactor);
- float stiffnessX = rp.getFloat(R.dimen.swipe_up_rect_x_stiffness);
- float dampingX = rp.getFloat(R.dimen.swipe_up_rect_x_damping_ratio);
mRectXSpring =
new SpringAnimation(this, RECT_CENTER_X)
.setSpring(
new SpringForce(endX)
- .setStiffness(stiffnessX)
- .setDampingRatio(dampingX)
+ .setStiffness(sCachedStiffnessX)
+ .setDampingRatio(sCachedDampingX)
).setStartValue(startX)
.setStartVelocity(xVelocityPxPerS)
.addEndListener(onXEndListener);
- float stiffnessY = rp.getFloat(R.dimen.swipe_up_rect_y_stiffness);
- float dampingY = rp.getFloat(R.dimen.swipe_up_rect_y_damping_ratio);
mRectYSpring =
new SpringAnimation(this, RECT_Y)
.setSpring(
new SpringForce(endY)
- .setStiffness(stiffnessY)
- .setDampingRatio(dampingY)
+ .setStiffness(sCachedStiffnessY)
+ .setDampingRatio(sCachedDampingY)
)
.setStartValue(startY)
.setStartVelocity(yVelocityPxPerS)
.addEndListener(onYEndListener);
- float stiffnessZ = rp.getFloat(R.dimen.swipe_up_rect_scale_stiffness_v2);
- float dampingZ = rp.getFloat(R.dimen.swipe_up_rect_scale_damping_ratio_v2);
mRectScaleAnim =
new SpringAnimation(this, RECT_SCALE_PROGRESS)
.setSpring(
new SpringForce(1f)
- .setStiffness(stiffnessZ)
- .setDampingRatio(dampingZ))
+ .setStiffness(sCachedStiffnessZ)
+ .setDampingRatio(sCachedDampingZ))
.setStartVelocity(velocityPxPerMs.y * minVisibleChange)
.setMaxValue(1f)
.setMinimumVisibleChange(minVisibleChange)
@@ -327,27 +349,28 @@ private void onUpdate() {
}
if (!mOnUpdateListeners.isEmpty()) {
- float currentWidth = Utilities.mapRange(mCurrentScaleProgress, mStartRect.width(),
- mTargetRect.width());
- float currentHeight = Utilities.mapRange(mCurrentScaleProgress, mStartRect.height(),
- mTargetRect.height());
+ float startWidth = mStartRect.width();
+ float startHeight = mStartRect.height();
+ float currentWidth = startWidth + mCurrentScaleProgress * (mTargetRect.width() - startWidth);
+ float currentHeight = startHeight + mCurrentScaleProgress * (mTargetRect.height() - startHeight);
+ float halfWidth = currentWidth / 2;
switch (mTracking) {
case TRACKING_TOP:
- mCurrentRect.set(mCurrentCenterX - currentWidth / 2,
+ mCurrentRect.set(mCurrentCenterX - halfWidth,
mCurrentY,
- mCurrentCenterX + currentWidth / 2,
+ mCurrentCenterX + halfWidth,
mCurrentY + currentHeight);
break;
case TRACKING_BOTTOM:
- mCurrentRect.set(mCurrentCenterX - currentWidth / 2,
+ mCurrentRect.set(mCurrentCenterX - halfWidth,
mCurrentY - currentHeight,
- mCurrentCenterX + currentWidth / 2,
+ mCurrentCenterX + halfWidth,
mCurrentY);
break;
case TRACKING_CENTER:
- mCurrentRect.set(mCurrentCenterX - currentWidth / 2,
+ mCurrentRect.set(mCurrentCenterX - halfWidth,
mCurrentY - currentHeight / 2,
- mCurrentCenterX + currentWidth / 2,
+ mCurrentCenterX + halfWidth,
mCurrentY + currentHeight / 2);
break;
}
diff --git a/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt b/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt
index 7aec25f9060..980dd9d2301 100644
--- a/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt
+++ b/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt
@@ -65,24 +65,25 @@ class ScalingWorkspaceRevealAnim(
playBlur: Boolean = true,
) {
companion object {
- private const val FADE_DURATION_MS = 200L
- private const val SCALE_DURATION_MS = 1000L
+ private const val FADE_DURATION_MS = 150L
+ private const val SCALE_DURATION_MS = 800L
+ private const val HOTSEAT_FADE_DELAY_MS = 30L
private const val MAX_ALPHA = 1f
private const val MIN_ALPHA = 0f
internal const val MAX_SIZE = 1f
- internal const val MIN_SIZE = 0.85f
+ internal const val MIN_SIZE = 0.92f
/**
* Custom interpolator for both the home and wallpaper scaling. Necessary because EMPHASIZED
* is too aggressive, but EMPHASIZED_DECELERATE is too soft.
*/
@JvmField
- val SCALE_INTERPOLATOR =
+ val SCALE_INTERPOLATOR =
PathInterpolator(
Path().apply {
moveTo(0f, 0f)
- cubicTo(0.045f, 0.0356f, 0.0975f, 0.2055f, 0.15f, 0.3952f)
- cubicTo(0.235f, 0.6855f, 0.235f, 1f, 1f, 1f)
+ cubicTo(0.05f, 0.07f, 0.1f, 0.4f, 0.2f, 0.6f)
+ cubicTo(0.3f, 0.85f, 0.4f, 1f, 1f, 1f)
}
)
@@ -161,10 +162,11 @@ class ScalingWorkspaceRevealAnim(
// This is because hotseat visibility can also be changed based off of alpha in
// WorkspaceRevealAnim which also calls setViewAlpha.
// b/428257480 Ideally we should be settings MultiValueAlpha with 2 channels instead.
+ val hotseatDelay = HOTSEAT_FADE_DELAY_MS.toFloat() / SCALE_DURATION_MS
animation.setViewAlpha(
hotseat,
MAX_ALPHA,
- Interpolators.clampToProgress(LINEAR, 0f, fadeClamp),
+ Interpolators.clampToProgress(LINEAR, hotseatDelay, fadeClamp + hotseatDelay),
)
}
@@ -195,8 +197,14 @@ class ScalingWorkspaceRevealAnim(
)
val blurAnimator = ValueAnimator.ofFloat(1f, 0f)
blurAnimator.setInterpolator(BLUR_INTERPOLATOR)
+ var lastBlurRadius = -1
blurAnimator.addUpdateListener {
- applyBlur(maxBlurRadius * blurAnimator.animatedValue as Float)
+ val blurRadius = (maxBlurRadius * blurAnimator.animatedValue as Float).toInt()
+ // Always apply when reaching 0 to ensure final state is clean.
+ if (Math.abs(lastBlurRadius - blurRadius) >= 4 || blurRadius == 0) {
+ applyBlur(blurRadius.toFloat())
+ lastBlurRadius = blurRadius
+ }
}
animation.add(blurAnimator)
@@ -255,6 +263,7 @@ class ScalingWorkspaceRevealAnim(
override fun onAnimationCancel(animation: Animator) {
super.onAnimationCancel(animation)
Log.d(TAG, "onAnimationCancel")
+ applyBlur(0f)
}
override fun onAnimationPause(animation: Animator) {
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index 7863dd6ebdb..74c0a2b0b56 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -1134,14 +1134,6 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC
val taskId = taskInfo.taskId
val mode = change.mode
- // Find the target tasks' root tasks since those are the split stages that need to
- // be animated (the tasks themselves are children and thus inherit animation).
- if (taskId == initialTaskId || taskId == secondTaskId) {
- check(mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
- "Expected task to be showing, but it is $mode"
- }
- }
-
if (taskId == initialTaskId) {
splitRoot1 = change
val parentToken1 = change.parent
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index 683a99583f4..cb396c3cc3b 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -953,7 +953,7 @@ public void enterSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
mTaskInfo = taskInfo;
PackageManager pm = mContext.getPackageManager();
- IconProvider provider = new IconProvider(mContext);
+ IconProvider provider = IconProvider.INSTANCE.get(mContext);
int displayId = ExternalDisplaysKt.getSafeDisplayId(taskInfo);
try {
diff --git a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
index d2b6eca2e1c..351adf599a1 100644
--- a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
@@ -177,19 +177,23 @@ private void startWorkspaceAnimation(@NonNull View view, @Nullable Bitmap bitmap
secondTaskEndingBounds);
FloatingTaskView firstFloatingTaskView = mController.getFirstFloatingTaskView();
- firstFloatingTaskView.getBoundsOnScreen(firstTaskStartingBounds);
- firstFloatingTaskView.addConfirmAnimation(pendingAnimation,
- new RectF(firstTaskStartingBounds), firstTaskEndingBounds,
- false /* fadeWithThumbnail */, true /* isStagedTask */);
+ if (firstFloatingTaskView != null) {
+ firstFloatingTaskView.getBoundsOnScreen(firstTaskStartingBounds);
+ firstFloatingTaskView.addConfirmAnimation(pendingAnimation,
+ new RectF(firstTaskStartingBounds), firstTaskEndingBounds,
+ false /* fadeWithThumbnail */, true /* isStagedTask */);
+ }
View backingScrim = recentsView.getSplitSelectController().getSplitAnimationController()
.addScrimBehindAnim(pendingAnimation, mLauncher, view.getContext());
FloatingTaskView secondFloatingTaskView = FloatingTaskView.getFloatingTaskView(mLauncher,
view, bitmap, icon, secondTaskStartingBounds);
- secondFloatingTaskView.setAlpha(1);
- secondFloatingTaskView.addConfirmAnimation(pendingAnimation, secondTaskStartingBounds,
- secondTaskEndingBounds, true /* fadeWithThumbnail */, false /* isStagedTask */);
+ if (secondFloatingTaskView != null) {
+ secondFloatingTaskView.setAlpha(1);
+ secondFloatingTaskView.addConfirmAnimation(pendingAnimation, secondTaskStartingBounds,
+ secondTaskEndingBounds, true /* fadeWithThumbnail */, false /* isStagedTask */);
+ }
pendingAnimation.addListener(new AnimatorListenerAdapter() {
private boolean mIsCancelled = false;
@@ -213,8 +217,12 @@ public void onAnimationEnd(Animator animation) {
}
private void cleanUp() {
- mLauncher.getDragLayer().removeView(firstFloatingTaskView);
- mLauncher.getDragLayer().removeView(secondFloatingTaskView);
+ if (firstFloatingTaskView != null) {
+ mLauncher.getDragLayer().removeView(firstFloatingTaskView);
+ }
+ if (secondFloatingTaskView != null) {
+ mLauncher.getDragLayer().removeView(secondFloatingTaskView);
+ }
mLauncher.getDragLayer().removeView(backingScrim);
mController.getSplitAnimationController().removeSplitInstructionsView(mLauncher);
mController.resetState();
diff --git a/quickstep/src/com/android/quickstep/util/SurfaceTransaction.java b/quickstep/src/com/android/quickstep/util/SurfaceTransaction.java
index 6ead146e9e0..bb02bf94b68 100644
--- a/quickstep/src/com/android/quickstep/util/SurfaceTransaction.java
+++ b/quickstep/src/com/android/quickstep/util/SurfaceTransaction.java
@@ -62,6 +62,15 @@ public SurfaceProperties setAlpha(float alpha) {
return this;
}
+ /**
+ * @param color The color value to apply to the surface.
+ * @return this Builder
+ */
+ public SurfaceProperties setColor(float[] color) {
+ mTransaction.setColor(mSurface, color);
+ return this;
+ }
+
/**
* @param opaque Whether the surface is opaque.
* @return this Builder
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index ff79237daf9..8bf8d68d384 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -177,7 +177,7 @@ private SwipePipToHomeAnimator(@NonNull Context context,
getEnterPipWithOverlaySrcRectHint(appBounds, aspectRatio.floatValue()));
mPipContentOverlay = new PipContentOverlay.PipAppIconOverlay(view.getContext(),
mAppBounds, mDestinationBounds,
- new IconProvider(context).getIcon(mActivityInfo), appIconSizePx);
+ IconProvider.INSTANCE.get(context).getIcon(mActivityInfo), appIconSizePx);
final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
mPipContentOverlay.attach(tx, mLeash);
Log.d(TAG, getContentOverlay() + " is created: " + reasonForCreateOverlay);
diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.kt b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.kt
index 8c7f4830dbf..dc4b89efcd3 100644
--- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.kt
+++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.kt
@@ -127,7 +127,9 @@ constructor(
}
fun initialize() {
- check(!isDestroyed) { "Cannot re-initialize a destroyed toast" }
+ if (isDestroyed) {
+ return
+ }
setupTranslations()
Executors.ORDERED_BG_EXECUTOR.execute {
var usageLimit: AppUsageLimit? = null
diff --git a/quickstep/src/com/android/quickstep/views/IconAppChipView.kt b/quickstep/src/com/android/quickstep/views/IconAppChipView.kt
index e14e6effe27..2d4897e68e0 100644
--- a/quickstep/src/com/android/quickstep/views/IconAppChipView.kt
+++ b/quickstep/src/com/android/quickstep/views/IconAppChipView.kt
@@ -20,6 +20,7 @@ import android.animation.ObjectAnimator
import android.animation.RectEvaluator
import android.animation.ValueAnimator
import android.content.Context
+import android.content.res.ColorStateList
import android.graphics.Canvas
import android.graphics.Outline
import android.graphics.Rect
@@ -128,6 +129,8 @@ constructor(
private val viewTranslationY: MultiPropertyFactory =
MultiPropertyFactory(this, VIEW_TRANSLATE_Y, INDEX_COUNT_TRANSLATION, SUM_AGGREGATOR)
+ private var isShowingLockIcon = false
+
// Width showing only the app icon and arrow. Max width should not be set to less than
// this.
private val minWidthAllowed = iconViewMarginStart + appIconSize + arrowSize + arrowMarginEnd
@@ -461,14 +464,17 @@ constructor(
val textTranslationXWithRtl = if (isRtl) -textTranslationX else textTranslationX
val arrowTranslationWithRtl = if (isRtl) -arrowTranslationX else arrowTranslationX
- animator!!.playTogether(
+ val expandAnimators = mutableListOf(
backgroundAnimator,
ObjectAnimator.ofFloat(iconView, SCALE_X, iconViewScaling),
ObjectAnimator.ofFloat(iconView, SCALE_Y, iconViewScaling),
ObjectAnimator.ofFloat(appTitle, TRANSLATION_X, textTranslationXWithRtl),
ObjectAnimator.ofFloat(iconArrowView, TRANSLATION_X, arrowTranslationWithRtl),
- ObjectAnimator.ofFloat(iconArrowView, SCALE_Y, -1f),
)
+ if (!isShowingLockIcon) {
+ expandAnimators.add(ObjectAnimator.ofFloat(iconArrowView, SCALE_Y, -1f))
+ }
+ animator!!.playTogether(*expandAnimators.toTypedArray())
animator!!.duration = MENU_BACKGROUND_REVEAL_DURATION.toLong()
status = AppChipStatus.Expanded
} else {
@@ -493,14 +499,17 @@ constructor(
invalidateOutline()
}
- animator!!.playTogether(
+ val collapseAnimators = mutableListOf(
expandedTextClipAnim,
backgroundAnimator,
ObjectAnimator.ofFloat(iconView, SCALE_PROPERTY, 1f),
ObjectAnimator.ofFloat(appTitle, TRANSLATION_X, 0f),
ObjectAnimator.ofFloat(iconArrowView, TRANSLATION_X, 0f),
- ObjectAnimator.ofFloat(iconArrowView, SCALE_Y, 1f),
)
+ if (!isShowingLockIcon) {
+ collapseAnimators.add(ObjectAnimator.ofFloat(iconArrowView, SCALE_Y, 1f))
+ }
+ animator!!.playTogether(*collapseAnimators.toTypedArray())
animator!!.duration = MENU_BACKGROUND_HIDE_DURATION.toLong()
status = AppChipStatus.Collapsed
sendToBack()
@@ -652,6 +661,21 @@ constructor(
drawable = null
}
+ fun setLockState(isLocked: Boolean) {
+ isShowingLockIcon = isLocked
+ if (isLocked) {
+ iconArrowView?.setImageResource(R.drawable.ic_lock_recent)
+ iconArrowView?.imageTintList = ColorStateList.valueOf(
+ resources.getColor(R.color.recent_app_locked_icon_color, context.theme)
+ )
+ iconArrowView?.scaleY = 1f
+ } else {
+ iconArrowView?.setImageResource(R.drawable.ic_chevron_down)
+ iconArrowView?.imageTintList = null
+ iconArrowView?.scaleY = 1f
+ }
+ }
+
override fun asView(): View = this
enum class AppChipStatus {
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 98c138d67a4..17b4bc429a5 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -74,8 +74,9 @@ public LauncherRecentsView(Context context, @Nullable AttributeSet attrs, int de
@Override
public void init(OverviewActionsView actionsView,
SplitSelectStateController splitPlaceholderView,
- @Nullable DesktopRecentsTransitionController desktopRecentsTransitionController) {
- super.init(actionsView, splitPlaceholderView, desktopRecentsTransitionController);
+ @Nullable DesktopRecentsTransitionController desktopRecentsTransitionController,
+ MemInfoView memInfoView) {
+ super.init(actionsView, splitPlaceholderView, desktopRecentsTransitionController, memInfoView);
setContentAlpha(0);
}
diff --git a/quickstep/src/com/android/quickstep/views/MemInfoView.java b/quickstep/src/com/android/quickstep/views/MemInfoView.java
new file mode 100644
index 00000000000..b53622b839f
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/MemInfoView.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2022 Project Kaleidoscope
+ * 2024-2025 crDroid Android Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.views;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.Debug;
+import android.os.Handler;
+import android.graphics.Rect;
+import android.text.format.Formatter;
+import android.util.AttributeSet;
+import android.util.FloatProperty;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.FrameLayout.LayoutParams;
+import android.widget.TextView;
+
+import com.android.internal.util.MemInfoReader;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.LauncherPrefs;
+import com.android.launcher3.R;
+import com.android.launcher3.Insettable;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.MultiValueAlpha;
+import com.android.launcher3.util.NavigationMode;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.lang.Runnable;
+import java.lang.ref.WeakReference;
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Locale;
+
+public class MemInfoView extends TextView implements Insettable {
+
+ private static final int UNIT_CONVERT_THRESHOLD = 1024; /* MiB */
+ private static final BigDecimal GB2MB = new BigDecimal(1024);
+
+ private static final int ALPHA_STATE_CTRL = 0;
+ public static final int ALPHA_FS_PROGRESS = 1;
+
+ private static final String TAG = "MemInfoView";
+
+ public static final FloatProperty STATE_CTRL_ALPHA =
+ new FloatProperty("state control alpha") {
+ @Override
+ public Float get(MemInfoView view) {
+ return view.getAlpha(ALPHA_STATE_CTRL);
+ }
+
+ @Override
+ public void setValue(MemInfoView view, float v) {
+ view.setAlpha(ALPHA_STATE_CTRL, v);
+ }
+ };
+
+ private final Rect mInsets = new Rect();
+
+ private DeviceProfile mDp;
+ private MultiValueAlpha mAlpha;
+ private ActivityManager mActivityManager;
+
+ private Handler mHandler;
+
+ private String mMemInfoText;
+
+ private ActivityManager.MemoryInfo memInfo;
+ private MemInfoReader mMemInfoReader;
+
+ private Context mContext;
+
+ String mTotalResult;
+
+ public MemInfoView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ mContext = context;
+ mAlpha = new MultiValueAlpha(this, 2);
+ mAlpha.setUpdateVisibility(true);
+ mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+ memInfo = new ActivityManager.MemoryInfo();
+ mMemInfoReader = new MemInfoReader();
+ mTotalResult = formatTotalMemory();
+
+ mMemInfoText = context.getResources().getString(R.string.meminfo_text);
+ setListener(context);
+ }
+
+ @Override
+ public void setVisibility(int visibility) {
+ if (visibility == VISIBLE) {
+ boolean showMeminfo = LauncherPrefs.RECENTS_MEMINFO.get(getContext());
+ if (!showMeminfo) visibility = GONE;
+ }
+
+ super.setVisibility(visibility);
+
+ if (visibility == VISIBLE) {
+ startMemoryMonitoring();
+ } else {
+ stopMemoryMonitoring();
+ }
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ updateVerticalMargin(DisplayController.getNavigationMode(getContext()));
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+ mInsets.set(insets);
+ updateVerticalMargin(DisplayController.getNavigationMode(getContext()));
+ updatePadding();
+ }
+
+ private void updatePadding() {
+ setPadding(mInsets.left, 0, mInsets.right, 0);
+ }
+
+ public void setDp(DeviceProfile dp) {
+ mDp = dp;
+ }
+
+ public void setAlpha(int alphaType, float alpha) {
+ mAlpha.get(alphaType).setValue(alpha);
+ }
+
+ public float getAlpha(int alphaType) {
+ return mAlpha.get(alphaType).getValue();
+ }
+
+ public void updateVerticalMargin(NavigationMode mode) {
+ LayoutParams lp = (LayoutParams) getLayoutParams();
+ int bottomMargin = mDp.getOverviewActionsClaimedSpaceBelow();
+
+ lp.setMargins(lp.leftMargin, lp.topMargin, lp.rightMargin, bottomMargin);
+ lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
+ }
+
+ private String formatTotalMemory() {
+ mActivityManager.getMemoryInfo(memInfo);
+ double totalMemoryGB = memInfo.totalMem / (1024.0 * 1024.0 * 1024.0);
+ int roundedMemoryGB = roundToNearestKnownRamSize(totalMemoryGB);
+ return roundedMemoryGB + " GB";
+ }
+
+ private int roundToNearestKnownRamSize(double memoryGB) {
+ int[] knownSizes = {1, 2, 3, 4, 6, 8, 10, 12, 16, 32, 48, 64};
+ if (memoryGB <= 0) return 1;
+ for (int size : knownSizes) {
+ if (memoryGB <= size) return size;
+ }
+ return knownSizes[knownSizes.length - 1];
+ }
+
+ private long getZramSize() {
+ if (!LauncherPrefs.RECENTS_MEMINFO_ZRAM.get(getContext()))
+ return 0;
+
+ long zramSize = 0;
+
+ try (BufferedReader reader = new BufferedReader(new FileReader("/sys/block/zram0/disksize"))) {
+ zramSize = Long.parseLong(reader.readLine().trim());
+ } catch (IOException | NumberFormatException e) {
+ Log.w(TAG, "Primary ZRAM location failed, trying fallback", e);
+ }
+
+ if (zramSize == 0) {
+ try (BufferedReader reader = new BufferedReader(new FileReader("/proc/swaps"))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (line.contains("zram0")) {
+ String[] parts = line.split("\\s+");
+ if (parts.length > 2) {
+ zramSize = Long.parseLong(parts[2]) * 1024; // KB to bytes
+ }
+ break;
+ }
+ }
+ } catch (IOException | NumberFormatException e) {
+ Log.w(TAG, "Fallback ZRAM location not available", e);
+ }
+ }
+
+ return zramSize;
+ }
+
+ public void setListener(Context context) {
+ setOnClickListener(view -> {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ intent.setClassName("com.android.settings", "com.android.settings.Settings$DevRunningServicesActivity");
+ context.startActivity(intent);
+ });
+ }
+
+ private long getTotalBackgroundMemory() {
+ long totalBackgroundMemory = 0;
+ List runningProcesses = mActivityManager.getRunningAppProcesses();
+ if (runningProcesses != null) {
+ int[] pids = new int[runningProcesses.size()];
+ for (int i = 0; i < runningProcesses.size(); i++) {
+ pids[i] = runningProcesses.get(i).pid;
+ }
+ Debug.MemoryInfo[] memoryInfos = mActivityManager.getProcessMemoryInfo(pids);
+ for (int i = 0; i < memoryInfos.length; i++) {
+ ActivityManager.RunningAppProcessInfo info = runningProcesses.get(i);
+ if (info.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND) {
+ long memorySize = memoryInfos[i].getTotalPss() * 1024L;
+ totalBackgroundMemory += memorySize;
+ }
+ }
+ }
+ return totalBackgroundMemory;
+ }
+
+ private void startMemoryMonitoring() {
+ if (mHandler == null) {
+ mHandler = MODEL_EXECUTOR.getHandler();
+ mHandler.post(mWorker);
+ }
+ }
+
+ private void stopMemoryMonitoring() {
+ if (mHandler != null) {
+ mHandler.removeCallbacks(mWorker);
+ mHandler = null;
+ }
+ }
+
+ private static class MemoryWorker implements Runnable {
+ private final WeakReference viewRef;
+
+ MemoryWorker(MemInfoView view) {
+ viewRef = new WeakReference<>(view);
+ }
+
+ @Override
+ public void run() {
+ MemInfoView view = viewRef.get();
+ if (view == null || view.mHandler == null) {
+ return;
+ }
+
+ view.mMemInfoReader.readMemInfo();
+ long freeMemory = view.mMemInfoReader.getFreeSize() +
+ view.mMemInfoReader.getCachedSize() +
+ view.getTotalBackgroundMemory();
+ long zramSize = view.getZramSize();
+
+ String availResult = Formatter.formatShortFileSize(view.mContext, freeMemory);
+ String text;
+ if (zramSize > 0) {
+ String zramResult = Formatter.formatShortFileSize(view.mContext, zramSize);
+ text = String.format(Locale.getDefault(), view.mMemInfoText, availResult, view.mTotalResult + " + " + zramResult);
+ } else {
+ text = String.format(Locale.getDefault(), view.mMemInfoText, availResult, view.mTotalResult);
+ }
+
+ MAIN_EXECUTOR.getHandler().post(() -> view.setText(text));
+
+ if (view.mHandler != null) {
+ view.mHandler.removeCallbacks(this);
+ view.mHandler.postDelayed(this, 3000);
+ }
+ }
+ }
+
+ private final MemoryWorker mWorker = new MemoryWorker(this);
+
+ @Override
+ protected void onDetachedFromWindow() {
+ stopMemoryMonitoring();
+ super.onDetachedFromWindow();
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index 674ce5b8219..e56fa5a9867 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -20,6 +20,7 @@
import android.app.ActivityManager;
import android.content.Context;
+import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.util.AttributeSet;
@@ -30,17 +31,21 @@
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
+import android.widget.TextView;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.NavigationMode;
+import com.android.launcher3.util.VibratorWrapper;
import com.android.quickstep.TaskOverlayFactory.OverlayUICallbacks;
import com.android.quickstep.util.LayoutUtils;
import com.android.wm.shell.shared.TypefaceUtils;
@@ -54,8 +59,10 @@
* View for showing action buttons in Overview
*/
public class OverviewActionsView extends FrameLayout
- implements OnClickListener, Insettable {
+ implements OnClickListener, Insettable, SharedPreferences.OnSharedPreferenceChangeListener {
+
public static final String TAG = "OverviewActionsView";
+ private static final boolean DEBUG = false;
private final Rect mInsets = new Rect();
/**
@@ -147,6 +154,7 @@ public boolean dispatchKeyEvent(KeyEvent event) {
/** Container for the action buttons below a focused, non-split Overview tile. */
protected LinearLayout mActionButtons;
private Button mSplitButton;
+ private Button mLockButton;
/**
* The "save app pair" button. Currently this is the only button that is not contained in
* mActionButtons, since it is the sole button that appears for a grouped task.
@@ -171,6 +179,20 @@ public boolean dispatchKeyEvent(KeyEvent event) {
private boolean mIsGroupedTask = false;
private boolean mCanSaveAppPair = false;
+ private boolean mScreenshot;
+ private boolean mClearAll;
+ private boolean mLens;
+ private boolean mLock;
+
+ private boolean mShowText = true;
+
+ private SharedPreferences mPrefs;
+ private boolean mPrefsRegistered;
+
+ private View mLockPillContainer;
+ private TextView mLockPillText;
+ private boolean mLockPillShowing = false;
+
public OverviewActionsView(Context context) {
this(context, null);
}
@@ -181,6 +203,41 @@ public OverviewActionsView(Context context, @Nullable AttributeSet attrs) {
public OverviewActionsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr, 0);
+ mPrefs = LauncherPrefs.getPrefs(context);
+ mScreenshot = LauncherPrefs.RECENTS_SCREENSHOT.get(context);
+ mClearAll = LauncherPrefs.RECENTS_CLEAR_ALL.get(context);
+ mLens = LauncherPrefs.RECENTS_LENS.get(context);
+ mLock = LauncherPrefs.RECENTS_LOCK.get(context);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ if (!mPrefsRegistered) {
+ mPrefs.registerOnSharedPreferenceChangeListener(this);
+ mPrefsRegistered = true;
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ setCallbacks(null);
+ clearChildClickListeners();
+ if (mPrefsRegistered) {
+ mPrefs.unregisterOnSharedPreferenceChangeListener(this);
+ mPrefsRegistered = false;
+ }
+ super.onDetachedFromWindow();
+ }
+
+ private void clearChildClickListeners() {
+ View v;
+ if ((v = findViewById(R.id.action_screenshot)) != null) v.setOnClickListener(null);
+ if ((v = findViewById(R.id.action_lock)) != null) v.setOnClickListener(null);
+ if ((v = findViewById(R.id.action_split)) != null) v.setOnClickListener(null);
+ if ((v = findViewById(R.id.action_save_app_pair)) != null) v.setOnClickListener(null);
+ if ((v = findViewById(R.id.action_clear_all)) != null) v.setOnClickListener(null);
+ if ((v = findViewById(R.id.action_lens)) != null) v.setOnClickListener(null);
}
@Override
@@ -207,15 +264,103 @@ protected void onFinishInflate() {
}
}, 1f /* initialValue */);
}
+ mLockPillContainer = findViewById(R.id.lock_pill_container);
+ mLockPillText = findViewById(R.id.lock_pill_text);
+ updateVisibilities();
+ }
+
+ public void showLockPill(boolean isCurrentlyLocked) {
+ if (mLockPillContainer == null || mLockPillShowing) return;
+ mLockPillShowing = true;
+ mLockPillText.setText(isCurrentlyLocked ? R.string.unlock_app : R.string.lock_app);
+ mLockPillContainer.setAlpha(0f);
+ mLockPillContainer.setVisibility(VISIBLE);
+ mLockPillContainer.animate().alpha(1f).setDuration(150).start();
+ mActionButtons.animate().alpha(0f).setDuration(150).start();
+ }
+
+ public void hideLockPill() {
+ if (mLockPillContainer == null || !mLockPillShowing) return;
+ mLockPillShowing = false;
+ mLockPillContainer.animate().alpha(0f).setDuration(150).withEndAction(() ->
+ mLockPillContainer.setVisibility(GONE)).start();
+ mActionButtons.animate().alpha(1f).setDuration(150).start();
+ }
+
+ private void updateVisibilities() {
+ boolean isLensVisible = mLens && Utilities.isGSAEnabled(getContext());
+ int visibleCount = (mScreenshot ? 1 : 0) + (mLock ? 1 : 0) + (mClearAll ? 1 : 0) + (isLensVisible ? 1 : 0);
+ mShowText = visibleCount < 4;
+ int drawablePadding = mShowText ? Utilities.dpToPx(8) : 0;
// The screenshot button is implemented as a Button in launcher3 and NexusLauncher, but is
// an ImageButton in go launcher (does not share a common class with Button). Take care when
// casting this.
- View screenshotButton = findViewById(R.id.action_screenshot);
+ Button screenshotButton = findViewById(R.id.action_screenshot);
screenshotButton.setOnClickListener(this);
+ screenshotButton.setVisibility(mScreenshot ? VISIBLE : GONE);
+ if (mShowText) {
+ screenshotButton.setText(R.string.recents_screenshot_title);
+ } else {
+ screenshotButton.setText(null);
+ }
+ screenshotButton.setContentDescription(
+ getContext().getString(R.string.recents_screenshot_title));
+ screenshotButton.setCompoundDrawablePadding(drawablePadding);
+
+ mLockButton = findViewById(R.id.action_lock);
+ mLockButton.setOnClickListener(this);
+ mLockButton.setVisibility(mLock ? VISIBLE : GONE);
+ if (mShowText) {
+ mLockButton.setText(mLockButton.isActivated()
+ ? R.string.action_lock_locked : R.string.action_lock_unlocked);
+ } else {
+ mLockButton.setText(null);
+ }
+ mLockButton.setContentDescription(getContext().getString(mLockButton.isActivated()
+ ? R.string.action_lock_locked : R.string.action_lock_unlocked));
+ mLockButton.setCompoundDrawablePadding(drawablePadding);
+
mSplitButton = findViewById(R.id.action_split);
mSplitButton.setOnClickListener(this);
mSaveAppPairButton.setOnClickListener(this);
+
+ Button clearallButton = findViewById(R.id.action_clear_all);
+ clearallButton.setOnClickListener(this);
+ clearallButton.setVisibility(mClearAll ? VISIBLE : GONE);
+ if (mShowText) {
+ clearallButton.setText(R.string.recents_clear_all_title);
+ } else {
+ clearallButton.setText(null);
+ }
+ clearallButton.setContentDescription(
+ getContext().getString(R.string.recents_clear_all_title));
+ clearallButton.setCompoundDrawablePadding(drawablePadding);
+
+ Button lensButton = findViewById(R.id.action_lens);
+ lensButton.setOnClickListener(this);
+ lensButton.setVisibility(isLensVisible ? VISIBLE : GONE);
+ if (mShowText) {
+ lensButton.setText(R.string.recents_lens_title);
+ } else {
+ lensButton.setText(null);
+ }
+ lensButton.setContentDescription(getContext().getString(R.string.recents_lens_title));
+ lensButton.setCompoundDrawablePadding(drawablePadding);
+ }
+
+ public void updateLockIcon(boolean isLocked) {
+ if (mLockButton != null) {
+ mLockButton.setActivated(isLocked);
+ if (mShowText) {
+ mLockButton.setText(isLocked
+ ? R.string.action_lock_locked : R.string.action_lock_unlocked);
+ } else {
+ mLockButton.setText(null);
+ }
+ mLockButton.setContentDescription(getContext().getString(isLocked
+ ? R.string.action_lock_locked : R.string.action_lock_unlocked));
+ }
}
/**
@@ -223,7 +368,7 @@ protected void onFinishInflate() {
*
* @param callbacks for callbacks, or {@code null} to clear the listener.
*/
- public void setCallbacks(T callbacks) {
+ public void setCallbacks(@Nullable T callbacks) {
mCallbacks = callbacks;
}
@@ -232,13 +377,25 @@ public void onClick(View view) {
if (mCallbacks == null) {
return;
}
- int id = view.getId();
+ final int id = view.getId();
if (id == R.id.action_screenshot) {
+ VibratorWrapper.INSTANCE.get(getContext()).vibrate(VibratorWrapper.EFFECT_CLICK);
mCallbacks.onScreenshot();
+ } else if (id == R.id.action_lock) {
+ VibratorWrapper.INSTANCE.get(getContext()).vibrate(VibratorWrapper.EFFECT_CLICK);
+ mCallbacks.onLockTaskRequested();
} else if (id == R.id.action_split) {
+ VibratorWrapper.INSTANCE.get(getContext()).vibrate(VibratorWrapper.EFFECT_CLICK);
mCallbacks.onSplit();
} else if (id == R.id.action_save_app_pair) {
+ VibratorWrapper.INSTANCE.get(getContext()).vibrate(VibratorWrapper.EFFECT_CLICK);
mCallbacks.onSaveAppPair();
+ } else if (id == R.id.action_clear_all) {
+ VibratorWrapper.INSTANCE.get(getContext()).vibrate(VibratorWrapper.EFFECT_CLICK);
+ mCallbacks.onClearAllTasksRequested();
+ } else if (id == R.id.action_lens) {
+ VibratorWrapper.INSTANCE.get(getContext()).vibrate(VibratorWrapper.EFFECT_CLICK);
+ mCallbacks.onLens();
}
}
@@ -255,6 +412,20 @@ public void setInsets(Rect insets) {
updatePadding();
}
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+ if (LauncherPrefs.RECENTS_SCREENSHOT.getSharedPrefKey().equals(key)) {
+ mScreenshot = prefs.getBoolean(key, true);
+ } else if (LauncherPrefs.RECENTS_CLEAR_ALL.getSharedPrefKey().equals(key)) {
+ mClearAll = prefs.getBoolean(key, true);
+ } else if (LauncherPrefs.RECENTS_LENS.getSharedPrefKey().equals(key)) {
+ mLens = prefs.getBoolean(key, false);
+ } else if (LauncherPrefs.RECENTS_LOCK.getSharedPrefKey().equals(key)) {
+ mLock = prefs.getBoolean(key, true);
+ }
+ updateVisibilities();
+ }
+
public void updateHiddenFlags(@ActionsHiddenFlags int visibilityFlags, boolean enable) {
if (enable) {
mHiddenFlags |= visibilityFlags;
@@ -291,8 +462,10 @@ public void updateDisabledFlags(@ActionsDisabledFlags int disabledFlags, boolean
* pair.
*/
public void updateForGroupedTask(boolean isGroupedTask, boolean canSaveAppPair) {
- Log.d(TAG, "updateForGroupedTask() called with: isGroupedTask = [" + isGroupedTask
- + "], canSaveAppPair = [" + canSaveAppPair + "]");
+ if (DEBUG) {
+ Log.d(TAG, "updateForGroupedTask() called with: isGroupedTask = [" + isGroupedTask
+ + "], canSaveAppPair = [" + canSaveAppPair + "]");
+ }
mIsGroupedTask = isGroupedTask;
mCanSaveAppPair = canSaveAppPair;
updateActionButtonsVisibility();
@@ -318,8 +491,10 @@ private void updateActionButtonsVisibility() {
boolean showGroupActions = mIsGroupedTask && mDp.getDeviceProperties().isTablet() &&
mCanSaveAppPair &&
!getContext().getSystemService(ActivityManager.class).isLowRamDevice();
- Log.d(TAG, "updateActionButtonsVisibility() called: showSingleTaskActions = ["
- + showSingleTaskActions + "], showGroupActions = [" + showGroupActions + "]");
+ if (DEBUG) {
+ Log.d(TAG, "updateActionButtonsVisibility() called: showSingleTaskActions = ["
+ + showSingleTaskActions + "], showGroupActions = [" + showGroupActions + "]");
+ }
getActionsAlphas().get(INDEX_GROUPED_ALPHA).setValue(showSingleTaskActions ? 1 : 0);
getGroupActionsAlphas().get(INDEX_GROUPED_ALPHA).setValue(showGroupActions ? 1 : 0);
}
@@ -444,15 +619,19 @@ public void updateDimension(DeviceProfile dp, Rect taskSize) {
requestLayout();
- int splitIconRes = dp.isLeftRightSplit
- ? R.drawable.ic_split_horizontal
- : R.drawable.ic_split_vertical;
- mSplitButton.setCompoundDrawablesRelativeWithIntrinsicBounds(splitIconRes, 0, 0, 0);
+ if (mSplitButton != null) {
+ mSplitButton.setCompoundDrawablesRelativeWithIntrinsicBounds(
+ dp.isLeftRightSplit ? R.drawable.ic_split_horizontal : R.drawable.ic_split_vertical,
+ 0, 0, 0
+ );
+ }
- int appPairIconRes = dp.isLeftRightSplit
- ? R.drawable.ic_save_app_pair_left_right
- : R.drawable.ic_save_app_pair_up_down;
- mSaveAppPairButton.setCompoundDrawablesRelativeWithIntrinsicBounds(
- appPairIconRes, 0, 0, 0);
+ if (mSaveAppPairButton != null) {
+ mSaveAppPairButton.setCompoundDrawablesRelativeWithIntrinsicBounds(
+ dp.isLeftRightSplit ? R.drawable.ic_save_app_pair_left_right
+ : R.drawable.ic_save_app_pair_up_down,
+ 0, 0, 0
+ );
+ }
}
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsDismissUtils.kt b/quickstep/src/com/android/quickstep/views/RecentsDismissUtils.kt
index e8b1cb5306f..e86c04b9db3 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsDismissUtils.kt
+++ b/quickstep/src/com/android/quickstep/views/RecentsDismissUtils.kt
@@ -35,6 +35,7 @@ import com.android.launcher3.util.DynamicResource
import com.android.launcher3.util.MSDLPlayerWrapper
import com.android.launcher3.util.OverviewReleaseFlags.enableGridOnlyOverview
import com.android.launcher3.views.ActivityContext
+import com.android.quickstep.LockedTaskManager
import com.android.quickstep.SystemUiProxy
import com.android.quickstep.util.TaskGridNavHelper
import com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY
@@ -49,6 +50,7 @@ import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlin.math.abs
import kotlin.math.max
+import kotlin.math.min
import kotlin.math.roundToInt
import kotlin.math.sign
@@ -163,16 +165,15 @@ constructor(
recentsView.showAsGrid() &&
dismissedTaskView != null &&
recentsView.currentPageTaskView is DesktopTaskView
- // The spring set that will reflow the tasks to fill the gap left by the dismissed task.
+ val dismissedTaskGap = getDismissedTaskGapForReflow(dismissedTaskView, isSplitSelection)
var (reflowSpringSet, tasksToReflow) =
createTaskGridReflowSpringSet(
dismissedTaskView,
- getDismissedTaskGapForReflow(dismissedTaskView, isSplitSelection),
+ dismissedTaskGap,
gridEndData,
isSplitSelection,
reflowSplitFromDesktopTile,
)
- // Animate remaining tasks when splitting a task while focused on a desktop task.
if (reflowSplitFromDesktopTile && dismissedTaskView != null) {
val splitWithLargeTileReflowSpringSet =
createSplitWithCurrentPageFadeOutReflowSpringSet(
@@ -186,7 +187,6 @@ constructor(
}
}
if (springSet == null) {
- // Only reflow, as there is no dismissed task to animate.
springSet = reflowSpringSet
} else if (reflowSpringSet != null) {
springSet.playAfterThreshold(
@@ -322,10 +322,16 @@ constructor(
/** Dismisses all */
fun dismissAllTasks() {
+ val lockedPkgMgr = LockedTaskManager.getInstance(recentsView.context)
val allDismissSprings =
recentsView.mUtils.taskViews
.reversed()
- .filter { taskView -> recentsView.isTaskViewVisible(taskView) }
+ .filter { taskView ->
+ recentsView.isTaskViewVisible(taskView) &&
+ !lockedPkgMgr.isPackageLocked(
+ taskView.firstTask?.key?.packageName ?: return@filter false
+ )
+ }
.mapNotNull { createDismissedTaskViewSpringAnimation(it) }
SpringSet(SpringAnimation(FloatValueHolder()).setSpring(SpringForce(1f)))
.playTogether(allDismissSprings)
@@ -494,26 +500,57 @@ constructor(
// Grid end translation to run after all reflow animations have completed.
val gridEndSpringSet =
if (reflowSplitFromDesktopTile) null else createGridEndTranslationSpringSet(gridEndData)
- val tasksWithOffsetsToReflow = getTasksToReflow(dismissedTaskView, towardsStart)
+ val tasksWithOffsetsToReflow =
+ if (recentsView.isOverlapEnabled && dismissedTaskView != null && !recentsView.showAsGrid()) {
+ getOverlapTasksToReflow(dismissedTaskView, dismissedTaskGap)
+ } else {
+ getTasksToReflow(dismissedTaskView, towardsStart)
+ }
if (tasksWithOffsetsToReflow.isEmpty()) {
return Pair(gridEndSpringSet, emptyList())
} else {
+ val isOverlap = recentsView.isOverlapEnabled && !recentsView.showAsGrid()
// Empty spring exists for conditional start, and to drive neighboring springs.
val reflowSpringAnimationDriver =
SpringAnimation(FloatValueHolder())
.setSpring(
- createExpressiveGridReflowSpringForce(finalPosition = dismissedTaskGap)
+ if (isOverlap) {
+ createOverlapReflowSpringForce(finalPosition = dismissedTaskGap)
+ } else {
+ createExpressiveGridReflowSpringForce(finalPosition = dismissedTaskGap)
+ }
)
+
recentsView.mTaskViewsDismissPrimaryTranslations.clear()
// Separate spring end manager for reflow to coordinate start of grid end springs.
val reflowSpringSet = SpringSet(reflowSpringAnimationDriver, dismissedTaskGap)
- buildDismissReflowSpringAnimationChain(
- tasksWithOffsetsToReflow,
- dismissedTaskGap,
- previousSpring = reflowSpringAnimationDriver,
- reflowSpringSet,
- isSplitSelection,
- )
+
+ if (isOverlap) {
+ reflowSpringAnimationDriver.addUpdateListener { _, value, _ ->
+ tasksWithOffsetsToReflow.forEach { (tv, _) ->
+ tv.primaryDismissTranslationProperty.set(tv, value)
+ if (tv.isRunningTask && recentsView.enableDrawingLiveTile) {
+ recentsView.runActionOnRemoteHandles { remoteTargetHandle ->
+ remoteTargetHandle.taskViewSimulator.taskPrimaryTranslation.value = value
+ }
+ recentsView.redrawLiveTile()
+ }
+ }
+ recentsView.doScrollScale()
+ }
+ tasksWithOffsetsToReflow.forEach { (tv, _) ->
+ recentsView.mTaskViewsDismissPrimaryTranslations[tv] = dismissedTaskGap.toInt()
+ }
+ } else {
+ buildDismissReflowSpringAnimationChain(
+ tasksWithOffsetsToReflow,
+ dismissedTaskGap,
+ previousSpring = reflowSpringAnimationDriver,
+ reflowSpringSet,
+ isSplitSelection,
+ )
+ }
+
val tasksToExclude = tasksWithOffsetsToReflow.map { (taskView, _) -> taskView }
if (gridEndSpringSet != null) {
reflowSpringSet.playAfterThreshold(
@@ -545,6 +582,23 @@ constructor(
val dismissedTaskGap =
if (dismissedTaskView == null) {
0f
+ } else if (isOverlapEnabled && !showAsGrid()) {
+ val dismissedPos =
+ pagedOrientationHandler.getPrimaryValue(
+ dismissedTaskView.x,
+ dismissedTaskView.y,
+ )
+ val activeTaskView = currentPageTaskView
+ val activePos =
+ activeTaskView?.let { taskView ->
+ pagedOrientationHandler.getPrimaryValue(taskView.x, taskView.y)
+ } ?: dismissedPos
+ val reflowFromLeft =
+ dismissedTaskView === activeTaskView || dismissedPos <= activePos
+
+ val physicalSlotWidth = pagedOrientationHandler.getPrimarySize(dismissedTaskView) + pageSpacing
+ val sign = if (reflowFromLeft) 1f else -1f
+ physicalSlotWidth.toFloat() * sign * (if (isRtl) -1f else 1f)
} else {
// If current page beyond last TaskView's index, use last TaskView to calculate
// offset.
@@ -566,6 +620,49 @@ constructor(
}
}
+ private fun getOverlapTasksToReflow(
+ dismissedTaskView: TaskView,
+ dismissedTaskGap: Float,
+ ): List> {
+ with(recentsView) {
+ val dismissedPos =
+ pagedOrientationHandler.getPrimaryValue(
+ dismissedTaskView.x,
+ dismissedTaskView.y,
+ )
+ val activeTaskView = currentPageTaskView
+ val activePos =
+ activeTaskView?.let { taskView ->
+ pagedOrientationHandler.getPrimaryValue(taskView.x, taskView.y)
+ } ?: dismissedPos
+ val reflowFromLeft =
+ dismissedTaskView === activeTaskView || dismissedPos <= activePos
+ val tasks =
+ mUtils.taskViews
+ .filterNot { it === dismissedTaskView || (it === activeTaskView && dismissedTaskView !== activeTaskView) }
+ .map { taskView ->
+ taskView to pagedOrientationHandler.getPrimaryValue(taskView.x, taskView.y)
+ }
+ .filter { (_, pos) ->
+ if (reflowFromLeft) pos < dismissedPos else pos > dismissedPos
+ }
+ .sortedBy { (_, pos) -> abs(dismissedPos - pos) }
+ .map { (taskView, _) -> taskView }
+ return tasks.mapIndexed { index, taskView -> taskView to index + 1 }
+ }
+ }
+
+ private fun createOverlapReflowSpringForce(finalPosition: Float = Float.MAX_VALUE): SpringForce {
+ val resourceProvider = DynamicResource.provider(recentsView.mContainer)
+ val damping =
+ resourceProvider.getFloat(R.dimen.expressive_dismiss_task_trans_x_damping_ratio)
+ val stiffness =
+ resourceProvider.getFloat(R.dimen.expressive_dismiss_task_trans_x_stiffness)
+ return SpringForce(finalPosition)
+ .setDampingRatio(min(1f, damping * 1.25f))
+ .setStiffness(stiffness * 0.35f)
+ }
+
private fun getTasksToReflow(
dismissedTaskView: TaskView?,
towardsStart: Boolean,
@@ -643,6 +740,8 @@ constructor(
if (taskViewOffsetPairs.isEmpty()) return previousSpring
var lastTaskViewSpring = previousSpring
var previousColumnDriverSpring = previousSpring
+ var previousColumnDriverTarget = dismissedTaskGap
+ var lastTaskViewTarget = dismissedTaskGap
var lastColumnOffset = taskViewOffsetPairs.first().second
taskViewOffsetPairs
.filter { (taskView, _) ->
@@ -660,6 +759,8 @@ constructor(
(if (recentsView.isRtl) -recentsView.mLastComputedTaskSize.right
else recentsView.mLastComputedTaskSize.right)
} else 0f
+ val taskReflowTarget =
+ recentsView.getStackDismissReflowTarget(taskView, dismissedTaskGap)
val taskViewSpringAnimation =
SpringAnimation(
taskView,
@@ -667,7 +768,7 @@ constructor(
taskView.primaryDismissTranslationProperty
),
)
- .setSpring(createExpressiveGridReflowSpringForce(dismissedTaskGap))
+ .setSpring(createExpressiveGridReflowSpringForce(taskReflowTarget))
.setStartValue(startValue)
// Update live tile on spring animation.
if (taskView.isRunningTask && recentsView.enableDrawingLiveTile) {
@@ -683,15 +784,27 @@ constructor(
// should both be pulled by the previous spring at the same time.
if (column != lastColumnOffset) {
previousColumnDriverSpring = lastTaskViewSpring
+ previousColumnDriverTarget = lastTaskViewTarget
lastColumnOffset = column
}
- previousColumnDriverSpring.addUpdateListener { _, value, _ ->
- taskViewSpringAnimation.animateToFinalPosition(value)
+ val driverSpringForTask = previousColumnDriverSpring
+ val driverTargetForTask = previousColumnDriverTarget
+ if (recentsView.isStackRecentsStyleActive) {
+ driverSpringForTask.addUpdateListener { _, value, _ ->
+ taskViewSpringAnimation.animateToFinalPosition(
+ getStackReflowSpringValue(value, driverTargetForTask, taskReflowTarget)
+ )
+ }
+ } else {
+ driverSpringForTask.addUpdateListener { _, value, _ ->
+ taskViewSpringAnimation.animateToFinalPosition(value)
+ }
}
lastTaskViewSpring = taskViewSpringAnimation
- reflowSpringSet.trackSpring(taskViewSpringAnimation, dismissedTaskGap)
+ lastTaskViewTarget = taskReflowTarget
+ reflowSpringSet.trackSpring(taskViewSpringAnimation, taskReflowTarget)
recentsView.mTaskViewsDismissPrimaryTranslations[taskView] =
- dismissedTaskGap.toInt()
+ taskReflowTarget.toInt()
}
return lastTaskViewSpring
}
@@ -721,6 +834,18 @@ constructor(
return otherGridRowReflowSpringSet
}
+ private fun getStackReflowSpringValue(
+ value: Float,
+ driverTarget: Float,
+ taskReflowTarget: Float,
+ ): Float {
+ return if (driverTarget == 0f) {
+ taskReflowTarget
+ } else {
+ value / driverTarget * taskReflowTarget
+ }
+ }
+
/** Animates the grid to compensate the clear all gap after dismissal. */
private fun createGridEndTranslationSpringSet(gridEndData: GridEndData): SpringSet? {
val gridEndOffset = gridEndData.gridEndOffset
@@ -732,6 +857,11 @@ constructor(
val gridEndSpring =
SpringAnimation(FloatValueHolder())
.setSpring(createExpressiveGridReflowSpringForce(finalPosition = gridEndOffset))
+ if (recentsView.isOverlapEnabled) {
+ gridEndSpring.addUpdateListener { _, _, _ ->
+ recentsView.doScrollScale()
+ }
+ }
val gridEndSpringSet = SpringSet(gridEndSpring, gridEndOffset)
recentsView.mUtils.taskViews.forEach { taskView ->
val taskViewGridEndSpringAnimation =
@@ -943,7 +1073,12 @@ constructor(
// Page snapping and relayout to run after all animations have completed.
val onFinishComplete = {
// Reset task translations as they may have updated via the dismiss animations.
- resetTaskVisuals()
+ // Overlap recents styles keep taskOffset translations through dismiss; resetting
+ // before removeViewInLayout clears them and causes a visible snap. Reset after
+ // layout catches up (below). Stock layout resets immediately as in AOSP.
+ if (!recentsView.isOverlapEnabled()) {
+ resetTaskVisuals()
+ }
// Denote if any task has been dismissed for grid rebalancing.
mAnyTaskHasBeenDismissed = true
@@ -1131,6 +1266,10 @@ constructor(
mUtils.updateChildTaskOrientations()
updateScrollSynchronously()
+ if (recentsView.isOverlapEnabled()) {
+ resetTaskVisuals()
+ }
+
val highestVisibleTaskView = getHighestVisibleTaskView()
if (showAsGrid() && highestVisibleTaskView != null) {
rebalanceTasksInGrid(highestVisibleTaskView)
@@ -1229,7 +1368,11 @@ constructor(
val resourceProvider = DynamicResource.provider(recentsView.mContainer)
return SpringForce(finalPosition)
.setDampingRatio(
- resourceProvider.getFloat(R.dimen.expressive_dismiss_task_trans_x_damping_ratio)
+ if (recentsView.isStackRecentsStyleActive) {
+ STACK_STYLE_REFLOW_DAMPING_RATIO
+ } else {
+ resourceProvider.getFloat(R.dimen.expressive_dismiss_task_trans_x_damping_ratio)
+ }
)
.setStiffness(
resourceProvider.getFloat(R.dimen.expressive_dismiss_task_trans_x_stiffness)
@@ -1415,6 +1558,7 @@ constructor(
private companion object {
// The additional damping to apply to tasks further from the dismissed task.
private const val ADDITIONAL_DISMISS_DAMPING_RATIO = 0.15f
+ private const val STACK_STYLE_REFLOW_DAMPING_RATIO = 1f
private const val RECENTS_SCALE_SPRING_MULTIPLIER = 1000f
private const val DEFAULT_DISMISS_THRESHOLD_FRACTION = 0.5f
private const val SPEED_UP_STIFFNESS = 100_000f
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 111c99b53a5..ba3d8c378a5 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -76,6 +76,8 @@
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NON_ZERO_ROTATION;
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_RECENTS;
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_SPLIT_SELECT_ACTIVE;
import static com.android.quickstep.views.RecentsViewUtils.DESK_EXPLODE_PROGRESS;
import static com.android.quickstep.views.TaskView.SPLIT_ALPHA;
@@ -94,6 +96,7 @@
import android.content.Intent;
import android.content.LocusId;
import android.content.pm.LauncherApps;
+import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.BlendMode;
@@ -153,6 +156,7 @@
import com.android.launcher3.Insettable;
import com.android.launcher3.MotionEventsUtils;
import com.android.launcher3.PagedView;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatedFloat;
@@ -192,6 +196,7 @@
import com.android.quickstep.BaseContainerInterface;
import com.android.quickstep.GestureState;
import com.android.quickstep.HighResLoadingState;
+import com.android.quickstep.LockedTaskManager;
import com.android.quickstep.OverviewCommandHelper;
import com.android.quickstep.OverviewComponentObserver;
import com.android.quickstep.RecentsAnimationController;
@@ -292,6 +297,7 @@ public abstract class RecentsView<
TaskVisualsChangeListener {
protected static final String TAG = "RecentsView";
+ private static final float STACK_STYLE_FULLSCREEN_PROGRESS = 0.35f;
public static final FloatProperty> CONTENT_ALPHA =
new FloatProperty<>("contentAlpha") {
@@ -437,6 +443,11 @@ public Float get(RecentsView recentsView) {
}
};
+ private boolean isPerTaskScrollScaleActive() {
+ TaskView running = getRunningTaskView();
+ return running != null && Math.abs(running.getScaleX() - 1f) > 1e-3f;
+ }
+
/** Same as normal SCALE_PROPERTY, but also updates page offsets that depend on this scale. */
public static final FloatProperty> RECENTS_SCALE_PROPERTY =
new FloatProperty<>("recentsScale") {
@@ -449,8 +460,10 @@ public void setValue(RecentsView view, float scale) {
view.runActionOnRemoteHandles(new Consumer() {
@Override
public void accept(RemoteTargetHandle remoteTargetHandle) {
- remoteTargetHandle.getTaskViewSimulator().recentsViewScale.value =
- scale;
+ if (!view.isPerTaskScrollScaleActive()) {
+ remoteTargetHandle.getTaskViewSimulator().recentsViewScale.value =
+ scale;
+ }
}
});
view.setTaskViewsResistanceTranslation(view.mTaskViewsSecondaryTranslation);
@@ -557,8 +570,10 @@ public Float get(RecentsView view) {
protected boolean mEnableDrawingLiveTile = false;
protected final Rect mTempRect = new Rect();
protected final RectF mTempRectF = new RectF();
+ private final Rect mTempTaskBoundsRect = new Rect();
private final PointF mTempPointF = new PointF();
private final Matrix mTempMatrix = new Matrix();
+ private final Matrix mAnimMatrix = new Matrix();
private final float[] mTempFloat = new float[1];
private final ArraySet mScrollListeners = new ArraySet<>();
@@ -620,6 +635,8 @@ public Float get(RecentsView view) {
private float mTaskThumbnailSplashAlpha = 0;
private boolean mBorderEnabled = false;
private boolean mShowAsGridLastOnLayout = false;
+ private boolean mEnableOverlap = false;
+ public boolean isOverlapEnabled() { return mEnableOverlap; }
protected final IntSet mTopRowIdSet = new IntSet();
private int mClearAllShortTotalWidthTranslation = 0;
@@ -852,6 +869,7 @@ public void onSplitSelectionExit(boolean launchedSplit) {
private OverviewActionsView mActionsView;
private ObjectAnimator mActionsViewAlphaAnimator;
private float mActionsViewAlphaAnimatorFinalValue;
+ private MemInfoView mMemInfoView;
@Nullable
private DesktopRecentsTransitionController mDesktopRecentsTransitionController;
@@ -1233,10 +1251,23 @@ public void updateThumbnail(Map thumbnailData) {
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
updateTaskStackListenerState();
+ if (visibility != VISIBLE && enableOverviewBackgroundWallpaperBlur()) {
+ mBlurUtils.setDrawLiveTileBelowRecents(false);
+ }
}
+ private final SharedPreferences.OnSharedPreferenceChangeListener mPrefListener =
+ (prefs, key) -> {
+ if (LauncherPrefs.RECENTS_STYLE.getSharedPrefKey().equals(key)) {
+ updateOverlapState();
+ resetTaskVisuals();
+ requestLayout();
+ }
+ };
+
public void init(OverviewActionsView actionsView, SplitSelectStateController splitController,
- @Nullable DesktopRecentsTransitionController desktopRecentsTransitionController) {
+ @Nullable DesktopRecentsTransitionController desktopRecentsTransitionController,
+ MemInfoView memInfoView) {
mIs3PLauncher = !OverviewComponentObserver.INSTANCE.get(mContext).isHomeAndOverviewSame();
mActionsView = actionsView;
mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, !hasTaskViews());
@@ -1244,6 +1275,7 @@ public void init(OverviewActionsView actionsView, SplitSelectStateController spl
mActionsView.updateFor3pLauncher(mIs3PLauncher);
mSplitSelectStateController = splitController;
mDesktopRecentsTransitionController = desktopRecentsTransitionController;
+ mMemInfoView = memInfoView;
}
public SplitSelectStateController getSplitSelectController() {
@@ -1264,9 +1296,17 @@ public boolean canLaunchFullscreenTask() {
return true;
}
+ private String mRecentsStyle = "default";
+
+ public void updateOverlapState() {
+ mRecentsStyle = LauncherPrefs.RECENTS_STYLE.get(getContext());
+ mEnableOverlap = !mRecentsStyle.equals("default");
+ }
+
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
+ updateOverlapState();
updateTaskStackListenerState();
mModel.getThumbnailCache().getHighResLoadingState().addCallback(this);
TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
@@ -1283,6 +1323,8 @@ protected void onAttachedToWindow() {
if (mDesktopVisibilityController != null) {
mDesktopVisibilityController.registerDesktopVisibilityListener(mUtils);
}
+ LauncherPrefs.getPrefs(getContext())
+ .registerOnSharedPreferenceChangeListener(mPrefListener);
}
@Override
@@ -1305,8 +1347,10 @@ protected void onDetachedFromWindow() {
if (mDesktopVisibilityController != null) {
mDesktopVisibilityController.unregisterDesktopVisibilityListener(mUtils);
}
- mTaskLaunchListener = null;
- mOnTaskLaunchCancelledRunnable = null;
+ if (mPrefListener != null) {
+ LauncherPrefs.getPrefs(getContext())
+ .unregisterOnSharedPreferenceChangeListener(mPrefListener);
+ }
reset();
}
@@ -1474,22 +1518,24 @@ public void launchSideTaskInLiveTileMode(int taskId, RemoteAnimationTarget[] app
ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
appAnimator.setDuration(RECENTS_LAUNCH_DURATION);
appAnimator.setInterpolator(ACCELERATE_DECELERATE);
- final Matrix matrix = new Matrix();
+ final SurfaceTransaction transaction = new SurfaceTransaction();
+ final int displayWidth = mContainer.getDeviceProfile().getDeviceProperties().getWidthPx();
+ final int displayHeight = mContainer.getDeviceProfile().getDeviceProperties().getHeightPx();
appAnimator.addUpdateListener(valueAnimator -> {
float percent = valueAnimator.getAnimatedFraction();
- SurfaceTransaction transaction = new SurfaceTransaction();
for (int i = apps.length - 1; i >= 0; --i) {
RemoteAnimationTarget app = apps[i];
- float dx = mContainer.getDeviceProfile().getDeviceProperties().getWidthPx() * (1 - percent) / 2
+ float dx = displayWidth * (1 - percent) / 2
+ app.screenSpaceBounds.left * percent;
- float dy = mContainer.getDeviceProfile().getDeviceProperties().getHeightPx() * (1 - percent) / 2
+ float dy = displayHeight * (1 - percent) / 2
+ app.screenSpaceBounds.top * percent;
- matrix.setScale(percent, percent);
- matrix.postTranslate(dx, dy);
+ mAnimMatrix.reset();
+ mAnimMatrix.setScale(percent, percent);
+ mAnimMatrix.postTranslate(dx, dy);
transaction.forSurface(app.leash)
.setAlpha(percent)
- .setMatrix(matrix);
+ .setMatrix(mAnimMatrix);
}
surfaceApplier.scheduleApply(transaction);
});
@@ -1902,6 +1948,7 @@ protected void onScrollOverPageChanged() {
}
private void vibrateForScroll() {
+ if (!LauncherPrefs.RECENTS_SCROLL_VIBRATE.get(getContext())) return;
long now = SystemClock.uptimeMillis();
if (now - mScrollLastHapticTimestamp > mScrollHapticMinGapMillis) {
mScrollLastHapticTimestamp = now;
@@ -2227,10 +2274,15 @@ public boolean isLoadingTasks() {
}
protected void removeAllTaskViews() {
+ LockedTaskManager lockedMgr = LockedTaskManager.getInstance(getContext());
// This handles an edge case where applyLoadPlan happens during a gesture when the only
// Task is one with excludeFromRecents, in which case we should not remove it.
CollectionsKt
- .filter(getTaskViews(), taskView -> !isGestureActive() || !taskView.isRunningTask())
+ .filter(getTaskViews(),
+ taskView -> {
+ if (taskView.isLocked()) return false;
+ return !isGestureActive() || !taskView.isRunningTask();
+ })
.forEach(this::removeView);
if (!hasTaskViews()) {
removeView(mAddDesktopButton);
@@ -2296,7 +2348,11 @@ public void resetTaskVisuals() {
simulator.taskPrimaryTranslation.value = 0;
simulator.taskSecondaryTranslation.value = 0;
simulator.fullScreenProgress.value = 0;
- simulator.recentsViewScale.value = 1;
+ TaskView running = getRunningTaskView();
+ simulator.recentsViewScale.value = (running != null && !showAsGrid())
+ ? getPagedOrientationHandler().getPrimaryValue(
+ running.getScaleX(), running.getScaleY())
+ : 1f;
});
// Reapply runningTask related attributes as they might have been reset by
// resetViewTransforms().
@@ -2308,6 +2364,9 @@ public void resetTaskVisuals() {
loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
setTaskModalness(0);
setColorTint(0);
+ if (mEnableOverlap) {
+ doScrollScale();
+ }
}
public void setFullscreenProgress(float fullscreenProgress) {
@@ -2318,8 +2377,13 @@ public void setFullscreenProgress(float fullscreenProgress) {
mClearAllButton.setFullscreenProgress(fullscreenProgress);
// Fade out the actions view quickly (0.1 range)
- mActionsView.getFullscreenAlpha().updateValue(
- mapToRange(fullscreenProgress, 0, 0.1f, 1f, 0f, LINEAR));
+ float alpha = mapToRange(fullscreenProgress, 0, 0.1f, 1f, 0f, LINEAR);
+ mActionsView.getFullscreenAlpha().updateValue(alpha);
+ mMemInfoView.setAlpha(MemInfoView.ALPHA_FS_PROGRESS, alpha);
+
+ if (mEnableOverlap) {
+ updatePageOffsets();
+ }
}
private void updateTaskStackListenerState() {
@@ -2513,17 +2577,20 @@ private float getTaskAlignmentTranslationY() {
}
protected Rect getTaskBounds(TaskView taskView) {
+ getTaskBounds(taskView, mTempTaskBoundsRect);
+ return mTempTaskBoundsRect;
+ }
+
+ protected void getTaskBounds(TaskView taskView, Rect outRect) {
int selectedPage = indexOfChild(taskView);
int primaryScroll = getPagedOrientationHandler().getPrimaryScroll(this);
int selectedPageScroll = getScrollForPage(selectedPage);
boolean isTopRow = mTopRowIdSet.contains(taskView.getTaskViewId());
- Rect outRect = new Rect(
- taskView.isGridTask() ? mLastComputedGridTaskSize : mLastComputedTaskSize);
+ outRect.set(taskView.isGridTask() ? mLastComputedGridTaskSize : mLastComputedTaskSize);
outRect.offset(
-(primaryScroll - (selectedPageScroll + getOffsetFromScrollPosition(selectedPage))),
(int) (showAsGrid() && enableGridOnlyOverview() && !isTopRow
? mTopBottomRowHeightDiff : 0));
- return outRect;
}
/** Gets the last computed task size */
@@ -3127,6 +3194,12 @@ public void onGestureAnimationEnd() {
}
mCurrentGestureEndTarget = null;
+
+ if (mEnableOverlap && mEnableDrawingLiveTile) {
+ switchToScreenshot(
+ () -> finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
+ null));
+ }
}
/**
@@ -3672,21 +3745,33 @@ public void clearIgnoreResetTask(int taskId) {
private void addDismissedTaskAnimations(TaskView taskView, long duration,
PendingAnimation anim) {
// Use setFloat instead of setViewAlpha as we want to keep the view visible even when it's
- // alpha is set to 0 so that it can be recycled in the view pool properly
+ // alpha is set to 0 so that it can be recycled in the view pool properly.
+ // Make the fade-out slightly smoother and a bit longer.
anim.setFloat(taskView, VIEW_ALPHA, 0,
- clampToProgress(isOnGridBottomRow(taskView) ? ACCELERATE : FINAL_FRAME, 0, 0.5f));
+ clampToProgress(
+ isOnGridBottomRow(taskView) ? ACCELERATE : FINAL_FRAME,
+ 0f,
+ 0.6f)); // was 0.5f
+
FloatProperty secondaryViewTranslate =
taskView.getSecondaryDismissTranslationProperty();
int secondaryTaskDimension = getPagedOrientationHandler().getSecondaryDimension(taskView);
int verticalFactor = getPagedOrientationHandler().getSecondaryTranslationDirectionFactor();
+ // Keep using dynamic resources (OEM can tune these), but we will reduce the travel distance
+ // and use a non-linear interpolator for a smoother feel.
ResourceProvider rp = DynamicResource.provider(mContainer);
SpringProperty sp = new SpringProperty(SpringProperty.FLAG_CAN_SPRING_ON_START)
.setDampingRatio(rp.getFloat(R.dimen.dismiss_task_trans_y_damping_ratio))
.setStiffness(rp.getFloat(R.dimen.dismiss_task_trans_y_stiffness));
- anim.add(ObjectAnimator.ofFloat(taskView, secondaryViewTranslate,
- verticalFactor * secondaryTaskDimension * 2).setDuration(duration), LINEAR, sp);
+ // Move the card ~1.5x instead of 2x its height off-screen to avoid a "teleport" feel.
+ float dismissDistance = verticalFactor * secondaryTaskDimension * 1.5f;
+
+ anim.add(ObjectAnimator.ofFloat(taskView, secondaryViewTranslate, dismissDistance)
+ .setDuration(duration),
+ FAST_OUT_SLOW_IN,
+ sp);
if (taskView.isRunningTask()) {
anim.addOnFrameCallback(() -> {
@@ -3728,17 +3813,19 @@ private void createInitialSplitSelectAnimation(PendingAnimation anim) {
splitAnimInitProps.getOriginalView(),
splitAnimInitProps.getOriginalBitmap(),
splitAnimInitProps.getIconDrawable(), startingTaskRect);
- firstFloatingTaskView.setAlpha(1);
- firstFloatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect,
- splitAnimInitProps.getFadeWithThumbnail(), splitAnimInitProps.isStagedTask());
- mSplitSelectStateController.setFirstFloatingTaskView(firstFloatingTaskView);
-
- // Allow user to click staged app to launch into fullscreen
- firstFloatingTaskView.setOnClickListener(view ->
- mSplitSelectStateController.getSplitAnimationController().
- playAnimPlaceholderToFullscreen(mContainer, view,
- Optional.of(() -> mSplitSelectStateController.resetState())));
- firstFloatingTaskView.setContentDescription(splitAnimInitProps.getContentDescription());
+ if (firstFloatingTaskView != null) {
+ firstFloatingTaskView.setAlpha(1);
+ firstFloatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect,
+ splitAnimInitProps.getFadeWithThumbnail(), splitAnimInitProps.isStagedTask());
+ mSplitSelectStateController.setFirstFloatingTaskView(firstFloatingTaskView);
+
+ // Allow user to click staged app to launch into fullscreen
+ firstFloatingTaskView.setOnClickListener(view ->
+ mSplitSelectStateController.getSplitAnimationController().
+ playAnimPlaceholderToFullscreen(mContainer, view,
+ Optional.of(() -> mSplitSelectStateController.resetState())));
+ firstFloatingTaskView.setContentDescription(splitAnimInitProps.getContentDescription());
+ }
// SplitInstructionsView: animate in
safeRemoveDragLayerView(mSplitSelectStateController.getSplitInstructionsView());
@@ -3967,10 +4054,12 @@ public void onAnimationEnd(Animator animation) {
lastTaskViewIndex);
int scrollDiff = newScroll[i] - oldScroll[i] + offset;
if (scrollDiff != 0) {
+ int gapFillTranslation = getGapFillTranslationForDismiss(
+ i, dismissedIndex, scrollDiffPerPage, scrollDiff, offset);
translateTaskWhenDismissed(
child,
Math.abs(i - dismissedIndex),
- scrollDiff,
+ gapFillTranslation,
anim,
splitTimings);
if (child instanceof TaskView taskView) {
@@ -4110,9 +4199,12 @@ public void accept(Boolean success) {
@SuppressWarnings("WrongCall")
private void onEnd(boolean success) {
- // Reset task translations as they may have updated via animations in
- // createTaskDismissAnimation
- resetTaskVisuals();
+ // Overlap recents styles keep taskOffset translations through dismiss; resetting
+ // before removeViewInLayout clears them and causes a visible snap. Reset after
+ // layout catches up (below). Stock layout resets immediately as in AOSP.
+ if (!mEnableOverlap || !success) {
+ resetTaskVisuals();
+ }
if (success) {
mAnyTaskHasBeenDismissed = true;
@@ -4212,6 +4304,10 @@ private void onEnd(boolean success) {
mTopRowIdSet.remove(dismissedTaskViewId);
if (taskCount == 1) {
+ if (mEnableOverlap) {
+ updateScrollSynchronously();
+ resetTaskVisuals();
+ }
if (!isSplitSelectionActive()) {
removeViewInLayout(mClearAllButton);
removeViewInLayout(mAddDesktopButton);
@@ -4235,6 +4331,10 @@ private void onEnd(boolean success) {
// Update scroll and snap to page.
updateScrollSynchronously();
+ if (mEnableOverlap) {
+ resetTaskVisuals();
+ }
+
if (showAsGrid) {
// Rebalance tasks in the grid
TaskView highestVisibleTaskView = getHighestVisibleTaskView();
@@ -4333,6 +4433,28 @@ private void onEnd(boolean success) {
* - Current page is rightmost page (leftmost for RTL)
* - Dragging an adjacent page on the left side (right side for RTL)
*/
+ /**
+ * Target for {@link TaskView#getPrimaryDismissTranslationProperty()} while a task is
+ * dismissed. Overlap styles (Kami/iOS/Oxygen) use page-slot spacing because page-scroll
+ * deltas from {@link #getPageScrolls} do not match the stack's visual overlap offsets.
+ */
+ private int getGapFillTranslationForDismiss(int childIndex, int dismissedIndex,
+ int scrollDiffPerPage, int scrollDiff, int dismissOffset) {
+ if (!mEnableOverlap) {
+ return scrollDiff;
+ }
+ int slotsFromDismissed = childIndex - dismissedIndex;
+ if (slotsFromDismissed <= 0) {
+ return 0;
+ }
+ int perSlot = mIsRtl ? scrollDiffPerPage : -scrollDiffPerPage;
+ int gapFillTranslation = perSlot * slotsFromDismissed;
+ if (slotsFromDismissed == 1) {
+ gapFillTranslation += dismissOffset - perSlot;
+ }
+ return gapFillTranslation;
+ }
+
private int getOffsetToDismissedTask(int scrollDiffPerPage, int dismissedIndex,
int lastTaskViewIndex) {
// If `mCurrentPage` is beyond `lastTaskViewIndex`, use the last TaskView instead to
@@ -4385,6 +4507,10 @@ private void translateTaskWhenDismissed(
+ splitTimings.getGridSlideDurationOffset(), 0f, 1f)
: 1f;
+ if (view instanceof TaskView taskView && isStackRecentsStyleActive()) {
+ scrollDiffPerPage = Math.round(getStackDismissReflowTarget(taskView, scrollDiffPerPage));
+ }
+
// Slide tiles in horizontally to fill dismissed area
pendingAnimation.setFloat(
view,
@@ -4434,6 +4560,10 @@ && getSplitSelectController().getAppPairsController().canSaveAppPair(
boolean isCurrentDesktop = taskView instanceof DesktopTaskView;
mActionsView.updateHiddenFlags(HIDDEN_DESKTOP, isCurrentDesktop);
+
+ if (taskView != null) {
+ mActionsView.updateLockIcon(taskView.isLocked());
+ }
}
/**
@@ -4502,8 +4632,14 @@ public PendingAnimation createAllTasksDismissAnimation(long duration) {
throw new IllegalStateException("Another pending animation is still running");
}
PendingAnimation anim = new PendingAnimation(duration);
+ LockedTaskManager lockedMgr = LockedTaskManager.getInstance(getContext());
for (TaskView taskView : getTaskViews()) {
+ String pkg = taskView.getFirstTask() != null
+ ? taskView.getFirstTask().key.getPackageName() : null;
+ if (pkg != null && lockedMgr.isPackageLocked(pkg)) {
+ continue;
+ }
addDismissedTaskAnimations(taskView, duration, anim);
}
@@ -4661,6 +4797,10 @@ private void dismissAllTasks(View view) {
mContainer.getStatsLogManager().logger().log(LAUNCHER_TASK_CLEAR_ALL);
}
+ public void dismissAllTasks() {
+ dismissAllTasks(null);
+ }
+
private void dismissCurrentTask() {
TaskView taskView = getNextPageTaskView();
if (taskView != null) {
@@ -4968,7 +5108,6 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto
.setScroll(getScrollOffset()));
setImportantForAccessibility(isModal() ? IMPORTANT_FOR_ACCESSIBILITY_NO
: IMPORTANT_FOR_ACCESSIBILITY_AUTO);
- recalculateTaskViewScreenEdgeIntersections();
}
private void updatePivots() {
@@ -5089,6 +5228,7 @@ private void updatePageOffsets() {
float totalTranslationX = (skipTranslationOffset ? 0f : translation) + modalTranslation
+ carouselHiddenOffsetSize;
if (child instanceof TaskView taskView) {
+ taskView.setBaseTaskOffsetTranslationX(totalTranslationX);
taskView.getPrimaryTaskOffsetTranslationProperty().set(taskView, totalTranslationX);
} else if (child instanceof ClearAllButton) {
getPagedOrientationHandler().getPrimaryViewTranslate().set(child,
@@ -5096,7 +5236,13 @@ private void updatePageOffsets() {
} else if (child instanceof AddDesktopButton addDesktopButton) {
addDesktopButton.setOffsetTranslationX(totalTranslationX);
}
- if (mEnableDrawingLiveTile && i == getRunningTaskIndex()) {
+ if (mEnableDrawingLiveTile && i == getRunningTaskIndex() && !mEnableOverlap) {
+ runActionOnRemoteHandles(
+ remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
+ .taskPrimaryTranslation.value = totalTranslationX);
+ redrawLiveTile();
+ }
+ if (mEnableDrawingLiveTile && i == getRunningTaskIndex() && !mEnableOverlap) {
runActionOnRemoteHandles(
remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
.taskPrimaryTranslation.value = totalTranslationX);
@@ -5111,6 +5257,102 @@ private void updatePageOffsets() {
}
}
updateCurveProperties();
+ if (mEnableOverlap) {
+ doScrollScale();
+ if (mEnableDrawingLiveTile && mRemoteTargetHandles != null) {
+ redrawLiveTile();
+ }
+ }
+ }
+
+ public boolean isOverlapStyleActive() {
+ return mEnableOverlap && mEnableDrawingLiveTile;
+ }
+
+ public boolean isStackRecentsStyleActive() {
+ return mRecentsStyle.equals("staple")
+ || mRecentsStyle.equals("ios")
+ || mRecentsStyle.equals("oxygen");
+ }
+
+ public float getStackDismissReflowTarget(TaskView taskView, float reflowTarget) {
+ if (!isStackRecentsStyleActive()) {
+ return reflowTarget;
+ }
+
+ float overlapFactor = getStackStyleOverlapFactor();
+ if (overlapFactor <= 0f) {
+ return reflowTarget;
+ }
+
+ boolean touchInLandscape = mOrientationState.getTouchRotation() != ROTATION_0
+ && mOrientationState.getTouchRotation() != ROTATION_180;
+ boolean layoutInLandscape = mOrientationState.getRecentsActivityRotation() != ROTATION_0
+ && mOrientationState.getRecentsActivityRotation() != ROTATION_180;
+ boolean canRotateRecents = mOrientationState.isRecentsActivityRotationAllowed();
+ boolean verticalScroll = !canRotateRecents && touchInLandscape && !layoutInLandscape;
+
+ int curScroll = verticalScroll ? getScrollY() : getScrollX();
+ int containerCenter = curScroll + (verticalScroll ? (getHeight() / 2) : (getWidth() / 2));
+ float childCenter = verticalScroll
+ ? (taskView.getTop() + (taskView.getHeight() / 2f))
+ : (taskView.getLeft() + (taskView.getWidth() / 2f));
+ float childSize = verticalScroll ? taskView.getHeight() : taskView.getWidth();
+ if (childSize == 0f) {
+ return reflowTarget;
+ }
+
+ float currentDist = childCenter - containerCenter;
+ float currentStackOffset = getStackStyleTranslation(currentDist, childSize, overlapFactor);
+ float targetStackOffset = getStackStyleTranslation(
+ currentDist + reflowTarget, childSize, overlapFactor);
+ return reflowTarget + targetStackOffset - currentStackOffset;
+ }
+
+ private float getStackStyleOverlapFactor() {
+ if (!mEnableOverlap || mFullscreenProgress >= STACK_STYLE_FULLSCREEN_PROGRESS) {
+ return 0f;
+ }
+ float stackProgress = Utilities.boundToRange(
+ 1f - (mFullscreenProgress / STACK_STYLE_FULLSCREEN_PROGRESS), 0f, 1f);
+ return FAST_OUT_SLOW_IN.getInterpolation(stackProgress);
+ }
+
+ private float getStackStyleTranslation(float dist, float childSize, float overlapFactor) {
+ float absDist = Math.abs(dist);
+ if (mRecentsStyle.equals("staple")) {
+ float stapleDistance = childSize * 0.60f;
+ if (absDist <= stapleDistance) {
+ return 0f;
+ }
+ float excess = absDist - stapleDistance;
+ float squish = Math.min(excess, (float) (Math.log10(1 + excess) * 12f));
+ float pullBack = absDist - (stapleDistance + squish);
+ float translation = (dist > 0) ? -pullBack : pullBack;
+ return translation * overlapFactor;
+ }
+
+ if (mRecentsStyle.equals("ios")) {
+ float stapleDistance = childSize * 0.1f;
+ if (dist >= 0 || absDist <= stapleDistance) {
+ return 0f;
+ }
+ float excess = absDist - stapleDistance;
+ float squish = Math.min(excess, (float) (Math.log10(1 + excess) * 45f));
+ float pullBack = absDist - (stapleDistance + squish);
+ return pullBack * overlapFactor;
+ }
+
+ if (mRecentsStyle.equals("oxygen")) {
+ if (dist >= 0 || absDist <= 0f) {
+ return 0f;
+ }
+ float squish = Math.min(absDist, (float) (Math.sqrt(absDist) * 7.5f));
+ float pullBack = absDist - squish;
+ return pullBack * overlapFactor;
+ }
+
+ return 0f;
}
/**
@@ -5235,7 +5477,7 @@ private float getVerticalOffsetSize(TaskView taskView, float offsetProgress) {
}
// First, get the position of the task relative to the top row.
- Rect taskPosition = getTaskBounds(taskView);
+ getTaskBounds(taskView, mTempTaskBoundsRect);
boolean isSelectedTaskTopRow = mTopRowIdSet.contains(getSelectedTaskView().getTaskViewId());
boolean isChildTopRow = mTopRowIdSet.contains(taskView.getTaskViewId());
@@ -5246,9 +5488,10 @@ private float getVerticalOffsetSize(TaskView taskView, float offsetProgress) {
// Next, calculate the distance to move the task off screen at scale = 1.
float distanceToOffscreen = 0;
if (isTopShift) {
- distanceToOffscreen = -taskPosition.bottom;
+ distanceToOffscreen = -mTempTaskBoundsRect.bottom;
} else if (isBottomShift) {
- distanceToOffscreen = mContainer.getDeviceProfile().getDeviceProperties().getHeightPx() - taskPosition.top;
+ distanceToOffscreen = mContainer.getDeviceProfile().getDeviceProperties().getHeightPx()
+ - mTempTaskBoundsRect.top;
}
return distanceToOffscreen * offsetProgress;
}
@@ -5550,10 +5793,12 @@ public boolean confirmSplitSelect(TaskView containerTaskView, Task task, Drawabl
return false;
}
- firstFloatingTaskView.getBoundsOnScreen(firstTaskStartingBounds);
- firstFloatingTaskView.addConfirmAnimation(pendingAnimation,
- new RectF(firstTaskStartingBounds), firstTaskEndingBounds,
- false /* fadeWithThumbnail */, true /* isStagedTask */);
+ if (firstFloatingTaskView != null) {
+ firstFloatingTaskView.getBoundsOnScreen(firstTaskStartingBounds);
+ firstFloatingTaskView.addConfirmAnimation(pendingAnimation,
+ new RectF(firstTaskStartingBounds), firstTaskEndingBounds,
+ false /* fadeWithThumbnail */, true /* isStagedTask */);
+ }
safeRemoveDragLayerView(mSecondFloatingTaskView);
@@ -5689,8 +5934,10 @@ protected void onRotateInSplitSelectionState() {
mTempRectF.set(mTempRect);
FloatingTaskView firstFloatingTaskView =
mSplitSelectStateController.getFirstFloatingTaskView();
- firstFloatingTaskView.updateOrientationHandler(getPagedOrientationHandler());
- firstFloatingTaskView.update(mTempRectF, /*progress=*/1f);
+ if (firstFloatingTaskView != null) {
+ firstFloatingTaskView.updateOrientationHandler(getPagedOrientationHandler());
+ firstFloatingTaskView.update(mTempRectF, /*progress=*/1f);
+ }
RecentsPagedOrientationHandler orientationHandler = getPagedOrientationHandler();
Pair>, FloatProperty>> taskViewsFloat =
@@ -6086,6 +6333,7 @@ public void setRecentsAnimationTargets(RecentsAnimationController recentsAnimati
final TransformParams params = remoteTargetHandle.getTransformParams();
if (mContainer instanceof RecentsWindowManager manager) {
params.setHomeBuilderProxy((builder, app, transformParams) -> {
+ mTmpMatrix.reset();
mTmpMatrix.setScale(
1f, 1f, app.localBounds.exactCenterX(), app.localBounds.exactCenterY());
builder.setMatrix(mTmpMatrix).setAlpha(1f).setShow();
@@ -6897,6 +7145,282 @@ public boolean scrollRight() {
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
dispatchScrollChanged();
+ updatePageOffsets();
+ }
+
+ public void doScrollScale() {
+ if (showAsGrid() || mContainer.getDeviceProfile().getDeviceProperties().isTablet()) return;
+ if (!isPageScrollsInitialized()) return;
+
+ int childCount = Math.min(mPageScrolls.length, getChildCount());
+ if (childCount == 0) return;
+
+ final boolean isStaple = mRecentsStyle.equals("staple");
+ final boolean isIOS = mRecentsStyle.equals("ios");
+ final boolean isOxygen = mRecentsStyle.equals("oxygen");
+
+ float mScrollScale = isOxygen ? 0.92f : 0.85f;
+
+ float overlapFactor = getStackStyleOverlapFactor();
+ final boolean applyOverlap = (overlapFactor > 0);
+
+ //nick@lmo-20231004 if rotating launcher is enabled, rotation works differently
+ // There are many edge cases (going from landscape app to recents, rotating in recents etc)
+ boolean touchInLandscape = mOrientationState.getTouchRotation() != ROTATION_0
+ && mOrientationState.getTouchRotation() != ROTATION_180;
+ boolean layoutInLandscape = mOrientationState.getRecentsActivityRotation() != ROTATION_0
+ && mOrientationState.getRecentsActivityRotation() != ROTATION_180;
+ boolean canRotateRecents = mOrientationState.isRecentsActivityRotationAllowed();
+
+ boolean verticalScroll = !canRotateRecents && touchInLandscape && !layoutInLandscape;
+
+ int curScroll = verticalScroll ? getScrollY() : getScrollX();
+ int containerCenter = curScroll + (verticalScroll ? (getHeight() / 2) : (getWidth() / 2));
+
+ int cachedChildSize = 0;
+
+ for (int i = 0; i < childCount; i++) {
+ View c = getChildAt(i);
+ if (!(c instanceof TaskView)) continue;
+ cachedChildSize = verticalScroll ? c.getHeight() : c.getWidth();
+ break;
+ }
+
+ if (cachedChildSize == 0) {
+ View firstChild = getChildAt(0);
+ if (firstChild == null) return;
+ cachedChildSize = verticalScroll ? firstChild.getHeight() : firstChild.getWidth();
+ }
+
+ int cachedScaleArea = (cachedChildSize > 0) ? (cachedChildSize + mPageSpacing) : 0;
+ final RecentsPagedOrientationHandler orientationHandler = getPagedOrientationHandler();
+
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+
+ int childSize = cachedChildSize > 0 ? cachedChildSize :
+ (verticalScroll ? child.getHeight() : child.getWidth());
+
+ if (childSize == 0) continue;
+
+ int scaleArea = (cachedScaleArea > 0) ? cachedScaleArea : (childSize + mPageSpacing);
+ int childPosition = mPageScrolls[i];
+ int scrollDelta = Math.abs(curScroll - childPosition);
+
+ float baseScale = mScrollScale;
+ if (scrollDelta <= scaleArea) {
+ baseScale = Utilities.mapToRange(scrollDelta, 0, scaleArea, 1f, mScrollScale, EMPHASIZED);
+ }
+
+ boolean styleApplied = false;
+
+ if (applyOverlap && child instanceof TaskView) {
+ TaskView tv = (TaskView) child;
+
+ float dismissTrans = tv.getPrimaryDismissTranslationProperty().get(tv);
+ float gridEndTrans = TaskView.GRID_END_TRANSLATION_X.get(tv);
+ float totalDismissTrans = dismissTrans + gridEndTrans;
+ float childCenter = verticalScroll
+ ? (child.getTop() + totalDismissTrans + (child.getHeight() / 2f))
+ : (child.getLeft() + totalDismissTrans + (child.getWidth() / 2f));
+ float dist = childCenter - containerCenter;
+ float absDist = Math.abs(dist);
+
+ if (isStaple) {
+ applyKamiStyle(tv, child, dist, absDist, childSize, baseScale, overlapFactor);
+ styleApplied = true;
+ } else if (isIOS) {
+ applyIOSStyle(tv, child, dist, absDist, childSize, baseScale, overlapFactor);
+ styleApplied = true;
+ } else if (isOxygen) {
+ applyOxygenStyle(tv, child, dist, absDist, childSize, baseScale, overlapFactor);
+ styleApplied = true;
+ }
+ }
+
+ final boolean isTaskView = child instanceof TaskView;
+ final TaskView tv = isTaskView ? (TaskView) child : null;
+
+ if (!styleApplied) {
+ if (child.getScaleX() != baseScale) {
+ child.setScaleX(baseScale);
+ child.setScaleY(baseScale);
+ }
+ if (tv != null) {
+ if (child.getTranslationZ() != 0f) child.setTranslationZ(0f);
+ if (child.getRotationY() != 0f) child.setRotationY(0f);
+ tv.setColorTint(0f, 0);
+ }
+ }
+ if (tv == null || mRemoteTargetHandles == null) continue;
+ final float primaryScale = orientationHandler.getPrimaryValue(
+ tv.getScaleX(), tv.getScaleY());
+ final float primaryTranslation = orientationHandler.getPrimaryValue(
+ tv.getTranslationX(), tv.getTranslationY());
+ final int[] taskIds = tv.getTaskIds();
+ for (RemoteTargetHandle rth : mRemoteTargetHandles) {
+ RemoteAnimationTargets targets = rth.getTransformParams().getTargetSet();
+ if (targets == null) continue;
+ for (int id : taskIds) {
+ if (targets.findTask(id) != null) {
+ TaskViewSimulator sim = rth.getTaskViewSimulator();
+ sim.recentsViewScale.value = primaryScale;
+ sim.taskPrimaryTranslation.value = primaryTranslation;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // Helper
+ private void applyKamiStyle(TaskView tv, View child, float dist, float absDist,
+ float childSize, float baseScale, float overlapFactor) {
+ float stapleDistance = childSize * 0.60f;
+
+ float currentTrans = tv.getBaseTaskOffsetTranslationX();
+
+ if (absDist > stapleDistance) {
+ float excess = absDist - stapleDistance;
+ float squish = (float) (Math.log10(1 + excess) * 12f);
+ squish = Math.min(excess, squish);
+ float targetVisualDist = stapleDistance + squish;
+ float pullBack = absDist - targetVisualDist;
+ float translation = (dist > 0) ? -pullBack : pullBack;
+
+ float overlapTrans = translation * overlapFactor;
+
+ tv.getPrimaryTaskOffsetTranslationProperty().set(tv, currentTrans + overlapTrans);
+
+ float zOffset = -(excess / childSize) * 20f * overlapFactor;
+ if (child.getTranslationZ() != zOffset) {
+ child.setTranslationZ(zOffset);
+ }
+
+ float stackScaleFactor = 1f - (excess / (childSize * 2.5f)) * 0.15f;
+ stackScaleFactor = Math.max(0.75f, stackScaleFactor);
+
+ float finalScale = baseScale * (1f - (1f - stackScaleFactor) * overlapFactor);
+ if (child.getScaleX() != finalScale) {
+ child.setScaleX(finalScale);
+ child.setScaleY(finalScale);
+ }
+ } else {
+
+ if (child.getTranslationZ() != 0f) child.setTranslationZ(0f);
+ if (child.getScaleX() != baseScale) {
+ child.setScaleX(baseScale);
+ child.setScaleY(baseScale);
+ }
+ }
+ }
+
+ private void applyIOSStyle(TaskView tv, View child, float dist, float absDist,
+ float childSize, float baseScale, float overlapFactor) {
+ float currentTrans = tv.getBaseTaskOffsetTranslationX();
+
+ if (dist < 0) {
+ float stapleDistance = childSize * 0.1f;
+ if (absDist > stapleDistance) {
+ float excess = absDist - stapleDistance;
+ float squish = (float) (Math.log10(1 + excess) * 45f);
+ squish = Math.min(excess, squish);
+ float targetVisualDist = stapleDistance + squish;
+ float pullBack = absDist - targetVisualDist;
+
+ float overlapTrans = pullBack * overlapFactor;
+
+ tv.getPrimaryTaskOffsetTranslationProperty().set(tv, currentTrans + overlapTrans);
+
+ float zOffset = -(excess / childSize) * 24f * overlapFactor;
+ if (child.getTranslationZ() != zOffset) {
+ child.setTranslationZ(zOffset);
+ }
+
+ float depthProgress = Math.min(1f, excess / (childSize * 1.5f));
+ tv.setColorTint(0.3f * depthProgress * overlapFactor, android.graphics.Color.BLACK);
+
+ float stackScaleFactor = 1f - (depthProgress * 0.15f);
+ float finalScale = baseScale * (1f - (1f - stackScaleFactor) * overlapFactor);
+
+ if (child.getScaleX() != finalScale) {
+ child.setScaleX(finalScale);
+ child.setScaleY(finalScale);
+ }
+ return;
+ }
+ }
+
+ if (child.getTranslationZ() != 0f) child.setTranslationZ(0f);
+ tv.setColorTint(0f, 0);
+ if (child.getScaleX() != baseScale) {
+ child.setScaleX(baseScale);
+ child.setScaleY(baseScale);
+ }
+ }
+
+ private void applyOxygenStyle(TaskView tv, View child, float dist, float absDist,
+ float childSize, float baseScale, float overlapFactor) {
+
+ float currentTrans = tv.getBaseTaskOffsetTranslationX();
+
+ if (dist < 0) {
+ float stapleDistance = 0f;
+ if (absDist > stapleDistance) {
+ float excess = absDist - stapleDistance;
+ float squish = (float) (Math.sqrt(excess) * 7.5f);
+ squish = Math.min(excess, squish);
+ float targetVisualDist = stapleDistance + squish;
+ float pullBack = absDist - targetVisualDist;
+
+ float overlapTrans = pullBack * overlapFactor;
+
+ tv.getPrimaryTaskOffsetTranslationProperty().set(tv, currentTrans + overlapTrans);
+
+ float zOffset = -(excess / childSize) * 12f * overlapFactor;
+ if (child.getTranslationZ() != zOffset) {
+ child.setTranslationZ(zOffset);
+ }
+
+ float stackScaleFactor = 1f - (excess / (childSize * 3f)) * 0.1f;
+ stackScaleFactor = Math.max(0.85f, stackScaleFactor);
+
+ float finalScale = baseScale * (1f - (1f - stackScaleFactor) * overlapFactor);
+ if (child.getScaleX() != finalScale) {
+ child.setScaleX(finalScale);
+ child.setScaleY(finalScale);
+ }
+ return;
+ }
+ }
+
+ if (child.getTranslationZ() != 0f) child.setTranslationZ(0f);
+ if (child.getScaleX() != baseScale) {
+ child.setScaleX(baseScale);
+ child.setScaleY(baseScale);
+ }
+ }
+
+ public float getScrollScale(RemoteTargetHandle rth) {
+ if (rth == null || showAsGrid() || mContainer.getDeviceProfile().getDeviceProperties().isTablet()) return 1f;
+ if (!isPageScrollsInitialized()) return 1f;
+ int childCount = Math.min(mPageScrolls.length, getChildCount());
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ if (!(child instanceof TaskView)) continue;
+ TaskView tv = (TaskView) child;
+ TransformParams params = rth.getTransformParams();
+ RemoteAnimationTargets targets = params.getTargetSet();
+ for (int id : tv.getTaskIds()) {
+ if (targets != null && targets.findTask(id) != null) {
+ return getPagedOrientationHandler().getPrimaryValue(
+ tv.getScaleX(),
+ tv.getScaleY()
+ );
+ }
+ }
+ }
+ return 1f;
}
/**
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java b/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java
index 8b2f2e58105..3b9a307a493 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java
@@ -35,6 +35,7 @@
import com.android.quickstep.BaseContainerInterface;
import com.android.quickstep.fallback.RecentsState;
import com.android.quickstep.util.SplitSelectStateController;
+import com.android.quickstep.views.MemInfoView;
/**
* Interface to be implemented by the parent view of RecentsView
@@ -200,4 +201,6 @@ default void applyOverwritesToLogItem(LauncherAtom.ItemInfo.Builder itemInfoBuil
* true otherwise
*/
void goToRecentsState(RecentsState recentsState, boolean animated);
+
+ MemInfoView getMemInfoView();
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskContainer.kt b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
index ca557697440..00367015bb7 100644
--- a/quickstep/src/com/android/quickstep/views/TaskContainer.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
@@ -118,6 +118,8 @@ class TaskContainer(
digitalWellBeingToast?.bind(task, taskView, snapshotView, stagePosition)
if (!enableRefactorTaskThumbnail()) {
thumbnailViewDeprecated.bind(task, overlay, taskView)
+ } else {
+ thumbnailView.bind(task)
}
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
index 86447489731..71a14b38dc0 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
@@ -162,7 +162,6 @@ class TaskMenuViewWithArrow : ArrowPopup where T : RecentsViewContainer, T
this.alignedOptionIndex = alignedOptionIndex
this.onClosedCallback = onClosedCallback
if (!populateMenu()) return false
- addScrim()
show()
return true
}
@@ -198,7 +197,7 @@ class TaskMenuViewWithArrow : ArrowPopup where T : RecentsViewContainer, T
// Add the spaces between items
val divider = ShapeDrawable(RectShape())
- divider.paint.color = resources.getColor(android.R.color.transparent)
+ divider.paint.color = resources.getColor(android.R.color.transparent, /* theme= */ null)
val dividerSpacing = resources.getDimension(R.dimen.task_menu_spacing).toInt()
optionLayout.showDividers = SHOW_DIVIDER_MIDDLE
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index effda1cea6a..4dbab454978 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -40,6 +40,7 @@ import android.view.ViewGroup
import android.view.ViewStub
import android.view.accessibility.AccessibilityNodeInfo
import android.widget.FrameLayout
+import android.widget.ImageView
import android.widget.Toast
import androidx.annotation.IntDef
import androidx.annotation.VisibleForTesting
@@ -80,6 +81,7 @@ import com.android.launcher3.util.ViewPool
import com.android.launcher3.util.coroutines.DispatcherProvider
import com.android.launcher3.util.rects.set
import com.android.quickstep.FullscreenDrawParams
+import com.android.quickstep.LockedTaskManager
import com.android.quickstep.RecentsModel
import com.android.quickstep.RemoteAnimationTargets
import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle
@@ -324,6 +326,11 @@ constructor(
var isEndQuickSwitchCuj = false
var isBeingDraggedForDismissal = false
var isBeingDismissed: Boolean = false
+ var isLocked: Boolean = false
+ private set
+
+ var lockedPackageName: String? = null
+ private set
private val systemGestureExclusionRectList = listOf(Rect()) // We only need 1 exclusion Rect
@@ -415,6 +422,7 @@ constructor(
field = value
applyTranslationX()
}
+ var baseTaskOffsetTranslationX = 0f
private var taskOffsetTranslationY = 0f
set(value) {
@@ -756,6 +764,8 @@ constructor(
override fun onRecycle() {
isBeingDraggedForDismissal = false
isBeingDismissed = false
+ isLocked = false
+ lockedPackageName = null
resetPersistentViewTransforms()
groupTask = null
@@ -1161,6 +1171,7 @@ constructor(
}
}
setOrientationState(orientedState)
+ updateLockState(firstTask?.key?.packageName)
}
private fun applyThumbnailSplashAlpha() {
@@ -1896,6 +1907,18 @@ constructor(
}
}
+ fun updateLockState(packageName: String?) {
+ lockedPackageName = packageName
+ val chip = findViewById(R.id.icon)
+ if (packageName == null) {
+ isLocked = false
+ chip?.setLockState(false)
+ return
+ }
+ isLocked = LockedTaskManager.getInstance(context).isPackageLocked(packageName)
+ chip?.setLockState(isLocked)
+ }
+
/**
* Returns a callback that updates the state of the filter and the recents overview
*
@@ -2121,6 +2144,7 @@ constructor(
// fullscreenTranslation and accumulatedTranslation should not be reset, as
// resetViewTransforms is called during QuickSwitch scrolling.
taskOffsetTranslationX = 0f
+ baseTaskOffsetTranslationX = 0f
taskResistanceTranslationX = 0f
splitSelectTranslationX = 0f
gridEndTranslationX = 0f
diff --git a/quickstep/src/com/android/quickstep/window/RecentsWindowManager.kt b/quickstep/src/com/android/quickstep/window/RecentsWindowManager.kt
index 31d85e62160..647c866c1fa 100644
--- a/quickstep/src/com/android/quickstep/window/RecentsWindowManager.kt
+++ b/quickstep/src/com/android/quickstep/window/RecentsWindowManager.kt
@@ -112,6 +112,7 @@ import com.android.quickstep.util.RecentsAtomicAnimationFactory
import com.android.quickstep.util.RecentsWindowProtoLogProxy
import com.android.quickstep.util.SplitSelectStateController
import com.android.quickstep.util.TISBindHelper
+import com.android.quickstep.views.MemInfoView
import com.android.quickstep.views.OverviewActionsView
import com.android.quickstep.views.RecentsView
import com.android.quickstep.views.RecentsViewContainer
@@ -177,6 +178,7 @@ constructor(
private var windowView: View? = null
private var actionsView: OverviewActionsView<*>? = null
private var scrimView: ScrimView? = null
+ private var memInfoView: MemInfoView? = null
private var callbacks: RecentsAnimationCallbacks? = null
@@ -308,6 +310,7 @@ constructor(
/* depthController= */ null,
desktopState,
),
+ memInfoView,
)
}
actionsView?.apply {
@@ -316,6 +319,7 @@ constructor(
}
scrimView = it.findViewById(R.id.scrim_view)
dragLayer = it.findViewById(R.id.drag_layer)
+ memInfoView = it.findViewById(R.id.meminfo)
it.systemUiVisibility =
(View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
@@ -830,6 +834,10 @@ constructor(
return R.style.OverviewBlurFallbackStyle
}
+ override fun getMemInfoView(): MemInfoView? {
+ return memInfoView
+ }
+
@AssistedFactory
interface Factory {
/** Creates a new instance of [RecentsWindowManager] for a given [context]. */
diff --git a/quickstep/src_protolog/com/android/launcher3/util/StateManagerProtoLogProxy.java b/quickstep/src_protolog/com/android/launcher3/util/StateManagerProtoLogProxy.java
index 914228ffdd7..19edd4fbffb 100644
--- a/quickstep/src_protolog/com/android/launcher3/util/StateManagerProtoLogProxy.java
+++ b/quickstep/src_protolog/com/android/launcher3/util/StateManagerProtoLogProxy.java
@@ -41,8 +41,16 @@ private static boolean willProtoLog() {
return IS_STATE_MANAGER_PROTOLOG_ENABLED && isProtoLogInitialized();
}
+ public static boolean isEnabled() {
+ return willProtoLog() || isLoggingToLogcat();
+ }
+
+ public static boolean isLoggingToLogcat() {
+ return IS_STATE_MANAGER_PROTOLOG_ENABLED && PROTO_LOG_GROUP.isLogToLogcat();
+ }
+
private static void logToLogcatIfNeeded(String message, Object... args) {
- if (!willProtoLog() || !PROTO_LOG_GROUP.isLogToLogcat()) {
+ if (!willProtoLog() && isLoggingToLogcat()) {
Log.d(PROTO_LOG_GROUP.getTag(), String.format(message, args));
}
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherProcessImageListenerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherProcessImageListenerTest.kt
new file mode 100644
index 00000000000..26849c58acc
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherProcessImageListenerTest.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep
+
+import android.util.Size
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import java.io.IOException
+import org.junit.Assert.assertThrows
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class LauncherProcessImageListenerTest {
+
+ private class FakeImageDecoderWrapper(
+ width: Int,
+ height: Int,
+ override val mimeType: String? = "image/png",
+ ) : LauncherProcessImageListener.ImageDecoderWrapper {
+
+ var targetSize: Size? = null
+
+ override val size: Size = Size(width, height)
+
+ override fun setTargetSize(width: Int, height: Int) {
+ targetSize = Size(width, height)
+ }
+ }
+
+ @Test
+ fun onHeaderDecoded_smallerThanMax_doesNotSetTargetSize() {
+ // 10x10 = 100 pixels * 8 bytes = 800 bytes. Limit 1000.
+ val decoder = FakeImageDecoderWrapper(10, 10)
+ val listener = LauncherProcessImageListener(1000, allowedMimeTypes)
+
+ listener.onHeaderDecoded(decoder)
+
+ assertThat(decoder.targetSize).isNull()
+ }
+
+ @Test
+ fun onHeaderDecoded_largerThanMax_setsTargetSize() {
+ // 10x10 = 100 pixels * 8 bytes = 800 bytes. Limit 200.
+ // Ratio 4. Sqrt(4) = 2. Scale 0.5. -> 5x5
+ val decoder = FakeImageDecoderWrapper(10, 10)
+ val listener = LauncherProcessImageListener(200, allowedMimeTypes)
+
+ listener.onHeaderDecoded(decoder)
+
+ assertThat(decoder.targetSize).isEqualTo(Size(5, 5))
+ }
+
+ @Test
+ fun onHeaderDecoded_unsupportedMimeType_throwsException() {
+ val decoder = FakeImageDecoderWrapper(10, 10, mimeType = "image/unsupported")
+ val listener = LauncherProcessImageListener(1000, allowedMimeTypes)
+
+ val exception =
+ assertThrows(IOException::class.java) { listener.onHeaderDecoded(decoder) }
+ assertThat(exception).hasMessageThat().contains("image/unsupported")
+ }
+
+ @Test
+ fun onHeaderDecoded_nullMimeType_throwsException() {
+ val decoder = FakeImageDecoderWrapper(10, 10, mimeType = null)
+ val listener = LauncherProcessImageListener(1000, allowedMimeTypes)
+
+ val exception =
+ assertThrows(IOException::class.java) { listener.onHeaderDecoded(decoder) }
+ assertThat(exception).hasMessageThat().contains("null")
+ }
+
+ @Test
+ fun onHeaderDecoded_supportedMimeTypes_doesNotThrow() {
+ val listener = LauncherProcessImageListener(1000, allowedMimeTypes)
+
+ for (mimeType in allowedMimeTypes) {
+ val decoder = FakeImageDecoderWrapper(10, 10, mimeType = mimeType)
+ listener.onHeaderDecoded(decoder)
+ }
+ }
+
+ @Test
+ fun onHeaderDecoded_supportedMimeTypesUpperCase_doesNotThrow() {
+ val listener = LauncherProcessImageListener(1000, allowedMimeTypes)
+ val decoder = FakeImageDecoderWrapper(10, 10, mimeType = "IMAGE/PNG")
+ listener.onHeaderDecoded(decoder)
+ }
+
+ @Test
+ fun getPreferredSize_smallerThanMax_returnsNull() {
+ // 100x100 = 10k pixels * 8 = 80k bytes. Limit 100k.
+ assertThat(LauncherProcessImageListener.getPreferredSize(100, 100, 100_000)).isNull()
+ }
+
+ @Test
+ fun getPreferredSize_larger_returnsCorrectScale() {
+ // 100x100 = 80k bytes. Limit 20k.
+ // Ratio = 4. Sqrt(4) = 2. Scale 0.5. -> 50x50.
+ assertThat(LauncherProcessImageListener.getPreferredSize(100, 100, 20_000))
+ .isEqualTo(Size(50, 50))
+ }
+
+ @Test
+ fun getPreferredSize_huge_returnsCorrectScale() {
+ // 100x100 = 80k bytes. Limit 5k.
+ // Ratio = 16. Sqrt(16) = 4. Scale 0.25 -> 25x25.
+ assertThat(LauncherProcessImageListener.getPreferredSize(100, 100, 5_000))
+ .isEqualTo(Size(25, 25))
+ }
+
+ @Test
+ fun getPreferredSize_fractionalScale_returnsCorrectScale() {
+ // 100x100 = 80k bytes. Limit 32k.
+ // Ratio = 2.5. Sqrt(2.5) ~= 1.58. Scale = 0.632.
+ // 100 * 0.632 = 63.
+ assertThat(LauncherProcessImageListener.getPreferredSize(100, 100, 32_000))
+ .isEqualTo(Size(63, 63))
+ }
+
+ private companion object {
+ private val allowedMimeTypes =
+ setOf(
+ "image/png",
+ "image/jpeg",
+ "image/webp",
+ "image/gif",
+ "image/bmp",
+ "image/x-ico",
+ "image/vnd.wap.wbmp",
+ "image/heif",
+ "image/heic",
+ "image/avif",
+ )
+ }
+}
diff --git a/res/color-night-v31/qsb_tertiary.xml b/res/color-night-v31/qsb_tertiary.xml
new file mode 100644
index 00000000000..ac0a65c79b2
--- /dev/null
+++ b/res/color-night-v31/qsb_tertiary.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/color-v31/qsb_primary.xml b/res/color-v31/qsb_primary.xml
new file mode 100644
index 00000000000..7a8ad7e5a13
--- /dev/null
+++ b/res/color-v31/qsb_primary.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/color-v31/qsb_quaternary.xml b/res/color-v31/qsb_quaternary.xml
new file mode 100644
index 00000000000..638855da0a4
--- /dev/null
+++ b/res/color-v31/qsb_quaternary.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/color-v31/qsb_secondary.xml b/res/color-v31/qsb_secondary.xml
new file mode 100644
index 00000000000..eec33d14a33
--- /dev/null
+++ b/res/color-v31/qsb_secondary.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/color-v31/qsb_tertiary.xml b/res/color-v31/qsb_tertiary.xml
new file mode 100644
index 00000000000..0f51b43fa85
--- /dev/null
+++ b/res/color-v31/qsb_tertiary.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/color-v33/overview_scrim.xml b/res/color-v33/overview_scrim.xml
index b9cda980b71..5dd9164df47 100644
--- a/res/color-v33/overview_scrim.xml
+++ b/res/color-v33/overview_scrim.xml
@@ -16,5 +16,6 @@
+ android:lStar="80"
+ android:alpha="0.4"/>
diff --git a/res/color-v33/overview_scrim_dark.xml b/res/color-v33/overview_scrim_dark.xml
index 84d22e65159..dbd593bc40e 100644
--- a/res/color-v33/overview_scrim_dark.xml
+++ b/res/color-v33/overview_scrim_dark.xml
@@ -16,5 +16,6 @@
+ android:lStar="35"
+ android:alpha="0.4"/>
diff --git a/res/drawable/avd_hidden_lock.xml b/res/drawable/avd_hidden_lock.xml
new file mode 100644
index 00000000000..d208150f957
--- /dev/null
+++ b/res/drawable/avd_hidden_lock.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/drawable/avd_hidden_unlock.xml b/res/drawable/avd_hidden_unlock.xml
new file mode 100644
index 00000000000..2199709fb71
--- /dev/null
+++ b/res/drawable/avd_hidden_unlock.xml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/drawable/avd_protected_lock.xml b/res/drawable/avd_protected_lock.xml
new file mode 100644
index 00000000000..464a5bf3cc4
--- /dev/null
+++ b/res/drawable/avd_protected_lock.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/drawable/avd_protected_unlock.xml b/res/drawable/avd_protected_unlock.xml
new file mode 100644
index 00000000000..374f943d084
--- /dev/null
+++ b/res/drawable/avd_protected_unlock.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/drawable/baseline_audiotrack_24.xml b/res/drawable/baseline_audiotrack_24.xml
new file mode 100644
index 00000000000..bd8e9daa03d
--- /dev/null
+++ b/res/drawable/baseline_audiotrack_24.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/res/drawable/bg_all_apps_searchbox.xml b/res/drawable/bg_all_apps_searchbox.xml
index b95e468c187..5c3ac0f5188 100644
--- a/res/drawable/bg_all_apps_searchbox.xml
+++ b/res/drawable/bg_all_apps_searchbox.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
-
+
-
\ No newline at end of file
+
+
diff --git a/res/color-v31/folder_preview_light.xml b/res/drawable/bg_quickspace.xml
similarity index 75%
rename from res/color-v31/folder_preview_light.xml
rename to res/drawable/bg_quickspace.xml
index 6727b2485ce..3e3a98db80b 100644
--- a/res/color-v31/folder_preview_light.xml
+++ b/res/drawable/bg_quickspace.xml
@@ -1,5 +1,5 @@
-
-
-
-
+
\ No newline at end of file
diff --git a/res/drawable/bkg_appseat.xml b/res/drawable/bkg_appseat.xml
new file mode 100644
index 00000000000..0d6a0ff4f30
--- /dev/null
+++ b/res/drawable/bkg_appseat.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/res/drawable/ic_app_info_icon_pack.xml b/res/drawable/ic_app_info_icon_pack.xml
new file mode 100644
index 00000000000..84962386531
--- /dev/null
+++ b/res/drawable/ic_app_info_icon_pack.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/res/drawable/ic_app_info_last_update.xml b/res/drawable/ic_app_info_last_update.xml
new file mode 100644
index 00000000000..a7a404d34e5
--- /dev/null
+++ b/res/drawable/ic_app_info_last_update.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/res/drawable/ic_app_info_source.xml b/res/drawable/ic_app_info_source.xml
new file mode 100644
index 00000000000..007f4a5e0f6
--- /dev/null
+++ b/res/drawable/ic_app_info_source.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/res/drawable/ic_app_info_version.xml b/res/drawable/ic_app_info_version.xml
new file mode 100644
index 00000000000..6588bdd58a8
--- /dev/null
+++ b/res/drawable/ic_app_info_version.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/res/drawable/ic_custom_seekbar_minus.xml b/res/drawable/ic_custom_seekbar_minus.xml
new file mode 100644
index 00000000000..da85accd33c
--- /dev/null
+++ b/res/drawable/ic_custom_seekbar_minus.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
diff --git a/res/drawable/ic_custom_seekbar_plus.xml b/res/drawable/ic_custom_seekbar_plus.xml
new file mode 100644
index 00000000000..66cd5c8ab00
--- /dev/null
+++ b/res/drawable/ic_custom_seekbar_plus.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
diff --git a/res/drawable/ic_custom_seekbar_reset.xml b/res/drawable/ic_custom_seekbar_reset.xml
new file mode 100644
index 00000000000..1516e4aa0f6
--- /dev/null
+++ b/res/drawable/ic_custom_seekbar_reset.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
diff --git a/res/drawable/ic_edit.xml b/res/drawable/ic_edit.xml
new file mode 100644
index 00000000000..11d55bd6ec5
--- /dev/null
+++ b/res/drawable/ic_edit.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
diff --git a/res/drawable/ic_gemini_color.xml b/res/drawable/ic_gemini_color.xml
new file mode 100644
index 00000000000..0eba9650741
--- /dev/null
+++ b/res/drawable/ic_gemini_color.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/res/drawable/ic_gemini_themed.xml b/res/drawable/ic_gemini_themed.xml
new file mode 100644
index 00000000000..be0219b132a
--- /dev/null
+++ b/res/drawable/ic_gemini_themed.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/res/drawable/ic_help.xml b/res/drawable/ic_help.xml
new file mode 100644
index 00000000000..e9a6a70ec53
--- /dev/null
+++ b/res/drawable/ic_help.xml
@@ -0,0 +1,24 @@
+
+
+
+
diff --git a/res/drawable/ic_hidden_locked.xml b/res/drawable/ic_hidden_locked.xml
new file mode 100644
index 00000000000..4cf3a0b67d7
--- /dev/null
+++ b/res/drawable/ic_hidden_locked.xml
@@ -0,0 +1,24 @@
+
+
+
+
diff --git a/res/drawable/ic_hidden_unlocked.xml b/res/drawable/ic_hidden_unlocked.xml
new file mode 100644
index 00000000000..c98d1813e0b
--- /dev/null
+++ b/res/drawable/ic_hidden_unlocked.xml
@@ -0,0 +1,26 @@
+
+
+
+
diff --git a/res/drawable/ic_homepage_appdrawer.xml b/res/drawable/ic_homepage_appdrawer.xml
deleted file mode 100644
index 4a8ceb43cea..00000000000
--- a/res/drawable/ic_homepage_appdrawer.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
- -
-
-
-
-
-
diff --git a/res/drawable/ic_homepage_homescreen.xml b/res/drawable/ic_homepage_homescreen.xml
deleted file mode 100644
index 7ef66807a3d..00000000000
--- a/res/drawable/ic_homepage_homescreen.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
- -
-
-
-
-
-
diff --git a/res/drawable/ic_homepage_icons.xml b/res/drawable/ic_homepage_icons.xml
deleted file mode 100644
index 1afc2b8d430..00000000000
--- a/res/drawable/ic_homepage_icons.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
- -
-
-
-
-
-
diff --git a/res/drawable/ic_homepage_misc.xml b/res/drawable/ic_homepage_misc.xml
deleted file mode 100644
index 7078dc13a32..00000000000
--- a/res/drawable/ic_homepage_misc.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
- -
-
-
-
-
-
diff --git a/res/drawable/ic_homepage_recents.xml b/res/drawable/ic_homepage_recents.xml
deleted file mode 100644
index 7eb9acc18f5..00000000000
--- a/res/drawable/ic_homepage_recents.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
- -
-
-
-
-
-
diff --git a/res/drawable/ic_homer.xml b/res/drawable/ic_homer.xml
new file mode 100644
index 00000000000..2bb7412ee32
--- /dev/null
+++ b/res/drawable/ic_homer.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/res/drawable/ic_icon_pack_plus.xml b/res/drawable/ic_icon_pack_plus.xml
new file mode 100644
index 00000000000..2686fe47605
--- /dev/null
+++ b/res/drawable/ic_icon_pack_plus.xml
@@ -0,0 +1,26 @@
+
+
+
+
diff --git a/res/drawable/ic_info_no_shadow.xml b/res/drawable/ic_info_no_shadow.xml
index 31cf51200ac..7d473602593 100644
--- a/res/drawable/ic_info_no_shadow.xml
+++ b/res/drawable/ic_info_no_shadow.xml
@@ -18,7 +18,7 @@
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
- android:tint="@color/materialColorOnSurface">
+ android:tint="@color/launcher_foreground_primary">
-
-
-
-
+
diff --git a/res/drawable/ic_launcher_foreground.xml b/res/drawable/ic_launcher_foreground.xml
index 2d10ca54e3a..5c3ac15eb1b 100644
--- a/res/drawable/ic_launcher_foreground.xml
+++ b/res/drawable/ic_launcher_foreground.xml
@@ -4,31 +4,11 @@
SPDX-License-Identifier: Apache-2.0
-->
-
-
+ android:width="54dp"
+ android:height="54dp"
+ android:viewportWidth="124"
+ android:viewportHeight="124">
-
-
-
-
-
-
-
-
-
+ android:fillColor="#A308A8"
+ android:pathData="M88.66,3.37h21.58L97.13,62c-2.51,11.15 -4,17.97 -5.18,20.52c3.37,15.51 8.3,23.25 14.73,23.25c7.6,0 11.54,-5.27 11.8,-15.81h4.41c-0.26,9.81 -2.03,17.76 -5.36,23.85c-3.33,6.05 -7.69,9.07 -13.09,9.07c-4.45,0 -7.78,-1.81 -9.98,-5.44c-2.16,-3.63 -4.54,-11.06 -7.04,-22.25c-8.77,18.45 -23.12,27.7 -42.99,27.7c-13.61,0 -24.37,-5.31 -32.41,-15.9C4.02,96.39 0,81.23 0,61.48C0,42.08 4.36,27 13.13,16.2C21.91,5.4 32.45,0 44.76,0c8.68,0 15.81,3.07 21.39,9.25c5.57,6.14 10.5,16.2 14.78,30.11L88.66,3.37L88.66,3.37zM77.51,53.01c-3.93,-13.52 -8.38,-24.11 -13.26,-31.76c-4.88,-7.6 -10.72,-11.41 -17.54,-11.41c-15.42,0 -23.12,18.06 -23.12,54.14c0,34.26 7.39,51.37 22.12,51.37c12.79,0 22.25,-15.12 28.39,-45.37L77.51,53.01L77.51,53.01z"/>
diff --git a/res/drawable/ic_lens_color.xml b/res/drawable/ic_lens_color.xml
new file mode 100644
index 00000000000..219d7326b5c
--- /dev/null
+++ b/res/drawable/ic_lens_color.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
diff --git a/res/drawable/ic_lens_themed.xml b/res/drawable/ic_lens_themed.xml
new file mode 100644
index 00000000000..ca3e3bce87f
--- /dev/null
+++ b/res/drawable/ic_lens_themed.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
diff --git a/res/drawable/ic_mic_color.xml b/res/drawable/ic_mic_color.xml
new file mode 100644
index 00000000000..7c002746841
--- /dev/null
+++ b/res/drawable/ic_mic_color.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
diff --git a/res/drawable/ic_mic_themed.xml b/res/drawable/ic_mic_themed.xml
new file mode 100644
index 00000000000..3646e5eaeff
--- /dev/null
+++ b/res/drawable/ic_mic_themed.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
diff --git a/res/drawable/ic_music_color.xml b/res/drawable/ic_music_color.xml
new file mode 100644
index 00000000000..9138bd8aad4
--- /dev/null
+++ b/res/drawable/ic_music_color.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
diff --git a/res/drawable/ic_music_note_24dp.xml b/res/drawable/ic_music_note_24dp.xml
new file mode 100644
index 00000000000..323633f8327
--- /dev/null
+++ b/res/drawable/ic_music_note_24dp.xml
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/res/drawable/ic_music_themed.xml b/res/drawable/ic_music_themed.xml
new file mode 100644
index 00000000000..15c1ce781cd
--- /dev/null
+++ b/res/drawable/ic_music_themed.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
diff --git a/res/drawable/ic_protected_locked.xml b/res/drawable/ic_protected_locked.xml
new file mode 100644
index 00000000000..8c39e3e79b3
--- /dev/null
+++ b/res/drawable/ic_protected_locked.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
diff --git a/res/drawable/ic_protected_unlocked.xml b/res/drawable/ic_protected_unlocked.xml
new file mode 100644
index 00000000000..6789fcb2bfb
--- /dev/null
+++ b/res/drawable/ic_protected_unlocked.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
diff --git a/res/drawable/ic_quickspace_alpha.xml b/res/drawable/ic_quickspace_alpha.xml
new file mode 100644
index 00000000000..d3d2d30054e
--- /dev/null
+++ b/res/drawable/ic_quickspace_alpha.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
diff --git a/res/drawable/ic_quickspace_crdroid.xml b/res/drawable/ic_quickspace_crdroid.xml
new file mode 100644
index 00000000000..f364370b19e
--- /dev/null
+++ b/res/drawable/ic_quickspace_crdroid.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/res/drawable/ic_quickspace_evening.xml b/res/drawable/ic_quickspace_evening.xml
new file mode 100644
index 00000000000..2192cf4d329
--- /dev/null
+++ b/res/drawable/ic_quickspace_evening.xml
@@ -0,0 +1,8 @@
+
+
+
+
\ No newline at end of file
diff --git a/res/drawable/ic_quickspace_midnight.xml b/res/drawable/ic_quickspace_midnight.xml
new file mode 100644
index 00000000000..5b83510320e
--- /dev/null
+++ b/res/drawable/ic_quickspace_midnight.xml
@@ -0,0 +1,7 @@
+
+
+
diff --git a/res/drawable/ic_quickspace_morning.xml b/res/drawable/ic_quickspace_morning.xml
new file mode 100644
index 00000000000..b74fc674873
--- /dev/null
+++ b/res/drawable/ic_quickspace_morning.xml
@@ -0,0 +1,8 @@
+
+
+
+
\ No newline at end of file
diff --git a/res/drawable/ic_quickspace_pacman.xml b/res/drawable/ic_quickspace_pacman.xml
new file mode 100644
index 00000000000..fd8f25d75bc
--- /dev/null
+++ b/res/drawable/ic_quickspace_pacman.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/res/drawable/ic_settings_appdrawer_expressive.xml b/res/drawable/ic_settings_appdrawer_expressive.xml
deleted file mode 100644
index afedd3d82c9..00000000000
--- a/res/drawable/ic_settings_appdrawer_expressive.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
diff --git a/res/drawable/ic_settings_homescreen_expressive.xml b/res/drawable/ic_settings_homescreen_expressive.xml
deleted file mode 100644
index d744813ae3c..00000000000
--- a/res/drawable/ic_settings_homescreen_expressive.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
diff --git a/res/drawable/ic_settings_icons_expressive.xml b/res/drawable/ic_settings_icons_expressive.xml
deleted file mode 100644
index 0ae5a0a541f..00000000000
--- a/res/drawable/ic_settings_icons_expressive.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
diff --git a/res/drawable/ic_settings_misc_expressive.xml b/res/drawable/ic_settings_misc_expressive.xml
deleted file mode 100644
index df0adec7470..00000000000
--- a/res/drawable/ic_settings_misc_expressive.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
diff --git a/res/drawable/ic_settings_recents_expressive.xml b/res/drawable/ic_settings_recents_expressive.xml
deleted file mode 100644
index d826e97997b..00000000000
--- a/res/drawable/ic_settings_recents_expressive.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
diff --git a/res/drawable/ic_super_g_color.xml b/res/drawable/ic_super_g_color.xml
new file mode 100644
index 00000000000..8afcb4b067d
--- /dev/null
+++ b/res/drawable/ic_super_g_color.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
diff --git a/res/drawable/ic_super_g_themed.xml b/res/drawable/ic_super_g_themed.xml
new file mode 100644
index 00000000000..13abf8e604f
--- /dev/null
+++ b/res/drawable/ic_super_g_themed.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
diff --git a/res/drawable/ic_tick.xml b/res/drawable/ic_tick.xml
new file mode 100644
index 00000000000..6a50cf0d111
--- /dev/null
+++ b/res/drawable/ic_tick.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/res/drawable/nt_recent_app_locked_icon.xml b/res/drawable/nt_recent_app_locked_icon.xml
new file mode 100644
index 00000000000..573521c52da
--- /dev/null
+++ b/res/drawable/nt_recent_app_locked_icon.xml
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/res/drawable/nt_recent_camera_locked_icon.xml b/res/drawable/nt_recent_camera_locked_icon.xml
new file mode 100644
index 00000000000..0946e24c1eb
--- /dev/null
+++ b/res/drawable/nt_recent_camera_locked_icon.xml
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/res/drawable/picture_in_picture_mobile_24px.xml b/res/drawable/picture_in_picture_mobile_24px.xml
new file mode 100644
index 00000000000..5334d273ae3
--- /dev/null
+++ b/res/drawable/picture_in_picture_mobile_24px.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/res/drawable/pill_ripple.xml b/res/drawable/pill_ripple.xml
new file mode 100644
index 00000000000..50afe2fbaee
--- /dev/null
+++ b/res/drawable/pill_ripple.xml
@@ -0,0 +1,9 @@
+
+
+ -
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/activity_hidden_apps.xml b/res/layout/activity_hidden_apps.xml
new file mode 100644
index 00000000000..efbe04549e2
--- /dev/null
+++ b/res/layout/activity_hidden_apps.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/all_apps_content.xml b/res/layout/all_apps_content.xml
index 925f4d963b4..8081c0e4dce 100644
--- a/res/layout/all_apps_content.xml
+++ b/res/layout/all_apps_content.xml
@@ -45,5 +45,12 @@
+
-
\ No newline at end of file
+
diff --git a/res/layout/all_apps_icon_horizontal_list.xml b/res/layout/all_apps_icon_horizontal_list.xml
new file mode 100644
index 00000000000..b18bedba71d
--- /dev/null
+++ b/res/layout/all_apps_icon_horizontal_list.xml
@@ -0,0 +1,29 @@
+
+
+
diff --git a/res/layout/all_apps_oneui_paged_layout.xml b/res/layout/all_apps_oneui_paged_layout.xml
new file mode 100644
index 00000000000..1759548ca4c
--- /dev/null
+++ b/res/layout/all_apps_oneui_paged_layout.xml
@@ -0,0 +1,23 @@
+
+
+
diff --git a/res/layout/app_info_bottom_sheet.xml b/res/layout/app_info_bottom_sheet.xml
new file mode 100644
index 00000000000..c7dba41addf
--- /dev/null
+++ b/res/layout/app_info_bottom_sheet.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/app_info_bottom_sheet_content.xml b/res/layout/app_info_bottom_sheet_content.xml
new file mode 100644
index 00000000000..9a472c287b1
--- /dev/null
+++ b/res/layout/app_info_bottom_sheet_content.xml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/color_picker_dialog.xml b/res/layout/color_picker_dialog.xml
new file mode 100644
index 00000000000..3ffbef5eacb
--- /dev/null
+++ b/res/layout/color_picker_dialog.xml
@@ -0,0 +1,137 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/color_preference_widget.xml b/res/layout/color_preference_widget.xml
new file mode 100644
index 00000000000..299c1d7f701
--- /dev/null
+++ b/res/layout/color_preference_widget.xml
@@ -0,0 +1,7 @@
+
+
diff --git a/res/layout/dialog_trust_welcome.xml b/res/layout/dialog_trust_welcome.xml
new file mode 100644
index 00000000000..e953d56e0c3
--- /dev/null
+++ b/res/layout/dialog_trust_welcome.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/color-v31/folder_background_light.xml b/res/layout/empty_view.xml
similarity index 71%
rename from res/color-v31/folder_background_light.xml
rename to res/layout/empty_view.xml
index 4dd088b8cd9..dbfe288d705 100644
--- a/res/color-v31/folder_background_light.xml
+++ b/res/layout/empty_view.xml
@@ -1,20 +1,17 @@
-
-
-
-
+
\ No newline at end of file
diff --git a/res/layout/item_hidden_app.xml b/res/layout/item_hidden_app.xml
new file mode 100644
index 00000000000..5c4b3d1268e
--- /dev/null
+++ b/res/layout/item_hidden_app.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/launcher.xml b/res/layout/launcher.xml
index 9303363e128..e5608ff59a7 100644
--- a/res/layout/launcher.xml
+++ b/res/layout/launcher.xml
@@ -58,7 +58,8 @@
android:layout_height="@dimen/workspace_page_indicator_height"
android:layout_gravity="bottom|center_horizontal"
android:gravity="center"
- android:orientation="horizontal">
+ android:orientation="horizontal"
+ launcher:layout_ignoreInsets="true">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/preference_widget_icons_preview.xml b/res/layout/preference_widget_icons_preview.xml
new file mode 100644
index 00000000000..7ac88caf10a
--- /dev/null
+++ b/res/layout/preference_widget_icons_preview.xml
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/quickspace_alternate_double.xml b/res/layout/quickspace_alternate_double.xml
new file mode 100644
index 00000000000..5174de93971
--- /dev/null
+++ b/res/layout/quickspace_alternate_double.xml
@@ -0,0 +1,210 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/quickspace_doubleline.xml b/res/layout/quickspace_doubleline.xml
new file mode 100644
index 00000000000..a15e929e354
--- /dev/null
+++ b/res/layout/quickspace_doubleline.xml
@@ -0,0 +1,125 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/reserved_container_workspace.xml b/res/layout/reserved_container_workspace.xml
new file mode 100644
index 00000000000..741f104e5cd
--- /dev/null
+++ b/res/layout/reserved_container_workspace.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
diff --git a/res/layout/search_container_all_apps.xml b/res/layout/search_container_all_apps.xml
index 067e209fc2f..2feb10aebcc 100644
--- a/res/layout/search_container_all_apps.xml
+++ b/res/layout/search_container_all_apps.xml
@@ -17,7 +17,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@id/search_container_all_apps"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
+ android:layout_height="@dimen/all_apps_search_bar_field_height"
android:layout_centerHorizontal="true"
android:layout_gravity="top|center_horizontal"
android:background="@drawable/bg_all_apps_searchbox"
@@ -29,10 +29,12 @@
android:inputType="text|textNoSuggestions|textCapWords"
android:maxLines="1"
android:paddingVertical="12dp"
- android:paddingStart="12dp"
+ android:paddingStart="20dp"
android:paddingEnd="12dp"
- android:drawablePadding="8dp"
- android:drawableStart="@drawable/ic_allapps_search"
+ android:layout_marginStart="12dp"
+ android:layout_marginEnd="12dp"
+ android:drawablePadding="@dimen/qsb_icon_padding"
+ android:drawableStart="@drawable/ic_super_g_themed"
android:saveEnabled="false"
android:scrollHorizontally="true"
android:singleLine="true"
diff --git a/res/layout/search_container_hotseat.xml b/res/layout/search_container_hotseat.xml
index 8f12ca07b00..0b9ead243a3 100644
--- a/res/layout/search_container_hotseat.xml
+++ b/res/layout/search_container_hotseat.xml
@@ -1,20 +1,87 @@
-
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/search_container_hotseat_pixel.xml b/res/layout/search_container_hotseat_pixel.xml
new file mode 100644
index 00000000000..26a0def2472
--- /dev/null
+++ b/res/layout/search_container_hotseat_pixel.xml
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/wallpaper_options_popup.xml b/res/layout/wallpaper_options_popup.xml
new file mode 100644
index 00000000000..4e482971f7b
--- /dev/null
+++ b/res/layout/wallpaper_options_popup.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
diff --git a/res/layout/wallpaper_options_popup_item.xml b/res/layout/wallpaper_options_popup_item.xml
new file mode 100644
index 00000000000..3d3800b0784
--- /dev/null
+++ b/res/layout/wallpaper_options_popup_item.xml
@@ -0,0 +1,9 @@
+
+
+
+
diff --git a/res/color-night-v31/folder_preview_dark.xml b/res/menu/menu_icon_pack.xml
similarity index 65%
rename from res/color-night-v31/folder_preview_dark.xml
rename to res/menu/menu_icon_pack.xml
index 6dd20a10935..30e04fae29c 100644
--- a/res/color-night-v31/folder_preview_dark.xml
+++ b/res/menu/menu_icon_pack.xml
@@ -1,5 +1,5 @@
-
-
-
+
+ android:id="@+id/menu_icon_pack"
+ android:icon="@drawable/ic_icon_pack_plus"
+ android:showAsAction="always"
+ android:title="@string/icon_pack_add" />
+
diff --git a/res/color-night-v31/folder_background_dark.xml b/res/menu/menu_trust_apps.xml
similarity index 66%
rename from res/color-night-v31/folder_background_dark.xml
rename to res/menu/menu_trust_apps.xml
index f41521098b3..43c21b142f5 100644
--- a/res/color-night-v31/folder_background_dark.xml
+++ b/res/menu/menu_trust_apps.xml
@@ -1,5 +1,5 @@
-
-
-
+
+ android:id="@+id/menu_trust_help"
+ android:icon="@drawable/ic_help"
+ android:showAsAction="always"
+ android:title="@string/trust_apps_help" />
+
diff --git a/res/mipmap-anydpi/ic_launcher.xml b/res/mipmap-anydpi/ic_launcher.xml
index 71034040bf4..3ff874648d0 100644
--- a/res/mipmap-anydpi/ic_launcher.xml
+++ b/res/mipmap-anydpi/ic_launcher.xml
@@ -1,6 +1,6 @@
-
+
diff --git a/res/mipmap-hdpi/ic_launcher.webp b/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 00000000000..6fed86e54d7
Binary files /dev/null and b/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/res/mipmap-hdpi/ic_launcher_background.png b/res/mipmap-hdpi/ic_launcher_background.png
deleted file mode 100644
index 051fde57d67..00000000000
Binary files a/res/mipmap-hdpi/ic_launcher_background.png and /dev/null differ
diff --git a/res/mipmap-hdpi/ic_launcher_foreground.png b/res/mipmap-hdpi/ic_launcher_foreground.png
deleted file mode 100644
index 6902d9b273b..00000000000
Binary files a/res/mipmap-hdpi/ic_launcher_foreground.png and /dev/null differ
diff --git a/res/mipmap-hdpi/ic_launcher_foreground.webp b/res/mipmap-hdpi/ic_launcher_foreground.webp
new file mode 100644
index 00000000000..71dfcf6a30a
Binary files /dev/null and b/res/mipmap-hdpi/ic_launcher_foreground.webp differ
diff --git a/res/mipmap-hdpi/ic_launcher_round.webp b/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 00000000000..6fed86e54d7
Binary files /dev/null and b/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/res/mipmap-mdpi/ic_launcher.webp b/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 00000000000..fdbaa774ef1
Binary files /dev/null and b/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/res/mipmap-mdpi/ic_launcher_background.png b/res/mipmap-mdpi/ic_launcher_background.png
deleted file mode 100644
index 928df10aa5d..00000000000
Binary files a/res/mipmap-mdpi/ic_launcher_background.png and /dev/null differ
diff --git a/res/mipmap-mdpi/ic_launcher_foreground.png b/res/mipmap-mdpi/ic_launcher_foreground.png
deleted file mode 100644
index 9e89f45de1f..00000000000
Binary files a/res/mipmap-mdpi/ic_launcher_foreground.png and /dev/null differ
diff --git a/res/mipmap-mdpi/ic_launcher_foreground.webp b/res/mipmap-mdpi/ic_launcher_foreground.webp
new file mode 100644
index 00000000000..2ee3cc9c5c2
Binary files /dev/null and b/res/mipmap-mdpi/ic_launcher_foreground.webp differ
diff --git a/res/mipmap-mdpi/ic_launcher_round.webp b/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 00000000000..fdbaa774ef1
Binary files /dev/null and b/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/res/mipmap-xhdpi/ic_launcher.webp b/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 00000000000..5deee71e145
Binary files /dev/null and b/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/res/mipmap-xhdpi/ic_launcher_background.png b/res/mipmap-xhdpi/ic_launcher_background.png
deleted file mode 100644
index 7ab72a327b8..00000000000
Binary files a/res/mipmap-xhdpi/ic_launcher_background.png and /dev/null differ
diff --git a/res/mipmap-xhdpi/ic_launcher_foreground.png b/res/mipmap-xhdpi/ic_launcher_foreground.png
deleted file mode 100644
index dd2cdc6356e..00000000000
Binary files a/res/mipmap-xhdpi/ic_launcher_foreground.png and /dev/null differ
diff --git a/res/mipmap-xhdpi/ic_launcher_foreground.webp b/res/mipmap-xhdpi/ic_launcher_foreground.webp
new file mode 100644
index 00000000000..e3555b2b51e
Binary files /dev/null and b/res/mipmap-xhdpi/ic_launcher_foreground.webp differ
diff --git a/res/mipmap-xhdpi/ic_launcher_round.webp b/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 00000000000..5deee71e145
Binary files /dev/null and b/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/res/mipmap-xxhdpi/ic_launcher.webp b/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 00000000000..f3652ba42df
Binary files /dev/null and b/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/res/mipmap-xxhdpi/ic_launcher_background.png b/res/mipmap-xxhdpi/ic_launcher_background.png
deleted file mode 100644
index 1ce350b5a22..00000000000
Binary files a/res/mipmap-xxhdpi/ic_launcher_background.png and /dev/null differ
diff --git a/res/mipmap-xxhdpi/ic_launcher_foreground.png b/res/mipmap-xxhdpi/ic_launcher_foreground.png
deleted file mode 100644
index fb6ab89d258..00000000000
Binary files a/res/mipmap-xxhdpi/ic_launcher_foreground.png and /dev/null differ
diff --git a/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/res/mipmap-xxhdpi/ic_launcher_foreground.webp
new file mode 100644
index 00000000000..34ee5a6d919
Binary files /dev/null and b/res/mipmap-xxhdpi/ic_launcher_foreground.webp differ
diff --git a/res/mipmap-xxhdpi/ic_launcher_round.webp b/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 00000000000..f3652ba42df
Binary files /dev/null and b/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/res/mipmap-xxxhdpi/ic_launcher.webp b/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 00000000000..2e0db6c5b21
Binary files /dev/null and b/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/res/mipmap-xxxhdpi/ic_launcher_background.png b/res/mipmap-xxxhdpi/ic_launcher_background.png
deleted file mode 100644
index 1fbc08e9125..00000000000
Binary files a/res/mipmap-xxxhdpi/ic_launcher_background.png and /dev/null differ
diff --git a/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/res/mipmap-xxxhdpi/ic_launcher_foreground.png
deleted file mode 100644
index a95e4f43413..00000000000
Binary files a/res/mipmap-xxxhdpi/ic_launcher_foreground.png and /dev/null differ
diff --git a/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
new file mode 100644
index 00000000000..198d292c515
Binary files /dev/null and b/res/mipmap-xxxhdpi/ic_launcher_foreground.webp differ
diff --git a/res/mipmap-xxxhdpi/ic_launcher_round.webp b/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 00000000000..2e0db6c5b21
Binary files /dev/null and b/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/res/values-ar-rSA/cr_strings.xml b/res/values-ar-rSA/cr_strings.xml
new file mode 100644
index 00000000000..767e3db4f8e
--- /dev/null
+++ b/res/values-ar-rSA/cr_strings.xml
@@ -0,0 +1,440 @@
+
+
+
+
+
+
+ إعدادات الشاشة الرئيسية
+
+ الشاشة الرئيسية
+ تعيين لوحة Google Now والمزيد
+ أيقونات
+ تعيين شكل الأيقونات والمزيد
+ قائمة التطبيقات
+ تخصيص قائمة التطبيقات الخاصة بك
+ تطبيقات الأخيرة
+ تجديد شاشة العرض العام
+ خيارات متنوعة
+ خيارات أخرى
+ العامة
+ واجهة المستخدم
+ مساحة سريعة
+ شريط البحث
+ الإختصارات السريعة
+
+ خيارات المطور
+ تمكين بعض الميزات المخفية على مسؤوليتك الخاصة
+
+ تخطيط القفل
+ لا يمكن إضافة الرموز والأدوات وإزالتها ونقلها على الشاشة الرئيسية
+ يمكن إضافة, حذف و نقل الأيقونات و الويدجتس على الشاشة الرئيسية
+ لا يمكن إضافة الويدجت إلى الشاشة الرئيسية
+
+ إظهار أسماء التطبيقات على الشاشة الرئيسية
+ إظهار أسماء التطبيقات في قائمة التطبيقات
+
+ اسحب للوصول إلى تطبيق جوجل
+
+ عند التمرير يساراً من الشاشة الرئيسية
+
+ عندما التمرير يميناً من الشاشة الرئيسية
+
+ استخدام تخصيصات الأيقونات في قائمة التطبيقات
+ اتبع تخصيصات الأيقونة المستخدمة على الشاشة الرئيسية
+
+ إيقاف مؤقت
+ إيقاف %1$s؟
+ الإشعارات لـ %1$s سيتم إيقافها مؤقتاً
+ تم إيقاف التطبيق مؤقتًا
+ تم إيقاف %1$s بشكل مباشر من المشغل
+ إيقاف مؤقت للتطبيق
+ تطبيقات العمل معطلة
+ إيقاف تطبيقات العمل
+ تفعيل تطبيقات العمل
+ لا يمكن لتطبيقات العمل إرسال إشعارات إليك أو استخدام بطاريتك أو الوصول إلى موقعك الجغرافي.
+ تطبيقات العمل الخاصة بك لا يمكن أن تتلقى مكالمات هاتفية أو رسائل نصية، أو ترسل إشعارات إليك، أو استخدم البطارية، أو الوصول إلى موقعك
+
+ الإقتراحات
+ لجميع التطبيقات & اقتراحات الشاشة الرئيسية
+
+ إعادة التشغيل
+ إعادة التشغيل يدويًا لتطبيق أي تعديلات في الإعدادات
+ إعادة تشغيل المشغل...
+ إعادة تشغيل المشغل لتطبيق التغييرات...
+ إعادة تشغيل المشغل لتحديث مكونات المشغل...
+
+ انقر نقرًا مزدوجًا للنوم
+ انقر نقرًا مزدوجًا على مساحة فارغة على الشاشة الرئيسة لإيقاف تشغيل الشاشة
+ اهتزاز عند النقر مرتين للنوم
+ تقديم ردود الفعل اللمسية عند النقر المزدوج لإيماءة السكون
+
+ اسحب لبدء البحث
+ فتح لوحة المفاتيح كلما أصبح درج التطبيقات مرئيًا
+
+ حجم الأيقونة
+
+ حجم الخط
+
+ استخدام خطين لاسم التطبيق
+
+ ارتفاع الصف
+
+ خلفية اللوحة السفلية
+ إضافة خلفية شفافة إلى قائمة التطبيقات
+
+ تعتيم الخلفية
+
+ سُمك
+
+ إيقاف
+ تم إيقاف التطبيق
+
+ البحث
+ عدسة جوجل
+ البحث الصوتي
+ وضع الذكاء
+ بحث المعازف
+ ابحث عن المعازف عند الضغط على أيقونة مسجل الصوت
+ بحث جوجل
+ إظهار شريط البحث في اسفل الشاشة
+ أيقونات مع الثيم
+ استخدام الثيم الحالي لأيقونات شريط البحث
+ حدود الزاوية
+
+ موقع شريط البحث للتطبيق
+ مخفي
+ في الأعلى
+ في الأسفل
+
+ تمرير خلفية الشاشة
+ تأثير تمرير خلفية الشاشة للشاشات المتعددة
+
+ تكبير خلفية الشاشة
+ تكبير الخلفية أو تصغيرها عند استخدام درج التطبيقات أو التطبيقات الحديثة
+
+ شريط الحالة
+
+ الظل العلوي
+
+ %1$s متاحة / %2$s كإجمالي
+ معلومات الذاكرة
+ معلومات ZRAM
+
+ لقطة شاشة
+ إزالة الكل
+ العدسة
+
+ السماح بالمبرالية القصيرة
+ تمكين تأثير التمرير الكامل للخلفية على عدد أصغر من الصفحات بدلاً من اقتطاع الخلفية
+ مركز صفحة واحدة
+ وسِّط الخلفية إن كانت هناك صفحة واحدة فقط
+
+ شريط التمرير
+ شريط التمرير على جانب درج التطبيق
+
+ شريط الحالة المُظلم
+ استخدم الوضع المُظلم دائماً في شريط الحالة في الشاشة الرئيسة
+
+ النظرة السريعة
+ إظهار في الجزء العلوي من الشاشة الرئيسية
+
+ صباح الخير.
+ مساء الخير.
+ طابت ظهيرتك.
+ طابت ليلتك.
+ يوم سعيد.
+ اليوم هو
+ إنها
+
+ فنان غير معروف
+ قيد التشغيل
+ بواسطة
+
+ قيد التشغيل
+ عرض الأغنية التي شغلتها
+
+ النمط الأدنى
+ التبديل إلى النمط الحديث
+
+ رسائل عشوائية
+ اجعل هاتفك أكثر حيوية مع الرسائل العشوائية
+
+ - صباح الخير!
+ - صباح الخير ، حان وقت التألق!
+ - يا له من يوم جميل!
+ - نتمنى لك يوماً سعيداً!
+ - ما رأيك بغفوة لخمس دقائق؟
+ - دعونا ننجز ذلك.
+ - الصباح هو لوحة قماشية جديدة لتحفتك اليومية.
+ - تبلور شروق الفرصة كل صباح.
+ - في الضوء، اكتشف القوة لإضاءة مسارك.
+ - يومك يبدأ بإمكانيات لا نهاية لها.
+ - كل شروق الشمس هي تذكرة بأنه يمكنك البدء من جديد.
+ - أيقظة، غادر اليوم القادم.
+ - إن أفكاركم يمكن أن تكون أساس الإنجازات العظيمة.
+ - إن العالم يستيقظ، وكذلك إمكانياتكم.
+ - الفصل الأول في مغامرتك اليومية.
+ - فلنجعل الروتينية الصباحية منطلقا للنجاح.
+ - شروق الشمس أم لا، إن إمكاناتك دائما في ازدياد.
+ - لقد حانَ الوقتُ لأفكارٍ جديدة وإجراءاتٍ جريئة.
+ - الطائر المُبكر يَصطاد الديدان، ولكن يمكنك أن تلتقط أحلامك.
+ - ابحث عن الشجاعة لمطاردة تطلعاتك.
+ - يرتفع ويظل، للعظمة في انتظار.
+ - وأفضل وقت للبدء هو الآن.
+
+
+ - قم بإزالة أحلامك، وليس الدقائق.
+ - في كل لحظة، فرصة جديدة.
+ - إحتواء الرحلة، وليس فقط الوجهة.
+ - اليوم هدية, لهذا السبب تسمى الحاضر.
+ - دع ابتسامتك تغير العالم.
+ - الحياة هي قصة؛ وجعل كل فصل يعد.
+ - ابحث عن الفرح في اللحظات العادية.
+ - إن أفضل شيء لم يأت بعد، ولا يزال قائما.
+ - فرصتك لتحويل \'ماذا إذا\' إلى \'ما هو.\'
+ - استولي على اليوم، بدءاً الآن.
+ - السعادة هي خيار يمكنك القيام به في أي وقت.
+ - صدقوا بنفسك، حتى في عصر النعاس.
+ - دع إجراءاتك تتكلم بصوت أعلى من الساعة.
+ - حياتك هي ما تصنعه منها وجعلها مذهلة.
+ - الأحلام كبيرة، تعمل بجد ولا تستسلم أبدا.
+ - أنت أقوى مما تعتقد.
+ - كل انتكاسة هي إعداد للتراجع.
+ - إحتواء الرحلة، وليس فقط الوجهة.
+ - صقل أهدافك، وليس مخاوفك.
+ - إمكاناتك لا تنتهي؛ قم بفتحها.
+
+
+ - احترموا اللحظة الحالية، مهما كانت الساعة.
+ - في كل غبار، العثور على فجر جديد من الفرص.
+ - الحياة هي رحلة؛ استمتع بشعرة على طول الطريق.
+ - موقفك يشكل واقعك.
+ - لا تزال قصتك مكتوبة، حتى مع اشتعال الشمس.
+ - التقط الجمال في اللحظات العادية.
+ - حان الوقت للتفكير وإعادة الضبط.
+ - اختر الفرح، بغض النظر عن الوقت على مدار الساعة.
+ - الفُرص لا تَخرُج في المساء.
+ - كُن السببَ في ابتسام أحدهم.
+ - إمكاناتك لا تعرف حدودا.
+ - لحظات السِحر يُمكن أن تحدث في أي ساعة.
+ - حياتك هي ما تصنعهُ مِنها وجَعلهِا مُذهِلة.
+ - ابقى فضوليًا، ابقى ممتنًا.
+ - يَجبْ أن تَكون أنتَ التغيير الذي ترغب في رؤيتهُ في العالم.
+ - صَدقوا بنفسكَ، أنتَ قادر على أشياء مُذهلة.
+ - الحد الوحيد هو الذي تُعيينه لِنفسك.
+ - يبدأ النجاح بخطوة واحدة.
+
+
+ - استولي على الليل، لأنها تحمل أسرارها الخاصة.
+ - في هدوء هذا المساء ، اكتشف الصبر.
+ - أعظمْ مُغامرات الحياةِ يمكن أن تبدأ في الظلام.
+ - رِحلتك مُستمرة حتى بعدَ غروب الشمس.
+ - دَعوا الليلةِ تَكون قِماشتك؛ ورَسمُها بالأحلام.
+ - في بطء هذا المساءِ، أجد سلامك الداخلي.
+ - النجوم تألق في أحلك الساعات.
+ - الأحلام كبيرة، حتى في الساعات المُتأخِرة من اليوم.
+ - حياتك هي قصة تَنتظر أن تُكتب.
+ - حانَ الوقتُ للتفكيرْ وإعادةِ الضّبط.
+ - مِن المُمكن أن تؤدي الأفكار الليلية المُتأخرة إلى الكشفِ عَنها في وقتٍ مُبكر مِن الصباح.
+ - ابحث عن السَعادةِ في مِيلاد هذا المساء.
+ - السماء الليلية هي قماش كبير من الأحلام.
+ - إمكاناتك لا تعرف وقت النوم ؛ متابعة مشاعرك.
+ - ابقى مُتفائلاً، حتى في أحلك الساعات.
+ - في وَسْط الفَوضى، أجد هدوءك الداخلي.
+ - أبقى إيجابيا, اعمل بجدٍ و اجعل ذلكَ يحدُث.
+
+
+ - الليالي هي هوامش الفرصة الهادئة.
+ - في سكون الليل، ابحث عن قوتك الداخلية.
+ - احتضن الظلام، فهو يحمل مفاتيح أحلامك.
+ - العالم ينام، ولكن إمكاناتك مستيقظة.
+ - قد تكون الليلة متأخرة، ولكن طموحاتك تأتي في الوقت المناسب.
+ - ابق فضوليًا، وابق مستيقظًا، ودع الليل يرشدك.
+ - ابحث عن الشجاعة لمطاردة أحلامك.
+ - الأشياء العظيمة لا تأتي أبداً من مناطق الراحة.
+ - لديك القدرة على لتحديد مصيرك الخاص.
+ - السعادة هي اختيار؛ اخترها كل يوم.
+ - الحياة هي ما تصنعه، لذا اجعلها ذات أهمية.
+ - حافظ على تركيزك، وحافظ على تصميمك، وحافظ على عدم توقفك.
+ - أنت مؤلف قصتك الخاصة.
+ - السعي من أجل التقدم، وليس الكمال.
+ - في كل يوم بداية جديدة، اجعلها بداية عظيمة.
+ - ألهم الآخرين من خلال كونك نفسك الحقيقية.
+ - موقفك يحدد اتجاهك
+
+
+ - التجاهل نعمة
+ - هل حان الوقت لتثبيت تحديث آخر بالفعل؟
+ - إصنع السلام، وليس الحرب
+ - أوه أهلاً، كيف الحال؟
+ - مرحبا بك في ما تر يكس الحقيقي
+ - كم عدد لقطات الشاشة التي تقوم بالتقاطها؟
+ - عرض مميزات AlphaDroid
+ - أنشر الحب وليس الفوضى
+ - لم نضرم النار!
+ - حان الوقت للقليل من الموسيقى الجيدة
+ - \u003C\u003C\u003C\u003C\u003C\u003C\u003C الراس
+ - تحتاج للاشتراك في AlphaDroid Premium لرؤية هذا
+ - تذكرLineage للUnicorn
+ - استمتع بعمر بطارية طويل
+ - البدء من نقطة الصفر
+ - نحبك 3000 مرة
+ - لذيذة حتى بدون سوشي
+ - قم بعمل شيء لطيف اليوم
+ - هذه أفضل تجربة رقمية، أليس كذلك؟
+ - لا أوهام ، مرحبا بالواقع!
+ - شكرا لك على دعمك
+ - لا يوجد مهرجان للأغبياء - فقط للكاملين!
+ - واحدة من أفضل اختيارات آلي البناء
+ - عقلانية لجنون عظمتك
+ - النهوض بسلام
+ - يا لها من تجربة جميلة، أليس كذلك؟
+ - التطور خرافة، أليس كذلك؟
+ - ما تقوم بتثبيته يعبر عنك، لا تكن ليناً
+ - ما الذي يدور في خاطرك؟
+ - أنا في إنتظار ولي الأمر
+ - Wubba Lubba Dub Dub
+ - الفائز ....؟
+ - rm -rf \/
+ - برينجلز ليست بالفعل شرائح بطاطس حقيقية..
+ - تذكر أن تتحقق من مستوى بطارية الجهاز!
+ - تحقق بريدك الإلكتروني/رسائلك.
+ - تحقق من قائمة العمل الخاصة بك إذا كان لديك واحدة.
+ - يمكنك تعطيل الفضاء السريع عن طريق إعدادات المنزل.
+ - هل أعجبك عملنا ؟ أرسل لنا شراب
+ - أفضل روم
+ - راسلنا على Telegram
+ - نحبك 3000 مرة
+
+ تعذر العثور على التقويم أو نشاط الساعة
+
+ مزود الطقس
+ تلقائي
+ فضاء جوجل الذكي
+ OmniJaws
+ تحديث الطقس
+ عرض تحديث الطقس الحالي
+ يتطلب تفعيل خدمة الطقس
+ غائم
+ مُمطر
+ مُشمِس
+ عاصف
+ مُثلج
+ رِياح قوية
+ ضبابي
+ الموقعْ الحالي
+ عرض موقع الطقس الحالي
+ الوضع الحالي
+ عرض ملخص حالة الطقس الحالية
+
+ إخفاء تلقائي لمؤشر الصفحة
+ إخفاء نقاط مؤشر الصفحة تلقائياً
+
+ مستوي ضبابية الخلفية
+ تعيين مستوى الضباب للتطبيقات الحديثة ودرج التطبيقات
+
+ معلومات التطبيق
+ المصدر
+ النظام
+ آخر تحديث
+ الإصدار
+ المزيد
+
+ حزمة الايقونات
+ الإفتراضي
+ تثبيت المزيد
+ لا يوجد متجر تطبيقات متاح
+
+ خلفية كاروسيل
+ التبديل السريع للخلفيات من قائمة الضغط المطول
+ تطبيق على شاشة القفل
+ تعيين نفس الخلفية على شاشة القفل
+
+ حصاة
+ وعاء
+ مربع صافي
+ مربع دائري
+ مستطيل مدبّب
+ دمعة
+ مستطيل ذو زوايا مستديرة
+ غائم
+ أسطوانة
+ زهرة
+ قلب
+ سداسي
+ سداسي مدور
+ نمط iOS
+ ورقة الشجر
+ مواء
+ Samsung Style
+ ممتد
+
+ تسميات التطبيق المظلمة
+ استخدام النص الداكن دائما لتسميات التطبيق في درج التطبيق
+
+ نمط درج التطبيقات
+ عادي (ورقة أسفل)
+ ملء الشاشة القديمة
+ نمط القائمة
+ نمط OneUI (الصفحة)
+
+ فرز درج التطبيقات
+ ترتيب أبجدي
+ الترتيب حسب تاريخ التثبيت
+ ترتيب حسب الإستخدام
+
+ لون الخلفية المخصصة
+ استخدام لون مخصص لخلفية درج التطبيق
+ لون الوضع الفاتح
+ لون الوضع المظلم
+
+ HSB
+ RGB
+ التدرج اللوني
+ التشبع
+ السطوع
+ أحمر
+ اخضر
+ أزرق
+ HEX
+
+ شريط البحث بنمط بكسل
+ ترتيب شريط البحث مع خلفية الطبقات
+ شفافية الطبقة الخارجية
+
+ إخفاء التطبيقات وحمايتها
+ افتح لإدارة التطبيقات المخفية والمحمية
+ يرجى مصادقة البصمة لفتح %1$s
+ جارٍ التحميل…\u2026
+ يرجى إعداد شاشة قفل الجهاز لتقييد الوصول إلى التطبيق
+ مساعدة
+ التطبيقات المخفية و الأدوات الخاصة بها ستكون مخفية أيضاً من واجهة التشغيل
+ التطبيقات المحمية تتطلب المصادقة لكي تُفتح
+
+ نمط تخطيط التطبيقات الحديثة
+ الإفتراضي
+ تأثير المقياس
+ نمط الكامي
+ نمط iOS
+ نمط OxygenOS
+
+ قفل/فتح التطبيق
+ التطبيق غير المقفل في التطبيقات الحديثة
+ التطبيق المقفل في التطبيقات الحديثة
+
+ الإهتزاز اثناء التمرير
+
diff --git a/res/values-az-rAZ/cr_strings.xml b/res/values-az-rAZ/cr_strings.xml
new file mode 100644
index 00000000000..df18f8ac1e7
--- /dev/null
+++ b/res/values-az-rAZ/cr_strings.xml
@@ -0,0 +1,440 @@
+
+
+
+
+
+
+ Home settings
+
+ Ana ekran
+ Google Now panelini və digərlərini tənzimlə
+ İkonlar
+ Set icon size and more
+ Proqram çəkməcəsi
+ Program çəkməcəsini fərdiləşdirin
+ Son elementlər
+ Revamp the overview screen
+ Qarışıq
+ Digər seçimlər
+ Ümumi parametrlər
+ İnterfeys
+ Quickspace
+ Search bar
+ Quick actions
+
+ Tərtibatçı seçimləri
+ Risk alaraq bəzi gizli özəllikləri aktivləşdirin
+
+ Lock layout
+ Icons and widgets can\'t be added, removed and moved on the homescreen
+ Icons and widgets can be added, removed and moved on the homescreen
+ It\'s not possible to add widgets to the home screen
+
+ Icon labels on desktop
+ Icon labels in drawer
+
+ Swipe to access Google app
+
+ When you swipe left from main Home screen
+
+ When you swipe right from main Home screen
+
+ Use icon customizations in drawer
+ Follow icon customizations used on home screen
+
+ Fasilə
+ Pause %1$s?
+ Notifications for %1$s will be paused
+ App paused
+ %1$s was directly paused from the launcher
+ Pause app
+ Work apps are off
+ Turn off work apps
+ Turn on work apps
+ Your work apps cannot send you notifications, use your battery, or access your location
+ Your work apps cannot receive phone calls or text messages, send you notifications, use your battery, or access your location
+
+ Suggestions
+ Bütün Proqramlar üçün & Əsas ekran təklifləri
+
+ Restart
+ Restart the launcher manually to apply any pending settings
+ Restarting launcher...
+ Restarting launcher to apply changes...
+ Restarting launcher to update launcher components...
+
+ Yuxuya keçmək üçün iki dəfə toxun
+ Double tap empty space on home screen to turn off the screen
+ Vibrate on double tap to sleep
+ Provide haptic feedback on double tap to sleep gesture
+
+ Swipe to start search
+ Open keyboard whenever app drawer becomes visible
+
+ Nişan ölçüsü
+
+ Yazı tipi ölçüsü
+
+ Use two lines for app label
+
+ Row height
+
+ Hotseat background
+ Add translucent background to the app dock
+
+ Arxa plan tutqunluğu
+
+ Stroke
+
+ Bağla
+ Tətbiq bağlandı
+
+ Axtarış
+ Google Lens
+ Səsli axtarış
+ AI mode
+ Music search
+ Start music search on pressing mic icon
+ Google axtarış paneli
+ Search bar in the bottom dock
+ Themed icons
+ Use themed icons for search bar
+ Corner radius
+
+ App search bar location
+ Hidden
+ Top
+ Bottom
+
+ Wallpaper scrolling
+ Wallpaper scrolling effect for multiple screens
+
+ Wallpaper zooming
+ Zoom in or out the wallpaper when using drawer or recent apps
+
+ Status paneli
+
+ Top shadow
+
+ %1$s Available | %2$s Total
+ Memory info
+ ZRAM info
+
+ Ekran görüntüsü
+ Hamısını təmizlə
+ Lens
+
+ Allow short parallax
+ Enable full wallpaper scroll effect on smaller numbers of pages instead of cropping the wallpaper
+ Single page center
+ Center wallpaper if only using a single page
+
+ Scrollbar
+ Scrollbar on side of the app drawer
+
+ Dark status bar
+ Always use dark status bar on home screen
+
+ Bir baxışda
+ Show at the top of your home screen
+
+ Sabahınız xeyir.
+ Good evening.
+ Good afternoon.
+ Gecəniz xeyrə.
+ Good day.
+ Today is
+ It\'s
+
+ Unknown Artist
+ Now playing
+ By
+
+ Now playing
+ Show the song you\'re playing
+
+ Minimalistic style
+ Switch to modern style
+
+ Random messages
+ Make your companion more lively with random messages
+
+ - Sabahınız xeyir!
+ - Sabahın xeyir, coşmaq vaxtıdır!
+ - Nə gözəl bir gündür!
+ - Günün gözəl keçsin!
+ - 5 dəqiqəlik sakitləşməyə necə baxırsan?
+ - Gəlin bu çörəyi alaq.
+ - Mornings are a fresh canvas for your daily masterpiece.
+ - Embrace the sunrise of opportunity each morning.
+ - In the early light, find the power to illuminate your path.
+ - Your day begins with endless possibilities.
+ - Every sunrise is a reminder that you can start anew.
+ - Awake with purpose, conquer the day ahead.
+ - Your thoughts can be the foundation of great achievements.
+ - The world awakens, and so does your potential.
+ - The first chapter in your daily adventure.
+ - Let your morning routine be the launchpad for success.
+ - Sunrise or not, your potential is always on the rise.
+ - It\'s time for fresh ideas and bold actions.
+ - The early bird catches the worm, but you can catch your dreams.
+ - Find the courage to chase your aspirations.
+ - Rise and shine, for greatness awaits.
+ - The best time to start is now.
+
+
+ - Chase your dreams, not the minutes.
+ - In every moment, a new opportunity.
+ - Embrace the journey, not just the destination.
+ - Today is a gift, that\'s why it\'s called the present.
+ - Let your smile change the world.
+ - Life is a story; make each chapter count.
+ - Find joy in the ordinary moments.
+ - The best is yet to come, keep it up.
+ - Your chance to turn \'what if\' into \'what is.\'
+ - Seize the day, starting right now.
+ - Happiness is a choice you can make any time.
+ - Believe in yourself, even on a sleepy afternoon.
+ - Let your actions speak louder than the clock.
+ - Your life is what you make of it; make it amazing.
+ - Dream big, work hard, and never give up.
+ - You are stronger than you think.
+ - Every setback is a setup for a comeback.
+ - Embrace the journey, not just the destination.
+ - Chase your goals, not your fears.
+ - Your potential is endless; unlock it.
+
+
+ - Embrace the present moment, no matter the hour.
+ - In every dusk, find a new dawn of opportunity.
+ - Life is a journey; enjoy the scenery along the way.
+ - Your attitude shapes your reality.
+ - Your story is still being written, even as the sun sets.
+ - Capture the beauty in the ordinary moments.
+ - It\'s time to reflect and reset.
+ - Choose joy, no matter the time on the clock.
+ - Opportunities don\'t clock out in the evening.
+ - Be the reason someone smiles.
+ - Your potential knows no bounds.
+ - Moments of magic can happen at any hour.
+ - Your life is what you make of it; make it extraordinary.
+ - Stay curious, stay grateful.
+ - Be the change you wish to see in the world.
+ - Believe in yourself; you are capable of amazing things.
+ - The only limit is the one you set for yourself.
+ - Success begins with a single step.
+
+
+ - Seize the night, for it holds its own mysteries.
+ - In the quiet of this evening, find serenity.
+ - Life\'s greatest adventures can start in the darkness.
+ - Your journey continues even after the sun has set.
+ - Let the night be your canvas; paint it with dreams.
+ - In the stillness of this evening, find your inner peace.
+ - Stars shine brightest in the darkest hours.
+ - Dream big, even in the late hours of the day.
+ - Your life is a story waiting to be written.
+ - It\'s time for reflection and renewal.
+ - Late night thoughts can lead to early morning revelations.
+ - Find joy in the stillness of this evening.
+ - The night sky is a vast canvas of dreams.
+ - Your potential knows no bedtime; pursue your passions.
+ - Stay hopeful, even in the darkest hours.
+ - In the midst of chaos, find your inner calm.
+ - Stay positive, work hard, make it happen.
+
+
+ - Nights are the quiet whispers of opportunity.
+ - In the stillness of the night, find your inner strength.
+ - Embrace the darkness, for it holds the keys to your dreams.
+ - The world sleeps, but your potential is wide awake.
+ - The night may be late, but your ambitions are timeless.
+ - Stay curious, stay awake, and let nights guide you.
+ - Find the courage to chase your dreams.
+ - Great things never come from comfort zones.
+ - You have the power to create your own destiny.
+ - Happiness is a choice; choose it every day.
+ - Life is what you make it, so make it count.
+ - Stay focused, stay determined, stay unstoppable.
+ - You are the author of your own story.
+ - Strive for progress, not perfection.
+ - Every day is a new beginning; make it a great one.
+ - Inspire others by being your authentic self.
+ - Your attitude determines your direction.
+
+
+ - Cahillik xoşbəxtlikdir
+ - Çoxdan başqa güncəlləməni quraşdırmaq vaxtı gəlmədi?
+ - Make peace, not war
+ - Salam, necəsən?
+ - Welcome to real Matrix!
+ - Neçə ekran görüntüsü çəkmisən?
+ - AlphaDroid ilə birdən çox özəlləşdirmələr
+ - Spread love, not havoc
+ - We didn\'t start the fire!
+ - Bəzi gözəl mahnıları dinləməyin vaxtıdır
+ - \u003C\u003C\u003C\u003C\u003C\u003C\u003C HEAD
+ - Bunu görə bilməyin üçün AlphaDroid Premium\'a ehtiyacın var
+ - Unicorn soyunu yadda saxla
+ - Xtended batareya ömrünün kefini çək
+ - Sıfırdan başlayaraq
+ - Səni 3000 dəfə sevirik
+ - Delicious even without Sushi
+ - Bu gün gözəl bir şeylər et
+ - Bu ən gözəl Pixel təcrübəsidir, düz deyil?
+ - İluzyonlara deyil, real həyata xoş gəldin!
+ - Bizi dəstəklədiyin üçün təşəkkür edirik
+ - Derps üçün festival yoxdur - sadəcə mükəmməllik!
+ - BuildBot\'ların ən yaxşı seçimlərindən biri
+ - Paranoyan üçün sağlamlıq
+ - Rising in peace
+ - Nə gözəl bir təcrübə, düz deyil?
+ - Təkamül bir əfsanədir, düzdü?
+ - Sən nə istifadə edirsənsə osan, Heç vaxt kartof olma
+ - Sənin düşüncən nədir?
+ - Expecto Patronum
+ - Wubba Lubba Dub Dub
+ - Qalib Qalib ....?
+ - rm -rf \/
+ - Pringles aren\'t actually potato chips..
+ - Remember to check device\'s battery level!
+ - Check your email/messages.
+ - Check your to-do list if you have one.
+ - You can disable quickspace via home settings.
+ - Bizi sevdin? Bizə bir pivə göndər
+ - Ən Yaxşı Rom.
+ - Bizə Telegram\'da salam de
+ - Səni 3000 dəfə sevirik
+
+ Unable to find calendar or clock activity
+
+ Weather provider
+ Auto
+ Google Smartspace
+ OmniJaws
+ Weather update
+ Display current weather update
+ Hava xidmətinin aktivləşdirilməsini tələb edir
+ Cloudy
+ Rainy
+ Sunny
+ Stormy
+ Snowy
+ Windy
+ Misty
+ Current location
+ Cari hava yerini göstərin
+ Current condition
+ Display current weather condition summary
+
+ Auto-hide page indicator
+ Hide the page indicator dots automatically
+
+ Background blur depth
+ Set blur depth level for recents and app drawer
+
+ Application info
+ Mənbə
+ Sistem
+ Son yenilənmə
+ Versiya
+ Daha çox
+
+ İkon paketi
+ Susmaya görə
+ Install more
+ There\'s no available app store
+
+ Wallpaper carousel
+ Quickly switch wallpapers from the long-press menu
+ Apply to lock screen
+ Set the same wallpaper on the lock screen
+
+ Pebble
+ Vessel
+ Pure square
+ Squircle
+ Tapered rect
+ Teardrop
+ Rounded rect
+ Cloudy
+ Cylindrical
+ Flower
+ Heart
+ Hexagon
+ Rounded hexagon
+ iOS Style
+ Leaf
+ Meow
+ Samsung Style
+ Stretched
+
+ Dark app labels
+ Always use dark text for app labels in the app drawer
+
+ App drawer style
+ Normal (bottom sheet)
+ Legacy fullscreen
+ List style
+ OneUI style (paged)
+
+ App drawer sorting
+ Sort alphabetically
+ Sort by install date
+ Sort by usage
+
+ Custom background color
+ Use a custom color for the app drawer background
+ Light mode color
+ Dark mode color
+
+ HSB
+ RGB
+ Hue
+ Saturation
+ Brightness
+ Red
+ Green
+ Blue
+ HEX
+
+ Pixel style search bar
+ Revamped search bar with layered background
+ Outer layer opacity
+
+ Hidden & Protected apps
+ Unlock to manage the hidden and protected apps
+ Authenticate to open %1$s
+ Loading\u2026
+ Please set up a secure lock screen to restrict app access
+ Yardım
+ Hidden apps and their widgets are hidden from the drawer
+ Protected apps require authentication to be opened from the launcher
+
+ Recents layout style
+ Susmaya görə
+ Scale effect
+ Kami style
+ iOS style
+ OxygenOS style
+
+ Lock/Unlock app
+ Unlocked app in recents
+ Locked app in recents
+
+ Scroll vibration
+
diff --git a/res/values-be-rBY/cr_strings.xml b/res/values-be-rBY/cr_strings.xml
new file mode 100644
index 00000000000..024a89dcd01
--- /dev/null
+++ b/res/values-be-rBY/cr_strings.xml
@@ -0,0 +1,440 @@
+
+
+
+
+
+
+ Налады галоўнага экрана
+
+ Галоўны экран
+ Усталяваць панэль Google Now і іншае
+ Значкі
+ Усталюйце памер значка і многае іншае
+ Спіс праграм
+ Налада свайго спісу праграм
+ Нядаўнія
+ Налады экрана нядаўніх праграм
+ Рознае
+ Іншыя налады
+ Агульныя
+ Інтэрфейс
+ Хуткая прастора
+ Радок пошуку
+ Хуткія дзеянні
+
+ Опцыі распрацоўшчыка
+ Уключыце некаторыя схаваныя функцыі на свой страх і рызыку
+
+ Забараніць рэдагаванне
+ Значкі і віджэты немагчыма дадаваць, выдаляць і перамяшчаць па галоўным экране
+ Значкі і віджэты можна дадаваць, выдаляць і перамяшчаць па галоўным экране
+ Немагчыма дадаваць віджэты на галоўны экран
+
+ Назвы значкоў на працоўным стале
+ Подпіс значкоў у меню праграм
+
+ Правядзіце пальцам для доступу да праграмы Google
+
+ Пры правядзенні пальцам налева ад галоўнага галоўнага экрана
+
+ Пры правядзенні пальцам направа ад галоўнага галоўнага экрана
+
+ Use icon customizations in drawer
+ Follow icon customizations used on home screen
+
+ Паўза
+ Прыпыніць %1$s?
+ Апавяшчэнні для %1$s будуць прыпынены
+ Праграма прыпынена
+ %1$s быў прыпынены непасрэдна з праграмы запуску
+ Прыпыніць праграму
+ Працоўныя праграмы адключаны
+ Адключыць працоўныя праграмы
+ Уключыць працоўныя праграмы
+ Вашы працоўныя праграмы не могуць адпраўляць вам апавяшчэнні, выкарыстоўваць зарад батарэі або атрымліваць доступ да вашага месцазнаходжання
+ Вашы працоўныя праграмы не могуць прымаць тэлефонныя званкі або тэкставыя паведамленні, адпраўляць вам апавяшчэнні, выкарыстоўваць зарад батарэі або атрымліваць доступ да вашага месцазнаходжання
+
+ Прапановы
+ Для прапаноў ўсіх праграм і галоўнага экрана
+
+ Перазапуск
+ Перазапусціць лаўнчар уручную, каб прымяніць усе налады
+ Перазапуск праграмы запуску...
+ Перазапуск праграмы запуску для ўжыцця змяненняў...
+ Перазапуск праграмы запуску для абнаўлення яе кампанентаў...
+
+ Выключаць экран пры падвойным націску
+ Double tap empty space on home screen to turn off the screen
+ Vibrate on double tap to sleep
+ Provide haptic feedback on double tap to sleep gesture
+
+ Swipe to start search
+ Open keyboard whenever app drawer becomes visible
+
+ Памер значка
+
+ Памер шрыфта
+
+ Выкарыстоўваць два радкі для назвы праграмы
+
+ Вышыня радку
+
+ Фон віджэта
+ Напаўпразрысты фон ніжняй панэлі праграм
+
+ Фон непразрыстасці
+
+ Штрых
+
+ Спыніць
+ Праграма завершана
+
+ Пошук
+ Google Lens
+ Галасавы пошук
+ AI mode
+ Пошук музыкі
+ Пачаць пошук музыкі, націснуўшы значок мікрафона
+ Панэль пошуку Google
+ Радок пошуку ў ніжняй панэлі
+ Тэматычныя значкі
+ Выкарыстоўвайце тэматычныя значкі для радка пошуку
+ Радыус вугла
+
+ App search bar location
+ Hidden
+ Top
+ Bottom
+
+ Пракрутка шпалер
+ Эфект пракруткі шпалер для некалькіх экранаў
+
+ Маштабаванне шпалер
+ Павелічэнне або памяншэнне шпалер у меню праграм і ў спісе нядаўніх праграм
+
+ Радок стану
+
+ Верхні цень
+
+ %1$s Даступна | %2$s Усяго
+ Інфармацыя аб памяці
+ Інфармацыя аб ZRAM
+
+ Здымак экрана
+ Ачысціць усё
+ Аб’ектыў
+
+ Дазволіць кароткі паралакс
+ Уключыце эфект поўнай пракруткі шпалер на меншай колькасці старонак замест абразання шпалер
+ Па цэнтры адной старонкі
+ Размясціць шпалеры па цэнтры, калі выкарыстоўваецца толькі адна старонка
+
+ Паласа прагорткі
+ Паласа пракруткі збоку ад меню праграм
+
+ Dark status bar
+ Always use dark status bar on home screen
+
+ З Першага Погляду
+ Паказваць у верхняй частцы экрана
+
+ Добрай раніцы.
+ Добры вечар.
+ Добры дзень.
+ Дабранач.
+ Good day.
+ Today is
+ Зараз
+
+ Unknown Artist
+ Зараз гучыць
+ By
+
+ Зараз гучыць
+ Паказаць песню, якую вы слухаеце
+
+ Мінімалістычны стыль
+ Пераключыцца на сучасны стыль
+
+ Выпадковыя паведамленні
+ Зрабіце свайго спадарожніка больш жывым з дапамогай выпадковых паведамленняў
+
+ - Добрай раніцы!
+ - Добрай раніцы, час качацца!
+ - Які цудоўны дзень!
+ - Добрага дня!
+ - А як з невялікім 5-хвілінным сном?
+ - Давайце зробім гэта.
+ - Mornings are a fresh canvas for your daily masterpiece.
+ - Embrace the sunrise of opportunity each morning.
+ - In the early light, find the power to illuminate your path.
+ - Your day begins with endless possibilities.
+ - Every sunrise is a reminder that you can start anew.
+ - Awake with purpose, conquer the day ahead.
+ - Your thoughts can be the foundation of great achievements.
+ - The world awakens, and so does your potential.
+ - The first chapter in your daily adventure.
+ - Let your morning routine be the launchpad for success.
+ - Sunrise or not, your potential is always on the rise.
+ - It\'s time for fresh ideas and bold actions.
+ - The early bird catches the worm, but you can catch your dreams.
+ - Find the courage to chase your aspirations.
+ - Rise and shine, for greatness awaits.
+ - The best time to start is now.
+
+
+ - Chase your dreams, not the minutes.
+ - In every moment, a new opportunity.
+ - Embrace the journey, not just the destination.
+ - Today is a gift, that\'s why it\'s called the present.
+ - Let your smile change the world.
+ - Life is a story; make each chapter count.
+ - Find joy in the ordinary moments.
+ - The best is yet to come, keep it up.
+ - Your chance to turn \'what if\' into \'what is.\'
+ - Seize the day, starting right now.
+ - Happiness is a choice you can make any time.
+ - Believe in yourself, even on a sleepy afternoon.
+ - Let your actions speak louder than the clock.
+ - Your life is what you make of it; make it amazing.
+ - Dream big, work hard, and never give up.
+ - You are stronger than you think.
+ - Every setback is a setup for a comeback.
+ - Embrace the journey, not just the destination.
+ - Chase your goals, not your fears.
+ - Your potential is endless; unlock it.
+
+
+ - Embrace the present moment, no matter the hour.
+ - In every dusk, find a new dawn of opportunity.
+ - Life is a journey; enjoy the scenery along the way.
+ - Your attitude shapes your reality.
+ - Your story is still being written, even as the sun sets.
+ - Capture the beauty in the ordinary moments.
+ - It\'s time to reflect and reset.
+ - Choose joy, no matter the time on the clock.
+ - Opportunities don\'t clock out in the evening.
+ - Be the reason someone smiles.
+ - Your potential knows no bounds.
+ - Moments of magic can happen at any hour.
+ - Your life is what you make of it; make it extraordinary.
+ - Stay curious, stay grateful.
+ - Be the change you wish to see in the world.
+ - Believe in yourself; you are capable of amazing things.
+ - The only limit is the one you set for yourself.
+ - Success begins with a single step.
+
+
+ - Seize the night, for it holds its own mysteries.
+ - In the quiet of this evening, find serenity.
+ - Life\'s greatest adventures can start in the darkness.
+ - Your journey continues even after the sun has set.
+ - Let the night be your canvas; paint it with dreams.
+ - In the stillness of this evening, find your inner peace.
+ - Stars shine brightest in the darkest hours.
+ - Dream big, even in the late hours of the day.
+ - Your life is a story waiting to be written.
+ - It\'s time for reflection and renewal.
+ - Late night thoughts can lead to early morning revelations.
+ - Find joy in the stillness of this evening.
+ - The night sky is a vast canvas of dreams.
+ - Your potential knows no bedtime; pursue your passions.
+ - Stay hopeful, even in the darkest hours.
+ - In the midst of chaos, find your inner calm.
+ - Stay positive, work hard, make it happen.
+
+
+ - Nights are the quiet whispers of opportunity.
+ - In the stillness of the night, find your inner strength.
+ - Embrace the darkness, for it holds the keys to your dreams.
+ - The world sleeps, but your potential is wide awake.
+ - The night may be late, but your ambitions are timeless.
+ - Stay curious, stay awake, and let nights guide you.
+ - Find the courage to chase your dreams.
+ - Great things never come from comfort zones.
+ - You have the power to create your own destiny.
+ - Happiness is a choice; choose it every day.
+ - Life is what you make it, so make it count.
+ - Stay focused, stay determined, stay unstoppable.
+ - You are the author of your own story.
+ - Strive for progress, not perfection.
+ - Every day is a new beginning; make it a great one.
+ - Inspire others by being your authentic self.
+ - Your attitude determines your direction.
+
+
+ - Няведанне ёсць Bliss
+ - Час прашываць яшчэ адно абнаўленне?
+ - Заключайце мір, а не вайну
+ - О, прывітанне, што здарылася?
+ - Welcome to real Matrix!
+ - Колькі скрыншотаў вы зрабілі?
+ - Адкрыйце дабро з AlphaDroid
+ - Распаўсюджвайце любоў, а не хаос
+ - Не мы распальвалі пажар!
+ - Час для добрай музыкі
+ - \u003C\u003C\u003C\u003C\u003C\u003C\u003C ГАЛАВА
+ - Для гэтага вам патрэбен AlphaDroid Premium
+ - Памятайце Lineage і Unicorn
+ - Атрымлівайце асалоду ад тэрміну службы батарэі
+ - Пачынаючы з нуля
+ - Мы любім цябе 3000
+ - Смачна нават без Сушы
+ - Зрабі што-небудзь прыемнае сёння
+ - Гэта лепшы Pixel Experience, ці не так?
+ - Ніякіх ілюзій, сардэчна запрашаем у рэальнасць!
+ - Дзякуй за вашу падтрымку
+ - Ніякага фестывалю для Derps - толькі дасканаласць!
+ - Адзін з лепшых выбаршчыкаў
+ - Разважлівасць для вашай паранойі
+ - Rising in peace
+ - Які цудоўны вопыт, ці не так?
+ - Evolution гэта міф, праўда?
+ - Вы тое, што прашываеце, не будзьце бульбай
+ - Што вы маеце на ўвазе?
+ - Expecto Patronum
+ - Wubba Lubba Dub Dub
+ - Пераможца Пераможца ....?
+ - rm -rf \/
+ - Pringles aren\'t actually potato chips..
+ - Remember to check device\'s battery level!
+ - Check your email/messages.
+ - Check your to-do list if you have one.
+ - You can disable quickspace via home settings.
+ - Любіце нас? Дашліце нам піва
+ - Лепшая прашыўка ў свеце.
+ - Пагаварыце з намі ў Telegram
+ - Мы любім цябе 3000
+
+ Unable to find calendar or clock activity
+
+ Weather provider
+ Auto
+ Google Smartspace
+ OmniJaws
+ Абнаўленне надвор\'я
+ Адлюстраванне бягучага абнаўлення надвор\'я
+ Патрабуецца ўключэнне службы надвор\'я
+ Воблачна
+ Дождж
+ Сонечна
+ Навальніца
+ Снег
+ Ветрана
+ Туман
+ Бягучае месцазнаходжанне
+ Адлюстраванне бягучага месцазнаходжання надвор\'я
+ Бягучы стан
+ Паказваць інфармацыю аб бягучых умовах надвор\'я
+
+ Auto-hide page indicator
+ Hide the page indicator dots automatically
+
+ Глыбіня размыцця фону
+ Усталюйце ўзровень глыбіні размыцця для апошніх і меню праграм
+
+ Application info
+ Крыніца
+ Сістэма
+ Апошняе абнаўленне
+ Версія
+ Яшчэ
+
+ Набор значкоў
+ Па змаўчанні
+ Усталяваць яшчэ
+ Няма даступнай крамы праграм
+
+ Wallpaper carousel
+ Quickly switch wallpapers from the long-press menu
+ Apply to lock screen
+ Set the same wallpaper on the lock screen
+
+ Pebble
+ Vessel
+ Pure square
+ Squircle
+ Tapered rect
+ Teardrop
+ Rounded rect
+ Воблачна
+ Cylindrical
+ Flower
+ Heart
+ Hexagon
+ Rounded hexagon
+ iOS Style
+ Leaf
+ Meow
+ Samsung Style
+ Stretched
+
+ Dark app labels
+ Always use dark text for app labels in the app drawer
+
+ App drawer style
+ Normal (bottom sheet)
+ Legacy fullscreen
+ List style
+ OneUI style (paged)
+
+ App drawer sorting
+ Sort alphabetically
+ Sort by install date
+ Sort by usage
+
+ Custom background color
+ Use a custom color for the app drawer background
+ Light mode color
+ Dark mode color
+
+ HSB
+ RGB
+ Hue
+ Saturation
+ Brightness
+ Red
+ Green
+ Blue
+ HEX
+
+ Pixel style search bar
+ Revamped search bar with layered background
+ Outer layer opacity
+
+ Схаваныя і абароненыя праграмы
+ Разблакуйце для кіравання ўтоенымі і абароненымі праграмамі
+ Аўтарызуйцеся, для адкрыцця %1$s
+ Загрузка\u2026
+ Калі ласка, усталюйце блакаванне экрана для абмежавання доступу да праграм
+ Дапамога
+ Схаваныя праграмы і іх віджэты схаваны з меню
+ Для адкрыцця абароненых праграм з лаунчэра патрабуецца аўтэнтыфікацыя
+
+ Recents layout style
+ Па змаўчанні
+ Scale effect
+ Kami style
+ iOS style
+ OxygenOS style
+
+ Lock/Unlock app
+ Unlocked app in recents
+ Locked app in recents
+
+ Scroll vibration
+
diff --git a/res/values-bg-rBG/alpha_strings.xml b/res/values-bg-rBG/alpha_strings.xml
new file mode 100644
index 00000000000..e8660a28519
--- /dev/null
+++ b/res/values-bg-rBG/alpha_strings.xml
@@ -0,0 +1,44 @@
+
+
+
+
+ Lock/Unlock app
+ Unlocked app in recents
+ Locked app in recents
+
+ Recents layout style
+ Stock
+ Kami Style
+ iOS Style
+ OxygenOS Style
+
+ - Ignorance is Bliss
+ - Make love, not war
+ - Oh hey, what\'s up?
+ - We love you 3000
+ - Thank you for your support
+ - One of the buildbot\'s best picks
+ - Pringles aren\'t actually potato chips..
+ - You can disable quickspace via home settings.
+ - I got nothing ¯\\_(ツ)_/¯
+
+
+ - I never apologize. I\'m sorry but that\'s the way I am.
+ - All my life, I\'ve had one dream: to achieve my many goals.
+ - Good drink... good meat... good God, let\'s eat!
+ - You\'ll have to speak up. I\'m wearing a towel.
+ - Change the channel, Marge...
+ - Roads are just a suggestion Marge, just like pants.
+ - It takes two to lie - one to lie and one to listen.
+ - Shut up, Flanders!
+ - I\'m not normally a praying man, but if you\'re up there, please save me, Superman.
+ - It\'s so simple to be wise... just think of something stupid to say and then don\'t say it.
+ - Stupid sexy Flanders...
+ - I hope I didn\'t brain my damage.
+ - Operator, give me the number for 911!
+ - Don\'t blame me, I voted for Kodos.
+
+
diff --git a/res/values-bg-rBG/cr_strings.xml b/res/values-bg-rBG/cr_strings.xml
new file mode 100644
index 00000000000..8f0292e42fa
--- /dev/null
+++ b/res/values-bg-rBG/cr_strings.xml
@@ -0,0 +1,440 @@
+
+
+
+
+
+
+ Настройки за началния екран
+
+ Начален екран
+ Приложи Google now панела и още
+ Икони
+ Задай размер на икона и още
+ Списък с приложения
+ Персонализиране на менюто с приложения
+ Скорошни
+ Промени екрана за преглеждания
+ Разни
+ Други опции
+ Основни
+ Интерфейс
+ Бързо пространство
+ Лента за търсене
+ Бързи действия
+
+ Опции за разработчици
+ Активирайте някои скрити функции на собствен риск
+
+ Оформление на заключването
+ Икони и джаджи не могат да бъдат добавяни, премахвани и местени на началния екран
+ Икони и джаджи могат да бъдат добавяни, премахвани и местени на началния екран
+ Невъзможно е да се добавят джаджи към началния екран
+
+ Етикети на иконите на началния екран
+ Етикети на иконите в менюто с приложения
+
+ Плъзнете за достъп до Google
+
+ When you swipe left from main Home screen
+
+ When you swipe right from main Home screen
+
+ Използвай персонализирани икони в чекмеджето
+ Следва персонализирани икони от началният екран
+
+ Пауза
+ Временно спиране на %1$s?
+ Известията за %1$s ще бъдат временно спрени
+ Приложението е временно спряно
+ Приложението %1$s беше временно спряно от стартера
+ Спирай приложение
+ Работните приложения са изключени
+ Изключи работните приложения
+ Включване на работните приложения
+ Работните ти приложения не могат да ти изпращат известия, да използват батерията ти или да имат достъп до местоположението ти
+ Работните ти приложения не могат да получават обаждания или съобщения, да ти изпращат известия, да използват батерията ти или да имат достъп до местоположението ти
+
+ Предложения
+ За всички приложения & Предложения на началния екран
+
+ Рестартиране
+ Рестартирай launcher-а ръчно, за да приложиш всички чакащи настройки
+ Рестартиране на launcher-а...
+ Рестартиране на launcher-a за прилагане на промените...
+ Рестартиране на launcher-а за обновение на зомпоненти...
+
+ Двукратно почукване за заспиване
+ Направи двоен натиск при празно място на екрана, за да се изключи екранът
+ Вибрирай на двоен натиск за заспиване
+ Проведи тактилна обратна връзка на жест за двоен натиск за заспиване
+
+ Плъзни, за да започнеш търсене
+ Показвай клавиатурата винаги когато списъкът с приложения е видим
+
+ Размер на иконата
+
+ Размер на шрифта
+
+ Използване на 2 редове за етикета на приложенията
+
+ Височина на реда
+
+ Фон на лентата с любими приложения
+ Добавяне на прозрачен фон към лентата с любими приложения
+
+ Прозрачност на фона
+
+ Щриха
+
+ Спри
+ Приложението е убито
+
+ Търсене
+ Google Lens
+ Гласово търсене
+ Режим на ИИ
+ Търсене на музика
+ Започни търсене на музика при натискане на иконата на микрофона
+ Google лента за търсене
+ Покажи лентата за търсене на долната част на екрана
+ Тематични икони
+ Използване на тематични икони за лентата за търсене
+ Радиус на ъгъл
+
+ Местоположение на търсачката
+ Скрито
+ Горе
+ Отдолу
+
+ Превъртане на тапета
+ Ефект на превъртане на тапета за няколко екрана
+
+ Увеличаване на тапет
+ Увеличаване или намаляване на изображението по време на използване на менюто с приложения или скорошни приложения
+
+ Лента на състоянието
+
+ Горна сянка
+
+ %1$s налични | %2$s
+ Информация за паметта
+ Информация за ZRAM
+
+ Екранна снимка
+ Изчистване на всичко
+ Lens
+
+ Позволи къс паралакс
+ Активирайте ефекта на пълно превъртане на тапета върху по-малък брой страници, вместо да се изрязва
+ Центрирана единствена страница
+ Центрирай тапета ако се използва само една страница
+
+ Плъзгач
+ Плъзгача да е отстрани от чекмеджето
+
+ Тъмна лента на състоянието
+ Винаги използвай тъмна лента на състоянието на началния екран
+
+ Бърз поглед
+ Покажи в горната част на началния ви екран
+
+ Добро утро.
+ Добър вечер.
+ Добър ден.
+ Лека нощ.
+ Хубав ден.
+ Днес е
+ Това е
+
+ Неизвестен изпълнител
+ Текуща песен
+ От
+
+ Текуща песен
+ Покажи песента, която изпълняваш
+
+ Minimalistic style
+ Switch to modern style
+
+ Случайни изречения
+ Направете спътника си по-жив със случайни съобщения
+
+ - Добро утро!
+ - Добро утро, време е за рок!
+ - Какъв чудесен ден!
+ - Приятен ден!
+ - Какво ще кажете за малка 5-минутна дрямка?
+ - Нека вземем този хляб.
+ - Сутрините са ново платно за вашия ежедневен шедьовър.
+ - Прегръщайте изгрева на възможностите всяка сутрин.
+ - В ранните зори, намери силата да осветиш пътя си.
+ - Денят ви започва с безкрайни възможности.
+ - Всеки изгрев е напомняне, че можеш да започнеш отначало.
+ - Будни с цел, завладей предстоящия ден.
+ - Вашите мисли могат да бъдат основата на големи постижения.
+ - Светът се пробужда, както и вашият потенциал.
+ - Първата глава от вашето ежедневно приключение.
+ - Нека сутрешната ви рутина бъде стартовата площадка за успех.
+ - Изгрев или не, вашият потенциал винаги е във възход.
+ - Време е за свежи идеи и смели действия.
+ - Ранното пиле хваща червея, но ти можеш да сбъднеш мечтите си.
+ - Намерете смелостта да преследвате стремежите си.
+ - Стани и блести, защото величието те очаква.
+ - Най-доброто време да започнете е сега.
+
+
+ - Преследвай мечтите си, не минутите.
+ - Във всеки момент, нова възможност.
+ - Прегърнете пътуването, не само дестинацията.
+ - Днес е подарък, затова се нарича \"подарък\".
+ - Нека усмивката ти промени света.
+ - Животът е история; направете всяка глава важна.
+ - Намирайте радост в обикновените моменти.
+ - Най-хубавото тепърва предстои, продължавайте така.
+ - Вашият шанс да превърнете „ами ако“ в „какво е“.
+ - Грабнете деня, започвайки още сега.
+ - Щастието е избор, който можеш да направиш по всяко време.
+ - Вярвай в себе си, дори в един сънлив следобед.
+ - Нека действията ви говорят по-силно от часовника.
+ - Животът ти е това, което направиш от него; направи го невероятен.
+ - Мечтай смело, работете усилено и никога не се отказвай.
+ - Вие сте по-силни, от колкото си мислите.
+ - Всеки неуспех е подготовка за завръщане.
+ - Прегърнете пътуването, не само дестинацията.
+ - Преследвай целите си, а не страховете си.
+ - Вашият потенциал е безкраен; отключете го.
+
+
+ - Прегърнете настоящия момент, независимо от часа.
+ - Във всеки здрач, откривай нова зора на възможностите.
+ - Животът е пътешествие; наслаждавайте се на пейзажите по пътя.
+ - Вашето отношение оформя вашата реалност.
+ - Историята ти все още се пише, дори когато слънцето залязва.
+ - Уловете красотата в обикновените моменти.
+ - Време е за размисъл и пренастройка.
+ - Изберете радостта, независимо от времето на часовника.
+ - Възможностите не се появяват вечер.
+ - Бъди причината някой да се усмихва.
+ - Вашият потенциал не знае никакви граници.
+ - Магически моменти могат да се случат по всяко време.
+ - Животът ти е това, което направиш от него; направи го необикновен.
+ - Останете любопитни, останете благодарни.
+ - Бъди промяната, която искаш да видиш в света.
+ - Вярвай в себе си; ти си способен на невероятни неща.
+ - Единственото ограничение е това, което ти сам си поставяш.
+ - Успехът започва с една-единствена стъпка.
+
+
+ - Грабни нощта, защото тя крие свои собствени мистерии.
+ - В тишината на тази вечер, намерете спокойствие.
+ - Най-великите приключения в живота могат да започнат в тъмнината.
+ - Пътуването ви продължава дори след залез слънце.
+ - Нека нощта бъде твоето платно; нарисувай я с мечти.
+ - В тишината на тази вечер, намерете вътрешния си мир.
+ - Звездите блестят най-ярко в най-тъмните часове.
+ - Мечтайте смело, дори в късните часове на деня.
+ - Животът ти е история, която чака да бъде написана.
+ - Време е за размисъл и обновление.
+ - Късните нощни мисли могат да доведат до разкрития рано сутрин.
+ - Намерете радост в тишината на тази вечер.
+ - Нощното небе е необятно платно от сънища.
+ - Твоят потенциал не знае време за лягане; преследвай страстите си.
+ - Бъдете надеждни, дори в най-мрачните часове.
+ - В разгара на хаоса, намерете вътрешното си спокойствие.
+ - Бъдете позитивни, работете усилено, направете го да се случи.
+
+
+ - Нощите са тихият шепот на възможностите.
+ - В тишината на нощта, открийте вътрешната си сила.
+ - Прегърни тъмнината, защото тя държи ключовете към мечтите ти.
+ - Светът спи, но твоят потенциал е напълно буден.
+ - Нощта може да е късна, но амбициите ви са вечни.
+ - Бъдете любопитни, стойте будни и оставете нощите да ви водят.
+ - Намери смелостта да преследваш мечтите си.
+ - Великите неща никога не са идвали от зоните на комфорт.
+ - Вие имате силата да създадете собствената си съдба.
+ - Щастието е избор; избирай го всеки ден.
+ - Животът е такъв, какъвто си го направиш, затова го направи значим.
+ - Останете фокусирани, останете решителни, останете неудържими.
+ - Вие сте авторът на собствената си история.
+ - Стремете се към прогрес, а не към съвършенство.
+ - Всеки ден е ново начало; направете го страхотен.
+ - Вдъхновявайте другите, като бъдете автентичното си аз.
+ - Твоето отношение определя посоката ти.
+
+
+ - Невежеството е блаженство
+ - Време ли е вече за инсталиране на друга актуализация?
+ - Сключете мир, а не война
+ - О, хей, какво има?
+ - Добре дошли към истинската Матрица!
+ - Колко скрийншота правите?
+ - Отворете най-доброто с crDroid
+ - Сподели любов, не хаос
+ - Ние не започнахме огънят!
+ - Време за хубава музика
+ - \u003C\u003C\u003C\u003C\u003C\u003C\u003C ГЛАВА
+ - Необходим Ви е crDroid Premium за да използвате това
+ - Спомнете си Lineage на Unicorn
+ - Насладете се на Удължен живот на батерията
+ - Започнете от нулата
+ - Обичаме Ви, вече сме 3000
+ - Вкусно даже и без Суши
+ - Направете нещо хубаво днес
+ - Това е най-добрият Pixel, нали?
+ - Без илюзии, добре дошли в реалността!
+ - Благодарим ви за подкрепата
+ - Не е фестивал за пустиняци - само перфе
+ - Една от най-добрите разработки
+ - Разум за вашата параноя
+ - Качване в мир
+ - Какво прекрасно преживяване, нали?
+ - Еволюцията е мит, нали?
+ - Ти си това което показваш, не бъди ботор.
+ - Какво мислите?
+ - Чакам Патрона
+ - Уба Луба Дъб Дъб
+ - Победител, Победител ....?
+ - rm -rf \/
+ - Прингълс не са истински картофени чипс..
+ - Запомни да провериш нивото на батерията!
+ - Проверете си е-пощата/съобщения.
+ - Проверете списъка си със задачи, ако имате такъв.
+ - Можете да деактивирате Quickspace чрез настройките на началната страница.
+ - Обичате ли ни? Изпратете ни бира
+ - Най-добрият ром.
+ - Поздравете ни по Telegram
+ - Обичаме Ви, вече сме 3000
+
+ Не може да се намери календар или активност от часовника
+
+ Източник на времето
+ Авто
+ Google \"Умно Пространство\"
+ OmniJaws
+ Времето досега
+ Покажи сегашното време
+ 需要启用天气服务
+ Облачно
+ Дъждовно
+ Sunny
+ Бурно време
+ Снежно
+ Ветровито
+ Мъгливо
+ Текущо местоположение
+ 显示当前位置的天气
+ Текущо състояние
+ Текстово описание на времето
+
+ Индикатор за автоматично скриване на страницата
+ Скрий точките на индикатор за на страницата автоматично
+
+ Замъгленост на тапета
+ Задаване на ниво на замъгляване за скорошни приложения и чекмедже с приложения
+
+ Информация за приложението
+ Източник
+ Система
+ Последно обновяване
+ Версия
+ Още
+
+ Пакет икони
+ По подразбиране
+ Инсталиране на още
+ Няма наличен магазин за приложения
+
+ Въртележка на тапети
+ Бързо смени тапети от менюто за дълго задържане
+ Приложи на закл. екран
+ Приложи същия тапет на закл. екран
+
+ Камъче
+ Съд
+ Остър квадрат
+ Комбинация от кръг и квадрат
+ Скосен правоъгълник
+ Сълза
+ Закръглен правоъгълник
+ Облачно
+ Цилиндрично
+ Цвете
+ Сърце
+ Шестоъгълник
+ Заоблен шестоъгълник
+ Стил на iOS
+ Листо
+ Мяу
+ Стил на Samsung
+ Разтегнато
+
+ Тъмни етикети на приложения
+ Винаги използвай тъмен текст за етикети на приложения в шкафчето
+
+ Стил на шкафчето на приложения
+ Нормално (долен лист)
+ Стар вид на пълния екран
+ Стил на списъка
+ Стил по OneUI (страници)
+
+ Подреждане на шкафчето с приложения
+ Подреди по азбучен ред
+ Подреди по време на инсталация
+ Подреди по използване
+
+ Персонализиран фонов цвят
+ Използвай собствен цвят за фона на шкафчето
+ Цвят по светъл режим
+ Цвят по тъмен режим
+
+ HSB
+ RGB
+ Нюанс
+ Наситеност на цвета
+ Яркост
+ Червено
+ Зелено
+ Синьо
+ HEX цвят
+
+ Стил от Pixel на търсачка
+ Пренаправена иконка за търсачка с фон на слой
+ Видимостта на външния слой
+
+ Скрити & Защитени приложения
+ Отключете за да управлявате скритите и защитените приложения
+ Идентифицирайте се за да отворите %1$s
+ Зарежда се\u2026
+ Моля, задайте сигурно заключване на екрана за да ограничи достъпа до дадено приложение
+ Помощ
+ Скритите приложения и техните приспособления са скрити от менюто с приложения
+ Защитените приложения изискват удостоверяване за да бъдат отворени от стартовия панел
+
+ Стил на скорошните
+ По подразбиране
+ Ефект на разширяване
+ Kami стил
+ стил по iOS
+ Стил по OxygenOS
+
+ Заключи/Отключи приложение
+ Отключено приложение в скорошните
+ Заключено приложение в скорошните
+
+ Scroll vibration
+
diff --git a/res/values-ca-rES/cr_strings.xml b/res/values-ca-rES/cr_strings.xml
new file mode 100644
index 00000000000..58afce7fa68
--- /dev/null
+++ b/res/values-ca-rES/cr_strings.xml
@@ -0,0 +1,440 @@
+
+
+
+
+
+
+ Configuració de la pantalla d\'inici
+
+ Pàgina d\'inici
+ Configura el panel de Google Now i altres
+ Icones
+ Configura la mida i altres opcions de les icones
+ Calaix d\'aplicacions
+ Personalitzeu el calaix d\'aplicacions
+ Recents
+ Revamp the overview screen
+ Miscel·lània
+ Altres opcions
+ General
+ Interfície
+ Quickspace
+ Barra de cerca
+ Accions ràpides
+
+ Opcions de desenvolupament
+ Activa algunes característiques ocultes baix la teva responsabilitat
+
+ Bloqueja la disposició
+ Icones i widgets no poden ser afegits, eliminats o moguts en la pantalla d\'inici
+ Icones i widgets poden ser afegits, eliminats o moguts en la pantalla d\'inici
+ No és possible afegir widgets a la pantalla d\'inici
+
+ Noms d\'icones a l\'escriptori
+ Nom d\'aplicacions al caixó
+
+ Fes lliscar el dit per accedir a l\'app de Google
+
+ Quan feu lliscar el dit cap a l\'esquerra des de la pantalla principal d\'inici
+
+ Quan feu lliscar el dit cap a la dreta des de la pantalla principal d\'inici
+
+ Use icon customizations in drawer
+ Follow icon customizations used on home screen
+
+ Posa en pausa
+ Posa en pausa %1$s?
+ Les notificacions de %1$s es posaran en pausa
+ Aplicació pausada
+ S\'ha posat en pausa %1$s directament des del llançador
+ Posa en pausa l\'aplicació
+ Les aplicacions de treball estan desactivades
+ Desactiva les aplicacions de treball
+ Activa les aplicacions de treball
+ Your work apps cannot send you notifications, use your battery, or access your location
+ Your work apps cannot receive phone calls or text messages, send you notifications, use your battery, or access your location
+
+ Suggeriments
+ For All Apps & Home screen suggestions
+
+ Reinicia
+ Reinicia el llançador manualment per aplicar qualsevol canvi pendent
+ Reiniciant el llançador...
+ Reiniciant el llançador per aplicar els canvis...
+ Reiniciant el llançador per a actualitzar els seus components...
+
+ Toca dues vegades per a posar en repòs
+ Double tap empty space on home screen to turn off the screen
+ Vibrate on double tap to sleep
+ Provide haptic feedback on double tap to sleep gesture
+
+ Swipe to start search
+ Open keyboard whenever app drawer becomes visible
+
+ Mida de les icones
+
+ Mida de la lletra
+
+ Utilitzar dues línies per als noms de les aplicacions
+
+ Alçada de fila
+
+ Hotseat background
+ Afegeix fons translúcid al dock d\'aplicacions
+
+ Opacitat del fons
+
+ Traça
+
+ Atura
+ S\'ha aturat l\'aplicació
+
+ Cerca
+ Google Lens
+ Cerca per veu
+ AI mode
+ Cercador de música
+ Inicia la cerca de música en prémer la icona del micròfon
+ Barra de cerca de Google
+ Barra de cerca al dock inferior
+ Icones tematitzades
+ Utilitza icones tematitzades a la barra de cerca
+ Radi de la cantonada
+
+ App search bar location
+ Hidden
+ Top
+ Bottom
+
+ Desplaçament de fons de pantalla
+ Wallpaper scrolling effect for multiple screens
+
+ Zoom del fons de pantalla
+ Fer zoom in o zoom out al fons de pantalla al utilitzar el caixó o les aplicacions recents
+
+ Barra d\'estat
+
+ Ombra superior
+
+ %1$s Disponible | %2$s Total
+ Informació de la memòria
+ ZRAM info
+
+ Captura de pantalla
+ Esborra-ho tot
+ Lent
+
+ Allow short parallax
+ Enable full wallpaper scroll effect on smaller numbers of pages instead of cropping the wallpaper
+ Single page center
+ Center wallpaper if only using a single page
+
+ Barra de desplaçament
+ Scrollbar on side of the app drawer
+
+ Dark status bar
+ Always use dark status bar on home screen
+
+ D\'una ullada
+ Show at the top of your home screen
+
+ Bon dia.
+ Bona nit.
+ Bona vesprada.
+ Bona nit.
+ Bon día.
+ Hui és
+ És
+
+ Artista desconegut
+ S\'està reproduint
+ Per
+
+ S\'està reproduint
+ Mostra la cançó en reproducció
+
+ Minimalistic style
+ Switch to modern style
+
+ Missatges aleatoris
+ Make your companion more lively with random messages
+
+ - Bon dia!
+ - Bon dia, moment de moure\'s!
+ - Quin dia més bonic!
+ - Que tingues un bon dia!
+ - Què et sembla fer una migdiada de cinc minutets?
+ - Anem a guanyar-nos el pa.
+ - Mornings are a fresh canvas for your daily masterpiece.
+ - Embrace the sunrise of opportunity each morning.
+ - In the early light, find the power to illuminate your path.
+ - Your day begins with endless possibilities.
+ - Every sunrise is a reminder that you can start anew.
+ - Awake with purpose, conquer the day ahead.
+ - Your thoughts can be the foundation of great achievements.
+ - The world awakens, and so does your potential.
+ - The first chapter in your daily adventure.
+ - Let your morning routine be the launchpad for success.
+ - Sunrise or not, your potential is always on the rise.
+ - It\'s time for fresh ideas and bold actions.
+ - The early bird catches the worm, but you can catch your dreams.
+ - Find the courage to chase your aspirations.
+ - Rise and shine, for greatness awaits.
+ - The best time to start is now.
+
+
+ - Chase your dreams, not the minutes.
+ - In every moment, a new opportunity.
+ - Embrace the journey, not just the destination.
+ - Today is a gift, that\'s why it\'s called the present.
+ - Let your smile change the world.
+ - Life is a story; make each chapter count.
+ - Find joy in the ordinary moments.
+ - The best is yet to come, keep it up.
+ - Your chance to turn \'what if\' into \'what is.\'
+ - Seize the day, starting right now.
+ - Happiness is a choice you can make any time.
+ - Believe in yourself, even on a sleepy afternoon.
+ - Let your actions speak louder than the clock.
+ - Your life is what you make of it; make it amazing.
+ - Dream big, work hard, and never give up.
+ - You are stronger than you think.
+ - Every setback is a setup for a comeback.
+ - Embrace the journey, not just the destination.
+ - Chase your goals, not your fears.
+ - Your potential is endless; unlock it.
+
+
+ - Embrace the present moment, no matter the hour.
+ - In every dusk, find a new dawn of opportunity.
+ - Life is a journey; enjoy the scenery along the way.
+ - Your attitude shapes your reality.
+ - Your story is still being written, even as the sun sets.
+ - Capture the beauty in the ordinary moments.
+ - It\'s time to reflect and reset.
+ - Choose joy, no matter the time on the clock.
+ - Opportunities don\'t clock out in the evening.
+ - Be the reason someone smiles.
+ - Your potential knows no bounds.
+ - Moments of magic can happen at any hour.
+ - Your life is what you make of it; make it extraordinary.
+ - Stay curious, stay grateful.
+ - Be the change you wish to see in the world.
+ - Believe in yourself; you are capable of amazing things.
+ - The only limit is the one you set for yourself.
+ - Success begins with a single step.
+
+
+ - Seize the night, for it holds its own mysteries.
+ - In the quiet of this evening, find serenity.
+ - Life\'s greatest adventures can start in the darkness.
+ - Your journey continues even after the sun has set.
+ - Let the night be your canvas; paint it with dreams.
+ - In the stillness of this evening, find your inner peace.
+ - Stars shine brightest in the darkest hours.
+ - Dream big, even in the late hours of the day.
+ - Your life is a story waiting to be written.
+ - It\'s time for reflection and renewal.
+ - Late night thoughts can lead to early morning revelations.
+ - Find joy in the stillness of this evening.
+ - The night sky is a vast canvas of dreams.
+ - Your potential knows no bedtime; pursue your passions.
+ - Stay hopeful, even in the darkest hours.
+ - In the midst of chaos, find your inner calm.
+ - Stay positive, work hard, make it happen.
+
+
+ - Nights are the quiet whispers of opportunity.
+ - In the stillness of the night, find your inner strength.
+ - Embrace the darkness, for it holds the keys to your dreams.
+ - The world sleeps, but your potential is wide awake.
+ - The night may be late, but your ambitions are timeless.
+ - Stay curious, stay awake, and let nights guide you.
+ - Find the courage to chase your dreams.
+ - Great things never come from comfort zones.
+ - You have the power to create your own destiny.
+ - Happiness is a choice; choose it every day.
+ - Life is what you make it, so make it count.
+ - Stay focused, stay determined, stay unstoppable.
+ - You are the author of your own story.
+ - Strive for progress, not perfection.
+ - Every day is a new beginning; make it a great one.
+ - Inspire others by being your authentic self.
+ - Your attitude determines your direction.
+
+
+ - La ignorància és una sort
+ - Ja és el moment d\'instal·lar una altra actualització?
+ - Fes l\'amor i no la guerra
+ - Ei, com estàs?
+ - Welcome to real Matrix!
+ - Quantes captures de pantalla fas?
+ - Descobreix el millor amb AlphaDroid
+ - Spread love, not havoc
+ - We didn\'t start the fire!
+ - Moment d\'escoltar un poc de bona música
+ - \u003C\u003C\u003C\u003C\u003C\u003C\u003C CAP
+ - Necessites AlphaDroid Prèmium per a poder vore açò
+ - Recorda el llinatge de l\'unicorn
+ - Gaudeix de la vida extesa de la bateria
+ - Començant des de zero
+ - T\'estimem 3000
+ - Delicious even without Sushi
+ - Fes alguna cosa bona avui
+ - Aquesta és la millor Pixel Experience, oi?
+ - Res d\'il·lusions, benvingut a la realitat!
+ - Gràcies pel teu suport
+ - No hi ha festivals per als babaus - només perfecció!
+ - Una de les millors tries del «buildbot»
+ - Trellat per a la teva paranoia
+ - Rising in peace
+ - Quina experiència més plaentera, veritat?
+ - L\'evolució és un mite, veritat?
+ - Ets allò que instal·les, així que tingues trellat
+ - En què estàs pensant?
+ - Expecto Patronum
+ - Wubba Lubba Dub Dub
+ - Ganador Ganador ....?
+ - rm -rf \/
+ - Pringles aren\'t actually potato chips..
+ - Remember to check device\'s battery level!
+ - Check your email/messages.
+ - Check your to-do list if you have one.
+ - You can disable quickspace via home settings.
+ - T\'agrada el AlphaDroid? Convida als desenvolupadors a una cervesa
+ - La millor «ROM» del món.
+ - Saluda\'ns en Telegram
+ - T\'estimem 3000
+
+ No és possible trobar el calendari o l\'activitat del rellotge
+
+ Weather provider
+ Auto
+ Google Smartspace
+ OmniJaws
+ Weather update
+ Display current weather update
+ És necessari que el servei meteorològic estigui activat
+ Cloudy
+ Rainy
+ Solejat
+ Tempestuós
+ Nevat
+ Ventós
+ Emboirat
+ Ubicació actual
+ Mostra la ubicació del temps meteorològic actual
+ Condició actual
+ Mostra l\'oratge actual
+
+ Auto-hide page indicator
+ Hide the page indicator dots automatically
+
+ Desenfocament de fons
+ Set blur depth level for recents and app drawer
+
+ Application info
+ Origen
+ Sistema
+ Darrera actualització
+ Versió
+ Més
+
+ Paquet d\'icones
+ Per defecte
+ Instal·la\'n més
+ No hi ha cap botiga d\'aplicacions disponible
+
+ Wallpaper carousel
+ Quickly switch wallpapers from the long-press menu
+ Apply to lock screen
+ Set the same wallpaper on the lock screen
+
+ Pebble
+ Vessel
+ Pure square
+ Squircle
+ Tapered rect
+ Teardrop
+ Rounded rect
+ Cloudy
+ Cylindrical
+ Flower
+ Heart
+ Hexagon
+ Rounded hexagon
+ iOS Style
+ Leaf
+ Meow
+ Samsung Style
+ Stretched
+
+ Dark app labels
+ Always use dark text for app labels in the app drawer
+
+ App drawer style
+ Normal (bottom sheet)
+ Legacy fullscreen
+ List style
+ OneUI style (paged)
+
+ App drawer sorting
+ Sort alphabetically
+ Sort by install date
+ Sort by usage
+
+ Custom background color
+ Use a custom color for the app drawer background
+ Light mode color
+ Dark mode color
+
+ HSB
+ RGB
+ Hue
+ Saturation
+ Brightness
+ Red
+ Green
+ Blue
+ HEX
+
+ Pixel style search bar
+ Revamped search bar with layered background
+ Outer layer opacity
+
+ Aplicacions amagades i protegides
+ Desbloqueja per gestionar les aplicacions amagades i protegides
+ Autentifica\'t per obrir %1$s
+ S\'està carregant\u2026
+ Si us plau, configura un bloqueig de pantalla segur per a restringir l\'accés a aplicacions
+ Ajuda
+ Les aplicacions amagades i els seus widgets estan amagades del calaix
+ Les aplicacions protegides requereixen autentificació per poder ser obertes des del llançador
+
+ Recents layout style
+ Per defecte
+ Scale effect
+ Kami style
+ iOS style
+ OxygenOS style
+
+ Lock/Unlock app
+ Unlocked app in recents
+ Locked app in recents
+
+ Scroll vibration
+
diff --git a/res/values-cs-rCZ/alpha_strings.xml b/res/values-cs-rCZ/alpha_strings.xml
new file mode 100644
index 00000000000..e8660a28519
--- /dev/null
+++ b/res/values-cs-rCZ/alpha_strings.xml
@@ -0,0 +1,44 @@
+
+
+
+
+ Lock/Unlock app
+ Unlocked app in recents
+ Locked app in recents
+
+ Recents layout style
+ Stock
+ Kami Style
+ iOS Style
+ OxygenOS Style
+
+ - Ignorance is Bliss
+ - Make love, not war
+ - Oh hey, what\'s up?
+ - We love you 3000
+ - Thank you for your support
+ - One of the buildbot\'s best picks
+ - Pringles aren\'t actually potato chips..
+ - You can disable quickspace via home settings.
+ - I got nothing ¯\\_(ツ)_/¯
+
+
+ - I never apologize. I\'m sorry but that\'s the way I am.
+ - All my life, I\'ve had one dream: to achieve my many goals.
+ - Good drink... good meat... good God, let\'s eat!
+ - You\'ll have to speak up. I\'m wearing a towel.
+ - Change the channel, Marge...
+ - Roads are just a suggestion Marge, just like pants.
+ - It takes two to lie - one to lie and one to listen.
+ - Shut up, Flanders!
+ - I\'m not normally a praying man, but if you\'re up there, please save me, Superman.
+ - It\'s so simple to be wise... just think of something stupid to say and then don\'t say it.
+ - Stupid sexy Flanders...
+ - I hope I didn\'t brain my damage.
+ - Operator, give me the number for 911!
+ - Don\'t blame me, I voted for Kodos.
+
+
diff --git a/res/values-cs-rCZ/cr_strings.xml b/res/values-cs-rCZ/cr_strings.xml
new file mode 100644
index 00000000000..237f54a6015
--- /dev/null
+++ b/res/values-cs-rCZ/cr_strings.xml
@@ -0,0 +1,440 @@
+
+
+
+
+
+
+ Nastavení domovské obrazovky
+
+ Domovská obrazovka
+ Nastavení panelu Google\u00a0Now a\u00a0dalších funkcí
+ Ikony
+ Nastavit velikost ikon a\u00a0další parametry
+ Seznam aplikací
+ Přizpůsobte si svůj seznam aplikací
+ Nedávné aplikace
+ Změnit styl obrazovky nedávných aplikací
+ Různé
+ Další možnosti
+ Obecné
+ Rozhraní
+ QuickSpace
+ Vyhledávací panel
+ Rychlé akce
+
+ Možnosti pro vývojáře
+ Zde můžete na vlastní riziko povolit některé skryté funkce
+
+ Zamknout rozložení
+ Ikony a widgety nelze přidat, odebrat a přesunout na domovské obrazovce
+ Ikony a widgety lze přidat, odebrat a přesunout na domovské obrazovce
+ Nelze přidat widgety na domovskou obrazovku
+
+ Popisky ikon na ploše
+ Popisky ikon v\u00a0seznamu aplikací
+
+ Přejetím přejít do aplikace Google
+
+ Při gestu přejetím vlevo z\u00a0domovské obrazovky
+
+ Při gestu přejetím vpravo z\u00a0domovské obrazovky
+
+ Use icon customizations in drawer
+ Follow icon customizations used on home screen
+
+ Pozastavit
+ Pozastavit %1$s?
+ Oznámení pro %1$s budou pozastavena
+ Aplikace pozastavena
+ Aplikace %1$s byla přímo pozastavena ze spouštěče
+ Pozastavit aplikaci
+ Pracovní aplikace jsou vypnuty
+ Vypnout pracovní aplikace
+ Zapnout pracovní aplikace
+ Pracovní aplikace vám nemohou odesílat oznámení, využívat baterii nebo přistupovat k vaší poloze
+ Pracovní aplikace vám nemohou odesílat oznámení, přijímat telefonní hovory nebo textové zprávy, využívat baterii nebo přistupovat k vaší poloze
+
+ Návrhy
+ Pro všechny aplikace a\u00a0návrhy na domácí obrazovce
+
+ Restartovat
+ Chcete-li aplikovat všechna čekající nastavení, restartujte spouštěč ručně
+ Restartování spouštěče…
+ Restartování spouštěče pro použití změn…
+ Restartování spouštěče pro aktualizaci komponent spouštěče…
+
+ Uspat dvojím poklepáním
+ Dvojím poklepáním na prázdné místo na domovské obrazovce vypnout displej
+ Zavibrovat při dvojím poklepání pro přechod do režimu spánku
+ Poskytuje hmatovou odezvu při použití gesta dvojím poklepání pro přechod do režimu spánku
+
+ Přejetím začít vyhledávat
+ Otevřít klávesnici pokaždé, když je zobrazen seznam aplikací
+
+ Velikost ikon
+
+ Velikost písma
+
+ Použít dva řádky pro popisek aplikace
+
+ Výška řádku
+
+ Pozadí Hotseat
+ Přidat průsvitné pozadí do doku aplikace
+
+ Neprůhlednost pozadí
+
+ Tah
+
+ Ukončit
+ Aplikace ukončena
+
+ Vyhledat
+ Google Lens
+ Hlasové vyhledávání
+ Režim AI
+ Vyhledání hudby
+ Spustit vyhledání hudby při stisknutí ikony mikrofonu
+ Vyhledávací panel Google
+ Vyhledávací lišta ve spodním doku
+ Ikony motivů
+ Použít ikony motivů pro vyhledávací panel
+ Poloměr rohu
+
+ Umístění vyhledávacího panelu aplikace
+ Skryté
+ Nahoře
+ Dole
+
+ Posuv tapety
+ Efekt posunu tapety pro více obrazovek
+
+ Přiblížení tapety
+ Přiblížit nebo oddálit tapetu při použití seznamu aplikací nebo nedávných aplikací
+
+ Stavový řádek
+
+ Horní stín
+
+ %1$s k dispozici | %2$s
+ Informace o\u00a0paměti
+ Informace o ZRAM
+
+ Snímek obrazovky
+ Vymazat vše
+ Lens
+
+ Povolit krátkou paralaxu
+ Povolit efekt posouvání celé tapety na menším počtu stránek namísto oříznutí tapety
+ Vycentrovat jedinou stránku
+ Vycentrovat tapetu pouze při použití jedné stránky
+
+ Posuvník
+ Posuvník po straně seznamu aplikací
+
+ Tmavý stavový řádek
+ Na domovské obrazovce vždy používat tmavý stavový řádek
+
+ Přehled událostí
+ Zobrazit v horní části Domovské obrazovky
+
+ Dobré ráno.
+ Dobrý večer.
+ Dobré odpoledne.
+ Dobrou noc.
+ Dobrý den.
+ Dnes je
+ Je
+
+ Neznámý umělec
+ Nyní se přehrává
+ Od
+
+ Nyní se přehrává
+ Zobrazit skladbu, která se právě přehrává
+
+ Minimalistický styl
+ Přepnout na moderní styl
+
+ Náhodné zprávy
+ Udělejte svého společníka živějším pomocí náhodných zpráv
+
+ - Dobré ránko!
+ - Dobré ráno! Je načase vstávat a\u00a0cvičit!
+ - Dneska je venku hezky!
+ - Měj se fanfárově!
+ - Což takhle si ještě na 5\u00a0minut zdřímnout?
+ - Pojď na to, zlato.
+ - Rána jsou čistým plátnem pro tvá každodenní umělecká díla.
+ - Každé ráno se chop východu příležitosti.
+ - Najdi sílu osvětlovat svoji cestu v\u00a0raném světle.
+ - Tvůj den začíná nekonečnými možnostmi.
+ - Každý východ slunce je připomínkou, že můžeš začít znovu.
+ - Probuď se s posláním, dobyj den před sebou.
+ - Tvé myšlenky mohou být základem velkých úspěchů.
+ - Svět se probouzí, a\u00a0stejně tak tvůj potenciál.
+ - První kapitola ve tvém každodenním dobrodružství.
+ - Nechť je tvá ranní rutina spouštěčem pro úspěch.
+ - Ať již východ slunce je či není, tvůj potenciál je vždy na vzestupu.
+ - Je čas na nové nápady a\u00a0odvážné činy.
+ - Ranní ptáče chytá červy, ale ty můžeš chytit své sny.
+ - Najdi odvahu usilovat o\u00a0své touhy.
+ - Vstávat a\u00a0cvičit, chystají se velké věci.
+ - Nejlepší čas začít nastal nyní.
+
+
+ - Proháněj se za svými sny, ne za minutami.
+ - V každém okamžiku je nová příležitost.
+ - Vydej se na cestu, ne jen k\u00a0cíli.
+ - Dnešek je dar, proto se mu říká přítomnost (the present).
+ - Nech tvůj úsměv změnit svět.
+ - Život je příběh. Tak ať každá kapitola stojí za to.
+ - Najdi radost v\u00a0běžných okamžicích.
+ - To nejlepší teprve přijde. Pokračuj v\u00a0tom, co děláš.
+ - Tvoje šance proměnit „co kdyby“ na „co je“.
+ - Využij den smysluplně, začni právě teď.
+ - Štěstí je volba, kterou můžeš udělat kdykoliv.
+ - Věř si i při ospalém odpoledni.
+ - Nechť tvoje činy mluví hlasitěji než hodiny.
+ - Tvůj život je takový, jaký si ho uděláš. Učiň jej úžasným.
+ - Sni o\u00a0velkých věcech, tvrdě pracuj, a\u00a0nikdy se nevzdávej.
+ - Jsi silnější, než si myslíš.
+ - Každý nezdar je šance pro návrat.
+ - Vydej se na cestu, ne jen k\u00a0cíli.
+ - Proháněj se za svými cíli, ne za svými obavami.
+ - Tvůj potenciál je nekonečný; odemkni jej.
+
+
+ - Přijmi současný okamžik bez ohledu na hodinu.
+ - V každém šeru najdi úsvit nové příležitosti.
+ - Život je jízda; užij si krajinu podél cesty.
+ - Tvůj přístup formuje tvoji realitu.
+ - Tvůj příběh je stále psán, i\u00a0když slunce zapadá.
+ - Zachyť krásu běžných okamžiků.
+ - Je čas se zamyslet a\u00a0začít od znova.
+ - Zvol si radost, bez ohledu na čas na hodinách.
+ - Příležitosti nevyprší k\u00a0večeru.
+ - Buď důvodem, proč se někdo usměje.
+ - Tvůj potenciál nezná žádné hranice.
+ - Kouzelné chvíle se mohou stát ve kteroukoliv hodinu.
+ - Tvůj život je takový, jaký si ho uděláš. Učiň jej neobyčejným.
+ - Zůstaň zvědavý, zůstaň vděčný.
+ - Staň se změnou, kterou si přeješ vidět ve světě.
+ - Věř si, dokážeš úžasných věcí.
+ - Jediný limit je ten, který sis stanovil(a) pro sebe.
+ - Úspěch začíná jediným krokem.
+
+
+ - Využij noc, protože drží svá vlastní tajemství.
+ - Najdi pokoj v\u00a0klidu dnešního večera.
+ - Největší dobrodružství života mohou začít ve tmě.
+ - Tvá cesta pokračuje i\u00a0po západu slunce.
+ - Nechť je noc tvým plátnem; vymaluj jej sny.
+ - Najdi svůj vnitřní mír v\u00a0klidu dnešního večera.
+ - V nejtemnějších hodinách září hvězdy nejjasněji.
+ - Sni o\u00a0velkých věcech, i\u00a0v\u00a0pozdních hodinách dne.
+ - Tvůj život je příběh, který čeká na napsání.
+ - Je čas na zamyšlení a\u00a0obnovu.
+ - Pozdní noční myšlenky mohou vést k\u00a0časně ranním objevům.
+ - Najdi radost ve stálosti dnešního večera.
+ - Noční obloha je rozsáhlé plátno snů.
+ - Tvůj potenciál nezná čas na odpočinek; usiluj o\u00a0své vášně.
+ - I v nejtemnějších hodinách zůstaň v\u00a0naději.
+ - Uprostřed chaosu najdi svůj vnitřní klid.
+ - Zůstaň pozitivní, tvrdě pracuj, uskutečni to.
+
+
+ - Noci jsou tichým šepotem příležitosti.
+ - Najdi svou vnitřní sílu v\u00a0klidu této noci.
+ - Využij temnotu, protože drží klíče ke tvým snům.
+ - Svět spí, zatímco tvůj potenciál bdí.
+ - Noc může být pokročilá, ale tvé ambice jsou věčné.
+ - Zůstaň zvědavý, zůstaň vzhůru, a\u00a0nech noci tě vést.
+ - Najdi odvahu usilovat o\u00a0své sny.
+ - Skvělé věci nikdy nepocházejí z\u00a0komfortních zón.
+ - Máš tu moc vytvářet svůj vlastní osud.
+ - Štěstí je volba; zvol si ji každý den.
+ - Život je takový, jaký si ho uděláš. Tak ať stojí za to.
+ - Zůstaň soustředěný, zůstaň odhodlaný, zůstaň nezastavitelný.
+ - Jsi autorem svého vlastního příběhu.
+ - Usiluj o\u00a0pokrok, nikoliv dokonalost.
+ - Každý den je nový začátek; udělej jej skvělý.
+ - Inspiruj ostatní tím, že budete svým autentickým já.
+ - Tvůj postoj určuje tvé směřování.
+
+
+ - Nevědomost je síla
+ - Že už by bylo načase flashnout další aktualizaci?
+ - Udržujte mír, ne válku
+ - Hej hola, jak je?
+ - Vítej ve skutečném Matrixu!
+ - Kolik snímků obrazovky vytvoříš?
+ - S AlphaDroidem zpřístupníš lecjaké vychytávky
+ - Šiř lásku, nikoliv chaos
+ - Nezahájili jsme palbu!
+ - Je čas na nějakou dobrou hudbu
+ - \u003C\u003C\u003C\u003C\u003C\u003C\u003C HROT
+ - Pro zobrazení tohoto obsahu potřebuješ mít AlphaDroid Premium
+ - Pamatuj na Lineage of the Unicorn
+ - Užij si Xtended výdrž baterie
+ - Začínáme od píky
+ - Milujeme tě 3000
+ - Vynikající i bez Sushi
+ - Vykonej dneska něco dobrého
+ - Toto je nejlepší Pixel Experience, že?
+ - Žádné iluze, vítej ve tvrdé realitě!
+ - Děkujeme ti za podporu
+ - Blbosti zde nemají místo\u00a0– pouze dokonalost!
+ - Jeden z\u00a0nejlepších výběrů buildbota
+ - Trochu Normálnosti pro tvou Paranoiu
+ - Povstáváme v míru
+ - Úžasný zážitek, že?
+ - Evoluce je mýtus, že?
+ - Jsi to, co flashuješ. Nebuď labuť
+ - Co se ti honí hlavou?
+ - Expecto Patronum
+ - Wubba Lubba Dub Dub
+ - Vítěz vítěz ....?
+ - rm -rf \/
+ - Pringles ve skutečnosti nejsou bramborové chipsy.
+ - Nezapomeň kontrolovat úroveň baterie zařízení!
+ - Zkontroluj svůj e‑mail a\u00a0zprávy.
+ - Zkontroluj svůj seznam věcí k\u00a0dokončení, pokud nějaký máš.
+ - Quickspace můžeš zakázat v\u00a0nastavení Domovské obrazovky.
+ - Máte nás rádi? Pošlete nám příspěvek na pivo :)
+ - Vůbec nejlepší ROM.
+ - Pozdravte nás na Telegramu
+ - Milujeme tě 3000
+
+ Nelze najít aktivitu kalendáře nebo hodin
+
+ Poskytovatel počasí
+ Automaticky
+ Google Smartspace
+ OmniJaws
+ Aktualizace počasí
+ Zobrazit aktuální aktualizaci počasí
+ Vyžaduje povolení služby počasí
+ Oblačno
+ Deštivo
+ Slunečno
+ Bouřka
+ Sněžení
+ Větrno
+ Mlhavo
+ Aktuální poloha
+ Zobrazit aktuální polohu počasí
+ Aktuální stav
+ Zobrazit souhrn aktuálních podmínek počasí
+
+ Automaticky skrýt ukazatel stránky
+ Automaticky skrývat tečky ukazatele stránky
+
+ Hloubka rozostření pozadí
+ Nastavit úroveň hloubky rozostření nedávných aplikací a\u00a0seznamu aplikací
+
+ Informace o aplikaci
+ Zdroj
+ Systém
+ Poslední aktualizace
+ Verze
+ Více
+
+ Balíček ikon
+ Výchozí
+ Instalovat další
+ Není k\u00a0dispozici žádný obchod s\u00a0aplikacemi
+
+ Povolit Denní tapetu
+ Rychlé přepnutí tapet z menu dlouhého stisku
+ Použít na zamykací obrazovku
+ Nastavit stejnou tapetu na zamykací obrazovce
+
+ Oblázek
+ Vessel
+ Čtverec
+ Superelipsa
+ Zúžený obdélník
+ Slza
+ Zaoblený čtverec
+ Oblačno
+ Válcový
+ Květina
+ Srdce
+ Šestiúhelník
+ Zaoblený šestiúhelník
+ Ve stylu iOS
+ List
+ Mňau
+ Ve stylu Samsungu
+ Natažený
+
+ Tmavé popisky aplikací
+ Vždy používat v seznamu aplikací tmavý text pro popisky aplikací
+
+ Styl seznamu aplikací
+ Normální (spodní list)
+ Legacy celá obrazovka
+ Styl seznamu
+ Styl OneUI (stránkový)
+
+ App drawer sorting
+ Seřadit podle abecedy
+ Seřadit podle data instalace
+ Seřadit podle využití
+
+ Vlastní barva pozadí
+ Use a custom color for the app drawer background
+ Light mode color
+ Dark mode color
+
+ HSB
+ RGB
+ Odstín
+ Sytost
+ Jas
+ Červená
+ Zelená
+ Modrá
+ HEX
+
+ Vyhledávací panel ve stylu Pixelů
+ Přepracovaný vyhledávací panel s vrstveným pozadím
+ Outer layer opacity
+
+ Skryté a\u00a0zabezpečené aplikace
+ Pro správu skrytých a\u00a0zabezpečených aplikací nejdříve odemkněte obrazovku
+ Proveďte ověření pro spuštění aplikace %1$s
+ Načítání\u2026
+ Pro omezení přístupu k\u00a0aplikaci prosím nastavte zabezpečenou obrazovku uzamčení
+ Nápověda
+ Skryté aplikace, včetně jejich widgetů, jsou skryty ze seznamu aplikací
+ Zabezpečené aplikace vyžadují ověření, aby mohly být otevřeny ze spouštěče
+
+ Styl rozložení nedávných aplikací
+ Výchozí
+ Efekt měřítka
+ Styl Kami
+ Styl iOS
+ Styl OxygenOS
+
+ Zamknout/odemknout aplikaci
+ Odemknutá aplikace v nedávných aplikacích
+ Zamknutá aplikace v nedávných aplikacích
+
+ Vibrace při posouvání
+
diff --git a/res/values-de-rDE/alpha_strings.xml b/res/values-de-rDE/alpha_strings.xml
new file mode 100644
index 00000000000..e8660a28519
--- /dev/null
+++ b/res/values-de-rDE/alpha_strings.xml
@@ -0,0 +1,44 @@
+
+
+
+
+ Lock/Unlock app
+ Unlocked app in recents
+ Locked app in recents
+
+ Recents layout style
+ Stock
+ Kami Style
+ iOS Style
+ OxygenOS Style
+
+ - Ignorance is Bliss
+ - Make love, not war
+ - Oh hey, what\'s up?
+ - We love you 3000
+ - Thank you for your support
+ - One of the buildbot\'s best picks
+ - Pringles aren\'t actually potato chips..
+ - You can disable quickspace via home settings.
+ - I got nothing ¯\\_(ツ)_/¯
+
+
+ - I never apologize. I\'m sorry but that\'s the way I am.
+ - All my life, I\'ve had one dream: to achieve my many goals.
+ - Good drink... good meat... good God, let\'s eat!
+ - You\'ll have to speak up. I\'m wearing a towel.
+ - Change the channel, Marge...
+ - Roads are just a suggestion Marge, just like pants.
+ - It takes two to lie - one to lie and one to listen.
+ - Shut up, Flanders!
+ - I\'m not normally a praying man, but if you\'re up there, please save me, Superman.
+ - It\'s so simple to be wise... just think of something stupid to say and then don\'t say it.
+ - Stupid sexy Flanders...
+ - I hope I didn\'t brain my damage.
+ - Operator, give me the number for 911!
+ - Don\'t blame me, I voted for Kodos.
+
+
diff --git a/res/values-de-rDE/cr_strings.xml b/res/values-de-rDE/cr_strings.xml
new file mode 100644
index 00000000000..7fb5ddddc55
--- /dev/null
+++ b/res/values-de-rDE/cr_strings.xml
@@ -0,0 +1,440 @@
+
+
+
+
+
+
+ Startbildschirm-Einstellungen
+
+ Startbildschirm
+ Google-Now-Panel einstellen und mehr
+ Symbole
+ Symbolgröße und weiteres anpassen
+ App-Übersicht
+ App-Übersicht konfigurieren
+ App-Verlauf
+ Anpassen des App-Verlaufs
+ Sonstiges
+ Andere Optionen
+ Allgemein
+ Benutzeroberfläche
+ Schnellauswahl
+ Suchleiste
+ Schnellaktionen
+
+ Entwickler-Optionen
+ Einige versteckte Funktionen auf eigene Gefahr aktivieren
+
+ Layout-Bearbeitung sperren
+ Symbole und Widgets können auf dem Startbildschirm nicht hinzugefügt, entfernt und verschoben werden
+ Symbole und Widgets können auf dem Startbildschirm hinzugefügt, entfernt und verschoben werden
+ Widgets können nicht zum Startbildschirm hinzugefügt werden
+
+ Symbolbeschriftungen auf dem Startbildschirm
+ Symbolbeschriftungen in der Übersicht
+
+ Wischen, um auf die Google-App zuzugreifen
+
+ Wenn auf der ersten Seite nach links gewischt wird
+
+ Wenn auf der ersten Seite nach rechts gewischt wird
+
+ Symbolanpassungen in App-Übersicht verwenden
+ Die gleichen Symbolanpassungen wie auf dem Startbildschirm verwenden
+
+ Pausieren
+ %1$s pausieren?
+ Benachrichtigungen von %1$s werden dann nicht mehr angezeigt
+ App pausiert
+ %1$s ist mithilfe des Launchers pausiert worden
+ App pausieren
+ Geschäftliche Apps sind deaktiviert
+ Geschäftliche Apps deaktivieren
+ Geschäftliche Apps aktivieren
+ Geschäftliche Apps können dir keine Benachrichtigungen senden, deinen Akku nicht nutzen und nicht auf deinen Standort zugreifen
+ Geschäftliche Apps können keine Anrufe und SMS empfangen, dir keine Benachrichtigungen senden, deinen Akku nicht nutzen und nicht auf deinen Standort zugreifen
+
+ App-Vorschläge
+ In Liste aller Apps & auf Startbildschirm
+
+ Neu starten
+ Startet den Startbildschirm neu, um ausstehende Änderungen an den Einstellungen anzuwenden
+ Launcher wird neu gestartet …
+ Launcher wird neu gestartet, um Änderungen anzuwenden …
+ Launcher wird neu gestartet, um Launcher-Elemente zu aktualisieren …
+
+ Doppeltippen zum Sperren
+ Mit Doppeltipp auf freien Startbildschirmbereich Bildschirm ausschalten
+ Vibration bei Doppeltippen zum Sperren
+ Haptisches Feedback beim Doppeltippen zum Sperren
+
+ Wischen zum Starten der Suche
+ Tastatur immer öffnen, sobald die App-Übersicht sichtbar ist
+
+ Symbolgröße
+
+ Schriftgröße
+
+ Zweizeilige App-Beschriftungen
+
+ Zeilenhöhe
+
+ Hintergrund im Dock
+ Transparenten Hintergrund im Dock anzeigen
+
+ Hintergrundtransparenz
+
+ Strichstärke
+
+ Beenden
+ App beendet
+
+ Suche
+ Google Lens
+ Sprachsuche
+ KI-Modus
+ Musik erkennen
+ Musikerkennung beim Drücken des Mikrofonsymbols starten
+ Google-Suchleiste
+ Suchleiste im unteren Dock
+ Symboldesigns
+ Symboldesigns in der Suchleiste verwenden
+ Eckenradius
+
+ Position der App-Suchleiste
+ Ausgeblendet
+ Oben
+ Unten
+
+ Hintergrundbild-Bewegung
+ Hintergrundbild-Bewegung beim Seitenwechsel
+
+ Hintergrundbild-Zoomeffekt
+ Herein- und Herauszoomen des Hintergrundbilds bei Verwendung von App-Übersicht und -Verlauf
+
+ Statusleiste
+
+ Schatten oben
+
+ Frei: %1$s | Gesamt: %2$s
+ RAM-Info
+ ZRAM-Info
+
+ Screenshot
+ Alle Apps schließen
+ Lens
+
+ Kurzes Parallax Scrollen erlauben
+ Beschränkt den Scroll-Effekt auf eine kleinere Seitenanzahl, anstatt das Hintergrundbild zu beschneiden
+ Bei nur einer Seite zentrieren
+ Zentriert das Hintergrundbild, wenn nur eine einzelne Seite verwendet wird
+
+ Bildlaufleiste
+ Bildlaufleiste neben der App-Übersicht
+
+ Dunkle Statusleiste
+ Auf Startbildschirm dunkle Statusleiste verwenden
+
+ Auf einen Blick
+ Oben auf dem Startbildschirm anzeigen
+
+ Guten Morgen.
+ Guten Abend.
+ Guten Nachmittag.
+ Gute Nacht.
+ Guten Tag.
+ Heute ist
+ Es ist
+
+ Unbekannter Interpret
+ Aktuelle Wiedergabe
+ Von
+
+ Aktuelle Wiedergabe
+ Zeigt das Lied, das gerade gespielt wird
+
+ Erweiterter Stil
+ Zu erweitertem Stil wechseln
+
+ Zufallsbotschaften
+ Mach mit Zufallsbotschaften dein Handy lebendiger
+
+ - Guten Morgen!
+ - Guten Morgen, Zeit zu Rocken!
+ - Was für ein schöner Tag!
+ - Schönen Tag!
+ - Wie wäre es mit einem kleinen Nickerchen?
+ - Schaffe, schaffe, Häusle baue.
+ - Die Morgenstunden sind wie eine frische Leinwand für dein tägliches Meisterwerk.
+ - Ergreife jeden Morgen die aufgehende Sonne der Gelegenheiten.
+ - In den ersten Lichtstrahlen finde die Kraft, deinen Weg zu erhellen.
+ - Dein Tag beginnt mit unendlichen Möglichkeiten.
+ - Ein jeder Sonnenaufgang ist wie eine Erinnerung an einen möglichen Neuanfang.
+ - Erwache mit einem Ziel und bezwinge den bevorstehenden Tag.
+ - Deine Gedanken können das Fundament großer Errungenschaften sein.
+ - Mit der Welt erwacht auch dein Potential.
+ - Das erste Kapitel deines täglichen Abenteuers.
+ - Lass deine Morgenroutine zum Startpunkt für Erfolg werden.
+ - Ob die Sonne nun aufgeht oder nicht; deine Pläne werden es.
+ - Es ist Zeit für frische Ideen und mutige Taten.
+ - Der frühe Vogel fängt zwar den Wurm, doch du kannst deine Träume einfangen.
+ - Finde den Mut, deinen Sehnsüchten zu folgen.
+ - Munter ans Werk, es steht Großes an.
+ - Die beste Zeit, um anzufangen, ist jetzt.
+
+
+ - Jage deinen Träumen und nicht den Minuten nach.
+ - In jedem Augenblick verbirgt sich eine neue Gelegenheit.
+ - Der Weg ist das Ziel.
+ - Der heutige Tag ist ein Geschenk, sofern du ihn so verpackst.
+ - Lass dein Lächeln die Welt verändern.
+ - Das Leben ist eine Geschichte; mach ein jedes Kapitel bedeutungsvoll.
+ - Sieh das Schöne auch in gewöhnlichen Augenblicken.
+ - Das Beste kommt noch, also bleib dran.
+ - Deine Gelegenheit, aus dem ‚Was wäre‘ ein ‚Was ist‘ zu machen.
+ - Nutze den Tag, ab diesem Moment.
+ - Glücklichsein ist eine Entscheidung, die du jederzeit fällen kannst.
+ - Glaub an dich, auch an verschlafenen Nachmittagen.
+ - Lass deine Taten lauter sprechen als die Zeit.
+ - Dein Leben ist, was du daraus machst; mach es zu was Tollem.
+ - Träum groß, arbeite hart und gib niemals auf.
+ - Du bist stärker als du meinst.
+ - Ein jeder Rückschlag ist die Chance auf ein Comeback.
+ - Der Weg ist das Ziel.
+ - Jage deinen Zielen nach, nicht deinen Ängsten.
+ - Dein Potential ist schier endlos; nutze es.
+
+
+ - Schätze den gegenwärtigen Moment, unabhängig von der Stunde.
+ - In jedem Dämmerlicht finde eine neue Morgendämmerung der Möglichkeiten.
+ - Das Leben ist eine Reise; genieß die Landschaft unterwegs.
+ - Deine Einstellung prägt deine Realität.
+ - Deine Geschichte wird noch immer geschrieben, selbst wenn die Sonne untergeht.
+ - Fange die Schönheit auch im Gewöhnlichen ein.
+ - Es ist Zeit zum Nachdenken und Neustarten.
+ - Entscheide dich fürs Glück, unabhängig von der Uhrzeit.
+ - Chancen haben niemals Feierabend.
+ - Sei der Grund für jemandes Lächeln.
+ - Dein Potential kennt keine Grenzen.
+ - Magische Momente gibt es zu jeder Stund.
+ - Dein Leben ist, was du daraus machst; mach es zu was Besonderem.
+ - Bleib neugierig, bleib dankbar.
+ - Sei die Veränderung, die du in der Welt sehen möchtest.
+ - Glaub an dich; du bist zu großen Dingen fähig.
+ - Die einzige Grenze ist die Selbstauferlegte.
+ - Erfolg beginnt mit einem einzelnen Schritt.
+
+
+ - Nutze die Nacht, denn sie birgt ihre eigenen Geheimnisse.
+ - Finde Seelenfrieden in der Stille dieses Abends.
+ - Die größten Abenteuer im Leben können im Dunkeln beginnen.
+ - Deine Reise geht auch nach Sonnenuntergang noch weiter.
+ - Lass die Nacht deine Leinwand sein; bemale sie mit Träumen.
+ - Finde in der Stille dieses Abends inneren Frieden.
+ - Die Sterne leuchten zu den dunkelsten Stunden am hellsten.
+ - Träume groß, auch zu später Stund.
+ - Dein Leben ist eine Geschichte, die nur darauf wartet, geschrieben zu werden.
+ - Es ist Zeit zum Nachdenken und Neustarten.
+ - Spätnächtliche Gedanken können zu frühmorgendlichen Offenbarungen führen.
+ - Finde Freude in der Ruhe dieses Abends.
+ - Der Nachthimmel ist eine riesige Leinwand der Träume.
+ - Dein Potential kennt keine Schlafenszeit; folge deinen Leidenschaften.
+ - Verlier nie die Hoffnung, auch nicht in den dunkelsten Stunden.
+ - Finde inmitten des Chaos inneren Frieden.
+ - Bleib positiv, arbeite hart, zieh es durch.
+
+
+ - Nächte sind leises Geflüster der Möglichkeiten.
+ - Finde in der Stille der Nacht innere Stärke.
+ - Genieße die Dunkelheit, denn sie hat die Schlüssel zu deinen Träumen in der Hand.
+ - Die Welt schläft, doch dein Potential ist hellwach.
+ - Die Nacht mag schon spät sein, doch deine Ambitionen sind zeitlos.
+ - Bleib neugierig, bleib wach und lass dir von den Nächten den Weg zeigen.
+ - Finde den Mut, deinen Träumen zu folgen.
+ - Großes geschieht niemals von der Komfortzone aus.
+ - Du hast die Macht, dein eigenes Schicksal zu schaffen.
+ - Glücklich zu sein ist eine Entscheidung; fälle sie jeden Tag.
+ - Das Leben ist, was du daraus machst, also mach was daraus.
+ - Bleib fokussiert, bleib entschlossen, bleib unaufhaltsam.
+ - Du bist der Autor deiner eigenen Geschichte.
+ - Strebe nach Fortschritt, nicht nach Perfektion.
+ - Jeder Tag ist ein Neuanfang; mach ihn zu einem Großen.
+ - Inspiriere andere, indem du authentisch und du selbst bist.
+ - Deine Einstellung bestimmt deine Richtung.
+
+
+ - Unwissenheit ist ein Segen.
+ - Wird’s mal wieder Zeit, ein neues Update zu flashen?
+ - Make peace, not war!
+ - Hey, alles fit?
+ - Willkommen in der wahren Matrix!
+ - Wie viele Screenshots machst du so?
+ - AlphaDroid bietet viele nützliche Anpassungsmöglichkeiten.
+ - Kein Bock auf Havoc.
+ - We didn’t start the fire!
+ - Zeit für gute Musik!
+ - \u003C\u003C\u003C\u003C\u003C\u003C\u003C HEAD
+ - AlphaDroid Premium benötigt, um diesen Text freizuschalten.
+ - Lineage & Unicorn haben bei uns nichts verloren.
+ - Viel Spaß mit der „Xtended“ Akkulaufzeit
+ - Zu Ground Zero sag ich nie „No“.
+ - Wir haben dich lieb, mal 3 000
+ - Sehr schmackhaft, auch ohne Sushi!
+ - Eine gute Tat pro Tag.
+ - AlphaDroid bietet ’ne tolle Pixel-Experience, oder?
+ - Keine Illusionen, willkommen in der AlphaDroid-Realität!
+ - Danke für deine Unterstützung!
+ - Kein DerpFest, sondern nur Perfektion hier bei AlphaDroid!
+ - Feinste Wahl vom Buildbot.
+ - AlphaDroid heilt deine Paranoia.
+ - Rising in Peace
+ - Was für ein schönes Betriebssystem, oder?
+ - Evolution ist doch nur ein Mythos, oder nicht?
+ - Du bist, was du flashst. Sei keine Potato.
+ - Woran denkst du gerade?
+ - Expecto Patronum
+ - Wubba Lubba Dub Dub
+ - Winner Winner …?
+ - rm -rf \/
+ - Pringles sind genau genommen gar keine Kartoffelchips.
+ - Denk dran, den Akkustand zu checken!
+ - Prüfe deine Mails und Nachrichten.
+ - Denk an deine To-do-Liste, falls du eine hast.
+ - Quickspace kannst du in den Startbildschirm-Einstellungen deaktivieren.
+ - Gefällt dir unsere Rom? Dann schick uns doch mal ’n Bier
+ - Beste Rom aller Zeiten.
+ - Besuch uns doch mal auf Telegram
+ - Wir haben dich lieb, mal 3 000
+
+ Keine Kalender- oder Weckeraktivität gefunden
+
+ Wetteranbieter
+ Automatisch
+ Google Smartspace
+ OmniJaws
+ Wetterinfo
+ Aktuelle Wetterinfo anzeigen
+ Dafür muss der Wetterdienst aktiviert sein
+ Bewölkt
+ Regen
+ Sonnig
+ Stürmisch
+ Schnee
+ Wind
+ Nebel
+ Aktueller Standort
+ Aktuellen Wetterstandort anzeigen
+ Wetterlage in Worten
+ Kurze schriftliche Übersicht der aktuellen Wetterlage anzeigen
+
+ Seitenzahlpunkte automatisch ausblenden
+ Blendet die unten angezeigten Seitenpunkte automatisch aus
+
+ Tiefenschärfegrad des Hintergrunds
+ Tiefenschärfegrad in App-Übersicht und -Verlauf anpassen
+
+ App-Info
+ Quelle
+ System
+ Letzte Aktualisierung
+ Version
+ Mehr
+
+ Symbolpaket
+ Standard
+ Weitere installieren
+ Es ist kein App-Store verfügbar
+
+ Hintergrundbild-Wechselmenü
+ Hintergrundbilder vom Gedrückthaltemenü aus schnell wechseln
+ Auf Sperrbildschirm anwenden
+ Dasselbe Hintergrundbild auf dem Sperrbildschirm festlegen
+
+ Steinchen
+ Vessel
+ Reines Quadrat
+ Quadratkreis
+ Trapezförmig
+ Tränenform
+ Abgerundetes Viereck
+ Bewölkt
+ Zylindrisch
+ Blume
+ Herz
+ Sechseck
+ Abgerundetes Sechseck
+ iOS-Stil
+ Laubblatt
+ Miau
+ Samsung-Stil
+ Gedehnt
+
+ Dunkle App-Beschriftungen
+ Immer dunklen Text für App-Namen in App-Übersicht verwenden
+
+ Stil der App-Übersicht
+ Normal (Seiten unten)
+ Vollbild (alt)
+ Listenstil
+ OneUI-Stil (Seiten)
+
+ Sortierung in der App-Übersicht
+ Alphabetisch sortieren
+ Nach Installationsdatum sortieren
+ Nach Nutzung sortieren
+
+ Benutzerdefinierte Hintergrundfarbe
+ Benutzerdefinierte Farbe für Hintergrund der App-Übersicht verwenden
+ Farbe bei hellem Design
+ Farbe bei dunklem Design
+
+ HSB
+ RGB
+ Farbton
+ Sättigung
+ Helligkeit
+ Rot
+ Grün
+ Blau
+ HEX
+
+ Suchleiste im Pixel-Stil
+ Überarbeitete Suchleiste mit mehrschichtigem Hintergrund
+ Deckkraft der äußeren Ebene
+
+ Versteckte & geschützte Apps
+ Entsperren, um versteckte und geschützte Apps zu verwalten
+ Authentifizieren, um %1$s zu öffnen
+ Lädt \u2026
+ Bitte eine Bildschirmsperre einrichten, um den Zugriff auf Apps einzuschränken
+ Hilfe
+ Versteckte Apps und ihre Widgets werden in der App-Übersicht nicht angezeigt
+ Geschützte Apps erfordern eine Authentifizierung, um gestartet werden zu können
+
+ Stil des Anwendungsverlaufslayouts
+ Standard
+ Skalierungseffekt
+ Kami-Stil
+ iOS-Stil
+ OxygenOS-Stil
+
+ App sperren/entsperren
+ Entsperrte App im Anwendungsverlauf
+ Gesperrte App im Anwendungsverlauf
+
+ Scroll-Vibration
+
diff --git a/res/values-el-rGR/alpha_strings.xml b/res/values-el-rGR/alpha_strings.xml
new file mode 100644
index 00000000000..e8660a28519
--- /dev/null
+++ b/res/values-el-rGR/alpha_strings.xml
@@ -0,0 +1,44 @@
+
+
+
+
+ Lock/Unlock app
+ Unlocked app in recents
+ Locked app in recents
+
+ Recents layout style
+ Stock
+ Kami Style
+ iOS Style
+ OxygenOS Style
+
+ - Ignorance is Bliss
+ - Make love, not war
+ - Oh hey, what\'s up?
+ - We love you 3000
+ - Thank you for your support
+ - One of the buildbot\'s best picks
+ - Pringles aren\'t actually potato chips..
+ - You can disable quickspace via home settings.
+ - I got nothing ¯\\_(ツ)_/¯
+
+
+ - I never apologize. I\'m sorry but that\'s the way I am.
+ - All my life, I\'ve had one dream: to achieve my many goals.
+ - Good drink... good meat... good God, let\'s eat!
+ - You\'ll have to speak up. I\'m wearing a towel.
+ - Change the channel, Marge...
+ - Roads are just a suggestion Marge, just like pants.
+ - It takes two to lie - one to lie and one to listen.
+ - Shut up, Flanders!
+ - I\'m not normally a praying man, but if you\'re up there, please save me, Superman.
+ - It\'s so simple to be wise... just think of something stupid to say and then don\'t say it.
+ - Stupid sexy Flanders...
+ - I hope I didn\'t brain my damage.
+ - Operator, give me the number for 911!
+ - Don\'t blame me, I voted for Kodos.
+
+
diff --git a/res/values-el-rGR/cr_strings.xml b/res/values-el-rGR/cr_strings.xml
new file mode 100644
index 00000000000..db14e79f59b
--- /dev/null
+++ b/res/values-el-rGR/cr_strings.xml
@@ -0,0 +1,440 @@
+
+
+
+
+
+
+ Ρυθμίσεις Αρχικής Οθόνης
+
+ Αρχική οθόνη
+ Ρύθμιση πλαισίου Google now και άλλα
+ Εικονίδια
+ Ορισμός μεγέθους εικονιδίου και πολλά άλλα
+ Καρτέλα εφαρμογών
+ Προσαρμογή καρτέλας εφαρμογών
+ Πρόσφατα
+ Ανανεώστε την οθόνη επισκόπησης
+ Διάφορα
+ Άλλες επιλογές
+ Γενικά
+ Διεπαφή
+ Quickspace
+ Μπάρα αναζήτησης
+ Γρήγορες ενέργειες
+
+ Επιλογές για προγραμματιστές
+ Ενεργοποιήστε μερικές κρυφές λειτουργίες με δική σας ευθύνη
+
+ Κλείδωμα διάταξης
+ Εικονίδια και widgets δεν μπορούν να προστεθούν, να αφαιρεθούν και να μετακινηθούν στην αρχική οθόνη
+ Εικονίδια και widgets μπορούν να προστεθούν, να αφαιρεθούν και να μετακινηθούν στην αρχική οθόνη
+ Δεν είναι δυνατή η προσθήκη widget στην αρχική οθόνη
+
+ Ετικέτες εικονιδίων στην επιφάνεια εργασίας
+ Ετικέτες εικονιδίων στο συρτάρι εφαρμογών
+
+ Σύρετε για πρόσβαση στην εφαρμογή Google
+
+ Όταν σύρετε προς τα αριστερά από την κύρια αρχική οθόνη
+
+ Όταν σύρετε προς τα δεξιά από την κύρια Αρχική οθόνη
+
+ Use icon customizations in drawer
+ Follow icon customizations used on home screen
+
+ Παύση
+ Παύση %1$s;
+ Οι ειδοποιήσεις για %1$s θα τεθούν σε παύση
+ Η εφαρμογή τέθηκε σε παύση
+ %1$s τέθηκε απευθείας σε παύση από τον εκκινητή
+ Παύση εφαρμογής
+ Οι εφαρμογές εργασίας είναι απενεργοποιημένες
+ Απενεργοποίηση εφαρμογών εργασίας
+ Ενεργοποίηση εφαρμογών εργασίας
+ Οι εφαρμογές εργασίας σας δεν μπορούν να σας στείλουν ειδοποιήσεις, να χρησιμοποιήσουν την μπαταρία σας ή να έχουν πρόσβαση στην τοποθεσία σας
+ Οι εφαρμογές εργασίας σας δεν μπορούν να λαμβάνουν τηλεφωνικές κλήσεις ή μηνύματα κειμένου, να σας στέλνουν ειδοποιήσεις, να χρησιμοποιούν την μπαταρία σας ή να έχουν πρόσβαση στην τοποθεσία σας
+
+ Προτάσεις
+ Προτάσεις για Όλες τις Εφαρμογές & Αρχικής οθόνης
+
+ Επανεκκίνηση
+ Επανεκκινήστε τον launcher χειροκίνητα για να εφαρμόσετε οποιεσδήποτε ρυθμίσεις σε εκκρεμότητα
+ Επανεκκίνηση του launcher...
+ Επανεκκίνηση του launcher για την εφαρμογή αλλαγών...
+ Επανεκκίνηση του launcher για ενημέρωση των στοιχείων του launcher...
+
+ Διπλό πάτημα για ύπνο
+ Double tap empty space on home screen to turn off the screen
+ Δόνηση στο διπλό πάτημα για αναστολή
+ Παροχή απτικής ανάδρασης στο διπλό πάτημα για χειρονομία \"ύπνου\"
+
+ Σύρετε για έναρξη αναζήτησης
+ Άνοιγμα πληκτρολογίου κάθε φορά που το συρτάρι εφαρμογών γίνεται ορατό
+
+ Μέγεθος εικονιδίου
+
+ Μέγεθος γραμ/σειράς
+
+ Χρήση δύο γραμμών για τις ετικέτες εφαρμογών
+
+ Ύψος σειράς
+
+ Φόντο Hotseat
+ Προσθήκη ημιδιαφανούς φόντου στη γραμμή εφαρμογών
+
+ Αδιαφάνεια φόντου
+
+ Χτύπημα
+
+ Τερματισμός
+ Η εφαρμογή τερματίστηκε
+
+ Αναζήτηση
+ Google Lens
+ Φωνητική αναζήτηση
+ AI mode
+ Αναζήτηση Μουσικής
+ Εκκίνηση της αναζήτησης μουσικής πατώντας το εικονίδιο μικροφώνου
+ Γραμμή αναζήτησης Google
+ Γραμμή αναζήτησης στην κάτω μπάρα εφαρμογών
+ Θεματικά εικονίδια
+ Χρήση θεματικών εικονιδίων για γραμμή αναζήτησης
+ Γωνιακή ακτίνα
+
+ App search bar location
+ Hidden
+ Top
+ Bottom
+
+ Κύλιση ταπετσαρίας
+ Εφέ κύλισης ταπετσαρίας για πολλαπλές οθόνες
+
+ Μεγέθυνση ταπετσαρίας
+ Μεγέθυνση ή σμίκρυνση της ταπετσαρίας όταν χρησιμοποιείτε συρτάρι ή πρόσφατες εφαρμογές
+
+ Γραμμή Κατάστασης
+
+ Πάνω σκιά
+
+ %1$s Διαθέσιμο | %2$s Σύνολο
+ Πληροφορίες μνήμης
+ Πληροφορίες ZRAM
+
+ Στιγμιότυπο οθόνης
+ Διαγραφή όλων
+ Lens
+
+ Να επιτρέπονται σύντομα parallax
+ Ενεργοποιήστε το εφέ κύλισης πλήρους ταπετσαρίας σε μικρότερο αριθμό σελίδων αντί να περικόψετε την ταπετσαρία
+ Κέντρο μιας σελίδας
+ Κεντράρισμα ταπετσαρίας αν χρησιμοποιείτε μόνο μια σελίδα
+
+ Μπάρα κύλισης
+ Μπάρα κύλισης στο πλάι του συρταριού εφαρμογών
+
+ Σκοτεινή γραμμή κατάστασης
+ Να χρησιμοποιείται πάντα η σκοτεινή γραμμή κατάστασης στην αρχική οθόνη
+
+ Με μια Ματιά
+ Εμφάνιση στο επάνω μέρος της αρχικής σας οθόνης
+
+ Καλημέρα.
+ Καλό απόγευμα.
+ Καλό μεσημέρι.
+ Καληνύχτα.
+ Καλή σας ημέρα.
+ Σήμερα είναι
+ Είναι
+
+ Άγνωστος Καλλιτέχνης
+ Τώρα Παίζει
+ Από
+
+ Τώρα Παίζει
+ Εμφάνιση του τραγουδιού που αναπαράγετε
+
+ Εκτεταμένο στυλ
+ Εναλλαγή σε εκτεταμένο στυλ
+
+ Τυχαία μηνύματα
+ Κάντε τον σύντροφό σας πιο ζωντανό με τυχαία μηνύματα
+
+ - Καλημέρα!
+ - Καλημέρα, ώρα να ροκάρουμε!
+ - Τι όμορφη μέρα!
+ - Να έχεις μια όμορφη μέρα!
+ - Τι λες για έναν μικρό υπνάκο 5 λεπτών;
+ - Ας πάρουμε αυτό το ψωμί.
+ - Τα πρωινά είναι ένας φρέσκος καμβάς για το καθημερινό σας αριστούργημα.
+ - Αγκαλιάστε την ανατολή της ευκαιρίας κάθε πρωί.
+ - Στο πρώιμο φως, βρείτε τη δύναμη να φωτίσετε το μονοπάτι σας.
+ - Η μέρα σας ξεκινά με ατελείωτες δυνατότητες.
+ - Κάθε ανατολή του ηλίου είναι μια υπενθύμιση ότι μπορείτε να ξεκινήσετε εκ νέου.
+ - Ξυπνήστε με σκοπό, κατακτήστε την επόμενη μέρα.
+ - Οι σκέψεις σας μπορούν να αποτελέσουν το θεμέλιο για μεγάλα επιτεύγματα.
+ - Ο κόσμος ξυπνά, το ίδιο και οι δυνατότητές σας.
+ - Το πρώτο κεφάλαιο στην καθημερινή σας περιπέτεια.
+ - Αφήστε την πρωινή σας ρουτίνα να είναι η βάση για την επιτυχία.
+ - Ανατολή ή όχι, οι δυνατότητές σας είναι πάντα σε άνοδο.
+ - Είναι ώρα για νέες ιδέες και τολμηρές πράξεις.
+ - Το πρώιμο πουλί πιάνει το σκουλήκι, αλλά μπορείς να πιάσεις τα όνειρά σου.
+ - Βρείτε το θάρρος να κυνηγήσετε τις φιλοδοξίες σας.
+ - Σηκωθείτε και λάμψτε, γιατί το μεγαλείο περιμένει.
+ - Η καλύτερη στιγμή για να ξεκινήσετε είναι τώρα.
+
+
+ - Κυνηγήστε τα όνειρά σας, όχι τα λεπτά.
+ - Σε κάθε στιγμή, μια νέα ευκαιρία.
+ - Αγκαλιάστε το ταξίδι, όχι μόνο τον προορισμό.
+ - Σήμερα είναι ένα δώρο, γι \'αυτό ονομάζεται το παρόν.
+ - Αφήστε το χαμόγελό σας να αλλάξει τον κόσμο.
+ - Η ζωή είναι μια ιστορία. κάντε κάθε κεφάλαιο να μετράει.
+ - Βρείτε χαρά στις συνηθισμένες στιγμές.
+ - Τα καλύτερα έρχονται, συνεχίστε έτσι.
+ - Η ευκαιρία σας να μετατρέψετε το «τι θα γινόταν αν» σε «τι είναι»
+ - Αδράξτε τη μέρα, ξεκινώντας τώρα.
+ - Η ευτυχία είναι μια επιλογή που μπορείς να κάνεις ανά πάσα στιγμή.
+ - Πιστέψτε στον εαυτό σας, ακόμη και σε ένα βραδινό μες.
+ - Αφήστε τις ενέργειές σας να μιλήσουν πιο δυνατά από το ρολόι.
+ - Η ζωή σας είναι αυτό που φτιάχνετε από αυτήν. κάντε το καταπληκτικό.
+ - Ονειρέψου πολύ, δούλεψε σκληρά και μην τα παρατάς ποτέ.
+ - Είσαι δυνατότερος απ \'όσο νομίζεις.
+ - Κάθε οπισθοδρόμηση είναι μια ρύθμιση για μια επιστροφή.
+ - Αγκαλιάστε το ταξίδι, όχι μόνο τον προορισμό.
+ - Κυνηγήστε τους στόχους σας, όχι τους φόβους σας.
+ - Οι δυνατότητές σας είναι ατελείωτες, ξεκλειδώστε τες.
+
+
+ - Αγκαλιάστε την παρούσα στιγμή, ανεξάρτητα από την ώρα.
+ - Σε κάθε σούρουπο, βρείτε μια νέα αυγή ευκαιρίας.
+ - Η ζωή είναι ένα ταξίδι, απολαύστε το τοπίο στην πορεία.
+ - Η στάση σας διαμορφώνει την πραγματικότητά σας.
+ - Η ιστορία σας εξακολουθεί να γράφεται, ακόμη και όταν ο ήλιος δύει.
+ - Αποτυπώστε την ομορφιά στις συνηθισμένες στιγμές.
+ - Ήρθε η ώρα να αναστοχαστείτε και να επαναφέρετε.
+ - Επιλέξτε τη χαρά, ανεξάρτητα από την ώρα του ρολογιού.
+ - Οι ευκαιρίες δεν τελειώνουν το βράδυ.
+ - Γίνε ο λόγος που κάποιος χαμογελά.
+ - Οι δυνατότητές σας δεν έχουν όρια.
+ - Στιγμές μαγείας μπορεί να συμβεί σε οποιαδήποτε ώρα.
+ - Η ζωή σας είναι αυτό που φτιάχνετε από αυτήν, κάντε το καταπληκτικό.
+ - Μείνετε περίεργοι, μείνετε ευγνώμονες.
+ - Γίνε η αλλαγή που θέλεις να δεις στον κόσμο.
+ - Πίστεψε στον εαυτό σου, είσαι ικανός για καταπληκτικά πράγματα.
+ - Το μόνο όριο είναι αυτό που θέτεις εσύ στον εαυτό σου.
+ - Η επιτυχία ξεκινά με ένα μόνο βήμα.
+
+
+ - Αδράξτε τη νύχτα, γιατί έχει τα δικά της μυστήρια.
+ - Στην ησυχία του απόψε, βρείτε γαλήνη.
+ - Οι μεγαλύτερες περιπέτειες της ζωής μπορούν να ξεκινήσουν στο σκοτάδι.
+ - Το ταξίδι σας συνεχίζεται ακόμα και μετά τη δύση του ήλιου.
+ - Αφήστε τη νύχτα να είναι ο καμβάς σας, ζωγραφίστε το με όνειρα.
+ - Στην ηρεμία αυτής της βραδιάς, βρείτε την εσωτερική σας γαλήνη.
+ - Τα αστέρια λάμπουν φωτεινότερα στις πιο σκοτεινές ώρες.
+ - Ονειρευτείτε πολύ, ακόμη και στις τελευταίες ώρες της ημέρας.
+ - Η ζωή σου είναι μια ιστορία που περιμένει να γραφτεί.
+ - Είναι ώρα για προβληματισμό και ανανέωση.
+ - Οι σκέψεις αργά το βράδυ μπορεί να οδηγήσουν σε αποκαλύψεις νωρίς το πρωί.
+ - Βρείτε χαρά στην ηρεμία αυτής της βραδιάς.
+ - Ο νυχτερινός ουρανός είναι ένας απέραντος καμβάς ονείρων.
+ - Οι δυνατότητές σας δεν γνωρίζουν την ώρα του ύπνου, κυνηγήστε τα πάθη σας.
+ - Μείνετε αισιόδοξοι, ακόμα και στις πιο σκοτεινές ώρες.
+ - Μέσα στο χάος, βρείτε την εσωτερική σας ηρεμία.
+ - Μείνετε θετικοί, δουλέψτε σκληρά, κάντε το πραγματικότητα.
+
+
+ - Οι νύχτες είναι οι ήσυχοι ψίθυροι ευκαιρίας.
+ - Στην ησυχία της νύχτας, βρείτε την εσωτερική σας δύναμη.
+ - Αγκάλιασε το σκοτάδι, γιατί κρατάει τα κλειδιά των ονείρων σου.
+ - Ο κόσμος κοιμάται, αλλά οι δυνατότητές σας είναι πολύ ξύπνιες.
+ - Η νύχτα μπορεί να είναι αργά, αλλά οι φιλοδοξίες σας είναι διαχρονικές.
+ - Μείνετε περίεργοι, μείνετε ξύπνιοι και αφήστε τις νύχτες να σας καθοδηγήσουν.
+ - Βρείτε το κουράγιο να κυνηγήσετε τα όνειρά σας.
+ - Τα σπουδαία πράγματα δεν προέρχονται ποτέ από τις ζώνες άνεσης.
+ - Έχετε τη δύναμη να δημιουργήσετε τη μοίρα σας.
+ - Η ευτυχία είναι επιλογή, επιλέξτε την κάθε μέρα.
+ - Η ζωή είναι αυτό που τη φτιάχνεις, γι\' αυτό κάνε τη να μετράει.
+ - Μείνετε συγκεντρωμένοι, μείνετε αποφασισμένοι, μείνετε ασταμάτητοι.
+ - Είστε ο συγγραφέας της δικής σας ιστορίας.
+ - Προσπαθήστε για πρόοδο, όχι τελειότητα.
+ - Κάθε μέρα είναι ένα νέο ξεκίνημα, κάντε το υπέροχο.
+ - Εμπνεύστε τους άλλους με το να είστε ο αυθεντικός εαυτός σας.
+ - Η στάση σας καθορίζει την κατεύθυνση σας.
+
+
+ - Η άγνοια είναι Ευτυχία
+ - Ήρθε ήδη η ώρα να flashaρουμε ακόμα μια ενημέρωση;
+ - Κάντε ειρήνη, όχι πόλεμο
+ - Ω γεια, τι λέει;
+ - Καλώς ήλθατε στο πραγματικό Matrix!
+ - Πόσα στιγμιότυπα οθόνης παίρνεις;
+ - Ανοιχτή καλοσύνη με AlphaDroid
+ - Διαδώστε αγάπη, όχι καταστροφή
+ - Δεν ανάψαμε εμείς τη φωτιά!
+ - Ώρα για λίγη καλή μουσική
+ - \u003C\u003C\u003C\u003C\u003C\u003C\u003C HEAD
+ - Χρειάζεστε το AlphaDroid Premium για να το δείτε αυτό
+ - Θυμήσου το Lineage του Unicorn
+ - Απόλαυσε την Xtended διάρκεια ζωής της μπαταρίας
+ - Ξεκινώντας από το απόλυτο μηδέν
+ - Σε αγαπάμε 3000
+ - Νόστιμο ακόμη και χωρίς Sushi
+ - Κάνε κάτι ωραίο σήμερα
+ - Αυτή είναι η καλύτερη εμπειρία Pixel, έτσι δεν είναι;
+ - Χωρίς ψευδαισθήσεις, καλώς ήρθατε στην πραγματικότητα!
+ - Ευχαριστούμε για την υποστήριξη
+ - Χωρίς φεστιβάλ για Derps - μόνο τελειότητα!
+ - Μία από τις καλύτερες επιλογές του buildbot
+ - Λογική για την Παράνοιά σας
+ - Η άνοδος στην ειρήνη
+ - Τι υπέροχη εμπειρία, έτσι δεν είναι;
+ - Η εξέλιξη είναι μύθος, σωστά;
+ - Είσαι ότι flashάρεις, μην είσαι Πατάτα
+ - Τι έχεις στο μυαλό σου;
+ - Expecto Patronum
+ - Wubba Lubba Dub Dub
+ - Νικητής Νικητής ....;
+ - rm -rf \/
+ - Τα Pringles στην πραγματικότητα δεν είναι πατατάκια..
+ - Θυμήσου να ελέγχεις τα επίπεδα μπαταρίας της συσκευής!
+ - Ελέγξτε τα email/μηνύματά σας.
+ - Ελέγξτε τη λίστα υποχρεώσεών σας εάν έχετε μια.
+ - Μπορείτε να απενεργοποιήσετε το quickspace μέσω των ρυθμίσεων αρχικής οθόνης.
+ - Μας αγαπάς; Στείλτε μας μια μπύρα
+ - Η καλύτερη Rom όλων.
+ - Πες μας γεια στο Telegram
+ - Σε αγαπάμε 3000
+
+ Αδυναμία εύρεσης δραστηριότητας ημερολογίου ή ρολογιού
+
+ Πάροχος Καιρού
+ Αυτόματα
+ Google Smartspace
+ OmniJaws
+ Ενημέρωση καιρού
+ Εμφάνιση τρέχουσας ενημέρωσης καιρού
+ Απαιτεί την ενεργοποίηση της υπηρεσίας καιρού
+ Συννεφιά
+ Βροχερός
+ Ηλιοφάνεια
+ Καταιγίδες
+ Χιονώδης
+ Θυελλώδεις
+ Ομιχλώδες
+ Τρέχουσα τοποθεσία
+ Εμφάνιση της τρέχουσας τοποθεσίας του καιρού
+ Τρέχουσα κατάσταση
+ Εμφάνιση σύνοψης της τρέχουσας καιρικής κατάστασης
+
+ Αυτόματη απόκρυψη ένδειξης σελίδας
+ Απόκρυψη των σημείων ένδειξης σελίδας αυτόματα
+
+ Βάθος θολώματος φόντου
+ Ορισμός επιπέδου βάθους θαμπώματος για πρόσφατα και συρτάρι εφαρμογών
+
+ Πληροφορίες εφαρμογής
+ Πηγή
+ Σύστημα
+ Τελευταία ενημέρωση
+ Έκδοση
+ Περισσότερα
+
+ Πακέτο εικονιδίων
+ Προεπιλογή
+ Εγκαταστήστε περισσότερα
+ Δεν υπάρχει κανένα διαθέσιμο κατάστημα εφαρμογών
+
+ Wallpaper carousel
+ Quickly switch wallpapers from the long-press menu
+ Apply to lock screen
+ Set the same wallpaper on the lock screen
+
+ Pebble
+ Vessel
+ Pure square
+ Squircle
+ Tapered rect
+ Teardrop
+ Rounded rect
+ Συννεφιά
+ Cylindrical
+ Flower
+ Heart
+ Hexagon
+ Rounded hexagon
+ iOS Style
+ Leaf
+ Meow
+ Samsung Style
+ Stretched
+
+ Dark app labels
+ Always use dark text for app labels in the app drawer
+
+ App drawer style
+ Normal (bottom sheet)
+ Legacy fullscreen
+ List style
+ OneUI style (paged)
+
+ App drawer sorting
+ Sort alphabetically
+ Sort by install date
+ Sort by usage
+
+ Custom background color
+ Use a custom color for the app drawer background
+ Light mode color
+ Dark mode color
+
+ HSB
+ RGB
+ Hue
+ Saturation
+ Brightness
+ Red
+ Green
+ Blue
+ HEX
+
+ Pixel style search bar
+ Revamped search bar with layered background
+ Outer layer opacity
+
+ Κρυφές & Προστατευόμενες εφαρμογές
+ Ξεκλειδώστε για να διαχειριστείτε τις κρυφές και προστατευόμενες εφαρμογές
+ Πιστοποίηση για άνοιγμα %1$s
+ Φόρτωση\u2026
+ Παρακαλώ ρυθμίστε ένα ασφαλές κλείδωμα οθόνης για να περιορίσετε την πρόσβαση στις εφαρμογές
+ Βοήθεια
+ Οι κρυφές εφαρμογές και τα widget τους αποκρύπτονται από το συρτάρι
+ Οι προστατευμένες εφαρμογές απαιτούν έλεγχο ταυτότητας για να ανοιχτούν από τον εκκινητή
+
+ Recents layout style
+ Προεπιλογή
+ Scale effect
+ Kami style
+ iOS style
+ OxygenOS style
+
+ Lock/Unlock app
+ Unlocked app in recents
+ Locked app in recents
+
+ Scroll vibration
+
diff --git a/res/values-es-rES/alpha_strings.xml b/res/values-es-rES/alpha_strings.xml
new file mode 100644
index 00000000000..95ddc3bc40f
--- /dev/null
+++ b/res/values-es-rES/alpha_strings.xml
@@ -0,0 +1,44 @@
+
+
+
+
+ Bloquear/Desbloquear aplicación
+ Aplicación desbloqueada en recientes
+ Aplicación bloqueada en recientes
+
+ Recents layout style
+ Stock
+ Kami Style
+ iOS Style
+ OxygenOS Style
+
+ - La ignorancia es una bendición
+ - Haz el amor, no la guerra
+ - Oh, hola, ¿qué pasa?
+ - Nosotros te amamos 3000
+ - Gracias por tu apoyo
+ - Una de las mejores elecciones del buildbot
+ - Las Pringles en realidad no son patatas fritas...
+ - Puedes desactivar el espacio rápido en la configuración de inicio.
+ - No tengo nada ¯\\_(ツ)_/¯
+
+
+ - Nunca me disculpo. Lo siento pero así soy.
+ - Toda mi vida he tenido un sueño: alcanzar mis muchas metas.
+ - Buena bebida...buena carne...buen Dios, ¡a comer!
+ - Tendrás que hablar más alto. Llevo una toalla.
+ - Cambia de canal, Marge...
+ - Los caminos son sólo una sugerencia, Marge, igual que los pantalones.
+ - Para mentir se necesitan dos: uno que mienta y otro que escuche.
+ - ¡Cállate, Flandes!
+ - Normalmente no soy un hombre de oración, pero si estás ahí arriba, por favor sálvame, Superman.
+ - Es tan sencillo ser sabio... sólo piensa en algo estúpido que decir y luego no lo digas.
+ - Estúpido y sensual Flandes...
+ - Espero no haberme hecho daño cerebral.
+ - Operador, dame el número para el 911!
+ - No me culpes, voté por Kodos.
+
+
diff --git a/res/values-es-rES/cr_strings.xml b/res/values-es-rES/cr_strings.xml
new file mode 100644
index 00000000000..8c44f1eddaf
--- /dev/null
+++ b/res/values-es-rES/cr_strings.xml
@@ -0,0 +1,440 @@
+
+
+
+
+
+
+ Ajustes de la pantalla de inicio
+
+ Pantalla de Inicio
+ Configura el panel de Google Now y más
+ Iconos
+ Configura el tamaño de los iconos y más
+ Cajón de aplicaciones
+ Personaliza tu cajón de aplicaciones
+ Recientes
+ Personaliza tu pantalla de recientes
+ Varios
+ Otras opciones
+ General
+ Interfaz
+ Espacio Rápido
+ Barra de búsqueda
+ Acciones rápidas
+
+ Opciones para desarrolladores
+ Activar algunas características ocultas bajo su propio riesgo
+
+ Diseño de pantalla de bloqueo
+ Iconos y widgets no pueden ser añadidos, eliminados o movidos en la pantalla de inicio
+ Iconos y widgets pueden ser añadidos, eliminados o movidos en la pantalla de inicio
+ No es posible añadir widgets a la pantalla de inicio
+
+ Mostrar etiquetas de iconos en la pantalla de inicio
+ Mostrar etiquetas de iconos en el cajón de aplicaciones
+
+ Deslizar para acceder a la aplicación de Google
+
+ Cuando deslizas a la izquierda desde la pantalla principal
+
+ Cuando deslizas a la derecha desde la pantalla de Inicio
+
+ Usar personalizaciones de iconos en el cajón
+ Seguir las personalizaciones de iconos usadas en la pantalla de inicio
+
+ Pausa
+ ¿Pausar %1$s?
+ Las notificaciones se pausarán por %1$s
+ Aplicación pausada
+ Pausada por %1$s directamente desde el lanzador
+ Pausar aplicación
+ Las aplicaciones de trabajo están desactivadas
+ Desactivar las aplicaciones de trabajo
+ Activar las aplicaciones de trabajo
+ Tus aplicaciones de trabajo no pueden enviar notificaciones, usar la batería ni acceder a la ubicación
+ Tus aplicaciones de trabajo no pueden recibir llamadas telefónicas o mensajes de texto, enviar notificaciones, usar la batería o acceder a la ubicación
+
+ Sugerencias
+ Para todas las aplicaciones y sugerencias de la pantalla de inicio
+
+ Reiniciar
+ Reinicia el lanzador manualmente para aplicar cualquier configuración pendiente
+ Reiniciando launcher...
+ Reiniciando launcher para aplicar cambios...
+ Reiniciando launcher para actualizar componentes del launcher...
+
+ Doble toque para apagar pantalla
+ Toca dos veces un espacio vacío en la pantalla de inicio para apagar la pantalla
+ Vibrar al tocar dos veces para suspender
+ Proporciona retroalimentación háptica en el gesto de doble toque para suspender
+
+ Desliza para comenzar la búsqueda
+ Abrir el teclado cada vez que el cajón de aplicaciones se vuelva visible
+
+ Tamaño del icono
+
+ Tamaño de la fuente
+
+ Use dos líneas para la etiqueta de la aplicación
+
+ Altura de las filas
+
+ Fondo del dock
+ Añadir fondo translúcido al dock de aplicaciones
+
+ Opacidad del fondo
+
+ Trazo
+
+ Terminar proceso
+ Aplicación finalizada
+
+ Buscar
+ Google Lens
+ Búsqueda por voz
+ Modo IA
+ Buscador de Música
+ Inicie la búsqueda de música al presionar el ícono del micrófono
+ Barra de búsqueda de Google
+ Mostrar la barra de búsqueda en la parte inferior
+ Iconos temáticos
+ Usar iconos temáticos para la barra de búsqueda
+ Radio de esquina
+
+ Ubicación de la barra de búsqueda de aplicaciones
+ Oculta
+ Arriba
+ Abajo
+
+ Desplazamiento de fondo de pantalla
+ Efecto de desplazamiento de fondo de pantalla para múltiples pantallas
+
+ Zoom del fondo de pantalla
+ Acercar o alejar el fondo de pantalla al usar el cajón o aplicaciones recientes
+
+ Barra de Estado
+
+ Sombra superior
+
+ %1$s Disponibles | %2$s Total
+ Información de memoria
+ Información de ZRAM
+
+ Captura de pantalla
+ Borrar todo
+ Lens
+
+ Permitir paralax corto
+ Activar efecto de desplazamiento del fondo de pantalla completo en un número menor de páginas en lugar de recortar el fondo de pantalla
+ Título de página única centrado
+ Centrar fondo de pantalla si hay una sola página
+
+ Barra de desplazamiento
+ Barra de desplazamiento al lado del cajón de aplicaciones
+
+ Barra de estado oscura
+ Usar siempre la barra de estado oscura en la pantalla de inicio
+
+ De un vistazo
+ Mostrar en la parte superior de la pantalla de inicio
+
+ Buenos días.
+ Buenas noches.
+ Buenas tardes.
+ Buenas noches.
+ Buen día.
+ Hoy es
+ Es
+
+ Artista desconocido
+ Reproduciendo
+ Por
+
+ Reproduciendo
+ Muestra la canción que estás reproduciendo
+
+ Estilo minimalista
+ Cambiar al estilo moderno
+
+ Mensajes aleatorios
+ Hazte mejor compañía con mensajes aleatorios
+
+ - ¡Buenos días!
+ - ¡Buenos días, hora de actuar!
+ - ¡Qué bonito día!
+ - ¡Que tengas un buen día!
+ - ¿Qué tal una pequeña siesta de 5 minutos?
+ - Vamos a ganarnos el pan.
+ - Las mañanas son un lienzo fresco para tu obra maestra diaria.
+ - Abraza el amanecer de la oportunidad cada mañana.
+ - En las primeras luces, encuentra el poder para iluminar tu camino.
+ - Tu día comienza con infinitas posibilidades.
+ - Cada amanecer es un recordatorio de que puedes empezar de nuevo.
+ - Despierta con un propósito, conquista el día que te espera.
+ - Tus pensamientos pueden ser la base de grandes logros.
+ - El mundo despierta y tu potencial también.
+ - El primer capítulo de tu aventura diaria.
+ - Deje que su rutina matutina sea la plataforma de despegue hacia el éxito.
+ - Amaneciendo o no, tu potencial siempre está en aumento.
+ - Es hora de nuevas ideas y acciones audaces.
+ - El madrugador atrapa al gusano, pero tú puedes atrapar tus sueños.
+ - Encuentra el valor para perseguir tus aspiraciones.
+ - Levántate y brilla, porque la grandeza te espera.
+ - El mejor momento para empezar es ahora.
+
+
+ - Persigue tus sueños, no los minutos.
+ - En cada momento, una nueva oportunidad.
+ - Acepta el viaje, no sólo el destino.
+ - Hoy es un regalo, por eso se llama presente.
+ - Deja que tu sonrisa cambie el mundo.
+ - La vida es una historia; haz que cada capítulo cuente.
+ - Encuentra alegría en los momentos ordinarios.
+ - Lo mejor está por llegar, sigue así.
+ - Tu oportunidad de convertir el \"qué pasaría si\" en \"lo que es\"
+ - Aprovecha el día, empezando ahora mismo.
+ - La felicidad es una elección que puedes tomar en cualquier momento.
+ - Cree en ti mismo, incluso en una tarde de sueño.
+ - Deja que tus acciones hablen más que el reloj.
+ - Tu vida es lo que haces con ella; hazla increíble.
+ - Sueña en grande, trabaja duro y nunca te rindas.
+ - Eres más fuerte de lo que piensas.
+ - Cada revés es una preparación para una remontada.
+ - Acepta el viaje, no sólo el destino.
+ - Busca tus objetivos, no tus miedos.
+ - Tu potencial es interminable; desbloquéalo.
+
+
+ - Abraze el momento actual, no importa la hora.
+ - En cada atardecer, encuentra un nuevo amanecer de oportunidad.
+ - La vida es un viaje; disfruta del escenario a lo largo del camino.
+ - Tu actitud da forma a tu realidad.
+ - Tu historia todavía está siendo escrita, incluso cuando el sol se pone.
+ - Captura la belleza en los momentos ordinarios.
+ - Es hora de reflexionar y reiniciar.
+ - Elige la alegría, no importa el tiempo en el reloj.
+ - Las oportunidades no terminan por la noche.
+ - Sé la razón por la que alguien sonríe.
+ - Tu potencial no conoce límites.
+ - Momentos de magia pueden ocurrir a cualquier hora.
+ - Tu vida es lo que haces de ella; hazla extraordinaria.
+ - Manténgase curioso, manténgase saludable.
+ - Sé el cambio que deseas ver en el mundo.
+ - Creé en ti mismo; eres capaz de hacer cosas increíbles.
+ - El único límite es el que tú mismo te fijas.
+ - El éxito comienza dando un solo pazo.
+
+
+ - Aprovecha de la noche, ya que contiene sus propios misterios.
+ - En la tranquilidad de esta noche, encuentra la serenidad.
+ - Las más grandes aventuras de la vida pueden comenzar en la oscuridad.
+ - Tu viaje continúa incluso después del que sol se haya ocultado.
+ - Deja que la noche sea tu lienzo; píntala en tus sueños.
+ - En la tranquilidad de la noche, encuentra tu fuerza interior.
+ - Las estrellas más brillantes brillan en las horas más oscuras.
+ - Sueña grande, incluso en las horas más cortas del día.
+ - Tu vida es una historia esperando a ser escrita.
+ - Es hora para reflexionar y renovar.
+ - Los pensamientos de madrugada pueden llevar a revelaciones en la mañana.
+ - Regocíjate en la tranquilidad del ocaso.
+ - El cielo nocturno es un vasto lienzo de sueños.
+ - Tu potencial no conoce el descanso, persigue tus pasiones.
+ - Mantente esperanzado, incluso en las horas más oscuras.
+ - En medio del caos, encuentra la calma.
+ - Mantente positivo, trabaja duro, y haz que ocurra.
+
+
+ - Las tranquilas noches son los susurros de la oportunidad.
+ - En la tranquilidad de la noche, encuentra tu fuerza interna.
+ - Abraza la oscuridad, porque esta tiene las llaves de tus sueños.
+ - El mundo duerme, pero tu potencial está muy despierto.
+ - La noche puede llegar tarde, pero sus ambiciones son eternas.
+ - Mantente curioso, quédate despierto y que las noches te guíen.
+ - Encuentra el coraje para perseguir tus aspiraciones.
+ - Las grandes cosas no provienen de las zonas de confort.
+ - Tienes el poder de crear tu propio destino.
+ - La felicidad es una elección; elegida cada día.
+ - La vida es lo que haces con ella, así que hazla contar.
+ - Mantente enfocado, mantente determinado, permanece imparable.
+ - Tú eres el autor de tu propia historia.
+ - Esfuérzate por progresar, no por ser perfecto.
+ - Todos los días son un nuevo comienzo, que tengas hoy uno bueno.
+ - Inspira a otros a ser su \"yo\" verdadero.
+ - Tu actitud determina la dirección.
+
+
+ - La ignorancia es una virtud
+ - ¿Es hora de instalar otra actualización ya?
+ - Haz la paz, no la guerra
+ - Hola, ¿qué pasa?
+ - ¡Bienvenidos a la verdadera Matrix!
+ - ¿Cuántas capturas de pantalla haces?
+ - Descubre lo mejor con AlphaDroid
+ - Esparce amor, no la mierda
+ - ¡No iniciamos el fuego!
+ - Hora de un poco de buena música
+ - \u003C\u003C\u003C\u003C\u003C\u003C\u003C HEAD
+ - Necesitas AlphaDroid Premium para ver esto
+ - Recuerda el Linaje (Lineage) del Unicornio (Unicorn)
+ - Disfruta la vida extendida de batería
+ - Empezando de cero
+ - Te amamos 3000
+ - Delicioso incluso sin Sushi
+ - Haz algo bueno hoy
+ - Esta es la mejor experiencia Pixel (Pixel experience), ¿verdad?
+ - Ilusiones no,! bienvenido a la realidad!
+ - Gracias por su apoyo
+ - No hay festival para Derps - !sólo perfección!
+ - Una de las mejores elecciones del buildbot
+ - Cordura para tu paranoia
+ - Levantarse en paz
+ - Qué experiencia tan encantadora, ¿no?
+ - La evolución es un mito, ¿verdad?
+ - Eres lo que flasheas, no seas patata
+ - ¿En que estas pensando?
+ - Expecto Patronum
+ - Wubba Lubba Dub Dub
+ - Ganador Ganador ....?
+ - rm -rf \/
+ - Las Pringles no son patatas fritas de verdad..
+ - ¡Recuerda comprobar el nivel de batería del dispositivo!
+ - Revisa tu correo electrónico/mensajes.
+ - Comprueba tu lista de tareas, si es que tienes una.
+ - Puedes desactivar el espacio rápido en la configuración de inicio.
+ - ¿Nos amas? Envíanos una cerveza
+ - La mejor ROM del mundo.
+ - Dinos \"hola\" en Telegram
+ - Te queremos 3000
+
+ No se puede encontrar el calendario o la actividad del reloj
+
+ Proveedor del clima
+ Automático
+ Google Smartspace
+ OmniJaws
+ Actualización del tiempo
+ Mostrar actualización actual del tiempo
+ Requiere que el servicio meteorológico esté habilitado
+ Nublado
+ Lluvioso
+ Soleado
+ Tormentoso
+ Nevado
+ Ventoso
+ Neblina
+ Ubicación actual
+ Mostrar ubicación meteorológica actual
+ Condición actual
+ Mostrar el resumen actual de la condición meteorológica
+
+ Ocultar automáticamente el indicador de página
+ Ocultar automáticamente los puntos del indicador de página
+
+ Desenfoque de fondo
+ Establecer nivel de profundidad de desenfoque para recientes y cajón de aplicaciones
+
+ Información de la aplicación
+ Fuente
+ Sistema
+ Última actualización
+ Versión
+ Más
+
+ Paquete de iconos
+ Predeterminado
+ Instalar más
+ No hay una tienda de aplicaciones disponible
+
+ Carrusel de fondos de pantalla
+ Cambia rápidamente los fondos de pantalla desde el menú de pulsación prolongada
+ Aplicar a la pantalla de bloqueo
+ Establecer el mismo fondo de pantalla en la pantalla de bloqueo
+
+ Guijarro
+ Vasija
+ Cuadrado puro
+ Cuadrado redondeado
+ Rectángulo cónico
+ Lágrima
+ Rectángulo redondeado
+ Nublado
+ Cilíndrico
+ Flor
+ Corazón
+ Hexágono
+ Hexágono redondeado
+ Estilo iOS
+ Hoja
+ Miau
+ Estilo Samsung
+ Estirado
+
+ Etiquetas de aplicaciones oscuras
+ Usar siempre texto oscuro en las etiquetas de apps en el cajón de aplicaciones
+
+ Estilo del cajón de aplicaciones
+ Normal (panel inferior)
+ Pantalla completa clásica
+ Estilo de lista
+ Estilo OneUI (paginado)
+
+ Orden del cajón de aplicaciones
+ Ordenar alfabéticamente
+ Ordenar por fecha de instalación
+ Ordenar por uso
+
+ Color de fondo personalizado
+ Usar un color personalizado para el fondo del cajón de aplicaciones
+ Color del modo claro
+ Color del modo oscuro
+
+ HSB
+ RGB
+ Tono
+ Saturación
+ Brillo
+ Rojo
+ Verde
+ Azul
+ HEX
+
+ Barra de búsqueda estilo Pixel
+ Barra de búsqueda rediseñada con fondo en capas
+ Opacidad de la capa exterior
+
+ Aplicaciones ocultas & y protegidas
+ Desbloquea para gestionar las aplicaciones ocultas y protegidas
+ Autenticar para abrir %1$s
+ Cargando\u2026
+ Por favor, configura un bloqueo de pantalla seguro para restringir el acceso a las aplicaciones
+ Ayuda
+ Las aplicaciones ocultas y sus widgets están ocultos del cajón
+ Las aplicaciones protegidas requieren autenticación para abrirse desde el lanzador
+
+ Estilo de diseño de recientes
+ Predeterminado
+ Efecto de escala
+ Estilo Kami
+ Estilo iOS
+ Estilo OxygenOS
+
+ Bloquear/Desbloquear aplicación
+ Aplicación desbloqueada en recientes
+ Aplicación bloqueada en recientes
+
+ Vibración de desplazamiento
+
diff --git a/res/values-fa-rIR/cr_strings.xml b/res/values-fa-rIR/cr_strings.xml
new file mode 100644
index 00000000000..8a92b3f8a72
--- /dev/null
+++ b/res/values-fa-rIR/cr_strings.xml
@@ -0,0 +1,446 @@
+
+
+
+
+
+
+ تنظیمات صفحه اصلی
+
+ صفحه اصلی
+ تنظیم پنل گوگل نَـو و بیشتر
+ نشانها
+ تنظیم شکل آیکون و بیشتر
+ منوی برنامه ها
+ شخصی سازی منوی برنامه ها
+ اخیر
+ صفحه نمای کلی را نو نما کنید
+ متفرقه
+ گزینه های دیگر
+ عمومی
+ رابط کاربری
+ فضای سریع
+ نوار جستجو
+ اقدامات سریع
+
+ گزینه های برنامه نویس
+ ویژگی های مخفی بیشتری را با مسئولیت خود فعال کنید
+
+ قفل کردن طرح چیدمان
+ نمادها و ویجت ها را نمی توان اضافه، حذف و در صفحه اصلی منتقل کرد
+ آیکون ها و ویجت ها را می توان در صفحه اصلی اضافه، حذف و منتقل کرد
+ امکان افزودن ویجت ها به صفحه اصلی وجود ندارد
+
+ نمایش نام برنامه ها در صفحه اصلی
+ نمایش نام برنامه ها در کشو برنامه ها
+
+ کشیدن برای دسترسی به برنامه گوگل
+
+ When you swipe left from main Home screen
+
+ When you swipe right from main Home screen
+
+ Use icon customizations in drawer
+ Follow icon customizations used on home screen
+
+ توقف
+ %1$s مکث شود?
+ اعلانهای %1$s متوقف میشوند
+ برنامه متوقف شده است
+ %1$s مستقیماً از راهانداز متوقف شد
+ مکث برنامه
+ برنامه های کاری خاموش هستند
+ خاموش کردن برنامه های کاری
+ روشن کردن برنامه های کاری
+ برنامه های کاری شما نمی توانند برای شما اعلان ارسال کنند، از باتری شما استفاده کنند یا به مکان شما دسترسی داشته باشند
+ برنامه های کاری شما نمی توانند تماس های تلفنی یا پیام های متنی را دریافت کنند، برای شما اعلان ارسال کنند، از باتری شما استفاده کنند یا به مکان شما دسترسی داشته باشند
+
+ پیشنهادها
+ پیشنهادها در صفحه خانه & برای همه برنامهها
+
+ راه اندازی مجدد
+ ریست کردن صفحه اصلی برای اعمال تنظیمات اعمال نشده
+ راه اندازی مجدد لانچر...
+ راهاندازی مجدد لانچر برای اعمال تغییرات...
+ راهاندازی مجدد برای بهروزرسانی مؤلفههای راهانداز...
+
+ برای حالت خواب دوبار ضربه بزنید
+ Double tap empty space on home screen to turn off the screen
+ لرزش هنگام خوابیدن با دوبار ضربه
+ ارائه بازخورد لمسی در حالت دو بار ضربه زدن برای خواب
+
+ برای جستجو انگشت خود را بکشید
+ هر زمان که کشوی برنامه نمایان شد، صفحه کلید را باز کنید
+
+ اندازه آیکون
+
+ اندازه قلم
+
+ استفاده از برچسب دو خطی برای نام برنامه
+
+ ارتفاع ردیف
+
+ پس زمینه صندلی داغ
+ پس زمینه شفاف را به داک برنامه اضافه کنید
+
+ شفافیت پسزمینه
+
+ حاشیه
+
+ توقف
+ برنامه بسته شد
+
+ جستجو
+ لنز Google
+ جستجو صوتی
+ AI mode
+ جستجو موسیقی
+ با فشار دادن آیکون میکروفون، جستجوی موسیقی را آغاز کنید
+ نوار جستوجوی گوگل
+ نوار جستجو در پایین
+ آیکن های تم
+ استفاده کردن از آیکن های تم برای پنل جستجو
+ شعاع گوشه
+
+ App search bar location
+ Hidden
+ Top
+ Bottom
+
+ چرخش کاغذ دیواری
+ جلوه چرخش کاغذ دیواری برای چندین صفحه
+
+ بزرگنمایی شدن کاغذ دیواری
+ هنگام استفاده از کشو یا برنامه های اخیر، کاغذ دیواری را بزرگ یا کوچک کنید
+
+ نوار وضعیت
+
+ سایه بالا
+
+ %1$s در دسترس | %2$s
+ اطلاعات حافظه
+ اطلاعات ZRAM
+
+ عکس گرفتن از صفحه
+ پاک کردن همه موارد
+ لنز
+
+ منظر کوتاه را مجاز کنید
+ به جای برش دادن کاغذدیواری، جلوه اسکرول کامل تصویر زمینه را در تعداد صفحات کمتری فعال کنید
+ تک صفحه مرکز
+ تصویر زمینه در مرکز اگر شما فقط یک صفحه دارید
+
+ نوار پیمایش
+ نوار پیمایش در کنار کشوی برنامه
+
+ نوار وضعیت تاریک
+ همیشه از نوار وضعیت تیره در صفحه اصلی استفاده کنید
+
+ در یک دید
+ در بالای صفحه اصلی نمایش داده شود
+
+ صبح بخیر.
+ عصر بخیر.
+ عصر بخیر.
+ شب بخیر.
+شب خوش.
+ روز بخیر.
+روز خوش.
+ امروز است
+ این است
+اینه
+
+ هنرمند ناشناس
+ در حال بازی کردن
+ توسط
+با
+
+ در حال بازی کردن
+ آهنگی را که در حال پخش آن هستید نشان دهید
+
+ Minimalistic style
+ Switch to modern style
+
+ پیام های تصادفی
+ با پیام های تصادفی همراه خود را سرزنده تر کنید
+
+ - صبح بخیر!
+ - صبح بخیر، وقتشه بترکونیم!
+ - چه روز قشنگی!
+ - روز خوبی داشته باشید!
+ - نظرتون در مورد یک چرت کوچک ۵ دقیقه ای چیه؟
+ - بریم یه نون بخوریم.
+ - صبح ها بوم تازه ای برای شاهکار روزانه شماست.
+ - هر روز صبح طلوع فرصت را در آغوش بگیرید.
+ - در نور اولیه، قدرت روشن کردن مسیر خود را پیدا کنید.
+ - روزتان با امکانات بی پایان آغاز می شود.
+ - هر طلوع خورشید یادآوری است که می توانید از نو شروع کنید.
+ - با هدف بیدار شوید، روز پیش رو را فتح کنید.
+ - افکار شما می تواند پایه و اساس دستاوردهای بزرگ باشد.
+ - جهان بیدار می شود و پتانسیل شما نیز بیدار می شود.
+ - فصل اول در ماجراجویی روزانه شما.
+ - بگذارید روال صبحگاهی شما سکوی پرتاب موفقیت باشد.
+ - طلوع خورشید یا نه، پتانسیل شما همیشه در حال افزایش است.
+ - زمان ایده های تازه و اقدامات جسورانه است.
+ - پرنده اولیه کرم را می گیرد، اما شما می توانید رویاهای خود را بگیرید.
+ - شجاعتِ دنبال کردنِ آرزوهای خود را پیدا کنید.
+ - برخیز و بدرخش، زیرا بزرگی در انتظار است.
+ - بهترین زمان برای شروع همین الان است.
+بهترین زمان برای شروع همین حالا است.
+
+
+ - دنبال رویاهات برو نه دقیقه ها.
+ - در هر لحظه، یک فرصت جدید.
+ - سفر را در آغوش بگیرید، نه فقط مقصد را.
+ - امروز یک هدیه است، به همین دلیل که به آن می گویند حال.
+ - بگذار لبخندت دنیا را تغییر دهد.
+ - زندگی یک داستان است؛ هر فصل را حساب کنید
+ - شادی را در لحظات معمولی بیابید.
+ - بهترین ها هنوز در راه است، همینطور ادامه دهید.
+ - شانس شما برای تبدیل \"چه می شود اگر\" به \"چیست\".
+ - امروز را غنیمت بشمارید، از همین الان شروع کنید.
+ - خوشبختی انتخابی است که هر زمانی می توانید انجام دهید.
+ - حتی در یک بعدازظهر خواب آلود به خود ایمان داشته باشید.
+ - بگذارید اعمال شما بلندتر از ساعت صحبت کنند.
+ - زندگی شما چیزی است که از آن می سازید. آن را شگفت انگیز کنید
+ - رویاهای بزرگ داشته باشید، سخت کار کنید و هرگز تسلیم نشوید.
+ - شما قوی تر از چیزی هستید که فکر می کنید.
+ - هر شکست، تنظیمی برای بازگشت است.
+ - سفر را در آغوش بگیرید، نه فقط مقصد را.
+ - اهداف خود را دنبال کنید، نه ترس های خود را.
+ - پتانسیل شما بی پایان است. آن را باز کنید
+
+
+ - لحظه حال را در آغوش بگیرید، بدون توجه به ساعت.
+ - در هر غروب، یک طلوع فرصت جدید پیدا کنید.
+ - زندگی یک سفر است؛ در طول مسیر از مناظر لذت ببرید
+ - نگرش شما واقعیت شما را شکل می دهد.
+ - داستان شما هنوز در حال نوشتن است، حتی با غروب خورشید.
+ - زیبایی را در لحظات معمولی ثبت کنید.
+ - زمان بازتاب و تنظیم مجدد است.
+ - بدون توجه به زمان ساعت، شادی را انتخاب کنید.
+ - فرصت ها در شب به دست نمی آیند.
+ - دلیل لبخند کسی باش
+ - پتانسیل شما حد و مرزی نمی شناسد.
+ - معجزه می تواند در هر ساعت اتفاق بیفتد.
+ - زندگی شما چیزی است که از آن می سازید. آن را فوق العاده کنید
+ - کنجکاو بمان، شکرگزار بمان.
+ - همان تغییری باش که دوست داری در دنیا ببینی.
+ - به خودت ایمان داشته باش؛ شما قادر به کارهای شگفت انگیز هستید
+ - تنها محدودیت، محدودیتی است که برای خود تعیین می کنید.
+ - موفقیت با یک قدم شروع می شود.
+
+
+ - شب را غنیمت بشمار، زیرا اسرار خودش را دارد.
+ - در خلوت این عصر، آرامش را پیدا کنید.
+ - بزرگترین ماجراهای زندگی می توانند در تاریکی شروع شوند.
+ - سفر شما حتی پس از غروب خورشید ادامه دارد.
+ - بگذار شب بوم نقاشی تو باشد. آن را با رویاها نقاشی کنید
+ - در سکون این عصر، آرامش درونی خود را بیاب.
+ - ستارگان در تاریک ترین ساعات درخشان ترین می درخشند.
+ - حتی در ساعات پایانی روز رویای بزرگ داشته باشید.
+ - زندگی شما داستانی است که منتظر نوشته شدن است.
+ - زمان تأمل و تجدید فرا رسیده است.
+ - افکار آخر شب می توانند به افشاگری های صبح زود منجر شوند.
+ - شادی را در سکون این غروب بیابید.
+ - آسمان شب بوم وسیعی از رویاهاست.
+ - پتانسیل شما زمان خواب را نمی شناسد. به دنبال علایق خود باشید
+ - حتی در تاریک ترین ساعات امیدوار باشید.
+ - در میان هرج و مرج، آرامش درونی خود را پیدا کنید.
+ - مثبت بمانید، سخت کار کنید، آن را محقق کنید.
+
+
+ - شبها زمزمههای آرام فرصتها هستند.
+ - در سکون شب، نیروی درونی خود را بیاب.
+ - تاریکی را در آغوش بگیرید، زیرا کلید رویاهای شما را در خود نگه می دارد.
+ - دنیا خواب است، اما پتانسیل شما کاملاً بیدار است.
+ - شب ممکن است دیر باشد، اما جاه طلبی های شما بی انتها است.
+ - کنجکاو بمانید، بیدار بمانید و بگذارید شب ها شما را راهنمایی کنند.
+ - شجاعت دنبال کردن رویاهایتان را پیدا کنید.
+ - چیزهای بزرگ هرگز از مناطق راحت به دست نمی آیند.
+ - شما این قدرت را دارید که سرنوشت خود را بسازید.
+ - خوشبختی یک انتخاب است؛ هر روز آن را انتخاب کنید
+ - زندگی همان چیزی است که شما آن را می سازید، پس آن را به حساب بیاورید.
+ - متمرکز بمان، مصمم بمان، توقف ناپذیر بمان.
+ - شما نویسنده داستان خودتان هستید.
+ - برای پیشرفت تلاش کن نه کمال.
+ - هر روز شروع جدیدی است؛ آن را عالی کنید
+ - الهام بخش دیگران با خود واقعی خود باشید.
+ - نگرش شما مسیر شما را تعیین می کند.
+
+
+ - نادیده بگیر تا شادروان شوی
+ - به این زودی موقع فلش کردن یه بهروزرسانی دیگه رسید؟
+ - صلح کن نه جنگ
+ - هی، چه خبرا؟
+ - به ماتریکس واقعی خوش آمدید!
+ - چقدر اسکرینشات میگیری؟
+ - با AlphaDroid به خوبیها سلام کن
+ - عشق را گسترش دهید، نه خرابی
+ - ما آتش را روشن نکردیم!
+ - وقت چندتا آهنگ خوبه
+ - \u003C\u003C\u003C\u003C\u003C\u003C\u003C HEAD
+ - برای دیدن این قسمت، به نسخه کامل AlphaDroid نیاز داری
+ - اصل و نسب تکشاخ رو بخاطر داشته باش
+Remember the Lineage of the Unicorn
+ - از طول عمر Xtended باتری لذت ببر
+ - از صفر شروع کن
+ - تو رو ۳۰۰۰تا دوست داریم
+ - حتی بدون سوشی هم خوشمزه است
+ - امروز یه کار خوب انجام بده
+ - این بهترین تجربه کاربری پیکسلیه، مگه نه؟
+ - توهم بی توهم، به واقعیت خوش اومدی!
+ - از حمایتت ممنونیم
+ - No festival for Derps - only perfection!
+ - یکی از بهترینهای buildbot
+ - Sanity for your Paranoia
+ - طلوع در آرامش
+ - چه تجربهی باحالی، مگه نه؟
+ - تکامل یه افسانهس، نه؟
+ - تو چیزی هستی که فلشش میکنی، سیبزمینی نباش
+ - چی تو ذهنته؟
+ - منتظر پاترونوس باش
+ - درد زیادی دارم تحمل میکنم بهم کمک کنین
+ - تو بردی و تو بردی ....؟
+ - rm -rf \/
+ - پرینگل ها در واقع چیپس سیب زمینی نیستند..
+ - به یاد داشته باشید که سطح باتری دستگاه را بررسی کنید!
+ - ایمیل/پیام های خود را بررسی کنید.
+ - اگر فهرستی از کارهایتان را دارید بررسی کنید.
+ - می توانید فضای سریع را از طریق تنظیمات خانه غیرفعال کنید.
+ - ما را دوست داری؟ برای ما آبجو بفرست
+ - بهترین رام همیشه.
+ - در تلگرام به ما سلام کنید
+ - تو رو ۳۰۰۰تا دوست داریم
+
+ فعالیت تقویم یا ساعت یافت نشد
+
+ ارائهدهنده وضعیت هوا
+ خودکار
+ فضای هوشمند گوگل
+ OmniJaws
+ به روز رسانی آب و هوا
+ نمایش به روز رسانی آب و هوای فعلی
+ نیازمند روشن بودن سرویس آب و هوا می باشد
+ ابری
+ بارانی
+ آسمان صاف
+ طوفانی
+ برفی
+ پرباد
+ مه آلود
+ موقعیت فعلی
+ نمایش مکان آب و هوای جاری
+ وضعیت فعلی
+ نمایش خلاصه وضعیت آب و هوای فعلی
+
+ نشانگر پنهان شدن خودکار صفحه
+ نقاط نشانگر صفحه را به طور خودکار پنهان کنید
+
+ عمق تاری پس زمینه
+ سطح عمق تاری را برای موارد اخیر و کشوی برنامه تنظیم کنید
+
+ اطلاعات برنامه
+ منبع
+ سیستم
+ آخرین بروزرسانی
+ نسخه
+ بیشتر
+
+ آیکون پک
+ پیشفرض
+ نصب بیشتر
+ هیچ فروشگاه برنامه در دسترس نیست
+
+ تصویر زمینه چرخشی
+ Quickly switch wallpapers from the long-press menu
+ Apply to lock screen
+ Set the same wallpaper on the lock screen
+
+ Pebble
+ Vessel
+ Pure square
+ Squircle
+ Tapered rect
+ Teardrop
+ Rounded rect
+ ابری
+ Cylindrical
+ Flower
+ Heart
+ Hexagon
+ Rounded hexagon
+ iOS Style
+ Leaf
+ Meow
+ Samsung Style
+ Stretched
+
+ Dark app labels
+ Always use dark text for app labels in the app drawer
+
+ App drawer style
+ Normal (bottom sheet)
+ Legacy fullscreen
+ List style
+ OneUI style (paged)
+
+ App drawer sorting
+ Sort alphabetically
+ Sort by install date
+ Sort by usage
+
+ Custom background color
+ Use a custom color for the app drawer background
+ Light mode color
+ Dark mode color
+
+ HSB
+ RGB
+ Hue
+ Saturation
+ Brightness
+ Red
+ Green
+ Blue
+ HEX
+
+ Pixel style search bar
+ Revamped search bar with layered background
+ Outer layer opacity
+
+ برنامههای پنهان & محافظت شده
+ برای مدیریت برنامههای پنهان و محافظت شده قفل را باز کنید
+ احراز هویت برای باز کردن %1$s
+ در حال بارگزاری\u2026
+ لطفا یک قفل برای کنترل برنامههای پنهان و محافظت شده، تنظیم کنید
+ کمک
+ برنامههای مخفی و ویجتهایشان از فهرست برنامه ها پنهان هستند
+ برای باز کردن برنامههای محافظت شده از صفحه اصلی نیاز به احراز هویت میباشد
+
+ Recents layout style
+ پیشفرض
+ Scale effect
+ Kami style
+ iOS style
+ OxygenOS style
+
+ Lock/Unlock app
+ Unlocked app in recents
+ Locked app in recents
+
+ Scroll vibration
+
diff --git a/res/values-fi-rFI/cr_strings.xml b/res/values-fi-rFI/cr_strings.xml
new file mode 100644
index 00000000000..3398597a59a
--- /dev/null
+++ b/res/values-fi-rFI/cr_strings.xml
@@ -0,0 +1,440 @@
+
+
+
+
+
+
+ Aloitusnäytön asetukset
+
+ Aloitusnäyttö
+ Säädä Google Now paneelin asetukset
+ Ikonit
+ Aseta kuvakkeen koko ja paljon muuta
+ Sovellusvalitisin
+ Muokkaa sovellusvalitsintasi
+ Viimeaikaiset
+ Määritä yleisnäkymä
+ Sekalaiset
+ Muut valinnat
+ Yleiset
+ Käyttöliittymä
+ Quickspace
+ Hakupalkki
+ Pikatoiminnot
+
+ Kehittäjäasetukset
+ Voit ottaa joitakin piilotettuja ominaisuuksia käyttöön omalla vastuullasi
+
+ Lukitse asettelu
+ Kuvakkeita ja widgettejä ei voi lisätä, poistaa tai siirtää aloitusnäytöllä
+ Kuvakkeita ja widgettejä voi lisätä, poistaa ja siirtää aloitusnäytöllä
+ Aloitusnäytölle ei ole mahdollista lisätä widgettejä
+
+ Kuvakkeiden otsikot aloitusnäytöllä
+ Kuvakkeiden otsikot sovellusvalikossa
+
+ Pyyhkäise käyttääksesi Google-sovellusta
+
+ When you swipe left from main Home screen
+
+ When you swipe right from main Home screen
+
+ Use icon customizations in drawer
+ Follow icon customizations used on home screen
+
+ Pysäytä
+ Pysäytetäänkö %1$s?
+ Ilmoitukset kohteelle %1$s pysäytetään
+ Sovellus pysäytetty
+ %1$s pysäytettiin suoraan käynnistimestä
+ Pysäytä sovellus
+ Work apps are off
+ Turn off work apps
+ Turn on work apps
+ Your work apps cannot send you notifications, use your battery, or access your location
+ Your work apps cannot receive phone calls or text messages, send you notifications, use your battery, or access your location
+
+ Ehdotukset
+ Kaikille sovelluksille & aloitusnäytön ehdotuksille
+
+ Käynnistä uudelleen
+ Käynnistä käynnistin uudelleen manuaalisesti ottaaksesi käyttöön kaikki odottavat asetukset
+ Käynnistetään käynnistysohjelma uudelleen...
+ Käynnistetään käynnistysohjelma uudelleen, jotta muutokset otetaan käyttöön...
+ Käynnistetään käynnistysohjelma uudelleen käynnistimen komponenttien päivittämiseksi...
+
+ Tuplanäpäytä horrostilaan
+ Double tap empty space on home screen to turn off the screen
+ Vibrate on double tap to sleep
+ Provide haptic feedback on double tap to sleep gesture
+
+ Swipe to start search
+ Open keyboard whenever app drawer becomes visible
+
+ Kuvakkeen koko
+
+ Tekstin koko
+
+ Use two lines for app label
+
+ Rivin korkeus
+
+ Sovellustelakan tausta
+ Lisää läpikuultava tausta sovellustelakkaan
+
+ Taustan läpinäkyvyys
+
+ Stroke
+
+ Tapa
+ Sovellus tapettu
+
+ Haku
+ Google Lens
+ Puhehaku
+ AI mode
+ Musiikkihaku
+ Aloita musiikkihaku painamalla mikrofonikuvaketta
+ Google-hakupalkki
+ Hakupalkki alhaalla
+ Teemakuvakkeet
+ Käytä teemakuvakkeita hakupalkille
+ Kulman säde
+
+ App search bar location
+ Hidden
+ Top
+ Bottom
+
+ Taustakuvan vieritys
+ Taustakuvan vierintäefekti usealle näytölle
+
+ Taustakuvan zoomaus
+ Suurenna tai loitonna taustakuvaa käyttäessäsi sovellusvalikkoa tai viimeaikaisia sovelluksia
+
+ Tilapalkki
+
+ Yläosan varjostus
+
+ %1$s Käytettävissä | %2$s Yhteensä
+ Muistin tiedot
+ ZRAM info
+
+ Kuvakaappaus
+ Tyhjennä kaikki
+ Lens
+
+ Salli lyhyt parallaksi
+ Ota käyttöön koko taustakuvan vierintätehoste pienimmillä sivumäärillä taustakuvan rajaamisen sijaan
+ Yhden sivun keskitys
+ Keskitä taustakuva jos vain yksi sivu on käytössä
+
+ Scrollbar
+ Scrollbar on side of the app drawer
+
+ Dark status bar
+ Always use dark status bar on home screen
+
+ Pikakatselu
+ Näytä aloitusnäytön yläosassa
+
+ Hyvää huomenta.
+ Hyvää iltaa.
+ Hyvää iltapäivää.
+ Hyvää yötä.
+ Hyvää päivää.
+ Tänään on
+ Se on
+
+ Tuntematon esittäjä
+ Nyt toistetaan
+ Tekijä
+
+ Nyt toistetaan
+ Näytä kappale, jota toistat
+
+ Minimalistinen tyyli
+ Vaihda moderniin tyyliin
+
+ Satunnaiset viestit
+ Tee kumppanisi vilkkaammaksi satunnaisten viestien avulla
+
+ - Hyvää huomenta!
+ - Hyvää huomenta, aika rokata!
+ - Mikä kaunis päivä!
+ - Hyvää päivänjatkoa!
+ - Miten olisi pieni 5 minuutin nokonen?
+ - Saadaan tämä leipä.
+ - Aamut ovat raikas kangas päivittäiseen mestariteokseen.
+ - Nauti mahdollisuuksien auringonnoususta joka aamu.
+ - Löydä varhaisesta valosta voima, joka valaisee polkuasi.
+ - Päiväsi alkaa loputtomilla mahdollisuuksilla.
+ - Jokainen auringonnousu on muistutus siitä, että voit aloittaa alusta.
+ - Herää määrätietoisesti, valloita edessä oleva päivä.
+ - Ajatuksesi voivat olla suurten saavutusten perusta.
+ - Maailma herää, ja niin tekee myös potentiaalisi.
+ - Päivittäisen seikkailusi ensimmäinen luku.
+ - Anna aamurutiinisi olla onnistumisen lähtökohta.
+ - Auringonnousu tai ei, potentiaalisi on aina nousussa.
+ - On uusien ideoiden ja rohkeiden toimien aika.
+ - Aikainen lintu nappaa madon, mutta sinä voit napata unelmasi.
+ - Löydä rohkeutta tavoitella haaveitasi.
+ - Nouse ja loista, sillä suuruus odottaa.
+ - Paras aika aloittaa on nyt.
+
+
+ - Tavoittele unelmiasi, älä minuutteja.
+ - Joka hetki on uusi mahdollisuus.
+ - Tärkeintä on matka, ei määränpää.
+ - Tämä päivä on lahja, siksi sitä kutsutaan nykyhetkeksi.
+ - Anna hymysi muuttaa maailmaa.
+ - Elämä on tarina; tee jokaisesta luvusta tärkeä.
+ - Löydä iloa tavallisista hetkistä.
+ - Paras on vielä tulossa, jatka samaan malliin.
+ - Mahdollisuutesi muuttaa \"mitä jos\" muotoon \"mitä on.\"
+ - Tartu päivään, alkaen juuri nyt.
+ - Onnellisuus on valinta, jonka voit tehdä milloin tahansa.
+ - Usko itseesi, vaikka olisitkin väsynyt iltapäivällä.
+ - Anna tekojesi puhua kovempaa kuin kello.
+ - Elämäsi on sellainen kuin teet siitä; tee siitä mahtava.
+ - Unelmoi suuresti, tee kovasti töitä äläkä koskaan anna periksi.
+ - Olet vahvempi kuin luuletkaan.
+ - Jokainen takaisku on comebackin pohjustus.
+ - Tärkeintä on matka, ei määränpää.
+ - Seuraa tavoitteitasi, älä pelkojasi.
+ - Potentiaalisi on rajaton; vapauta se.
+
+
+ - Nauti nykyhetkestä kellonajasta riippumatta.
+ - Löydä jokaisesta hämärästä uuden mahdollisuuden aamunkoitto.
+ - Elämä on matka; nauti maisemista matkan varrella.
+ - Asenteesi muokkaa todellisuuttasi.
+ - Tarinaasi kirjoitetaan yhä, vaikka aurinko laskee.
+ - Sieppaa kauneus tavallisista hetkistä.
+ - On aika miettiä ja aloittaa alusta.
+ - Valitse ilo, kellonajasta riippumatta.
+ - Mahdollisuudet eivät katoa illalla.
+ - Ole syy jonkun hymyyn.
+ - Potentiaalisi on rajaton.
+ - Maagisia hetkiä voi tapahtua milloin tahansa.
+ - Elämäsi on sellainen kuin teet siitä; tee siitä poikkeuksellinen.
+ - Pysy uteliaana, pysy kiitollisena.
+ - Ole se muutos, jonka haluat nähdä maailmassa.
+ - Usko itseesi; pystyt hämmästyttäviin asioihin.
+ - Ainoa raja on se, jonka asetat itsellesi.
+ - Onnistuminen alkaa yhdestä askeleesta.
+
+
+ - Tartu yöhön, sillä se pitää sisällään omat salaisuutensa.
+ - Tämän illan hiljaisuudesta löytyy tyyneyttä.
+ - Elämän suurimmat seikkailut voivat alkaa pimeydestä.
+ - Matkasi jatkuu vielä auringonlaskun jälkeenkin.
+ - Anna yön olla kankaasi; maalaa se unelmilla.
+ - Löydä sisäinen rauhasi tämän illan hiljaisuudessa.
+ - Tähdet loistavat kirkkaimmin pimeimpinä tunteina.
+ - Unelmoi suuresti, vielä päivän myöhäisinä tunteinakin.
+ - Elämäsi on tarina, joka odottaa kirjoittamistaan.
+ - On aika pohtia ja uudistua.
+ - Myöhäisen yön ajatukset voivat johtaa varhaisen aamun paljastuksiin.
+ - Löydä ilo tämän illan hiljaisuudesta.
+ - Yötaivas on valtava unelmien kangas.
+ - Potentiaalisi ei tunne nukkumaanmenoaikaa; jatka intohimojasi.
+ - Pysy toiveikkaana jopa pimeimpinä tunteina.
+ - Löydä kaaoksen keskellä sisäinen rauhallisuutesi.
+ - Pysy positiivisena, tee kovasti töitä ja toteuta se.
+
+
+ - Yöt ovat mahdollisuuksien hiljaisia kuiskauksia.
+ - Löydä sisäinen voimasi yön hiljaisuudessa.
+ - Syleile pimeyttä, sillä siinä on avaimet unelmiisi.
+ - Maailma nukkuu, mutta potentiaalisi on hereillä.
+ - Ilta voi olla myöhäinen, mutta tavoitteesi ovat ajattomia.
+ - Pysy uteliaana, pysy hereillä ja anna yön ohjata sinua.
+ - Löydä rohkeutta tavoitella unelmiasi.
+ - Suuret asiat eivät koskaan tule mukavuusalueilta.
+ - Sinulla on valta luoda oma kohtalosi.
+ - Onnellisuus on valinta, valitse se joka päivä.
+ - Elämä on sitä, mitä siitä tekee, joten tee siitä merkityksellinen.
+ - Pysy keskittyneenä, pysy päättäväisenä, pysy pysäyttämättömänä.
+ - Olet oman tarinasi kirjoittaja.
+ - Tavoittele edistystä, älä täydellisyyttä.
+ - Jokainen päivä on uusi alku; tee siitä hieno.
+ - Inspiroi muita olemalla aito itsesi.
+ - Asenteesi määrää suuntasi.
+
+
+ - Tieto lisää tuskaa
+ - Onko aika asentaa toinen päivitys jo?
+ - Solmi rauha, älä sodi
+ - Ai hei, mitä kuuluu?
+ - Welcome to real Matrix!
+ - Kuinka monta kuvakaappausta otat?
+ - Avaa hyvyys AlphaDroidilla
+ - Levitä rakkautta, älä tuhoa
+ - Emme aloittaneet tulta!
+ - Aika kuunnella hyvää musiikkia
+ - \u003C\u003C\u003C\u003C\u003C\u003C\u003C PÄÄ
+ - Tarvitset AlphaDroid Premiumin nähdäksesi tämän
+ - Muista yksisarvisen sukulinja
+ - Nauti pidemmästä akun kestosta
+ - Aloita alkupisteestä
+ - Rakastamme sinua 3000
+ - Herkullista jopa ilman Sushiä
+ - Tee jotain mukavaa tänään
+ - Tämä on paras Pixel Kokemus, eikö vain?
+ - Ei illuusioita, tervetuloa todellisuuteen!
+ - Kiitos tuestasi
+ - Ei juhlaa urpoille - vain täydellisyyttä!
+ - Yksi buildbotin parhaista valinnoista
+ - Järkevyyttä vainoharhaisuudellesi
+ - Rising in peace
+ - Mikä ihana kokemus, eikö olekin?
+ - Evoluutio on myytti, eikö?
+ - Olet mitä asennat, älä ole peruna
+ - Mitä sinulla on mielessäsi?
+ - Odotettu suojelija
+ - Wubba Lubba Dub Dub
+ - Voittaja Voittaja ....?
+ - rm -rf \/
+ - Pringlesit eivät oikeastaan ole perunalastuja..
+ - Muista tarkistaa laitteen akun varaustaso!
+ - Tarkista sähköpostisi/viestisi.
+ - Tarkista tehtävälistasi, jos sinulla on sellainen.
+ - Voit poistaa quickspace-tilan käytöstä kotiasetuksista.
+ - Rakastatko meitä? Lähetä meille olutta
+ - Paras Rom Ikinä.
+ - Tule moikkaamaan meitä Telegramissa
+ - Rakastamme sinua 3000
+
+ Kalenterin tai kellon toimintaa ei löydy
+
+ Weather provider
+ Auto
+ Google Smartspace
+ OmniJaws
+ Sääpäivitys
+ Näytä nykyisen sijainnin sää
+ Vaatii sääpalvelun käyttöönottamisen
+ Pilvinen
+ Sateinen
+ Aurinkoinen
+ Myrskyinen
+ Luminen
+ Tuulinen
+ Sumuinen
+ Nykyinen sijainti
+ Näytä nykyisen sijainnin sää
+ Nykytila
+ Näytä yhteenveto nykyisistä sääolosuhteista
+
+ Auto-hide page indicator
+ Hide the page indicator dots automatically
+
+ Taustan sumennuksen syvyys
+ Aseta sumennuksen syvyys viimeaikaisille ja sovellusvalikolle
+
+ Application info
+ Lähde
+ Järjestelmä
+ Päivitetty viimeksi
+ Versio
+ Lisää
+
+ Kuvakepaketti
+ Oletus
+ Asenna lisää
+ Sovelluskauppaa ei ole käytettävissä
+
+ Wallpaper carousel
+ Quickly switch wallpapers from the long-press menu
+ Apply to lock screen
+ Set the same wallpaper on the lock screen
+
+ Pebble
+ Vessel
+ Pure square
+ Squircle
+ Tapered rect
+ Teardrop
+ Rounded rect
+ Pilvinen
+ Cylindrical
+ Flower
+ Heart
+ Hexagon
+ Rounded hexagon
+ iOS Style
+ Leaf
+ Meow
+ Samsung Style
+ Stretched
+
+ Dark app labels
+ Always use dark text for app labels in the app drawer
+
+ App drawer style
+ Normal (bottom sheet)
+ Legacy fullscreen
+ List style
+ OneUI style (paged)
+
+ App drawer sorting
+ Sort alphabetically
+ Sort by install date
+ Sort by usage
+
+ Custom background color
+ Use a custom color for the app drawer background
+ Light mode color
+ Dark mode color
+
+ HSB
+ RGB
+ Hue
+ Saturation
+ Brightness
+ Red
+ Green
+ Blue
+ HEX
+
+ Pixel style search bar
+ Revamped search bar with layered background
+ Outer layer opacity
+
+ Piilotetut & Suojatut sovellukset
+ Avaa hallitaksesi piilotettuja ja suojattuja sovelluksia
+ Tunnistaudu avataksesi %1$s
+ Ladataan\u2026
+ Aseta turvallinen näytön lukitus rajataksesi pääsyä sovelluksiin
+ Ohje
+ Piilotetut sovellukset ja niiden widgetit piilotetaan sovellusvalikosta
+ Suojatut sovellukset tarvitsevat tunnistautumisen, jotta ne voidaan avata
+
+ Recents layout style
+ Oletus
+ Scale effect
+ Kami style
+ iOS style
+ OxygenOS style
+
+ Lock/Unlock app
+ Unlocked app in recents
+ Locked app in recents
+
+ Scroll vibration
+
diff --git a/res/values-fr-rFR/alpha_strings.xml b/res/values-fr-rFR/alpha_strings.xml
new file mode 100644
index 00000000000..e8660a28519
--- /dev/null
+++ b/res/values-fr-rFR/alpha_strings.xml
@@ -0,0 +1,44 @@
+
+
+
+
+ Lock/Unlock app
+ Unlocked app in recents
+ Locked app in recents
+
+ Recents layout style
+ Stock
+ Kami Style
+ iOS Style
+ OxygenOS Style
+
+ - Ignorance is Bliss
+ - Make love, not war
+ - Oh hey, what\'s up?
+ - We love you 3000
+ - Thank you for your support
+ - One of the buildbot\'s best picks
+ - Pringles aren\'t actually potato chips..
+ - You can disable quickspace via home settings.
+ - I got nothing ¯\\_(ツ)_/¯
+
+
+ - I never apologize. I\'m sorry but that\'s the way I am.
+ - All my life, I\'ve had one dream: to achieve my many goals.
+ - Good drink... good meat... good God, let\'s eat!
+ - You\'ll have to speak up. I\'m wearing a towel.
+ - Change the channel, Marge...
+ - Roads are just a suggestion Marge, just like pants.
+ - It takes two to lie - one to lie and one to listen.
+ - Shut up, Flanders!
+ - I\'m not normally a praying man, but if you\'re up there, please save me, Superman.
+ - It\'s so simple to be wise... just think of something stupid to say and then don\'t say it.
+ - Stupid sexy Flanders...
+ - I hope I didn\'t brain my damage.
+ - Operator, give me the number for 911!
+ - Don\'t blame me, I voted for Kodos.
+
+
diff --git a/res/values-fr-rFR/cr_strings.xml b/res/values-fr-rFR/cr_strings.xml
new file mode 100644
index 00000000000..6b34b0eb028
--- /dev/null
+++ b/res/values-fr-rFR/cr_strings.xml
@@ -0,0 +1,440 @@
+
+
+
+
+
+
+ Paramètres d\'accueil
+
+ Écran d’accueil
+ Activer Google Now et autres
+ Icônes
+ Définir la taille des icônes et plus
+ Tiroir d\'applications
+ Personnaliser votre tiroir d\'applications
+ Récents
+ Personnaliser le menu récent
+ Divers
+ Autres options
+ Général
+ Interface
+ Espace rapide
+ Barre de recherche
+ Actions rapides
+
+ Options pour les développeurs
+ Activez certaines fonctionnalités cachées à vos risques et périls
+
+ Verrouiller la mise en page
+ Les icônes et widgets ne peuvent pas être ajoutés, supprimés ou déplacés sur l\'écran d\'accueil
+ Les icônes et widgets peuvent être ajoutés, supprimés ou déplacés sur l\'écran d\'accueil
+ Il n\'est pas possible d\'ajouter des widgets à l\'écran d\'accueil
+
+ Nom d\'application sur le bureau
+ Nom d\'application dans le tiroir
+
+ Glisser pour accéder à l\'application Google
+
+ Lorsque vous faites glisser vers la gauche depuis l\'écran d\'accueil principal
+
+ Lorsque vous faites glisser depuis la gauche sur l\'écran d\'accueil principal
+
+ Use icon customizations in drawer
+ Follow icon customizations used on home screen
+
+ Pause
+ Pause %1$s?
+ Les notifications pour %1$s seront suspendues
+ Application en pause
+ %1$s a été mis en pause directement depuis le lanceur
+ Mettre en pause l\'application
+ Les applications professionnelles sont désactivées
+ Désactiver les applications professionnelles
+ Activer les applications professionnelles
+ Vos applis professionnelles ne peuvent pas vous envoyer de notifications, utiliser votre batterie ni accéder à votre position
+ Vos applications professionnelles ne peuvent pas recevoir des appels téléphoniques ou des sms, vous envoyer des notifications, utiliser votre batterie ou accéder à votre position
+
+ Suggestions
+ Pour toutes les applications & ajout à l\'écran d\'accueil
+
+ Redémarrer
+ Redémarrez le lanceur manuellement pour appliquer tous les paramètres en attente
+ Redémarrage du lanceur...
+ Redémarrage du lanceur pour appliquer les modifications...
+ Redémarrage du lanceur pour mettre à jour les composants...
+
+ Double-cliquez pour mettre en veille
+ Appuyez deux fois sur un espace libre de l\'écran d\'accueil pour éteindre l\'écran
+ Vibrer lors d\'un appui double pour éteindre l\'écran
+ Fournir un retour haptique lors d\'un double appui pour éteindre l\'écran
+
+ Glisser pour lancer la recherche
+ Ouvrir le clavier chaque fois que le tiroir d\'applications devient visible
+
+ Taille des icônes
+
+ Taille de police
+
+ Utiliser deux lignes pour le label de l\'application
+
+ Hauteur de ligne
+
+ Arrière plan
+ Ajouter un arrière-plan translucide au tiroir d\'applications
+
+ Opacité de l\'arrière-plan
+
+ Largeur
+
+ Terminer
+ Application terminée
+
+ Rechercher
+ Google Lens
+ Recherche vocale
+ Mode IA
+ Recherche de musique
+ Démarrer la recherche de musique en appuyant sur l\'icône du micro
+ Barre de recherche inférieure
+ Barre de recherche dans le dock inférieur
+ Icônes thématiques
+ Utiliser les icônes thématiques pour la barre de recherche
+ Arrondi des angles
+
+ App search bar location
+ Hidden
+ Top
+ Bottom
+
+ Défilement du fond d\'écran
+ Effet de défilement du fond d\'écran pour plusieurs écrans
+
+ Zoom sur le fond d\'écran
+ Zoomer ou dézoomer sur le fond d\'écran à l\'ouverture du tiroir ou des applications récentes
+
+ Barre d\'état
+
+ Ombre en haut
+
+ %1$s Disponible | %2$s Total
+ Information sur la RAM
+ Information sur l\'extension de mémoire ZRAM
+
+ Capture d\'écran
+ Tout effacer
+ Lentille d\'objectif
+
+ Autoriser la parallaxe courte
+ Activer l\'effet de défilement complet du fond d\'écran sur un nombre réduit de pages au lieu de le recadrer
+ Centre de page unique
+ Centrer le fond d\'écran si une seule page est utilisée
+
+ Barre de défilement
+ Barre de défilement sur le côté du tiroir d\'applications
+
+ Barre d\'état sombre
+ Toujours utiliser la barre d\'état sombre sur l\'écran d\'accueil
+
+ «En un coup d\'œil»
+ Montrer en haut de votre écran d\'accueil
+
+ Bonjour.
+ Bonsoir.
+ Bon après-midi.
+ Bonne nuit.
+ Bonne journée.
+ Nous sommes le
+ Nous sommes le
+
+ Artiste Inconnu
+ Lecture en cours
+ Par
+
+ Lecture en cours
+ Afficher la musique que vous jouez
+
+ Style minimaliste
+ Basculer vers le style moderne
+
+ Messages aléatoires
+ Rendre votre compagnon plus vivant avec des messages aléatoires
+
+ - Bonjour !
+ - Bonjour, il est temps d\'y aller !
+ - Quelle belle journée !
+ - Bonne journée !
+ - Que diriez-vous d\'une pause de 5 minutes ?
+ - Allez, on se motive !
+ - Le matin est une toile fraîche pour votre chef-d\'œuvre quotidien.
+ - Saisissez chaque matin l\'opportunité qui s\'offre à vous.
+ - Dans les premières lueurs du jour, trouvez le pouvoir d\'éclairer votre chemin.
+ - Votre journée commence par des possibilités infinies.
+ - Chaque lever de soleil nous rappelle qu\'il est possible de prendre un nouveau départ.
+ - Réveillez-vous avec un but précis, et partez à la conquête de la journée qui s\'annonce.
+ - Vos pensées peuvent être à l\'origine de grandes réalisations.
+ - Le monde se réveille et votre potentiel aussi.
+ - Le premier chapitre de votre aventure quotidienne.
+ - Faites de votre routine matinale le point de départ de votre réussite.
+ - Que le soleil se lève ou non, votre potentiel est toujours en hausse.
+ - Il est temps d\'adopter des idées nouvelles et d\'agir avec audace.
+ - L\'avenir appartient à ceux qui se lèvent tôt.
+ - Trouvez le courage de poursuivre vos aspirations.
+ - Haut les cœurs, car la grandeur vous attend.
+ - Le meilleur moment pour commencer est maintenant.
+
+
+ - Poursuivez vos rêves, pas les minutes.
+ - À chaque instant, une nouvelle opportunité.
+ - Appréciez le trajet, pas seulement la destination.
+ - Aujourd\'hui est un cadeau, c\'est pourquoi on l\'appelle le présent.
+ - Laissez votre sourire changer le monde.
+ - La vie est une histoire : faites compter chaque chapitre.
+ - Trouvez de la joie dans les moments ordinaires.
+ - Le meilleur reste encore à venir, continuez comme ça.
+ - Saisissez votre chance de changer \"j\'aurais dû faire\" en \"j\'ai fait\".
+ - Saisissez l\'occasion, dès maintenant.
+ - Le bonheur est un choix que vous pouvez faire à tout moment.
+ - Croyez en vous, même par un après-midi tranquille.
+ - Laissez vos actes parler plus fort que l\'horloge.
+ - Votre vie est ce que vous en faites. Rendez-la incroyable.
+ - Rêvez grand, travaillez dur, et n\'abandonnez jamais.
+ - Vous êtes plus fort que vous ne le pensez.
+ - Chaque pas en arrière est un tremplin pour avancer.
+ - Appréciez le trajet, pas seulement la destination.
+ - Poursuivez vos objectifs, pas vos craintes.
+ - Votre potentiel est infini, libérez-le.
+
+
+ - Profitez du moment présent, peu importe l\'heure.
+ - À chaque crépuscule, il y a une nouvelle aube d\'opportunité.
+ - La vie est un voyage, profitez du paysage tout au long de la route.
+ - Votre attitude façonne votre réalité.
+ - Votre histoire continue de s\'écrire, même quand le soleil se couche.
+ - Capturez la beauté des moments ordinaires.
+ - Il est temps de réfléchir et de repartir à zéro.
+ - Choisissez la joie, peu importe l\'heure.
+ - Les opportunités n\'attendent pas le soir.
+ - Soyez la raison pour laquelle quelqu\'un sourit.
+ - Votre potentiel ne connaît aucune limite.
+ - Des moments de magie peuvent se produire à n\'importe quelle heure.
+ - Votre vie est ce que vous en faites. Rendez-la incroyable.
+ - Restez curieux, restez reconnaissant.
+ - Soyez le changement que vous voulez voir dans ce monde.
+ - Croyez en vous-même : vous êtes capable de choses étonnantes.
+ - La seule limite est celle que vous avez fixée.
+ - La réussite commence par un premier pas.
+
+
+ - Saisissez la nuit, car elle contient ses propres mystères.
+ - Dans le calme de cette soirée, trouvez la sérénité.
+ - Les plus grandes aventures de la vie peuvent commencer dans les ténèbres.
+ - Votre voyage se poursuit même après le coucher du soleil.
+ - Laissez la nuit être votre toile ; peignez-la de rêves.
+ - Dans le calme de cette soirée, trouvez votre paix intérieure.
+ - Les étoiles brillent dans les heures les plus sombres.
+ - Rêvez grand, même à la fin de la journée.
+ - Votre vie est une histoire qui attend d\'être écrite.
+ - Il est temps de réfléchir et de se renouveler.
+ - Les réflexions de fin de soirée peuvent déboucher sur des révélations au petit matin.
+ - Trouvez de la joie dans le calme de cette soirée.
+ - Le ciel nocturne est une vaste toile de rêves.
+ - Votre potentiel ne se repose pas, vivez vos passions.
+ - Restez optimiste, même dans les heures les plus sombres.
+ - Au milieu du chaos, trouvez votre calme intérieur.
+ - Restez positif, travaillez dur, faites en sorte que cela se produise.
+
+
+ - Les nuits sont les chuchotements silencieux de l\'opportunité.
+ - Dans le calme de la nuit, trouvez votre force intérieure.
+ - Embrassez les ténèbres, car elles détiennent les clés de vos rêves.
+ - Le monde est endormi, mais votre potentiel est bien éveillé.
+ - La nuit est peut-être avancée, mais vos ambitions sont intemporelles.
+ - Restez curieux, restez éveillé et laissez les nuits vous guider.
+ - Trouvez le courage de poursuivre vos rêves.
+ - Les bonnes choses ne viennent jamais des zones de confort.
+ - Vous avez le pouvoir de créer votre propre destin.
+ - Le bonheur est un choix : choisissez-le tous les jours.
+ - La vie est ce que vous en faites, alors faites en sorte qu\'elle compte.
+ - Restez concentré, restez déterminé, soyez inarrêtable.
+ - Vous êtes l\'auteur de votre propre histoire.
+ - Luttez pour le progrès, pas pour la perfection.
+ - Chaque jour est un nouveau commencement : faites qu\'il soit grandiose.
+ - Inspirez les autres en étant authentique.
+ - Votre attitude détermine votre orientation.
+
+
+ - Heureux sont les ignorants
+ - Est-ce déjà le moment de flasher une autre mise à jour ?
+ - Faire la paix, pas la guerre
+ - Hey ! Quoi de neuf ?
+ - Bienvenue dans real Matrix
+ - Combien de captures d\'écran prenez-vous ?
+ - Libérez ce qui est bon avec AlphaDroid
+ - Répandez l\'amour, pas le chaos
+ - Nous n\'avons pas déclenché l\'incendie !
+ - Il est temps d\'écouter de la bonne musique
+ - \u003C\u003C\u003C\u003C\u003C\u003C\u003C HEAD
+ - Vous avez besoin de AlphaDroid Premium pour voir ceci
+ - Souviens-toi de l\'héritage de la licorne
+ - Profitez d\'une durée de vie de la batterie prolongée
+ - A partir du début
+ - Nous vous aimons 3000
+ - Délicieux même sans sushi
+ - Faites quelque chose de bien aujourd\'hui
+ - Ceci est la meilleure expérience Pixel, n\'est-ce pas ?
+ - Pas d\'illusion, bienvenue dans la réalité !
+ - Merci pour votre soutien
+ - Rien d\'autre que la perfection !
+ - Un des meilleurs choix de Buildbot
+ - Pas de Paranoïa ici
+ - S\'élever en paix
+ - Quelle belle expérience, n\'est-ce-pas ?
+ - L\'évolution est un mythe, n\'est-ce pas ?
+ - On est ce qu\'on flashe, ne sois pas une Potato
+ - Tu penses à quoi ?
+ - Expecto Patronum
+ - Wubba Lubba Dub Dub
+ - Et le vainqueur est...
+ - rm -rf \/
+ - Les Pringles ne sont pas des chips...
+ - N\'oubliez pas de vérifier le niveau de batterie de l\'appareil !
+ - Vérifiez vos emails/messages.
+ - Vérifiez votre liste de tâches si vous en avez une.
+ - Vous pouvez désactiver l\'espace rapide via les paramètres du menu principal.
+ - Vous nous aimez ? Envoyez-nous une bière
+ - La meilleure ROM qui soit.
+ - Dites nous bonjour sur Telegram
+ - On vous aime aussi
+
+ Impossible de trouver l\'activité du calendrier ou de l\'horloge
+
+ Fournisseur météo
+ Auto
+ Google Smartspace
+ OmniJaws
+ Mise à jour météo
+ Afficher la météo actuelle
+ Nécessite que le service météo soit activé
+ Nuageux
+ Pluvieux
+ Ensoleillé
+ Orageux
+ Neigeux
+ Venteux
+ Brumeux
+ Emplacement actuel
+ Afficher la position actuelle de la météo
+ Conditions météo
+ Afficher le résumé des conditions météo actuelles
+
+ Masquage automatique de l\'indicateur de page
+ Masquer automatiquement les points indicateurs de page
+
+ Niveau de flou
+ Définir le niveau de flou pour le tiroir et récent
+
+ Informations de l\'application
+ Source
+ Système
+ Dernière mise à jour
+ Version
+ Plus
+
+ Pack d\'icônes
+ Par défaut
+ Installer plus
+ Il n\'y a aucun magasin d\'applications disponible
+
+ Wallpaper carousel
+ Quickly switch wallpapers from the long-press menu
+ Apply to lock screen
+ Set the same wallpaper on the lock screen
+
+ Pebble
+ Vessel
+ Pure square
+ Squircle
+ Tapered rect
+ Teardrop
+ Rounded rect
+ Nuageux
+ Cylindrical
+ Flower
+ Heart
+ Hexagon
+ Rounded hexagon
+ iOS Style
+ Leaf
+ Meow
+ Samsung Style
+ Stretched
+
+ Dark app labels
+ Always use dark text for app labels in the app drawer
+
+ App drawer style
+ Normal (bottom sheet)
+ Legacy fullscreen
+ List style
+ OneUI style (paged)
+
+ App drawer sorting
+ Sort alphabetically
+ Sort by install date
+ Sort by usage
+
+ Custom background color
+ Use a custom color for the app drawer background
+ Light mode color
+ Dark mode color
+
+ HSB
+ RGB
+ Hue
+ Saturation
+ Brightness
+ Red
+ Green
+ Blue
+ HEX
+
+ Pixel style search bar
+ Revamped search bar with layered background
+ Outer layer opacity
+
+ Applications cachées & protégées
+ Déverrouiller pour gérer les applications cachées et protégées
+ Authentification pour ouvrir %1$s
+ Chargement\u2026
+ Veuillez configurer un écran de verrouillage pour restreindre l\'accès aux applications
+ Aide
+ Les applications cachées et leurs widgets sont cachés du tiroir d\'applications
+ Les applications protégées nécessitent une authentification pour être ouvertes depuis le lanceur
+
+ Recents layout style
+ Par défaut
+ Scale effect
+ Kami style
+ iOS style
+ OxygenOS style
+
+ Lock/Unlock app
+ Unlocked app in recents
+ Locked app in recents
+
+ Scroll vibration
+
diff --git a/res/values-hi-rIN/alpha_strings.xml b/res/values-hi-rIN/alpha_strings.xml
new file mode 100644
index 00000000000..e8660a28519
--- /dev/null
+++ b/res/values-hi-rIN/alpha_strings.xml
@@ -0,0 +1,44 @@
+
+
+
+
+ Lock/Unlock app
+ Unlocked app in recents
+ Locked app in recents
+
+ Recents layout style
+ Stock
+ Kami Style
+ iOS Style
+ OxygenOS Style
+
+ - Ignorance is Bliss
+ - Make love, not war
+ - Oh hey, what\'s up?
+ - We love you 3000
+ - Thank you for your support
+ - One of the buildbot\'s best picks
+ - Pringles aren\'t actually potato chips..
+ - You can disable quickspace via home settings.
+ - I got nothing ¯\\_(ツ)_/¯
+
+
+ - I never apologize. I\'m sorry but that\'s the way I am.
+ - All my life, I\'ve had one dream: to achieve my many goals.
+ - Good drink... good meat... good God, let\'s eat!
+ - You\'ll have to speak up. I\'m wearing a towel.
+ - Change the channel, Marge...
+ - Roads are just a suggestion Marge, just like pants.
+ - It takes two to lie - one to lie and one to listen.
+ - Shut up, Flanders!
+ - I\'m not normally a praying man, but if you\'re up there, please save me, Superman.
+ - It\'s so simple to be wise... just think of something stupid to say and then don\'t say it.
+ - Stupid sexy Flanders...
+ - I hope I didn\'t brain my damage.
+ - Operator, give me the number for 911!
+ - Don\'t blame me, I voted for Kodos.
+
+
diff --git a/res/values-hi-rIN/cr_strings.xml b/res/values-hi-rIN/cr_strings.xml
new file mode 100644
index 00000000000..54d1ea13f7c
--- /dev/null
+++ b/res/values-hi-rIN/cr_strings.xml
@@ -0,0 +1,440 @@
+
+
+
+
+
+
+ होम पेज की सेटिंग
+
+ होम स्क्रीन
+ गूगल नाउ पैनल तथा अन्य सेट करें
+ ऑयकोन्स
+ आइकॉन आकार, आकृति तथा अन्य सेट करें
+ एप्लिकेशन दराज
+ अपना एप्लिकेशन ड्रॉअर कस्टमाइज़ करें
+ हाल ही के
+ अवलोकन स्क्रीन में सुधार करें
+ अन्य तरह का
+ अन्य विकल्प
+ आम
+ इंटरफेस
+ त्वरित स्थान
+ खोज पट्टी
+ त्वरित क्रियाएं
+
+ विकासक के विकल्प
+ अपने स्वयं के जोखिम पर कुछ छिपी हुई विशेषताएं सक्षम करें
+
+ लॉक लेआउट
+ आइकंस और विजेट्स को होमस्क्रीन पर जोड़ा, हटाया और हिलाया नहीं जा सकता है
+ आइकंस और विजेट्स को होमस्क्रीन पर जोड़ा, हटाया और हिलाया जा सकता है
+ होमस्क्रीन पर विजेट्स जोड़ना संभव नहीं है
+
+ होम स्क्रीन पर चिह्न लेबल दिखाएं
+ ड्रॉअर में चिह्न लेबल दिखाएं
+
+ Google ऐप एक्सेस करने के लिए स्वाइप करें
+
+ When you swipe left from main Home screen
+
+ When you swipe right from main Home screen
+
+ Use icon customizations in drawer
+ Follow icon customizations used on home screen
+
+ थोड़ा रुकें
+ विराम %1$s?
+ %1$s के लिए सूचनाएं रोक दी जाएंगी
+ ऐप रोका गया
+ %1$s को लॉन्चर से सीधे रोक दिया गया
+ ऐप रोकें
+ Work apps are off
+ Turn off work apps
+ Turn on work apps
+ Your work apps cannot send you notifications, use your battery, or access your location
+ Your work apps cannot receive phone calls or text messages, send you notifications, use your battery, or access your location
+
+ सुझाव
+ सभी ऐप्स के लिए & होम स्क्रीन सुझाव
+
+ Restart
+ Restart the launcher manually to apply any pending settings
+ Restarting launcher...
+ Restarting launcher to apply changes...
+ Restarting launcher to update launcher components...
+
+ स्क्रीन बंद करने के लिए दो बार स्पर्श करें
+ Double tap empty space on home screen to turn off the screen
+ Vibrate on double tap to sleep
+ Provide haptic feedback on double tap to sleep gesture
+
+ Swipe to start search
+ Open keyboard whenever app drawer becomes visible
+
+ आइकन माप
+
+ फ़ॉन्ट परिमाण
+
+ Use two lines for app label
+
+ पंक्ति ऊँचाई
+
+ Hotseat background
+ अनुप्रयोग डॉक में पारदर्शी पृष्ठभूमि जोड़ें
+
+ बैकग्राउंड अपारदर्शिता
+
+ Stroke
+
+ mar
+ App को मार डाला
+
+ खोजें
+ गूगल लेंस
+ बोलकर खोजें
+ AI mode
+ Music search
+ Start music search on pressing mic icon
+ गूगल सर्च बार
+ निचले डॉक में खोज बार दिखाएं
+ थीम वाले आइकन
+ सर्च बार के लिए थीम वाले आइकॉन का इस्तेमाल करें
+ कोने का अर्द्ध व्यास
+
+ App search bar location
+ Hidden
+ Top
+ Bottom
+
+ वॉलपेपर स्क्रॉल
+ Wallpaper scrolling effect for multiple screens
+
+ Wallpaper zooming
+ Zoom in or out the wallpaper when using drawer or recent apps
+
+ स्थिति पट्टी
+
+ ऊपरी छाया
+
+ %1$s available | %2$s
+ मेमोरी जानकारी
+ ZRAM info
+
+ स्क्रीनशॉट
+ सब कुछ मिटाएं
+ लेंस
+
+ Allow short parallax
+ Enable full wallpaper scroll effect on smaller numbers of pages instead of cropping the wallpaper
+ Single page center
+ Center wallpaper if only using a single page
+
+ Scrollbar
+ Scrollbar on side of the app drawer
+
+ Dark status bar
+ Always use dark status bar on home screen
+
+ एक नज़र में
+ Show at the top of your home screen
+
+ सुप्रभात.
+ Good evening.
+ Good afternoon.
+ Good night.
+ Good day.
+ Today is
+ It\'s
+
+ Unknown Artist
+ Now playing
+ By
+
+ Now playing
+ Show the song you\'re playing
+
+ Minimalistic style
+ Switch to modern style
+
+ Random messages
+ Make your companion more lively with random messages
+
+ - सुप्रभात!
+ - सुप्रभात, रॉक करने का समय!
+ - कितना अच्छा दिन है!
+ - आपका दिन शुभ हो!
+ - 5 मिनट की छोटी झपकी के बारे में क्या?
+ - चलो यह रोटी लेते हैं।
+ - Mornings are a fresh canvas for your daily masterpiece.
+ - Embrace the sunrise of opportunity each morning.
+ - In the early light, find the power to illuminate your path.
+ - Your day begins with endless possibilities.
+ - Every sunrise is a reminder that you can start anew.
+ - Awake with purpose, conquer the day ahead.
+ - Your thoughts can be the foundation of great achievements.
+ - The world awakens, and so does your potential.
+ - The first chapter in your daily adventure.
+ - Let your morning routine be the launchpad for success.
+ - Sunrise or not, your potential is always on the rise.
+ - It\'s time for fresh ideas and bold actions.
+ - The early bird catches the worm, but you can catch your dreams.
+ - Find the courage to chase your aspirations.
+ - Rise and shine, for greatness awaits.
+ - The best time to start is now.
+
+
+ - Chase your dreams, not the minutes.
+ - In every moment, a new opportunity.
+ - Embrace the journey, not just the destination.
+ - Today is a gift, that\'s why it\'s called the present.
+ - Let your smile change the world.
+ - Life is a story; make each chapter count.
+ - Find joy in the ordinary moments.
+ - The best is yet to come, keep it up.
+ - Your chance to turn \'what if\' into \'what is.\'
+ - Seize the day, starting right now.
+ - Happiness is a choice you can make any time.
+ - Believe in yourself, even on a sleepy afternoon.
+ - Let your actions speak louder than the clock.
+ - Your life is what you make of it; make it amazing.
+ - Dream big, work hard, and never give up.
+ - You are stronger than you think.
+ - Every setback is a setup for a comeback.
+ - Embrace the journey, not just the destination.
+ - Chase your goals, not your fears.
+ - Your potential is endless; unlock it.
+
+
+ - Embrace the present moment, no matter the hour.
+ - In every dusk, find a new dawn of opportunity.
+ - Life is a journey; enjoy the scenery along the way.
+ - Your attitude shapes your reality.
+ - Your story is still being written, even as the sun sets.
+ - Capture the beauty in the ordinary moments.
+ - It\'s time to reflect and reset.
+ - Choose joy, no matter the time on the clock.
+ - Opportunities don\'t clock out in the evening.
+ - Be the reason someone smiles.
+ - Your potential knows no bounds.
+ - Moments of magic can happen at any hour.
+ - Your life is what you make of it; make it extraordinary.
+ - Stay curious, stay grateful.
+ - Be the change you wish to see in the world.
+ - Believe in yourself; you are capable of amazing things.
+ - The only limit is the one you set for yourself.
+ - Success begins with a single step.
+
+
+ - Seize the night, for it holds its own mysteries.
+ - In the quiet of this evening, find serenity.
+ - Life\'s greatest adventures can start in the darkness.
+ - Your journey continues even after the sun has set.
+ - Let the night be your canvas; paint it with dreams.
+ - In the stillness of this evening, find your inner peace.
+ - Stars shine brightest in the darkest hours.
+ - Dream big, even in the late hours of the day.
+ - Your life is a story waiting to be written.
+ - It\'s time for reflection and renewal.
+ - Late night thoughts can lead to early morning revelations.
+ - Find joy in the stillness of this evening.
+ - The night sky is a vast canvas of dreams.
+ - Your potential knows no bedtime; pursue your passions.
+ - Stay hopeful, even in the darkest hours.
+ - In the midst of chaos, find your inner calm.
+ - Stay positive, work hard, make it happen.
+
+
+ - Nights are the quiet whispers of opportunity.
+ - In the stillness of the night, find your inner strength.
+ - Embrace the darkness, for it holds the keys to your dreams.
+ - The world sleeps, but your potential is wide awake.
+ - The night may be late, but your ambitions are timeless.
+ - Stay curious, stay awake, and let nights guide you.
+ - Find the courage to chase your dreams.
+ - Great things never come from comfort zones.
+ - You have the power to create your own destiny.
+ - Happiness is a choice; choose it every day.
+ - Life is what you make it, so make it count.
+ - Stay focused, stay determined, stay unstoppable.
+ - You are the author of your own story.
+ - Strive for progress, not perfection.
+ - Every day is a new beginning; make it a great one.
+ - Inspire others by being your authentic self.
+ - Your attitude determines your direction.
+
+
+ - अज्ञानता परमानंद है
+ - क्या यह पहले से ही एक और अपडेट फ्लैश करने का समय है?
+ - Make peace, not war
+ - अरे, क्या हाल है?
+ - Welcome to real Matrix!
+ - आप कितने स्क्रीनशॉट लेते हैं?
+ - AlphaDroid के साथ अच्छाई खोलें
+ - Spread love, not havoc
+ - We didn\'t start the fire!
+ - कुछ अच्छे संगीत के लिए समय
+ - \u003C\u003C\u003C\u003C\u003C\u003C\u003C सिर
+ - इसे देखने के लिए आपको AlphaDroid Premium चाहिए
+ - यूनिकॉर्न की वंशावली याद रखें
+ - विस्तारित बैटरी जीवन का आनंद लें
+ - ग्राउंड जीरो से शुरू
+ - हम आपसे प्यार करते हैं 3000
+ - Delicious even without Sushi
+ - आज कुछ अच्छा करो
+ - यह सबसे अच्छा पिक्सेल अनुभव है, है ना?
+ - कोई भ्रम नहीं, वास्तविकता में आपका स्वागत है!
+ - आपके सहयोग के लिए धन्यवाद
+ - Derps के लिए कोई त्योहार नहीं - केवल पूर्णता!
+ - बिल्डबॉट की सबसे अच्छी पसंद में से एक
+ - आपके व्यामोह के लिए पवित्रता
+ - Rising in peace
+ - कितना प्यारा अनुभव है, है ना?
+ - विकास एक मिथक है, है ना?
+ - तुम वही हो जो तुम चमकते हो, आलू मत बनो
+ - आपके दिमाग मे क्या है
+ - Expecto Patronum
+ - Wubba Lubba Dub Dub
+ - Winner Winner ....?
+ - rm -rf \/
+ - Pringles aren\'t actually potato chips..
+ - Remember to check device\'s battery level!
+ - Check your email/messages.
+ - Check your to-do list if you have one.
+ - You can disable quickspace via home settings.
+ - पसंद आया? डेवलपर को कुछ प्यार भेजें
+ - आज तक का सबसे अच्छी रोम
+ - टेलीग्राम पर हमसे संपर्क करें
+ - हम आपसे प्यार करते हैं 3000
+
+ Unable to find calendar or clock activity
+
+ Weather provider
+ Auto
+ Google Smartspace
+ OmniJaws
+ Weather update
+ Display current weather update
+ Requires weather service to be enabled
+ Cloudy
+ Rainy
+ Sunny
+ Stormy
+ Snowy
+ Windy
+ Misty
+ Current location
+ Display current weather location
+ Current condition
+ Display current weather condition summary
+
+ Auto-hide page indicator
+ Hide the page indicator dots automatically
+
+ Background blur depth
+ Set blur depth level for recents and app drawer
+
+ Application info
+ स्रोत
+ सिस्टम
+ पिछला अपडेट
+ संस्करण
+ अधिक
+
+ चिह्न पैक
+ डिफॉल्ट
+ अधिक स्थापित करें
+ कोई उपलब्ध ऐप स्टोर नहीं है
+
+ Wallpaper carousel
+ Quickly switch wallpapers from the long-press menu
+ Apply to lock screen
+ Set the same wallpaper on the lock screen
+
+ Pebble
+ Vessel
+ Pure square
+ Squircle
+ Tapered rect
+ Teardrop
+ Rounded rect
+ Cloudy
+ Cylindrical
+ Flower
+ Heart
+ Hexagon
+ Rounded hexagon
+ iOS Style
+ Leaf
+ Meow
+ Samsung Style
+ Stretched
+
+ Dark app labels
+ Always use dark text for app labels in the app drawer
+
+ App drawer style
+ Normal (bottom sheet)
+ Legacy fullscreen
+ List style
+ OneUI style (paged)
+
+ App drawer sorting
+ Sort alphabetically
+ Sort by install date
+ Sort by usage
+
+ Custom background color
+ Use a custom color for the app drawer background
+ Light mode color
+ Dark mode color
+
+ HSB
+ RGB
+ Hue
+ Saturation
+ Brightness
+ Red
+ Green
+ Blue
+ HEX
+
+ Pixel style search bar
+ Revamped search bar with layered background
+ Outer layer opacity
+
+ छिपा हुआ & संरक्षित ऐप्स
+ छिपे हुए और संरक्षित ऐप्स को प्रबंधित करने के लिए अनलॉक करें
+ खोलने केलिए प्रमाणित करे
+ लोड हो रहा है \u2026
+ एप्लिकेशन एक्सेस को प्रतिबंधित करने के लिए कृपया एक सुरक्षित लॉक स्क्रीन सेट करें
+ सहायता
+ छिपे हुए एप्लिकेशन और उनके विजेट दराज से छिपे हुए हैं
+ संरक्षित ऐप्स को लॉन्चर से खोले जाने के लिए प्रमाणीकरण की आवश्यकता होती है
+
+ Recents layout style
+ डिफॉल्ट
+ Scale effect
+ Kami style
+ iOS style
+ OxygenOS style
+
+ Lock/Unlock app
+ Unlocked app in recents
+ Locked app in recents
+
+ Scroll vibration
+
diff --git a/res/values-hu-rHU/alpha_strings.xml b/res/values-hu-rHU/alpha_strings.xml
new file mode 100644
index 00000000000..e8660a28519
--- /dev/null
+++ b/res/values-hu-rHU/alpha_strings.xml
@@ -0,0 +1,44 @@
+
+
+
+
+ Lock/Unlock app
+ Unlocked app in recents
+ Locked app in recents
+
+ Recents layout style
+ Stock
+ Kami Style
+ iOS Style
+ OxygenOS Style
+
+ - Ignorance is Bliss
+ - Make love, not war
+ - Oh hey, what\'s up?
+ - We love you 3000
+ - Thank you for your support
+ - One of the buildbot\'s best picks
+ - Pringles aren\'t actually potato chips..
+ - You can disable quickspace via home settings.
+ - I got nothing ¯\\_(ツ)_/¯
+
+
+ - I never apologize. I\'m sorry but that\'s the way I am.
+ - All my life, I\'ve had one dream: to achieve my many goals.
+ - Good drink... good meat... good God, let\'s eat!
+ - You\'ll have to speak up. I\'m wearing a towel.
+ - Change the channel, Marge...
+ - Roads are just a suggestion Marge, just like pants.
+ - It takes two to lie - one to lie and one to listen.
+ - Shut up, Flanders!
+ - I\'m not normally a praying man, but if you\'re up there, please save me, Superman.
+ - It\'s so simple to be wise... just think of something stupid to say and then don\'t say it.
+ - Stupid sexy Flanders...
+ - I hope I didn\'t brain my damage.
+ - Operator, give me the number for 911!
+ - Don\'t blame me, I voted for Kodos.
+
+
diff --git a/res/values-hu-rHU/cr_strings.xml b/res/values-hu-rHU/cr_strings.xml
new file mode 100644
index 00000000000..5cf3d77b681
--- /dev/null
+++ b/res/values-hu-rHU/cr_strings.xml
@@ -0,0 +1,440 @@
+
+
+
+
+
+
+ Kezdőoldal-beállítások
+
+ Kezdőképernyő
+ A \'Google Now\' panel és egyebek beállítása
+ Ikonok
+ Ikonméret-beállítás és egyebek
+ Alkalmazásfiók
+ Testreszabhatja az Alkalmazásfiókját
+ Előzmények
+ Az áttekintési nézet újraformálása
+ Különlegességek
+ Minden, ami egyedivé tesz
+ Általános
+ Felület
+ Gyorselérés
+ Keresési sáv
+ Azonnali műveletek
+
+ Fejlesztői beállítások
+ Saját felelősségére rejtett beállításokat engedélyezhet
+
+ Elrendezés-zárolás
+ A kezdőképernyőn nem engedélyezett az ikon- és modulhozzáadás, eltávolítás és mozgatás
+ A kezdőképernyőn engedélyezett az ikon- és modulhozzáadás, eltávolítás és mozgatás
+ Nem lehet kisalkalmazásokat hozzáadni a kezdőképernyőhöz
+
+ Asztali ikonfeliratok
+ Alkalmazásfióki ikonfeliratok
+
+ Google-alkalmazás csúsztatással
+
+ Amikor a fő Kezdőképernyőn balra csúsztat
+
+ Amikor a fő Kezdőképernyőn jobbra csúsztat
+
+ Ikon-testreszabások az Alkalmazásfiókban
+ Követi a kezdőképernyő ikon-testreszabását
+
+ Szüneteltetés
+ Szünetelteti ezt: %1$s?
+ %1$s értesítései szüneteltetésre kerülnek
+ Alkalmazás-szüneteltetés
+ %1$s közvetlen szüneteltetése a Launcherből
+ Alkalmazás-szüneteltetés
+ A munkaalkalmazások ki vannak kapcsolva
+ A munkaalkalmazások kikapcsolása
+ A munkaalkalmazások bekapcsolása
+ A munkaalkalmazások nem küldhetnek értesítéseket, nem használhatják az akkumlátort, és nem férhetnek hozzá a helyadatokhoz.
+ A munkaalkalmazások nem fogadhatnak hívásokat és szöveges üzeneteket, nem küldhetnek önnek értesítéseket, nem használhatják az akkumulátort és nem férhetnek hozzá a helyadatokhoz
+
+ Javaslatok
+ Javaslatok a Kezdőképernyőhöz és minden alkalmazáshoz
+
+ Újraindítás
+ Launcher-újraindítás a függőben lévő változások alkalmazásához
+ A Launcher újraindítása...
+ A változások miatti újraindulás...
+ Launcher alkotóelem frissítése miatti újraindulás...
+
+ Alvómód duplakoppal
+ A képernyő kikapcsolásához koppintson duplán a kezdőképernyő egy üres területére
+ Az \'Alvómód duplakoppal\' rezgő visszajelzése
+ Rezgéses visszajelzést ad a dupla koppintásos alvás gesztusról
+
+ Csúsztatás a kereséshez
+ Nyíljon meg a billentyűzet, amikor az alkalmazásfiók láthatóvá válik
+
+ Ikonméret
+
+ Betűméret
+
+ Kétsoros alkalmazásnevek
+
+ Sormagasság
+
+ Hotseat háttér
+ Áttetsző háttereket adhat az alkalmazásfiókhoz
+
+ Háttérátlátszóság
+
+ Körvonal
+
+ Leállítás
+ Az alkalmazás leállt
+
+ Keresés
+ Google Lens
+ Hangalapú keresés
+ AI Mód
+ Zenekeresés
+ A mikrofonikon megnyomásával indíthat keresést
+ Google-keresősáv
+ Alsódokki keresősáv
+ Témázott ikonok
+ A keresősávban témázott ikonok használhatók
+ Saroklekerekítés
+
+ Az alkalmazás-keresősáv helyzete
+ Rejtett
+ Felül
+ Alul
+
+ Háttérkép-görgetés
+ Háttérképgörgetési effekt több képernyőre
+
+ Háttérkép-zoomolás
+ Nagyítsa vagy kicsinyítse a háttérképet az alkalmazásfiók vagy az előzmények használatakor
+
+ Értesítési sáv
+
+ Felső árnyék
+
+ %2$s memóból %1$s érhető el
+ Memória-információ
+ ZRAM-információ
+
+ Képernyőkép
+ Az összes törlése
+ Objektív
+
+ Rövid paralaxis engedélyezése
+ Engedélyezze a teljes háttérképgörgetési hatását kisebb oldalszámon a háttérkép levágása helyett
+ Centerezett egyoldalas
+ Helyezze középre a háttérképet, ha csak egyetlen oldalt használ
+
+ Görgetősáv
+ A keresősáv az Alkalmazásfiók oldalán
+
+ Sötét állapotsor
+ Mindig használja a sötét állapotsort a kezdőképernyőn
+
+ Belepillantás
+ Az Ön kezdőképernyőjének tetején jelenik meg
+
+ Jó reggelt.
+ Jó estét.
+ Jó napot.
+ Jó éjszakát.
+ Jó napot.
+ Ma
+ Ez
+
+ Ismeretlen előadó
+ Most hallható
+ Előadja
+
+ Most hallható
+ Az épp hallható szám kiírása
+
+ Minimálstílus
+ Átkapcsolás egy modern stílusra
+
+ Véletlenszerű üzenetek
+ Minden élénkebbé válik véletlenszerű üzenetekkel
+
+ - Jó reggelt!
+ - Jó reggelt, szóljon a rock!
+ - Micsoda szép nap!
+ - Legyen szép napja!
+ - Mit szólna egy 5 perces szunyókáláshoz?
+ - Lásson neki a reggelinek.
+ - A reggelek adják a friss vásznat a napi remekművéhez.
+ - Élje át minden reggel a lehetőségek napfelkeltéjét.
+ - Találja meg a korai fényben azt az erőt, amellyel megvilágíthatja az utat.
+ - Minden nap végtelen lehetőségekkel kezdődik.
+ - Minden napfelkelte emlékeztet arra, hogy minden újrakezdhető.
+ - Ébredjen céltudatosan, győzze le az Ön előtt álló napot.
+ - Gondolataiból születhetnek a nagyszerű eredmények
+ - A világ felébred, és az Ön lehetőségei is.
+ - Így indul az Ön napi kalandja.
+ - A reggeli megszokásaiból alakul ki a sikerességének indítópultja.
+ - Napkelte vagy sem, az Ön potenciálja folyton növekszik.
+ - Itt az ideje a friss ötleteknek és a merész tetteknek.
+ - A hajnali madár elkapja a férget, ahogy te is elérheted az álmaidat.
+ - Találja meg bátorságát és érje el céljait.
+ - Kelj fel és ragyogj, hogy naggyá válhass.
+ - Most jött el az ideje elkezdeni.
+
+
+ - Az álmait hajszolja, ne a perceket.
+ - Minden másodperc egy új lehetőség.
+ - Hogy a célodhoz érj, utaznod is kell.
+ - A mai nap ajándék, ezért hívják meglepetésnek.
+ - Az Ön mosolya változtathatja meg a világot.
+ - Az élet egy történet; minden fejezete számít.
+ - Találja meg az örömöt a hétköznapi pillanatokban.
+ - A legjobb még hátravan, ne adja fel!
+ - Változtassa a \'mi lenne ha\'-t \'mi van\'-ra.
+ - Kaparintsa meg a mai napot, kezdje most.
+ - A boldogság egy lehetőség, Ön dönt hogy választja-e.
+ - Bízzon magában még egy álmos délutánon is.
+ - Hagyja, hogy tettei hangosabban ketyegjenek, mint az óra.
+ - Az Ön élete olyan, amilyenné alakítja; tegye bámulatossá.
+ - Álmodjon nagyot, dolgozzon keményen és sose adja fel.
+ - Ön sokkal erősebb, mint gondolná.
+ - Minden hanyatlás egy felemelkedés előkészülete.
+ - Hogy a célodhoz érj, utaznod is kell.
+ - A céljaival törődjön, ne a félelmeivel.
+ - A lehetőségek végtelenek. Használja ki őket!
+
+
+ - Élje meg a pillanatot! Ne foglalkozzon az órákkal.
+ - Minden alkonyatban találja meg egy lehetőség új hajnalát.
+ - Az élet egy utazás; élvezze a tájat útközben.
+ - Az Ön hozzáállása alakítja az Ön valóságát.
+ - Az Ön élettörténete folyamatosan íródik, még akkor is, amikor a nap lenyugszik.
+ - Ragadja meg a szépséget a hétköznapi pillanatokban.
+ - Ideje újragondolni és újrakezdeni.
+ - Válassza az örömöt, nem számít, mennyi időt lát az órán.
+ - Este sem fogynak el a lehetőségek.
+ - Mosolyogtasson meg valakit.
+ - Az Ön lehetőségei nem ismernek határokat.
+ - A varázslatos pillanatok bármelyik percben megtörténhetnek.
+ - Az Ön élete olyan, amilyenné alakítja; tegye rendkívülivé.
+ - Maradjon kíváncsi, maradjon hálás.
+ - Legyen Ön a változás, amit a világban látni kíván.
+ - Bízzon magában; Ön csodálatos dolgokra képes.
+ - Egyetlen korlát létezik: amit az ember önmagának szab.
+ - Minden siker egy lépéssel kezdődik.
+
+
+ - Ragadja meg az éjszakát, mert megvannak a maga titkai.
+ - Az este csendjében találja meg a nyugalmat.
+ - Az élet legnagyobb kalandjai a sötétben kezdődhetnek.
+ - Az Ön útja a naplemente után is folytatódik.
+ - Legyen az éjszaka az Ön festővászona, fesse tele álmokkal.
+ - Az este csendjében találja meg belső békéjét.
+ - A csillagok a legsötétebb órákban ragyognak a legfényesebben.
+ - A késői órákban is álmodjon nagyot.
+ - Az élet egy történet, amely megírásra vár.
+ - Itt az ideje a gondolkodásnak és a megújulásnak.
+ - A késő esti gondolatok kora reggeli kinyilatkoztatásokhoz vezethetnek.
+ - Találja meg az örömöt a mai este csendjében.
+ - Az éjszakai égbolt az álmok hatalmas vászna.
+ - A Ön lehetőségei nem ismerik az alvásidőt; űzze-hajtsa a szenvedélyeit.
+ - Legyen bizakodó, még a legsötétebb órákban is.
+ - A káosz közepette is találja meg belső nyugalmát.
+ - Maradjon pozitív, dolgozzon keményen, valósítsa meg.
+
+
+ - Az éjszaka a lehetőségek csendes suttogása.
+ - Az éjszaka csendjében találja meg belső erejét.
+ - Ölelje át a sötétséget, mert abban vannak álmai kulcsai.
+ - A világ alszik, de a lehetőségek ébren vannak.
+ - Lehet, hogy késő este van, de az ambíciók időtlenek.
+ - Maradjon kíváncsi, maradjon ébren, és hagyja, hogy az éjszaka kalauzolja.
+ - Találja meg bátorságát és érje el álmait.
+ - A legjobb dolgok soha nem a komfortzónából jönnek.
+ - Megvan az ereje, hogy létrehozza saját sorsát.
+ - A boldogság egy lehetőség; válassza ezt minden nap.
+ - Számítson rá, hogy az élete olyan, amilyenné Ön alakítja.
+ - Maradjon koncentrált, maradjon határozott, maradjon megállíthatatlan.
+ - Ön írja saját életének regényét.
+ - Ne a tökéletességre törekedjen, hanem a fejlődésre.
+ - Minden nap egy új kezdet; tegye naggyá.
+ - Inspiráljon másokat azzal, hogy hiteles marad.
+ - A hozzáállása határozza meg az irányt.
+
+
+ - Boldogok a lelki-szegények
+ - Itt az ideje már egy új frissítést telepíteni?
+ - Hozz békét, ne háborút
+ - Ó, helló, mi a helyzet?
+ - Üdvözlünk a valódi Mátrixban!
+ - Hány képernyőképet készített?
+ - Találjon klassz funkciókat a AlphaDroidban
+ - Szeretetet árasszon, ne pusztítást
+ - Nem mi gyújtottunk tüzet!
+ - Ideje jó zenét hallgatni!
+ - \u003C\u003C\u003C\u003C\u003C\u003C\u003C FEJ
+ - AlphaDroid Prémium-ra van szüksége, hogy láthassa ezt
+ - Emlékezzen az Egyszarvú eredetére
+ - Élvezze a Kiterjesztett akkuidőt
+ - Induljunk újra nulláról
+ - Ne telefonozzon! Szerelmeskedjen!
+ - Szusi nélkül is finom
+ - Tegyen ma valami kedveset
+ - Ez a legmenőbb Pixel Experience, vagy mégsem?
+ - Nem ámítás, legyen üdvözölve a valóságban!
+ - Köszönjük a támogatását!
+ - Nincs ostobák fesztiválja - csak a tökéletesség!
+ - Az építőrobot egyik legjobb válogatása
+ - Kivezető út a Paranoiájából
+ - Ébredj békében
+ - Milyen remek élmény, nem igaz?
+ - Az evolúció csak kitaláció, ugye?
+ - Azzá válik, amit flessel...ne legyen már Krumpli
+ - Mi jár a fejében?
+ - Expecto Patrorum (Támogatást elfogadunk)
+ - Subi-Dubi-Dúúúúú
+ - A győztes pedig.......?
+ - rm -rf \/
+ - A Pringles valójában nem burgonyacsipsz.
+ - Tudja mindig mennyire van feltöltve az akksi!
+ - Nézze át e-mailjeit / üzeneteit.
+ - Nézzen rá nincs-e valami új elvégzendő dolga.
+ - A beállításokban módosíthat a Belepillantás viselkedésén
+ - Kedvel minket? Küldjön nekünk sört!
+ - Minden idők legjobb rendszere.
+ - Köszönjön ránk a Telegramon
+ - Ne telefonozzon! Szerelmeskedjen!
+
+ Nem található naptári vagy óratevékenység
+
+ Időjárás szolgáltató
+ Automatikus
+ Google okostér
+ OmniJaws
+ Az \'Időjárás\" frissítése
+ A jelenlegi tartózkodási hely adatait frissíti
+ Az időjárás-szolgáltatás engedélyezése szükséges
+ Felhős
+ Esős
+ Tiszta égbolt
+ Viharos
+ Havas
+ Szeles
+ Ködös
+ Jelenlegi tartózkodási hely
+ A jelenlegi tartózkodási hely adatait jeleníti meg
+ Jelenlegi időjárás
+ Megjeleníti az épp aktuális hőfokot és adatokat
+
+ Oldaljelző automatikus rejtése
+ Automatikusan eltünteti az oldaljező pontokat
+
+ A háttér-elmosás mélysége
+ Az előzmények és az alkalmazásfiók elmosási értéke állítható itt
+
+ Alkalmazás információ
+ Forrás
+ Rendszer
+ Utoljára frissítve
+ Verzió
+ Továbbiak
+
+ Ikoncsomag
+ Alapértelmezett
+ Továbbiak telepítése
+ Nincs hely ahonnan letölthetne
+
+ Váltakozó háttérképek
+ Gyors háttérkép-váltás a hosszan nyomással elérhető menüből
+ Alkalmazás a záróképernyőre
+ Ugyanazt a háttérképet állítsa be a zárolási képernyőre
+
+ Kavics
+ Váza
+ Tökéletes négyzet
+ Négyzetes kör
+ Trapéz-szerű
+ Könnycsepp
+ Kerekített téglalap
+ Felhő
+ Henger-szerű
+ Virág
+ Szív
+ Hatszög
+ Kerekített hatszög
+ iOS-stílus
+ Falevél
+ Miau
+ Samsung-stílus
+ Nyújtott
+
+ Sötét alkalmazásnevek
+ Az alkalmazásfiókban mindig sötét betűkkel írja ki az alkalmazások neveit
+
+ Az alkalmazásfiók stílusa
+ Megszokott (alsó lap)
+ Örökölt fullképernyős
+ Lista stílusú
+ OneUI stílusú (oldalas)
+
+ Rendezés az alkalmazásfiókban
+ A-B-C sorrend
+ Dátum szerinti sorrend
+ Használat szerinti sorrend
+
+ Egyéni háttérszín
+ Egyéni szín használata az alkalmazásfiók hátteréhez
+ A \'Világos mód\' színe
+ A \'Sötét mód\' színe
+
+ HSB
+ RGB
+ Színárnyalat
+ Telítettség
+ Fényerő
+ Vörös
+ Zöld
+ Kék
+ HEX
+
+ Pixel stílusú keresősáv
+ Újraálmodott keresősáv réteges háttérrel
+ A külső réteg átlátszósága
+
+ Rejtett és védett alkalmazások
+ Feloldás a rejtett és védett alkalmazások kezeléséhez
+ Hitelesítés a következő megnyitásához: %1$s
+ Betöltés\u2026
+ Kérjük, állítson be biztonságos (védett) képernyőzárat az alkalmazás-hozzáférések korlátozására
+ Súgó
+ A rejtett alkalmazások és kisalkalmazásaik az alkalmazásfiókban sem láthatóak
+ Egy védett alkalmazás launcherből való megnyitása hitelesítést igényel
+
+ Az Előzmények elrendezési stílusa
+ Alapértelmezett
+ Skálázott
+ Kami
+ iOS
+ OxigenOS
+
+ Alkalmazás zárolása/feloldása
+ Feloldott alkalmazások az előzményekben
+ Zárolt alkalmazások az előzményekben
+
+ Rezgés görgetéskor
+
diff --git a/res/values-in-rID/alpha_strings.xml b/res/values-in-rID/alpha_strings.xml
new file mode 100644
index 00000000000..1d5cd3fb86e
--- /dev/null
+++ b/res/values-in-rID/alpha_strings.xml
@@ -0,0 +1,44 @@
+
+
+
+
+ Kunci/Buka kunci aplikasi
+ Aplikasi yang tidak terkunci baru - baru ini
+ Kunci aplikasi di baru - baru ini
+
+ Gaya tata letak Terbaru
+ Bawaan
+ Gaya Kami
+ Gaya iOS
+ Gaya OxygenOS
+
+ - Ketidaktahuan adalah kebahagiaan
+ - Bercinta, bukan perang
+ - Oh hai, apa kabar?
+ - Kamu mencintaimu 3000
+ - Terima kasih atas dukunganmu
+ - Salah satu pilihan terbaik buildbot
+ - Pringles sebenarnya bukan keripik kentang..
+ - Kamu bisa mematikan quickspace di pengaturan home
+ - Aku tidak punya apa - apa ¯\_(ツ)_/¯
+
+
+ - Aku tak pernah minta maaf. Maaf, tapi begitulah diriku.
+ - Sepanjang hidupku, aku punya satu impian: mencapai banyak tujuanku.
+ - Minuman yang enak... daging yang enak... Tuhan yang baik, mari kita makan!
+ - Kamu harus bicara lebih keras. Aku pakai handuk.
+ - Ganti saluran, Marge...
+ - Jalan hanyalah sebuah saran Marge, seperti halnya celana
+ - Dibutuhkan dua orang untuk berbohong - satu untuk berbohong dan satu untuk mendengarkan.
+ - Diam, Flanders!
+ - Aku biasanya bukan orang yang suka berdoa, tapi jika kau ada di sana, tolong selamatkan aku, Superman.
+ - Sangat mudah untuk menjadi bijak...pikirkan saja sesuatu yang bodoh untuk dikatakan, lalu jangan katakan.
+ - Flanders seksi yang bodoh...
+ - Kuharap otakku tidak terluka
+ - Operator, berikan saya nomor 911!
+ - Jangan salahkan saya, saya memilih Kodos.
+
+
diff --git a/res/values-in-rID/cr_strings.xml b/res/values-in-rID/cr_strings.xml
new file mode 100644
index 00000000000..777d9f9b61c
--- /dev/null
+++ b/res/values-in-rID/cr_strings.xml
@@ -0,0 +1,440 @@
+
+
+
+
+
+
+ Setelan beranda
+
+ Layar utama
+ Atur panel Google Now dan lainnya
+ Ikon
+ Atur ukuran ikon dan lainnya
+ Semua aplikasi
+ Kustomisasi tampilan aplikasi anda
+ Baru-baru ini
+ Mengubah tampilan layar
+ Lain-lain
+ Opsi lainnya
+ Umum
+ Antarmuka
+ QuickSpace
+ Bilah penelusuran
+ Aksi cepat
+
+ Opsi pengembang
+ Mengaktifkan fitur tersembunyi dengan risiko
+
+ Kunci tata letak
+ Ikon dan widget tidak dapat ditambahkan, dihapus dan dipindahkan pada layar beranda
+ Ikon dan widget dapat ditambahkan, dihapus dan dipindahkan pada layar beranda
+ Hal ini tidak mungkin untuk menambahkan widget ke layar beranda
+
+ Label ikon di desktop
+ Label ikon di laci
+
+ Geser untuk mengakses aplikasi Google
+
+ Saat Anda menggeser ke kanan dari Layar Beranda
+
+ Saat Anda menggeser ke kanan dari Layar Beranda
+
+ Gunakan kustomisasi ikon di laci
+ Ikuti penyesuaian ikon yang digunakan pada layar beranda
+
+ Jeda
+ Jeda %1$s?
+ Notifikasi untuk %1$s akan dijeda
+ Aplikasi dijeda
+ %1$s langsung dijeda dari peluncur
+ Jeda aplikasi
+ Aplikasi kerja tidak aktif
+ Tunda Aplikasi Kerja
+ Aktifkan aplikasi kerja
+ Aplikasi kerja tidak akan dapat mengirim notifikasi, menggunakan baterai, atau mengakses lokasi
+ Aplikasi kerja tidak akan dapat menerima panggilan telepon atau pesan teks, mengirim notifikasi, menggunakan baterai, atau mengakses lokasi
+
+ Saran
+ Untuk Semua Aplikasi & saran Layar Utama
+
+ Mulai ulang
+ Mulai ulang peluncur secara manual untuk menerapkan setelan yang tertunda
+ Memulai ulang peluncur...
+ Memulai ulang peluncur untuk menerapkan perubahan...
+ Memulai ulang peluncur untuk memperbarui komponen peluncur...
+
+ Ketuk dua kali untuk tidur
+ Ketuk dua kali layar kosong di beranda untuk mematikan layar
+ Getar dengan ketukan ganda untuk tidur
+ Berikan umpan balik haptik pada gestur ketuk dua kali untuk mengaktifkan mode tidur
+
+ Geser untuk mulai mencari
+ Buka keyboard setiap kali laci aplikasi terlihat
+
+ Ukuran ikon
+
+ Ukuran huruf
+
+ Gunakan dua baris untuk label aplikasi
+
+ Tinggi baris
+
+ Latar belakang hotseat
+ Tambahkan latar belakang tembus pandang di dok aplikasi
+
+ Transparansi latar Belakang
+
+ Garis
+
+ Matikan
+ Aplikasi dimatikan
+
+ Pencarian
+ Google Lens
+ Pencarian suara
+ Mode AI
+ Pencarian musik
+ Mulai pencarian musik dengan menekan ikon mikrofon
+ Bilah pencarian Google
+ Bilah pencarian di dok bawah
+ Ikon bertema
+ Gunakan ikon bertema untuk bilah pencarian
+ Radius sudut
+
+ Lokasi bilah pencarian aplikasi
+ Tersembunyi
+ Atas
+ Bawah
+
+ Menggulir wallpaper
+ Efek pengguliran wallpaper untuk beberapa tampilan
+
+ Pembesaran wallpaper
+ Perbesar atau perkecil wallpaper saat menggunakan laci atau aplikasi terbaru
+
+ Bilah Status
+
+ Bayangan atas
+
+ %1$s Tersedia | %2$s Total
+ Info memori
+ Info ZRAM
+
+ Tangkapan layar
+ Bersihkan semua
+ Lensa
+
+ Izinkan paralaks pendek
+ Aktifkan efek gulir wallpaper penuh pada sejumlah kecil halaman alih-alih memotong wallpaper
+ Pusat satu halaman
+ Tengahkan wallpaper jika hanya menggunakan satu halaman
+
+ Bilah pengguliran
+ Bilah gulir di sisi laci aplikasi
+
+ Bilah status gelap
+ Selalu gunakan bilah status gelap di layar beranda
+
+ Sekilas
+ Tampilkan di bagian atas layar beranda Anda
+
+ Selamat pagi.
+ Selamat malam.
+ Selamat siang.
+ Selamat malam.
+ Hari yang indah.
+ Hari ini adalah
+ Ini
+
+ Artis tidak diketahui
+ Sedang Diputar
+ Oleh
+
+ Sedang Diputar
+ Tampilkan lagu yang Anda putar
+
+ Gaya minimalis
+ Beralih ke gaya modern
+
+ Pesan random
+ Jadikan teman Anda lebih hidup dengan pesan random
+
+ - Selamat Pagi
+ - Selamat pagi, saatnya bergoyang
+ - Hari yang indah
+ - Semoga harimu menyenangkan
+ - Bagaimana dengan tidur siang kecil selama 5 menit
+ - Mari bekerja keras.
+ - Pagi hari adalah kanvas segar untuk mahakarya harian Anda.
+ - Rangkullah terbitnya peluang setiap pagi.
+ - Di awal cahaya, temukan kekuatan untuk menerangi jalan Anda.
+ - Hari Anda dimulai dengan kemungkinan yang tak terbatas.
+ - Setiap matahari terbit adalah pengingat bahwa Anda dapat memulai hal baru.
+ - Bangunlah dengan tujuan, taklukkan hari yang akan datang.
+ - Pikiran Anda bisa menjadi landasan pencapaian besar.
+ - Dunia bangkit, begitu pula potensi Anda.
+ - Bab pertama dalam petualangan harian Anda.
+ - Biarkan rutinitas pagi Anda menjadi landasan menuju kesuksesan.
+ - Matahari terbit atau tidak, potensi Anda selalu meningkat.
+ - Waktunya untuk ide segar dan tindakan berani.
+ - Burung yang datang lebih awal akan menangkap cacing, tetapi Anda dapat menangkap impian Anda.
+ - Temukan keberanian untuk mengejar aspirasi Anda.
+ - Bangkit dan bersinar, karena kehebatan menanti.
+ - Waktu terbaik untuk memulai adalah sekarang.
+
+
+ - Kejar impianmu, bukan menitnya.
+ - Setiap saat, ada peluang baru.
+ - Rangkullah perjalanannya, bukan hanya tujuannya.
+ - Hari ini adalah anugerah, makanya disebut hadiah.
+ - Biarkan senyummu mengubah dunia.
+ - Hidup adalah sebuah cerita; membuat setiap bab berarti.
+ - Temukan kegembiraan di momen-momen biasa.
+ - Yang terbaik masih akan datang, pertahankan.
+ - Kesempatan Anda untuk mengubah \'bagaimana jika\' menjadi \'apa adanya\'
+ - Manfaatkan hari ini, mulai sekarang juga.
+ - Kebahagiaan adalah pilihan yang bisa Anda buat kapan saja.
+ - Percayalah pada diri sendiri, bahkan di sore hari yang mengantuk.
+ - Biarkan tindakan Anda berbicara lebih keras daripada waktu.
+ - Hidup Anda adalah apa yang Anda hasilkan; menjadikannya luar biasa.
+ - Bermimpilah yang besar, bekerja keras, dan pantang menyerah.
+ - Anda lebih kuat dari yang Anda kira.
+ - Setiap kemunduran adalah persiapan untuk bangkit kembali.
+ - Rangkullah perjalanannya, bukan hanya tujuannya.
+ - Kejar tujuan Anda, bukan ketakutan Anda.
+ - Potensi Anda tidak terbatas; membukanya.
+
+
+ - Rangkul lah momen saat ini, kapan pun waktunya.
+ - Di setiap senja, temukan peluang baru.
+ - Hidup adalah perjalanan; menikmati pemandangan sepanjang perjalanan.
+ - Sikap Anda membentuk realitas Anda.
+ - Kisahmu masih ditulis, bahkan saat matahari terbenam.
+ - Abadikan keindahan dalam momen biasa.
+ - Saatnya untuk merenung dan mengatur ulang.
+ - Pilihlah kegembiraan, tidak peduli jam berapa pun.
+ - Peluang tidak akan habis di malam hari.
+ - Jadilah alasan seseorang tersenyum.
+ - Potensi Anda tidak mengenal batas.
+ - Momen ajaib bisa terjadi kapan saja.
+ - Hidup Anda adalah apa yang Anda hasilkan; menjadikannya luar biasa.
+ - Tetap penasaran, tetap bersyukur.
+ - Jadilah perubahan yang ingin Anda lihat di dunia.
+ - Percaya pada dirimu sendiri; kamu mampu melakukan hal-hal menakjubkan.
+ - Satu-satunya batasan adalah batasan yang Anda tentukan sendiri.
+ - Kesuksesan dimulai dengan satu langkah.
+
+
+ - Manfaatkan malam, karena ia menyimpan misteri tersendiri.
+ - Dalam kesunyian malam ini, temukanlah ketenangan.
+ - Petualangan terbesar dalam hidup bisa dimulai dalam kegelapan.
+ - Perjalanan Anda berlanjut bahkan setelah matahari terbenam.
+ - Biarkan malam menjadi kanvasmu; melukisnya dengan mimpi.
+ - Dalam keheningan malam ini, temukan kedamaian batinmu.
+ - Bintang bersinar paling terang di saat-saat paling gelap.
+ - Bermimpilah yang besar, bahkan di pengujung hari.
+ - Hidup Anda adalah cerita yang menunggu untuk ditulis.
+ - Saatnya untuk refleksi dan pembaharuan.
+ - Pikiran larut malam bisa mengarah pada wahyu di pagi hari.
+ - Temukan kegembiraan dalam keheningan malam ini.
+ - Langit malam adalah kanvas mimpi yang luas.
+ - Potensi Anda tidak mengenal waktu tidur; kejar passionmu.
+ - Tetaplah berharap, bahkan di saat-saat tergelap sekalipun.
+ - Di tengah kekacauan, temukan ketenangan batin Anda.
+ - Tetap positif, bekerja keras, wujudkan.
+
+
+ - Malam adalah bisikan tenang tentang peluang.
+ - Dalam keheningan malam, temukan kekuatan batinmu.
+ - Rangkullah kegelapan, karena kegelapan menyimpan kunci impian Anda.
+ - Dunia tertidur, namun potensi Anda tetap terjaga.
+ - Malam mungkin sudah larut, tetapi ambisi Anda tidak lekang oleh waktu.
+ - Tetaplah penasaran, tetap terjaga, dan biarkan malam membimbing Anda.
+ - Temukan keberanian untuk mengejar impian Anda.
+ - Hal-hal besar tidak pernah datang dari zona nyaman.
+ - Anda memiliki kekuatan untuk menciptakan takdir Anda sendiri.
+ - Kebahagiaan adalah pilihan; memilihnya setiap hari.
+ - Hidup adalah apa yang Anda buat, jadi buatlah itu berarti.
+ - Tetap fokus, tetap bertekad, tetap tak terhentikan.
+ - Anda adalah penulis cerita Anda sendiri.
+ - Berusahalah untuk kemajuan, bukan kesempurnaan.
+ - Setiap hari adalah awal yang baru; menjadikannya sesuatu yang hebat.
+ - Menginspirasi orang lain dengan menjadi diri-sejati Anda.
+ - Sikap Anda menentukan arah Anda.
+
+
+ - Ketidaktahuan adalah kebahagiaan sejati
+ - Apakah ini waktu yang tepat untuk menginstal pembaruan?
+ - Berdamai, bukan perang
+ - Oh hai, apa kabar?
+ - Selamat datang di Matrix yang sesungguhnya!
+ - Berapa banyak tangkapan layar yang kamu ambil?
+ - Kebaikan terbuka dengan AlphaDroid
+ - Sebarkan cinta, bukan malapetaka
+ - Kami tidak menyalakan api!
+ - Waktunya mendengarkan musik
+ - \u003C\u003C\u003C\u003C\u003C\u003C\u003C HEAD
+ - Anda membutuhkan AlphaDroid Premium untuk melihat ini
+ - Ingat garis keturunan Unicorn
+ - Nikmati daya tahan baterai yang bertambah
+ - Dimulai dari paling nol
+ - Kami mencintaimu 3000
+ - Lezat bahkan tanpa Sushi
+ - Lakukan sesuatu yang baik hari ini
+ - Inilah pengalaman Pixel yang terbaik, betul tidak?
+ - Tidak ada ilusi, selamat datang ke kenyataan!
+ - Terima kasih atas dukungan anda
+ - Tidak ada festival untuk kebodohan - hanya kesempurnaan!
+ - Salah satu pilihan terbaik Buildbot
+ - Sanitas untuk Paranoia Anda
+ - Bangkit dalam damai
+ - Benar-benar pengalaman yang menyenangkan, bukan?
+ - Evolusi adalah mitos, bukan?
+ - Anda adalah apa yang Anda flash, jangan menjadi Kentang
+ - Apa yang ada di pikiran Anda?
+ - Harapkan Patronum
+ - Sulih Suara Wubba Lubba
+ - Menang Menang ....?
+ - rm -rf \/
+ - Pringles sebenarnya bukan keripik kentang..
+ - Ingatlah untuk memeriksa level baterai perangkat!
+ - Periksa email/pesan Anda.
+ - Periksa daftar tugasmu jika kamu memilikinya.
+ - Kamu dapat menonaktifkan quickspace melalui setelan beranda.
+ - Suka dengan yang kami kerjakan? Berikan kami segelas bir
+ - Rom terbaik yang pernah ada.
+ - Sampaikan salam kepada kami di Telegram
+ - Kami mencintaimu 3000x
+
+ Tidak dapat menemukan aktivitas kalender atau jam
+
+ Penyedia cuaca
+ Otomatis
+ Ruang pintar Google
+ OmniJaws
+ Pembaruan info cuaca
+ Tampilkan cuaca saat ini menurut lokasi
+ Membutuhkan layanan cuaca untuk diaktifkan
+ Berawan
+ Hujan
+ Cerah
+ Badai
+ Bersalju
+ Berangin
+ Berkabut
+ Lokasi saat ini
+ Tampilkan cuaca saat ini menurut lokasi
+ Kondisi saat ini
+ Tampilkan kondisi cuaca dan suhu saat ini
+
+ Indikator sembunyikan halaman secara otomatis
+ Sembunyikan titik indikator halaman secara otomatis
+
+ Kedalaman blur latar belakang
+ Setel tingkat kedalaman blur untuk baru-baru ini dan laci aplikasi
+
+ Info aplikasi
+ Sumber
+ Sistem
+ Update yang lalu
+ Versi
+ Lainnya
+
+ Paket ikon
+ Bawaan
+ Instal lebih banyak
+ Tidak ada toko aplikasi yang tersedia
+
+ Galeri wallpaper
+ Ganti wallpaper dengan cepat dari menu tekan lama
+ Terapkan ke layar kunci
+ Atur wallpaper yang sama di layar kunci
+
+ Kerikil
+ Kapal
+ Persegi murni
+ Kotak melengkung
+ Persegi panjang meruncing
+ Tetesan Air
+ Persegi panjang membulat
+ Berawan
+ Berbentuk silinder
+ Bunga
+ Hati
+ Segi enam
+ Segi enam membulat
+ Gaya iOS
+ Daun
+ Meong
+ Gaya Samsung
+ Diregangkan
+
+ Label aplikasi gelap
+ Selalu gunakan teks gelap untuk label aplikasi di laci aplikasi
+
+ Gaya laci aplikasi
+ Normal (lembaran bawah)
+ Layar penuh
+ Gaya daftar
+ Gaya OneUI (berhalaman)
+
+ Pengurutan laci aplikasi
+ Urutkan berdasarkan abjad
+ Urutkan berdasarkan tanggal pemasangan
+ Urutkan berdasarkan penggunaan
+
+ Warna latar belakang kustom
+ Gunakan warna kustom untuk latar belakang laci aplikasi
+ Warna mode terang
+ Warna mode gelap
+
+ HSB
+ RGB
+ Warna
+ Saturasi
+ Kecerahan
+ Merah
+ Hijau
+ Biru
+ HEX
+
+ Bilah pencarian bergaya Pixel
+ Tampilan bilah pencarian yang diperbarui dengan latar belakang berlapis
+ Opasitas lapisan luar
+
+ Tersembunyi & Aplikasi dilindungi
+ Buka kunci untuk mengelola aplikasi yang disembunyikan dan dilindungi
+ Autentikasi untuk membuka %1$s
+ Memuat\u2026
+ Silakan mengatur kunci layar untuk membatasi akses aplikasi
+ Bantuan
+ Aplikasi tersembunyi beserta widgetnya akan disembunyikan dari laci aplikasi
+ Aplikasi yang dilindungi memerlukan autentikasi untuk dibuka dari peluncur
+
+ Gaya tata letak Terkini
+ Bawaan
+ Efek skala
+ Gaya pokok
+ Gaya iOS
+ Gaya OxygenOS
+
+ Kunci/Buka kunci aplikasi
+ Aplikasi terbuka di daftar aplikasi terbaru
+ Aplikasi terkunci di daftar aplikasi terbaru
+
+ Getaran gulir
+
diff --git a/res/values-it-rIT/alpha_strings.xml b/res/values-it-rIT/alpha_strings.xml
new file mode 100644
index 00000000000..e8660a28519
--- /dev/null
+++ b/res/values-it-rIT/alpha_strings.xml
@@ -0,0 +1,44 @@
+
+
+
+
+ Lock/Unlock app
+ Unlocked app in recents
+ Locked app in recents
+
+ Recents layout style
+ Stock
+ Kami Style
+ iOS Style
+ OxygenOS Style
+
+ - Ignorance is Bliss
+ - Make love, not war
+ - Oh hey, what\'s up?
+ - We love you 3000
+ - Thank you for your support
+ - One of the buildbot\'s best picks
+ - Pringles aren\'t actually potato chips..
+ - You can disable quickspace via home settings.
+ - I got nothing ¯\\_(ツ)_/¯
+
+
+ - I never apologize. I\'m sorry but that\'s the way I am.
+ - All my life, I\'ve had one dream: to achieve my many goals.
+ - Good drink... good meat... good God, let\'s eat!
+ - You\'ll have to speak up. I\'m wearing a towel.
+ - Change the channel, Marge...
+ - Roads are just a suggestion Marge, just like pants.
+ - It takes two to lie - one to lie and one to listen.
+ - Shut up, Flanders!
+ - I\'m not normally a praying man, but if you\'re up there, please save me, Superman.
+ - It\'s so simple to be wise... just think of something stupid to say and then don\'t say it.
+ - Stupid sexy Flanders...
+ - I hope I didn\'t brain my damage.
+ - Operator, give me the number for 911!
+ - Don\'t blame me, I voted for Kodos.
+
+
diff --git a/res/values-it-rIT/cr_strings.xml b/res/values-it-rIT/cr_strings.xml
new file mode 100644
index 00000000000..916393252a0
--- /dev/null
+++ b/res/values-it-rIT/cr_strings.xml
@@ -0,0 +1,440 @@
+
+
+
+
+
+
+ Impostazioni schermata Home
+
+ Schermata Home
+ Imposta il pannello Google now ed altro
+ Icone
+ Imposta le dimensioni delle icone e altro ancora
+ Menu applicazioni
+ Personalizza il menù delle applicazioni
+ Recenti
+ Modifica la schermata delle app recenti
+ Varie
+ Altre opzioni
+ Generale
+ Interfaccia
+ Quickspace
+ Barra di ricerca
+ Azioni rapide
+
+ Opzioni sviluppatore
+ Abilita alcune funzioni nascoste a tuo rischio
+
+ Blocca disposizione
+ Non è possibile aggiungere, rimuovere e spostare icone e widget nella schermata home
+ È possibile aggiungere, rimuovere e spostare icone e widget nella schermata home
+ Non é possibile aggiungere widget alla schermata home
+
+ Etichette delle icone sul desktop
+ Etichette delle icone nel drawer
+
+ Scorri per accedere all\'app di Google
+
+ Quando scorri a sinistra dalla schermata Home
+
+ Quando scorri verso destra dalla schermata home
+
+ Use icon customizations in drawer
+ Follow icon customizations used on home screen
+
+ Pausa
+ Pausa %1$s?
+ Le notifiche di %1$s verranno nascoste
+ App in pausa
+ %1$s è stata messa in pausa dal launcher
+ Pausa app
+ Le app di lavoro sono disattivate
+ Disattiva app di lavoro
+ Attiva app di lavoro
+ Le app di lavoro non possono inviarti notifiche, usare la batteria o accedere alla tua posizione
+ Le tue app di lavoro non possono ricevere chiamate o messaggi di testo, inviarti notifiche, utilizzare la batteria o accedere alla tua posizione
+
+ Suggerimenti
+ Per tutte le app & suggerimenti sulla schermata Home
+
+ Riavvia
+ Riavvia manualmente il launcher per applicare tutte le impostazioni in sospeso
+ Riavvio del launcher...
+ Riavvio del launcher per applicare le modifiche...
+ Riavvio del launcher per aggiornare i componenti del launcher...
+
+ Doppio tocco per bloccare il telefono
+ Double tap empty space on home screen to turn off the screen
+ Vibra al doppio tocco per sospendere
+ Fornisci un feedback tattile al gesto doppio tocco per sospendere
+
+ Scorri per iniziare la ricerca
+ Apri la tastiera ogni volta che il drawer delle app diventa visibile
+
+ Dimensione icone
+
+ Dimensione font
+
+ Usa due righe per l\'etichetta dell\'app
+
+ Altezza riga
+
+ Sfondo barra inferiore
+ Mostra lo sfondo trasparente nella barra dell\'app in basso
+
+ Opacità dello sfondo
+
+ Tratto
+
+ Termina
+ Applicazione terminata
+
+ Cerca
+ Google Lens
+ Ricerca vocale
+ AI mode
+ Ricerca musica
+ Avvia la ricerca musicale sulla pressione dell\'icona del microfono
+ Barra di ricerca Google
+ Barra di ricerca nel dock inferiore
+ Icone a tema
+ Usa icone a tema per la barra di ricerca
+ Raggio dell\'angolo
+
+ App search bar location
+ Hidden
+ Top
+ Bottom
+
+ Scorrimento dello sfondo
+ Effetto di scorrimento dello sfondo per schermi multipli
+
+ Ingrandimento sfondo
+ Ingrandisci o riduci lo sfondo quando si usa il cassetto app o le app recenti
+
+ Barra di stato
+
+ Ombra superiore
+
+ %1$s Disponibile | %2$s Totale
+ Informazioni sulla memoria
+ Informazioni ZRAM
+
+ Screenshot
+ Cancella tutto
+ Lens
+
+ Consenti parallasse breve
+ Abilita l\'effetto di scorrimento completo dello sfondo su un numero minore di pagine invece di ritagliare lo sfondo
+ Centra singola pagina
+ Centra lo sfondo se si utilizza solo una singola pagina
+
+ Barra di scorrimento
+ Barra di scorrimento sul lato del cassetto delle applicazioni
+
+ Barra di stato scura
+ Usa sempre la barra di stato scura nella schermata home
+
+ Riepilogo
+ Mostra nella parte superiore della tua schermata Home
+
+ Buongiorno.
+ Buonasera.
+ Buon pomeriggio.
+ Buonanotte.
+ Buona giornata.
+ Oggi è
+ Oggi è
+
+ Artista Sconosciuto
+ In riproduzione
+ Da
+
+ In riproduzione
+ Mostra la canzone che stai riproducendo
+
+ Stile minimalista
+ Passa allo stile esteso
+
+ Messaggi casuali
+ Rendi il tuo compagno più vivace con messaggi casuali
+
+ - Buongiorno!
+ - Buongiorno, è ora di svegliarsi!
+ - Che bella giornata!
+ - Buona giornata!
+ - Che ne dici di un sonnellino di 5 minuti?
+ - Mettiamoci al lavoro.
+ - Le mattine sono una tela fresca per il vostro capolavoro quotidiano.
+ - Abbracciare l\'alba di opportunità ogni mattina.
+ - Nella luce precoce, trova il potere di illuminare il tuo cammino.
+ - La tua giornata inizia con infinite possibilità.
+ - Ogni alba è un promemoria che puoi ricominciare di nuovo.
+ - Sveglia con scopo, conquista il giorno successivo.
+ - I vostri pensieri possono essere il fondamento di grandi realizzazioni.
+ - Il mondo si risveglia, e così fa il tuo potenziale.
+ - Il primo capitolo della tua avventura quotidiana.
+ - Lascia che la tua routine mattutina sia il trampolino di lancio per il successo.
+ - Alba o no, il tuo potenziale è sempre in aumento.
+ - È tempo di idee fresche e azioni audaci.
+ - Chi dorme ha l\'oro in bocca, ma tu puoi catturare i tuoi sogni.
+ - Trova il coraggio di inseguire le tue aspirazioni.
+ - Alzati e risplendi, perché la grandezza ti aspetta.
+ - Il momento migliore per iniziare è adesso.
+
+
+ - Insegui i tuoi sogni, non i minuti.
+ - In ogni momento, una nuova opportunità.
+ - Abbraccia il viaggio, non solo la destinazione.
+ - Oggi è un dono, per questo si chiama presente.
+ - Lascia che il tuo sorriso cambi il mondo.
+ - La vita è una storia; fai in modo che ogni capitolo conti.
+ - Trova la gioia nei momenti ordinari.
+ - Il meglio deve ancora venire, continuate così.
+ - La tua possibilità di trasformare \"e se\" in \"ciò che è\".
+ - Cogli l\'attimo, iniziando proprio adesso.
+ - La felicità è una scelta che puoi fare in qualsiasi momento.
+ - Credi in te stesso, anche in un pomeriggio assonnato.
+ - Lascia che le tue azioni parlino più forte dell\'orologio.
+ - La tua vita è ciò che ne fai; rendilo fantastico.
+ - Sogna in grande, lavora duro e non mollare mai.
+ - Sei più forte di quanto pensi.
+ - Ogni battuta d\'arresto è una preparazione per un ritorno.
+ - Abbraccia il viaggio, non solo la destinazione.
+ - Insegui i tuoi obiettivi, non le tue paure.
+ - Il tuo potenziale è infinito; sbloccalo.
+
+
+ - Abbraccia il momento presente, non importa l\'ora.
+ - In ogni crepuscolo, trova una nuova alba di opportunità.
+ - La vita è un viaggio; goditi il panorama lungo il percorso.
+ - Il tuo atteggiamento modella la tua realtà.
+ - La tua storia è ancora in fase di scrittura, anche se il sole tramonta.
+ - Cattura la bellezza nei momenti ordinari.
+ - È tempo di riflettere e resettare.
+ - Scegli la gioia, indipendentemente dall\'ora dell\'orologio.
+ - Le opportunità non finiscono la sera.
+ - Sii la ragione per cui qualcuno sorride.
+ - Il tuo potenziale non conosce limiti.
+ - I momenti di magia possono accadere a qualsiasi ora.
+ - La tua vita è ciò che ne fai, rendila straordinaria.
+ - Sii curioso, sii grato.
+ - Sii il cambiamento che desideri vedere nel mondo.
+ - Credi in te stesso; sii capace di cose straordinarie.
+ - L\'unico limite è quello che ti poni.
+ - Il successo inizia con un singolo passo.
+
+
+ - Cogli la notte, perché racchiude i suoi misteri.
+ - Nella quiete di questa sera, trova la serenità.
+ - Le più grandi avventure della vita possono iniziare nell\'oscurità.
+ - Il tuo viaggio continua anche dopo che il sole è tramontato.
+ - Lascia che la notte sia la tua tela; dipingilo con i sogni.
+ - Nel silenzio della notte, trova la tua forza interiore.
+ - Le stelle brillano più luminose nelle ore più buie.
+ - Sogna in grande, anche nelle ore tarde della giornata.
+ - La tua vita è una storia che aspetta di essere scritta.
+ - È tempo di riflessione e di rinnovamento.
+ - I pensieri a tarda notte possono portare a rivelazioni mattutine.
+ - Trova gioia nella quiete di questa sera.
+ - Il cielo notturno è una vasta tela di sogni.
+ - Il tuo potenziale non conosce l\'ora di coricarsi; persegui le tue passioni.
+ - Mantieni la speranza, anche nelle ore più buie.
+ - Nel mezzo del caos, trova la tua calma interiore.
+ - Rimani positivo, lavora duro, realizzalo.
+
+
+ - Le notti sono i silenziosi sussurri delle opportunità.
+ - Nel silenzio della notte, trova la tua forza interiore.
+ - Abbraccia l\'oscurità, perché contiene le chiavi dei tuoi sogni.
+ - Il mondo dorme, ma il tuo potenziale è completamente sveglio.
+ - La notte potrebbe essere tarda, ma le tue ambizioni sono senza tempo.
+ - Sii curioso, resta sveglio e lascia che le notti ti guidino.
+ - Trova il coraggio di inseguire i tuoi sogni.
+ - Le grandi cose non vengono mai dalle zone di comfort.
+ - Hai il potere di creare il tuo destino.
+ - La felicità è una scelta; accoglila ogni giorno.
+ - La vita è ciò che fai, quindi falla contare.
+ - Rimani concentrato, rimani determinato, rimani inarrestabile.
+ - Sei l\'autore della tua storia.
+ - Lotta per il progresso, non per la perfezione.
+ - Ogni giorno è un nuovo inizio; rendilo fantastico.
+ - Ispira gli altri essendo te stesso autentico.
+ - il tuo atteggiamento determina la tua direzione.
+
+
+ - Beata ignoranza
+ - È gia il momento di flashare un altro aggiornamento?
+ - Fai la pace, non la guerra
+ - Ehi, come va?
+ - Benvenuto nel Matrix reale!
+ - Quanti screenshot hai fatto?
+ - Apri il bene con AlphaDroid
+ - Diffondi amore, non devastazione
+ - Non abbiamo acceso il fuoco!
+ - E\' tempo per della buona musica
+ - \u003C\u003C\u003C\u003C\u003C\u003C\u003C HEAD
+ - Hai bisogno di AlphaDroid Premium per vedere questo
+ - Ricorda la linea dell\'unicorno
+ - Goditi la durata Xstesa della batteria
+ - A partire dal punto zero
+ - Ti amiamo 3000
+ - Delizioso anche senza Sushi
+ - Fai qualcosa di bello oggi
+ - Questa è la migliore Esperienza Pixel, vero?
+ - Nessuna illusione, benvenuto alla realtà!
+ - Grazie per il tuo supporto
+ - Nessun festival per gli stupidi - solo perfezione!
+ - Una delle migliori scelte del buildbot
+ - Buonsenso per la tua Paranoia
+ - Crescita in pace
+ - Che bella esperienza, vero?
+ - L\'evoluzione è un mito, vero?
+ - Sei quello che flashi, non essere una patata
+ - Cosa ti passa per la mente?
+ - Aspetto il Protettore
+ - Wubba Lubba Dub Dub
+ - Vincitore Vincitore ....?
+ - rm -rf \/
+ - Le Pringles non sono in realtà patatine..
+ - Ricordati di controllare il livello della batteria del dispositivo!
+ - Controlla le tue email/messaggi.
+ - Controlla la tua lista di cose da fare se ne hai una.
+ - È possibile disattivare lo spazio rapido tramite le impostazioni home.
+ - Ti piacciamo? Mandaci una birra
+ - Migliore Rom Di Sempre.
+ - Salutaci su Telegram
+ - Ti amiamo 3000
+
+ Impossibile trovare l\'attività del calendario o dell\'orologio
+
+ Provider Meteo
+ Automatico
+ Google Smartspace
+ OmniJaws
+ Aggiornamento meteo
+ Visualizza l\'aggiornamento meteo corrente
+ Richiede l\'attivazione del servizio meteo
+ Nuvoloso
+ Piovoso
+ Soleggiato
+ Tempestoso
+ Nevoso
+ Ventoso
+ Nebbioso
+ Posizione attuale
+ Visualizza la posizione meteo corrente
+ Condizione corrente
+ Visualizza il riepilogo delle condizioni meteo correnti
+
+ Nascondi automaticamente l\'indicatore di pagina
+ Nascondi automaticamente i punti dell\'indicatore di pagina
+
+ Sfocatura sfondo
+ Imposta il livello di profondità della sfocatura per i recenti e il drawer delle app
+
+ Info app
+ Fonte
+ Sistema
+ Ultimo aggiornamento
+ Versione
+ Altro
+
+ Pacchetto icone
+ Predefinito
+ Installa altri pacchetti
+ Non ci sono app store disponibili
+
+ Wallpaper carousel
+ Quickly switch wallpapers from the long-press menu
+ Apply to lock screen
+ Set the same wallpaper on the lock screen
+
+ Pebble
+ Vessel
+ Pure square
+ Squircle
+ Tapered rect
+ Teardrop
+ Rounded rect
+ Nuvoloso
+ Cylindrical
+ Flower
+ Heart
+ Hexagon
+ Rounded hexagon
+ iOS Style
+ Leaf
+ Meow
+ Samsung Style
+ Stretched
+
+ Dark app labels
+ Always use dark text for app labels in the app drawer
+
+ App drawer style
+ Normal (bottom sheet)
+ Legacy fullscreen
+ List style
+ OneUI style (paged)
+
+ App drawer sorting
+ Sort alphabetically
+ Sort by install date
+ Sort by usage
+
+ Custom background color
+ Use a custom color for the app drawer background
+ Light mode color
+ Dark mode color
+
+ HSB
+ RGB
+ Hue
+ Saturation
+ Brightness
+ Red
+ Green
+ Blue
+ HEX
+
+ Pixel style search bar
+ Revamped search bar with layered background
+ Outer layer opacity
+
+ App nascoste & protette
+ Sblocca per gestire le app nascoste e protette
+ Autenticati per aprire %1$s
+ Caricamento\u2026
+ Per favore imposta una schermata di blocco sicura per limitare l\'accesso alle app
+ Aiuto
+ Le app nascoste e i relativi widget sono nascosti dal drawer
+ Le applicazioni protette richiedono l\'autenticazione per essere aperte dal launcher
+
+ Recents layout style
+ Predefinito
+ Scale effect
+ Kami style
+ iOS style
+ OxygenOS style
+
+ Lock/Unlock app
+ Unlocked app in recents
+ Locked app in recents
+
+ Scroll vibration
+
diff --git a/res/values-iw-rIL/cr_strings.xml b/res/values-iw-rIL/cr_strings.xml
new file mode 100644
index 00000000000..94fbb6a8ab3
--- /dev/null
+++ b/res/values-iw-rIL/cr_strings.xml
@@ -0,0 +1,440 @@
+
+
+
+
+
+
+ הגדרות דף הבית
+
+ דף הבית
+ הגדר את פאנל Google now כעת ועוד
+ סמלים
+ הגדרת גודל הסמל ועוד
+ מגירת היישומים
+ התאמה אישית של מגירת היישומים
+ מהזמן האחרון
+ ארגון מחדש של מסך התצוגה המסכמת
+ שונות
+ אפשרויות נוספות
+ כללי
+ ממשק
+ החלל המהיר
+ סרגל החיפוש
+ פעולות מהירות
+
+ אפשרויות מפתחים
+ אפשר כמה תכונות נסתרות על אחריותך בלבד
+
+ נעילת פריסת התצוגה
+ לא ניתן להוסיף, להסיר ולהזיז סמלים ויישומונים על מסך הבית
+ ניתן להוסיף, להסיר ולהזיז סמלים ויישומונים על מסך הבית
+ אין אפשרות להוסיף יישומונים למסך הבית
+
+ תוויות סמלים במסך הבית
+ תוויות סמלים במגירת היישומים
+
+ החלק כדי לגשת לאפליקציה גוגל
+
+ בעת החלקה שמאלה ממסך הבית
+
+ בעת החלקה ימינה ממסך הבית
+
+ Use icon customizations in drawer
+ Follow icon customizations used on home screen
+
+ השהיה
+ השהיית %1$s?
+ התראות עבור %1$s יושהו
+ היישום מושהה
+ %1$s הושהה ישירות מהמשגר
+ השהיית היישום
+ ייישומים של העבודה מכובים
+ כיבוי יישומים של העבודה
+ הפעלת יישומים של העבודה
+ יישומי העבודה שלך לא יכולים להציג לך התראות, להשתמש בסוללה או לקבל גישה למיקומך
+ יישומי העבודה שלך לא יכולים לקבל שיחות טלפון או הודעות מלל, להציג לך התראות, להשתמש בסוללה או לקבל גישה למיקומך
+
+ הצעות
+ עבור כל היישומים & הצעות למסך הבית
+
+ הפעלה מחדש
+ הפעלה מחדש של משגר היישומים (לאונצ\'ר) להחלת הגדרות ששונו
+ הפעלה מחדש של הלאונצ\'ר...
+ הפעלה מחדש של הלאונצ\'ר להחלת השינויים...
+ הפעלה מחדש של הלאונצ\'ר לעדכון רכיבי הלאונצ\'ר...
+
+ הקשה כפולה לכיבוי המסך
+ Double tap empty space on home screen to turn off the screen
+ Vibrate on double tap to sleep
+ Provide haptic feedback on double tap to sleep gesture
+
+ Swipe to start search
+ Open keyboard whenever app drawer becomes visible
+
+ גודל הסמל
+
+ גודל גופן
+
+ פריסת תווית יישומים בשתי שורות
+
+ גובה השורה
+
+ רקע המושב החם
+ הוספת רקע שקוף לסרגל היישומים
+
+ שקיפות הרקע
+
+ עובי הקו
+
+ כפיית הפסקת היישום
+ היישום נסגר
+
+ חיפוש
+ Google Lens
+ חיפוש קולי
+ AI mode
+ חיפוש מוסיקה
+ הפעלת חיפוש מוסיקה בלחיצה על סמל המיקרופון
+ סרגל החיפוש של Google
+ שורת חיפוש בסרגל התחתון
+ סמלי ערכת הנושא
+ הצגת סמלי ערכת הנושא בשורת החיפוש
+ רדיוס הפינות
+
+ App search bar location
+ Hidden
+ Top
+ Bottom
+
+ גלילת הטפט
+ אפקט גלילה לטפט עבור מסכים מרובים
+
+ שינוי מרחק לטפט
+ התקרבות או התרחקות הטפט בעת השימוש במגירת היישומים או היישומים האחרונים
+
+ שורת מצב
+
+ צללית עילית
+
+ %1$s זמין /%2$s בסך הכל
+ מידע אודות הזכרון
+ מידע אודות ZRAM
+
+ צילום המסך
+ ניקוי הכל
+ עדשה
+
+ התרת היסט (פרלקסה) קצר
+ הפעלת אפקט גלילה מלא לטפט על פני מספר קטן יותר של דפים במקום חיתוך הטפט
+ מירכוז בדף יחיד
+ התמקדות הטפט במרכז בשימוש בדף יחיד
+
+ סרגל הגלילה
+ הצגת סרגל הגלילה לצד מגירת היישומים
+
+ שורת המצב כהה
+ Always use dark status bar on home screen
+
+ בקצרה
+ הצגה בקצה העליון של מסך הבית שלך
+
+ בוקר טוב.
+ ערב טוב.
+ אחר הצהריים טובים.
+ לילה טוב.
+ יום טוב.
+ היום הוא
+ כעת
+
+ אמן לא ידוע
+ מתנגן כעת
+ על ידי
+
+ מתנגן כעת
+ הצגת השיר המתנגן כעת
+
+ סגנון מינימליסטי
+ החלפה לסגנון מודרני
+
+ הודעות אקראיות
+ אפשר להחיות את השיחה עם הודעות אקראיות
+
+ - בוקר טוב!
+ - בוקר טוב, הגיע הזמן לזוז!
+ - איזה יום יפה בחוץ!
+ - שיהיה לך יום נפלא!
+ - מה לגבי שנ\"צ של חמש דקות?
+ - בוא נעשה את זה.
+ - בקרים הם בד ציור ריק ליצירות היומיות שלך.
+ - אמצו את זריחת השמש של הזדמנויות כל בוקר.
+ - אורו המוקדם של הבוקר יכול להאיר את הדרך שלך.
+ - יומך מתחיל עם אינסוף אפשרויות.
+ - כל זריחה היא תזכורת לכך שאפשר להתחיל מחדש.
+ - התעוררו עם מטרה, כיבשו את היום שלפניכם.
+ - מחשבותיך יכולות לשמש בסיס להישגים אדירים.
+ - העולם מתעורר, וכך גם הפוטנציאל שלך.
+ - הפרק הראשון בהרפתקה היומית שלך.
+ - שגרת הבוקר שלך היא כן השיגור להצלחה.
+ - זריחה או לא, הפוטנציאל שלך תמיד בעלייה.
+ - זה הזמן לרעיונות טריים ולעשייה נועזת.
+ - המקדים להשכים זוכה בפרס, אבל תוכלו ללכוד את חלומותיכם.
+ - איזרו אומץ לחתור למימוש השאיפות שלכם.
+ - עורו, קומו, כי מחכה לכם גדוּלה.
+ - הזמן הטוב ביותר להתחיל הוא עכשיו.
+
+
+ - רידפו אחר חלומותיכם, לא אחר הדקות.
+ - בכל רגע, הזדמנות חדשה.
+ - תיהנו מהמסע, לא רק מהיעד.
+ - Today is a gift, that\'s why it\'s called the present.
+ - תנו לחיוך שלכם לשנות את העולם.
+ - החיים הם סיפור; עשו שכל פרק יהיה משמעותי.
+ - מיצאו הנאה ברגע הרגיל.
+ - הטוב ביותר עוד יגיע, תמשיכו כך.
+ - ההזדמנות שלך להפוך את \'הלואי\' ל\'הווה\'
+ - תיפסו את היום, התחילו ממש עכשיו.
+ - האושר הוא בחירה שניתן לבחור בכל זמן.
+ - האמינו בעצמכם, גם באחה\"ץ מנומנם.
+ - תנו למעשיכם להשמיע קול חזק משל השעון.
+ - חייכם הם מה שתעשו מהם; עשו אותם מדהימים.
+ - חילמו בגדול, עיבדו קשה, ולעולם אל תוותרו.
+ - יש בכם עוצמה גדולה יותר ממה שאתם חושבים.
+ - כל נסיגה היא הכנה לשיבה.
+ - תיהנו מהמסע, לא רק מהיעד.
+ - רידפו אחר מטרותיכם, לא אחר חששותיכם.
+ - הפונציאל שלכם אינסופי; שחררו אותו.
+
+
+ - אמצו את הרגע הנוכחי, לא משנה מה השעה.
+ - בכל דמדומי ערב, מיצאו שחר חדש של הזדמנויות.
+ - החיים הם מסע; תיהנו מהנוף שבדרך.
+ - הגישה שלך מעצבת את המציאות שלך.
+ - הסיפור שלך ממשיך להיכתב, גם כאשר השמש שוקעת.
+ - מיצאו את היופי שברגעים הרגילים.
+ - זה הזמן להרהר ולאתחל מחדש.
+ - ביחרו בהנאה, לא משנה השעה.
+ - הזדמנויות אינן נועלות את המשרד בסוף היום.
+ - היו אתם הסיבה לחיוך של מישהו.
+ - אין גבול לפוטנציאל שלך.
+ - רגעי קסם יכולים להתחולל בכל זמן.
+ - חייכם הם מה שתעשו מהם; עשו אותם מדהימים.
+ - היו סקרנים, היו מלאי תודה.
+ - היו אתם השינוי שהייתם רוצים לראות בעולם.
+ - האמינו בעצמכם; יש בכם יכולת לדברים מדהימים.
+ - המגבלה היחידה היא זו שהטלת על עצמך.
+ - ההצלחה מתחילה בצעד יחיד.
+
+
+ - איחזו בלילה, כי הוא אוצר תעלומות משלו.
+ - In the quiet of this evening, find serenity.
+ - ההרפתקה הגדולה של החיים יכולה להתחיל בחשיכה.
+ - המסע שלך נמשך גם אחרי שקיעת החמה.
+ - הלילה הוא בד ציור ריק; ציבעו אותו בחלומות.
+ - בדממת הערב מיצאו את השלום הפנימי שלכם.
+ - הכוכבים זורחים חזק יותר בשעות הכי חשוכות.
+ - חילמו חלומות גדולים, אפילו בשעות המאוחרות של היום.
+ - חייך הם סיפור שממתין להיכתב.
+ - זה הזמן להרהר ולאתחל מחדש.
+ - מחשבות בשעת לילה מאוחרת יכולות להוביל להארות בשעות הבוקר המוקדמות.
+ - הפיקו הנאה מהשלווה של הערב.
+ - שמי הלילה הם בד ציור ענק של חלומות.
+ - Your potential knows no bedtime; pursue your passions.
+ - Stay hopeful, even in the darkest hours.
+ - In the midst of chaos, find your inner calm.
+ - Stay positive, work hard, make it happen.
+
+
+ - Nights are the quiet whispers of opportunity.
+ - In the stillness of the night, find your inner strength.
+ - Embrace the darkness, for it holds the keys to your dreams.
+ - The world sleeps, but your potential is wide awake.
+ - The night may be late, but your ambitions are timeless.
+ - Stay curious, stay awake, and let nights guide you.
+ - Find the courage to chase your dreams.
+ - Great things never come from comfort zones.
+ - You have the power to create your own destiny.
+ - Happiness is a choice; choose it every day.
+ - Life is what you make it, so make it count.
+ - Stay focused, stay determined, stay unstoppable.
+ - You are the author of your own story.
+ - Strive for progress, not perfection.
+ - Every day is a new beginning; make it a great one.
+ - Inspire others by being your authentic self.
+ - Your attitude determines your direction.
+
+
+ - בורות היא אושר
+ - האם הגיע כבר הזמן לצרוב עדכון נוסף?
+ - עשו שלום, לא מלחמה
+ - היי, מה קורה?
+ - ברוך בואך למטריקס האמיתי!
+ - כמה צילומי מסך אתה מצלם?
+ - טוב לב עם AlphaDroid
+ - הפיצו אהבה, לא חורבן
+ - לא אנחנו הצתנו את הלהבה!
+ - הגיע הזמן לקצת מוזיקה טובה
+ - ראש \u003C\u003C\u003C\u003C\u003C\u003C\u003C
+ - אתה צריך AlphaDroid Premium כדי לראות את זה
+ - זכור מאין הגעת ולאן אתה הולך
+ - תיהנה מחיי סוללה ארוכים
+ - והתחלנו מכלום ושום דבר...
+ - אנחנו אוהבים אותך 3000
+ - ערב לחיך אפילו בלי סושי
+ - תעשו משהו נחמד היום
+ - זו חוויית הפיקסל הטובה ביותר, לא?
+ - בלי אשליות, ברוכים הבאים למציאות!
+ - תודה על תמיכתך!
+ - אין פסטיבל לדרפס - רק שלמות!
+ - אחת מהבחירות הטובות ביותר של הבוטים
+ - קח קצת שפיות בשביל הפרנויה שלך
+ - זריחה לשלום
+ - איזו חוויה מקסימה, לא?
+ - האבולוציה היא מיתוס, נכון?
+ - אתה מה שאתה צורב, אל תהיה תפו\"א
+ - על מה אתה חושב?
+ - אקספקטו פטרונום
+ - Wubba Lubba Dub Dub
+ - ווינר ווינר ....?
+ - rm -rf \/
+ - פרינגלס הם לא באמת צ\'יפס תפוחי אדמה..
+ - זיכרו לבדוק את רמת הסוללה!
+ - בידקו את הדוא\"ל/ההודעות שלכם.
+ - בידקו את רשימת המשימות שלכם אם יש לכם כזו.
+ - ניתן להשבית את quickspace באמצעות הגדרות הבית.
+ - אהבת אותנו? אפשר לשלוח לנו כוס בירה
+ - ה-ROM הטוב ביותר מאז ומעולם.
+ - אפשר לאמר לנו היי בטלגרם
+ - אנחנו אוהבים אותך 3000
+
+ לא נמצאו פעילויות יומן או שעון
+
+ Weather provider
+ Auto
+ Google Smartspace
+ OmniJaws
+ עדכון מזג האוויר
+ הצגת העדכון הנוכחי של מזג-האוויר
+ נדרש ששירות מזג-האוויר יהיה פעיל
+ מעונן
+ גשום
+ שמשי
+ סוער
+ מושלג
+ משבי רוח
+ מעורפל
+ המיקום הנוכחי
+ הצגת המיקום הנוכחי של מזג-האוויר
+ התנאים הנוכחיים
+ הצגת הסיכום הנוכחי של תנאי מזג-האוויר
+
+ Auto-hide page indicator
+ Hide the page indicator dots automatically
+
+ עומק טשטוש הרקע
+ הגדרת עומק הטשטוש למגירת היישומים וליישומים האחרונים
+
+ Application info
+ מקור
+ מערכת
+ עודכן לאחרונה
+ גרסא
+ עוד
+
+ ערכת סמלים
+ ברירת המחדל
+ התקנת עוד
+ אין חנות אפליקציות זמינה
+
+ Wallpaper carousel
+ Quickly switch wallpapers from the long-press menu
+ Apply to lock screen
+ Set the same wallpaper on the lock screen
+
+ Pebble
+ Vessel
+ Pure square
+ Squircle
+ Tapered rect
+ Teardrop
+ Rounded rect
+ מעונן
+ Cylindrical
+ Flower
+ Heart
+ Hexagon
+ Rounded hexagon
+ iOS Style
+ Leaf
+ Meow
+ Samsung Style
+ Stretched
+
+ Dark app labels
+ Always use dark text for app labels in the app drawer
+
+ App drawer style
+ Normal (bottom sheet)
+ Legacy fullscreen
+ List style
+ OneUI style (paged)
+
+ App drawer sorting
+ Sort alphabetically
+ Sort by install date
+ Sort by usage
+
+ Custom background color
+ Use a custom color for the app drawer background
+ Light mode color
+ Dark mode color
+
+ HSB
+ RGB
+ Hue
+ Saturation
+ Brightness
+ Red
+ Green
+ Blue
+ HEX
+
+ Pixel style search bar
+ Revamped search bar with layered background
+ Outer layer opacity
+
+ & יישומים מוגנים מוסתרים
+ ביטול נעילה כדי לנהל את היישומים המוסתרים והמוגנים
+ אימות לפתיחת %1$s
+ טעינת \u2026
+ נדרש להגדיר מסך נעילה מאובטח להגבלת גישה ליישומים
+ עזרה
+ יישומים מוסתרים והווידג\'טים שלהם אינם מוצגים במגירת היישומים
+ יישומים מוגנים מחייבים אימות לפתיחה מהמשגר
+
+ Recents layout style
+ ברירת המחדל
+ Scale effect
+ Kami style
+ iOS style
+ OxygenOS style
+
+ Lock/Unlock app
+ Unlocked app in recents
+ Locked app in recents
+
+ Scroll vibration
+
diff --git a/res/values-ja-rJP/cr_strings.xml b/res/values-ja-rJP/cr_strings.xml
new file mode 100644
index 00000000000..4918f1ae1d7
--- /dev/null
+++ b/res/values-ja-rJP/cr_strings.xml
@@ -0,0 +1,440 @@
+
+
+
+
+
+
+ ホームの設定
+
+ ホーム画面
+ Google Now パネルとその他の設定をします
+ アイコン
+ アイコンサイズとその他を設定します
+ アプリドロワー
+ アプリドロワーのカスタマイズが行えます
+ 最近
+ 概要画面を更新します
+ その他
+ その他のオプション
+ 一般
+ インターフェース
+ クイックスペース
+ 検索バー
+ クイックアクション
+
+ 開発者オプション
+ 隠された機能を有効化 (自己責任)
+
+ レイアウトをロック
+ アイコンやウィジェットの追加、削除、移動を許可しない
+ アイコンやウィジェットの追加、削除、移動を許可します
+ ホーム画面にウィジェットを追加することはできません
+
+ デスクトップ上のアイコンラベル
+ ドロワーのアイコンラベル
+
+ スワイプしてGoogleアプリを開く
+
+ メインホーム画面から右にスワイプして表示
+
+ メインホーム画面から左にスワイプして表示
+
+ Use icon customizations in drawer
+ Follow icon customizations used on home screen
+
+ 一時停止
+ %1$s を一時停止しますか?
+ %1$s の通知を一時停止します
+ アプリを一時停止しました
+ %1$s を一時停止しました
+ アプリを一時停止
+ 仕事用アプリはオフです
+ 仕事用アプリをオフにする
+ 仕事用アプリをオンにする
+ 仕事用アプリからの通知、バッテリーの使用、位置情報へのアクセスはできません。
+ 仕事用アプリでは、電話やテキストメッセージの受信、通知の送信、バッテリーの使用、位置情報へのアクセスはできません。
+
+ 提案
+ 全てのアプリとホーム画面の候補
+
+ 再起動
+ 設定を適用するにはランチャーを再起動してください
+ ランチャーを再起動しています..
+ 変更を適用するためにランチャーを再起動しています...
+ ランチャーを再起動してランチャーのコンポーネントを更新しています...
+
+ ダブルタップでスリープ
+ Double tap empty space on home screen to turn off the screen
+ Vibrate on double tap to sleep
+ Provide haptic feedback on double tap to sleep gesture
+
+ Swipe to start search
+ Open keyboard whenever app drawer becomes visible
+
+ アイコンのサイズ
+
+ フォントのサイズ
+
+ アプリのラベルを2行にする
+
+ 行の高さ
+
+ ホットシートの背景
+ アプリドックに半透明の背景を追加する
+
+ 背景の透明度
+
+ ストローク
+
+ 強制停止
+ アプリを強制停止しました
+
+ 検索
+ Google レンズ
+ 音声検索
+ AI mode
+ 音楽の検索
+ マイクアイコンを押して音楽検索を開始します
+ Google 検索バー
+ ドックの下部に検索バーを表示します
+ テーマアイコン
+ 検索バーにテーマアイコンを使用する
+ 角の丸み
+
+ App search bar location
+ Hidden
+ Top
+ Bottom
+
+ 壁紙をスクロール
+ 複数ページでの壁紙のスクロール効果
+
+ 壁紙の拡大
+ ドロワーやアプリ履歴を使用時に壁紙をズームインやズームアウトをします
+
+ ステータスバー
+
+ 上部を暗くする
+
+ %1$s Available | %2$s Total
+ メモリ情報
+ ZRAMの情報
+
+ スクリーンショット
+ すべてクリア
+ レンズ
+
+ 短い視差を許可
+ 壁紙をトリミングする代わりにページ数の少ない場合は壁紙全体のスクロール効果を有効にする
+ 単一ページを中央にする
+ ホーム画面が単一ページの場合は壁紙を中央に表示します
+
+ スクロールバー
+ アプリドロワー横のスクロールバー
+
+ 暗いステータスバー
+ Always use dark status bar on home screen
+
+ At A Glance
+ ホーム画面の上部に表示します
+
+ おはようございます.
+ こんばんは。
+ こんにちは。
+ おやすみなさい.
+ 良い一日を
+ 今日は
+ それは
+
+ 不明なアーティスト
+ 再生中
+ によって
+
+ 再生中
+ 再生中の曲を表示します
+
+ ミニマルスタイル
+ モダンスタイルに切り替えます
+
+ ランダムメッセージ
+ ランダムメッセージで仲間を盛り上げる
+
+ - おはようございます!
+ - おはようございます、さぁ始めましょうか!
+ - なんて美しい日でしょう!
+ - 良い一日を!
+ - 5分間の短い昼寝はどうでしょうか?
+ - このパンを食べよう。
+ - 朝は毎日の傑作のための新鮮なキャンバスです。
+ - 毎朝機会の日の出を受け入れなさい。
+ - 早い光の中で、道を照らす力を見つけてください。
+ - あなたの一日は無限の可能性から始まります。
+ - すべての日の出は、あなたが新しく始めることができることを思い出させるものです。
+ - 目的で目を覚まし、一日先を征服します。
+ - あなたの考えは偉大な成果の基礎になり得ます。
+ - 世界は目覚め、そしてあなたの可能性も目覚めます。
+ - あなたの毎日の冒険の最初の章。
+ - あなたの朝のルーチンが成功のためのランチパッドであることを許可しなさい。
+ - 日の出であろうとなかろうと、あなたの可能性は常に上昇しています。
+ - それは新鮮なアイデアと大胆な行動の時間です。
+ - 早い鳥はワームを捕まえますが、あなたの夢をキャッチすることができます。
+ - 夢を追いかける勇気を見つけてください。
+ - 素晴らしさのために、上昇と輝き、待っています。
+ - 最もよい時期は今である。
+
+
+ - 時間ではなく、夢を追いかけてください。
+ - すべての瞬間、新しい機会。
+ - 目的地だけでなく、旅を受け入れましょう。
+ - 今日は贈り物ですので、プレゼントと呼ばれています。
+ - あなたの笑顔が世界を変えましょう。
+ - 人生は物語であり、各章を数えてください。
+ - 普通の瞬間に喜びを見つけます。
+ - 最善はまだ来て、それを維持することです。
+ - 「もし」を「何であるか」に変えるチャンス。
+ - 今すぐ始めて、一日をつかみなさい。
+ - 幸福とは、いつでも作ることができる選択です。
+ - 自分自身を信じて, でも、眠い午後.
+ - あなたの行動を時計よりも大きく話すようにしなさい。
+ - あなたの人生はそれを作り出すものであり、それを素晴らしいものにします。
+ - 大きな夢、一生懸命働き、そして決してあきらめない。
+ - あなたは思っている以上に強い。
+ - すべての挫折は、カムバックのためのセットアップです。
+ - 目的地だけでなく、旅を受け入れましょう。
+ - あなたの恐れではなく、目標を追いなさい。
+ - あなたの可能性は無限にあります。それを解放してください。
+
+
+ - 時間に関係なく、現在の瞬間を受け入れてください。
+ - すべての夕暮れで、機会の新しい夜明けを見つける。
+ - 人生は旅であり、道に沿って景色を楽しむ。
+ - 態度が現実を形作っています
+ - 日が沈む時でさえ、あなたの物語はまだ書かれている。
+ - 普通の瞬間の美しさをキャプチャします。
+ - 反省してリセットする時期です。
+ - 時計の時間に関係なく、喜びを選択してください。
+ - 機会は夕方に出発しません。
+ - 誰かが笑顔を見せる理由になりなさい。
+ - あなたの潜在的な限界を知らない。
+ - 魔法の瞬間はいつでも起こり得ます。
+ - あなたの人生はそれを作り出すものであり、それを素晴らしいものにします。
+ - 好奇心を持ち、感謝し続けてください。
+ - あなたが望む世界の変化になってください。
+ - あなた自身を信じなさい; あなたは素晴らしい事ができる。
+ - 唯一の制限は、あなた自身のために設定したものです。
+ - 成功は1つのステップから始まります。
+
+
+ - 夜を捉えましょう。夜には独自の謎があるからです。
+ - 今夜の静けさの中で、静けさを見つけてください。
+ - 人生最大の冒険は暗闇から始まることもあります。
+ - 太陽が沈んだ後も旅は続きます。
+ - 夜をキャンバスにしましょう。 夢で描いてください。
+ - 今夜の静けさの中で、心の平安を見つけてください。
+ - 星は最も暗い時間に最も明るく輝きます。
+ - たとえ一日の遅い時間であっても、大きな夢を持ちましょう。
+ - あなたの人生は書かれるのを待っている物語です。
+ - それは反省と更新の時期です。
+ - 深夜の考えは早朝の啓示につながる可能性があります。
+ - 今夜の静けさの中に喜びを見つけましょう。
+ - 夜空の夢の広大なキャンバスです。
+ - あなたの潜在能力は寝る時間を知らない; あなたの情熱を追求しなさい。
+ - 最も暗い時間の中でも、希望を持っていてください。
+ - 混沌の中で、あなたの内なる静けさを見つけてください。
+ - ポジティブなまま、一生懸命働き、それを実現します。
+
+
+ - 夜は機会の静かなささやきです。
+ - 夜の静けさの中で、自分の内なる強さを見つけてください。
+ - それはあなたの夢への鍵を握っているので、暗闇を受け入れてください。
+ - 世界は眠りますが、あなたの可能性は目を覚ましています。
+ - 夜は遅くなるかもしれませんが、あなたの野心は時代を超えています。
+ - 好奇心を持ち、目を覚まし、夜を案内しましょう。
+ - あなたの夢を追いかける勇気を見つけなさい。
+ - 偉大なものは、快適ゾーンから来ることはありません。
+ - あなたは自分の運命を創造する力があります。
+ - 幸福は選択であり、毎日それを選びなさい。
+ - 人生はあなたがそれを作るものです、だから、それをカウントしてください。
+ - 常に集中し、決定的な滞在、止められないままにしてください。
+ - あなたはあなた自身の物語の著者です。
+ - 完璧ではなく、進歩のために努力しなさい。
+ - 毎日が新しい始まりであり、素晴らしいものになります。
+ - あなたの本物であることによって他の人を刺激する。
+ - あなたの態度があなたの方向を決めます。
+
+
+ - 無知は至福なことです
+ - 既に別のアップデートをフラッシュする時間ですか?
+ - 戦争ではなく平和にする
+ - こんにちは、何かありましたか?
+ - リアルマトリックスへようこそ!
+ - スクリーンショットは何枚撮りますか?
+ - AlphaDroid でのオープンな良さ
+ - Havoc ではない愛が広がる
+ - 私達は火をつけていません!
+ - 良い音楽を聴く時間です
+ - \u003C\u003C\u003C\u003C\u003C\u003C\u003C HEAD
+ - これを見るには AlphaDroid Premium が必要です
+ - ユニコーンの Lineage を覚えておいて下さい
+ - Xtended なバッテリーライフを楽しんでください!
+ - グラウンドゼロから始める
+ - We love you 3000
+ - 寿司なしでもおいしいです
+ - 今日は素敵なことをしましょう
+ - これは最高の Pixel Experience ですよね?
+ - 幻想ではありません。リアルへようこそ!
+ - ご支援いただきありがとうございます
+ - Derp のための祭りではありません - 唯一完璧!
+ - ビルドボットの最高のピックの1つです
+ - パラノイアの正気
+ - Rising in peace
+ - なんて素敵な経験でしょうか?
+ - Evolution は神話ですよね?
+ - あなたはフラッシュするもので、ポテトではありません
+ - ご意見をお聞かせください!
+ - エクスペクト・パトローナム
+ - Wubba Lubba Dub Dub
+ - 勝者の勝者…?
+ - rm -rf \/
+ - プリングルは実際にポテトチップスではありません。
+ - デバイスのバッテリー残量を確認してください!
+ - メール/メッセージを確認してください。
+ - To Do リストがある場合は確認してください。
+ - ホーム設定でクイックスペースを無効にできます。
+ - 私たちを愛していますか?ビールを送って下さい
+ - 史上最高のロム
+ - Telegramでお待ちしています
+ - We love you 3000
+
+ カレンダーまたは時計のアクティビティが見つかりません
+
+ Weather provider
+ Auto
+ Google Smartspace
+ OmniJaws
+ 天気予報
+ 現在の天気の最新情報を表示します
+ 天気サービスを有効にする必要があります
+ 曇り
+ 雨
+ 快晴
+ 嵐
+ 雪
+ 強風
+ 霧
+ 現在地
+ 現在地の天気を表示します
+ 現在の天気
+ 現在の天候の概要を表示します
+
+ Auto-hide page indicator
+ Hide the page indicator dots automatically
+
+ 背景のぼかし度合い
+ アプリ履歴とアプリドロワーのぼかし度合いを設定する
+
+ Application info
+ ソース
+ システム
+ 最終更新
+ バージョン
+ 詳細
+
+ アイコンパック
+ デフォルト
+ その他をインストール
+ 利用可能なアプリストアがありません
+
+ Wallpaper carousel
+ Quickly switch wallpapers from the long-press menu
+ Apply to lock screen
+ Set the same wallpaper on the lock screen
+
+ Pebble
+ Vessel
+ Pure square
+ Squircle
+ Tapered rect
+ Teardrop
+ Rounded rect
+ 曇り
+ Cylindrical
+ Flower
+ Heart
+ Hexagon
+ Rounded hexagon
+ iOS Style
+ Leaf
+ Meow
+ Samsung Style
+ Stretched
+
+ Dark app labels
+ Always use dark text for app labels in the app drawer
+
+ App drawer style
+ Normal (bottom sheet)
+ Legacy fullscreen
+ List style
+ OneUI style (paged)
+
+ App drawer sorting
+ Sort alphabetically
+ Sort by install date
+ Sort by usage
+
+ Custom background color
+ Use a custom color for the app drawer background
+ Light mode color
+ Dark mode color
+
+ HSB
+ RGB
+ Hue
+ Saturation
+ Brightness
+ Red
+ Green
+ Blue
+ HEX
+
+ Pixel style search bar
+ Revamped search bar with layered background
+ Outer layer opacity
+
+ 非表示 & 保護アプリ
+ ロック解除して非表示と保護されたアプリを管理する
+ 認証して %1$s を開く
+ \u2026 を読み込み中...
+ アプリのアクセス制限をするには画面ロックの設定をしてください
+ ヘルプ
+ 非表示アプリとそのウィジェットはドロワー内で非表示になります
+ 保護されているアプリを開くには認証が必要です
+
+ Recents layout style
+ デフォルト
+ Scale effect
+ Kami style
+ iOS style
+ OxygenOS style
+
+ Lock/Unlock app
+ Unlocked app in recents
+ Locked app in recents
+
+ Scroll vibration
+
diff --git a/res/values-ko-rKR/cr_strings.xml b/res/values-ko-rKR/cr_strings.xml
new file mode 100644
index 00000000000..0479f0273dd
--- /dev/null
+++ b/res/values-ko-rKR/cr_strings.xml
@@ -0,0 +1,440 @@
+
+
+
+
+
+
+ 홈 설정
+
+ 홈 화면
+ 구글 나우 패널 및 관련 설정
+ 아이콘
+ 아이콘 크기 및 관련 설정
+ 앱 서랍
+ 앱 서랍 맞춤 설정
+ 최근 앱
+ 오버뷰 화면 개량
+ 기타 설정
+ 기타 옵션
+ 일반
+ 인터페이스
+ 퀵스페이스
+ 검색 바
+ 빠른 동작
+
+ 개발자 옵션
+ 위험할 수 있는 숨겨진 기능들을 허용합니다
+
+ 레이아웃 잠금
+ 아이콘과 위젯을 홈 화면에 추가하거나 제거, 이동할 수 없습니다
+ 아이콘 및 위젯을 홈 화면에 추가, 제거, 이동할 수 있습니다
+ 홈 화면에 위젯을 추가할 수 없습니다
+
+ 홈 화면에 아이콘 이름
+ 앱 서랍에 아이콘 이름
+
+ 스와이프하여 Google 앱에 액세스
+
+ 메인 홈 화면에서 왼쪽으로 스와이프할 때
+
+ 메인 홈 화면에서 오른쪽으로 스와이프할 때
+
+ 앱 서랍에서 사용자 지정 아이콘 사용
+ 홈 화면의 아이콘 설정 따름
+
+ 일시중지
+ %1$s간 일시정지할까요?
+ %1$s의 알림이 일시중지됩니다
+ 앱 일시중지됨
+ 런처에 의해 %1$s간 일시정지합니다
+ 앱 일시중지
+ 워크 앱 꺼짐
+ 워크 앱 끄기
+ 워크 앱 켜기
+ 워크앱이 알림을 보내거나 배터리, 위치 정보에 접근할 수 없습니다
+ 워크앱이 통화, 문자 메시지, 알림, 배터리, 또는 위치 정보를 받아볼 수 없습니다
+
+ 제안
+ 모든 앱 & 홈 화면 제안
+
+ 재시작
+ 변경한 설정을 적용하려면 런처를 재실행하세요
+ 런처 재시작 중...
+ 변경 사항을 적용하기 위해 런처 재시작 중...
+ 런처 구성요소를 업데이트하기 위해 런처 재시작 중...
+
+ 두 번 탭하여 화면 끄기
+ 홈 화면의 빈 공간을 두 번 두드려 화면 끄기
+ 두 번 탭하여 화면 끄기하면 진동
+ 두 번 탭하여 화면 끄기 동작에 햅틱 피드백 제공
+
+ 스와이프하여 검색 시작
+ 앱 서랍이 열리면 키보드 표시
+
+ 아이콘 크기
+
+ 글꼴 크기
+
+ 앱 라벨에 두 줄 사용
+
+ 행 높이
+
+ 독 영역 배경
+ 앱 서랍에 반투명 배경 추가
+
+ 배경 불투명도
+
+ 스트로크
+
+ 작업 지우기
+ 앱 종료됨
+
+ 검색
+ 구글 렌즈
+ 음성 검색
+ AI모드
+ 음악 검색
+ 마이크 아이콘을 눌러 음악 검색을 시작하세요
+ Google 검색 바
+ 하단에 검색 바
+ 테마형 아이콘
+ 테마형 아이콘을 검색 바에 사용
+ 모서리 각도
+
+ 애플리케이션 검색창 위치
+ 숨김
+ 상단
+ 하단
+
+ 배경화면 스크롤
+ 여러개의 화면을 위한 배경화면 스크롤링 효과
+
+ 배경화면 줌
+ 앱 서랍이나 최근 앱을 사용할 때 배경화면을 줌인하거나 줌아웃
+
+ 상태 표시줄
+
+ 상단 그림자
+
+ %1$s Available | %2$s Total
+ 메모리 정보
+ ZRAM 정보
+
+ 스크린샷
+ 모두 지우기
+ 렌즈
+
+ 짧은 패럴랙스 허용
+ 적은 수의 페이지일 때 배경을 자르기보다 전체 배경을 스크롤하기
+ 단일 페이지 중앙
+ 단일 페이지만 사용할 때 배경을 가운데 정렬하기
+
+ 스크롤바
+ 앱 서랍 옆에 스크롤 바
+
+ 어두운 상태바
+ 홈 화면에서 항상 어두운 상태 표시줄 사용
+
+ 한 눈에 보기
+ 홈 화면 상단에 표시
+
+ 좋은 아침이에요!
+ 안녕하세요!
+ 안녕하세요!
+ 좋은 밤 보내세요.
+ 좋은 하루!
+ 오늘은
+ 이것은
+
+ 알 수 없는 아티스트
+ 지금 재생 중
+ 의
+
+ 지금 재생 중
+ 지금 재생중인 곡 표시하기
+
+ Minimalistic style
+ Switch to modern style
+
+ 랜덤 메시지
+ 무작위 문구를 통해 컴패니언을 더 생생하게 만들기
+
+ - 굿모닝!
+ - 좋은 아침입니다. 일어나세요!
+ - 아름다운 날이네요!
+ - 좋은 하루 보내세요!
+ - 5 분 낮잠은 어떻습니까?
+ - 오늘 하루도 힘냅시다.
+ - 아침은 오늘이란 명작을 그려나갈 깨끗한 캔버스에요.
+ - 매 아침마다 생겨나는 가능성을 만끽하세요.
+ - 새벽이 당신의 앞길을 비춥니다.
+ - 오늘 하루를 무한한 가능성으로 시작합니다.
+ - 모든 일출처럼 항상 새로 시작할 수 있습니다.
+ - 목적을 가지고 일어난다면, 하루를 먼저 정복할 수 있어요.
+ - 당신의 생각이 위대한 성취의 원천입니다.
+ - 세상이 깨어납니다. 당신의 가능성처럼요.
+ - 오늘 모험의 첫 페이지.
+ - 아침 습관이 성공을 위한 발판이 되어줄거에요.
+ - 내일 해가 뜨지 않더라도, 당신의 가능성은 떠오를 거에요.
+ - 신선한 발상과 담대한 행동을 보여줄 때.
+ - 일찍 일어난 새는 벌레를 잡지만, 당신은 꿈을 잡을 수 있어요.
+ - 당신의 꿈을 쫒을 용기를 내봐요.
+ - 위대함이 기다립니다. 밝게 빛나세요!
+ - 시작하기 가장 좋은 순간은 지금입니다.
+
+
+ - 시계바늘 말고, 꿈을 쫓으세요.
+ - 매 순간 새로운 기회가 찾아옵니다.
+ - 목적지만이 아니라, 과정도 즐기세요
+ - 오늘은 선물로 주어진 금과 같습니다. 지금이요.
+ - 당신의 미소가 세상을 바꿉니다.
+ - 인생은 이야기와 같아요. 매 순간을 멋지게 장식하세요!
+ - 일상 속의 행복을 찾아서
+ - 최고는 아직 남아 있어요, 계속 해봐요.
+ - \"만약에\"를 실현할 기회
+ - 오늘을 즐기세요. 지금 당장.
+ - 행복은 매 순간 당신이 결정하는 선택지입니다.
+ - 자신을 가져요, 졸린 오후라도요!
+ - 당신의 행동이 시곗바늘보다 많은 것을 증명합니다.
+ - 당신의 인생은 당신이 만들어갑니다. 멋진 삶을 살아가세요
+ - 꿈은 크게, 일은 열심히, 그리고 절대 포기하지 마세요.
+ - 당신이 생각하는 것 보다 당신은 강합니다.
+ - 모든 좌절은 더 큰 도약을 위한 준비 단계입니다.
+ - 목적지만이 아니라, 과정도 즐기세요
+ - 두려움이 아닌 목표를 쫒아가세요
+ - 당신의 가능성은 무한합니다. 도전하세요.
+
+
+ - 시간에 관계없이, 지금 이 순간을 만끽하세요
+ - 노을이 진 후에, 새로운 기회의 여명이 찾아옵니다.
+ - 삶은 여정입니다. 풍경을 즐기세요.
+ - 당신의 태도가 당신을 만듭니다.
+ - 태양이 질 때도 당신의 이야기는 여전히 써지고 있습니다.
+ - 일상속에서 아름다움을 간직하세요.
+ - 자신을 돌아보고 재정비할 시간입니다.
+ - 시간이 언제라도 즐거움을 고르세요.
+ - 기회는 밤낮을 가리지 않습니다.
+ - 다른 사람이 웃을 수 있는 이유가 되어주세요.
+ - 당신의 가능성에는 한계가 없습니다.
+ - 마법같은 순간은 언제라도 찾아옵니다.
+ - 당신의 인생은 당신이 만들어갑니다. 특별한 삶을 살아가세요.
+ - 호기심을 가지고, 위대해지세요.
+ - 당신이 바라는 세상을 만드는 변화를 일으켜 보세요.
+ - 자신을 믿어요. 멋진 일들을 해낼 수 있어요.
+ - 유일한 한계는 스스로 정하는거에요.
+ - 성공도 첫걸음에서 시작합니다.
+
+
+ - 밤을 즐겨요. 밤은 신비로우니까요.
+ - 이 고요한 저녁 속에서 평온함을 찾으시길 바랍니다.
+ - 인생의 위대한 여정은 어둠속에서 시작할 수도 있습니다.
+ - 해가 지더라도 당신의 여정은 계속됩니다.
+ - 밤이란 캔버스 위에 꿈을 그려요.
+ - 오후의 정적속에서 내면의 평화를 찾습니다.
+ - 별은 가장 어두울 때 가장 밝게 빛납니다.
+ - 하루의 마지막일지라도 큰 꿈을 꾸세요.
+ - 당신의 인생은 기록되길 기다리는 이야기입니다.
+ - 성찰과 새로운 변화를 위한 시간입니다.
+ - 늦은 밤의 고민이 이른 아침의 변화로 이어집니다.
+ - 저녁의 고요속에서 즐거움을 찾으세요.
+ - 밤하늘은 꿈을 위한 넓은 캔버스와 같아요.
+ - 당신의 가능성은 잠들 때를 모릅니다. 열정을 불태우세요.
+ - 가장 어두운 때에도 희망을 버리지 마세요.
+ - 혼란의 한 가운데서도 내면의 침착함을 찾아보세요.
+ - 긍정적인 태도로, 열심히, 이뤄나가요.
+
+
+ - 밤은 기회의 조용한 속삭임입니다.
+ - 밤의 정적속에서 내면의 힘을 찾으세요.
+ - 어둠을 받아들이세요. 그 안에 당신의 꿈을 여는 열쇠가 있습니다.
+ - 세계는 잠들었지만, 당신의 가능성은 맑게 깨어있습니다.
+ - 밤이 늦어도, 당신의 야망은 영향받지 않아요.
+ - 호기심을 유지하고 깨어 있으세요. 밤이 당신을 이끌어 줄 것입니다.
+ - 당신의 꿈을 쫓을 용기를 내봐요.
+ - 위대함은 편한데서 오지 않아요.
+ - 당신은 당신의 운명을 스스로 결정할 힘이 있습니다.
+ - 행복은 선택입니다. 매일 선택하세요.
+ - 인생은 스스로 만들어가는 것입니다. 순간순간을 가치 있게 만드세요.
+ - 집중력을 유지하고, 단호하게, 멈춤 없이 나아가세요.
+ - 자신만의 이야기의 지은이는 자시자신입니다.
+ - 완벽이 아니라 전진을 향해 가세요.
+ - 매일은 새로운 시작입니다. 오늘을 최고의 날로 만드세요.
+ - 가장 나다운 모습으로 타인에게 영감을 전달하세요.
+ - 당신의 태도가 당신의 방향을 결정합니다.
+
+
+ - 무지는 축복입니다!
+ - 다른 ROM을 플래시해야합니까?
+ - 전쟁보다 평화
+ - 오, 무슨 일이에요?
+ - 진짜 매트릭스에 오신걸 환영합니다!
+ - 얼마나 많은 스크린샷을 찍으셨나요?
+ - Open goodness with AlphaDroid
+ - Spread love, not havoc
+ - 우리가 불낸게 아니야!
+ - 좋은 음악을 들을 시간이에요.
+ - \u003C\u003C\u003C\u003C\u003C\u003C\u003C HEAD
+ - 이것을 보려면 AlphaDroid Premium이 필요합니다
+ - 유니콘의 혈통을 기억하십시오
+ - 더 나은 배터리 수명을 즐기세요
+ - 제로에서 시작
+ - 3000만큼 사랑해요.
+ - 스시가 없어도 맛있어요
+ - 오늘 좋은 일을 해 보세요
+ - 이것이 최고의 픽셀 경험이죠, 그렇지 않나요?
+ - 환상이 없습니다. 현실에 오신 것을 환영합니다!
+ - 후원해 주셔서 감사합니다.
+ - Derps를 위한 축제는 없습니다 - 오직 완벽함!
+ - 빌드 봇의 최고의 선택 중 하나
+ - 편집증에 대한 정신
+ - 평안 속에서 깨어나길
+ - 정말 멋진 경험이지 않나요?
+ - 진화는 신화입니다.
+ - 당신이 깜박이는 것입니다, 감자가되지 마십시오?
+ - 무슨 생각을 하고 계신가요?
+ - 익스펙토 페트로눔
+ - 워바러바덥덥
+ - 이겼닭 ....?
+ - rm -rf \/
+ - 사실 프링글스는 감자칩이 아니에요..
+ - 배터리 충전상태를 확인하세요!
+ - 이메일/메시지를 확인하세요
+ - 할 일 목록이 있다면 한번 확인해 보세요.
+ - 홈 설정에서 퀵스페이스를 해제할 수 있습니다.
+ - 후원하기
+ - 최고의 롬.
+ - Telegram에서 만나요!
+ - 3000만큼 사랑해요.
+
+ 일정이나 알림을 찾을 수 없습니다
+
+ 날씨 정보 출처
+ 자동
+ Google 스마트스페이스
+ OmniJaws
+ 날씨 업데이트
+ 현재 위치의 날씨 표시
+ 날씨 정보 서비스 활성화가 필요합니다
+ 흐림
+ 비
+ 맑음
+ 폭풍
+ 눈
+ 바람
+ 안개
+ 현재 위치
+ 현재 위치의 날씨 표시
+ 현재 상태
+ 현재 날씨 요약 표시
+
+ 페이지 표시 자동 숨깁
+ 알림 표시 점 자동 숨기기
+
+ 배경 흐림
+ 최근 앱과 앱 서랍에서 블러 깊이 레벨 설정
+
+ 애플리케이션 정보
+ 출처
+ 시스템
+ 마지막 업데이트
+ 버전
+ 더 보기
+
+ 아이콘 팩
+ 기본값
+ 추가로 설치
+ 사용할 수 있는 앱 스토어가 없습니다
+
+ 슬라이드형 배경화면
+ 메뉴 길게 눌러서 배경화면 빠르게 전환
+ 잠금화면에 적용
+ 잠금 화면에 동일한 배경화면 설정
+
+ 조약돌
+ 항아리
+ 정사각형
+ 모서리가 둥근 정사각형
+ 사다리꼴 사각형
+ 물방울
+ 모서리가 둥근 직사각형
+ 흐림
+ 원통형
+ 꽃
+ 하트
+ 육각형
+ 모서리가 둥근 육각형
+ iOS 스타일
+ 잎사귀
+ 야옹
+ 삼성 스타일
+ 늘인 모양
+
+ 어두운 앱 라벨
+ 앱 서랍의 앱 라벨에 항상 어두운 색 글씨 사용
+
+ 앱 서랍 스타일
+ 일반 (하단 시트)
+ 레거시 전체 화면
+ 목록 스타일
+ OneUI 스타일(페이지)
+
+ 앱 서랍 정렬
+ 알파벳순으로 정렬
+ 설치 날짜순으로 정렬
+ 사용량 순으로 정렬
+
+ 배경 색 사용자 지정
+ 앱 서랍 배경에 사용자 지정 색상 사용
+ 밝은 모드 색
+ 어두운 모드 색
+
+ HSB
+ RGB
+ 색조
+ 채도
+ 명도
+ 빨간색
+ 초록색
+ 파란색
+ HEX
+
+ Pixel 스타일 검색 바
+ 레이어드 배경으로 새롭게 디자인된 검색 바
+ 외부층 불투명도
+
+ 숨겨지거나 보호된 앱
+ 숨겨지거나 보호된 앱을 관리하려면 잠금을 해제하세요.
+ 인증하여 %1$s 열기
+ 로드 중\u2026
+ 앱 접근을 제한하려면 잠금 화면 보안을 설정하세요
+ 도움말
+ 숨겨진 앱과 해당 앱의 위젯은 앱 서랍에서 숨겨집니다.
+ 실행기에서 보호된 앱을 실행하기 위해 인증이 필요합니다.
+
+ Recents layout style
+ 기본값
+ Scale effect
+ Kami style
+ iOS style
+ OxygenOS style
+
+ Lock/Unlock app
+ Unlocked app in recents
+ Locked app in recents
+
+ Scroll vibration
+
diff --git a/res/values-ku-rTR/cr_strings.xml b/res/values-ku-rTR/cr_strings.xml
new file mode 100644
index 00000000000..71a7596fdff
--- /dev/null
+++ b/res/values-ku-rTR/cr_strings.xml
@@ -0,0 +1,440 @@
+
+
+
+
+
+
+ Home settings
+
+ ڕوونمای ماڵەوە
+ دانانی تەختەی گوگڵ ناو و زیاتر
+ ئایکۆنەکان
+ Mezinahiya îkonê û hêj bêtir bicîh bikin
+ پلیکانەی ئەپ
+ سازدانی پلیکانەی ئەپەکانت
+ Demek berê
+ Dîmena pêşandanê ji nû ve nûve bikin
+ هەمەجۆر
+ بژاردەکانی تر
+ Giştî
+ Interface
+ Quickspace
+ Barê lêgerînê
+ Çalakiyên bilez
+
+ بژاردەکانی گەشەپێدەر
+ هەنێ تایبەتمەندی شاراوە کارابکە لەسەر بەرپرسیاریەتی خۆت
+
+ Lock layout
+ Icons and widgets can\'t be added, removed and moved on the homescreen
+ Icons and widgets can be added, removed and moved on the homescreen
+ It\'s not possible to add widgets to the home screen
+
+ Etîketên îkonê li ser sermaseyê
+ Etîketên îkonê di dorê de
+
+ Swipe ji bo gihîştina sepana Google
+
+ Gava ku hûn ji ekrana malê ya sereke rast bişopînin
+
+ Gava ku hûn ji dîmendera malê ya sereke çepê bizivirînin
+
+ Use icon customizations in drawer
+ Follow icon customizations used on home screen
+
+ وەستان
+ Pause %1$s?
+ Notifications for %1$s will be paused
+ App paused
+ %1$s was directly paused from the launcher
+ Pause app
+ Work apps are off
+ Turn off work apps
+ Turn on work apps
+ Your work apps cannot send you notifications, use your battery, or access your location
+ Your work apps cannot receive phone calls or text messages, send you notifications, use your battery, or access your location
+
+ Pêşniyarên
+ Ji bo Hemû Apps & Pêşniyarên ekrana malê
+
+ Restart
+ Restart the launcher manually to apply any pending settings
+ Restarting launcher...
+ Restarting launcher to apply changes...
+ Restarting launcher to update launcher components...
+
+ دووانەکرتە بۆ کوژانەوە
+ Double tap empty space on home screen to turn off the screen
+ Vibrate on double tap to sleep
+ Provide haptic feedback on double tap to sleep gesture
+
+ Swipe to start search
+ Open keyboard whenever app drawer becomes visible
+
+ Mezinahiya îkonê
+
+ Mezinahiya tîpan
+
+ Use two lines for app label
+
+ Bilindahiya rêzê
+
+ Paşxaneya Hotseat
+ Paşnava şefaf li qereqola sepanê zêde bikin
+
+ Nerazîbûna paşperdeyê
+
+ Stroke
+
+ داخستن
+ ئەپەکە داخرا
+
+ Gerr
+ Google Lens
+ Lêgerîna deng
+ AI mode
+ Music search
+ Start music search on pressing mic icon
+ Barê lêgerîna Google
+ Barê lêgerînê di binê dokê de
+ Îkonên Mijara
+ Ji bo barika lêgerînê îkonên mijarê bikar bînin
+ Radyoya goşeyê
+
+ App search bar location
+ Hidden
+ Top
+ Bottom
+
+ Wallpaper scrolling
+ Ji bo gelek ekranan bandorek gerandina dîwaran
+
+ Wallpaper zooming
+ Dema ku kêşan an serîlêdanên dawîn bikar tînin dîwarê dîwarê mezin bikin an jê bikin
+
+ Bara Rewşê
+
+ Siya jorîn
+
+ %1$s available | %2$s
+ Agahdariya bîranînê
+ ZRAM info
+
+ Screenshot
+ Hemî paqij bike
+ Lens
+
+ Allow short parallax
+ Enable full wallpaper scroll effect on smaller numbers of pages instead of cropping the wallpaper
+ Single page center
+ Center wallpaper if only using a single page
+
+ Scrollbar
+ Scrollbar on side of the app drawer
+
+ Dark status bar
+ Always use dark status bar on home screen
+
+ At A Glance
+ Show at the top of your home screen
+
+ بەیانی باش.
+ Good evening.
+ Good afternoon.
+ شەوشاد.
+ Good day.
+ Today is
+ It\'s
+
+ Unknown Artist
+ Now playing
+ By
+
+ Now playing
+ Show the song you\'re playing
+
+ Minimalistic style
+ Switch to modern style
+
+ Random messages
+ Make your companion more lively with random messages
+
+ - بەیانی باش!
+ - بەیانی باش, کاتی ڕۆکە!
+ - چ ڕۆژێکی جوانە!
+ - هیوای ڕۆژێکی خۆش!
+ - چی لەبارەی 5 خولەکی کەمی وەنەوز؟
+ - با ئەم نانە بخۆین.
+ - Mornings are a fresh canvas for your daily masterpiece.
+ - Embrace the sunrise of opportunity each morning.
+ - In the early light, find the power to illuminate your path.
+ - Your day begins with endless possibilities.
+ - Every sunrise is a reminder that you can start anew.
+ - Awake with purpose, conquer the day ahead.
+ - Your thoughts can be the foundation of great achievements.
+ - The world awakens, and so does your potential.
+ - The first chapter in your daily adventure.
+ - Let your morning routine be the launchpad for success.
+ - Sunrise or not, your potential is always on the rise.
+ - It\'s time for fresh ideas and bold actions.
+ - The early bird catches the worm, but you can catch your dreams.
+ - Find the courage to chase your aspirations.
+ - Rise and shine, for greatness awaits.
+ - The best time to start is now.
+
+
+ - Chase your dreams, not the minutes.
+ - In every moment, a new opportunity.
+ - Embrace the journey, not just the destination.
+ - Today is a gift, that\'s why it\'s called the present.
+ - Let your smile change the world.
+ - Life is a story; make each chapter count.
+ - Find joy in the ordinary moments.
+ - The best is yet to come, keep it up.
+ - Your chance to turn \'what if\' into \'what is.\'
+ - Seize the day, starting right now.
+ - Happiness is a choice you can make any time.
+ - Believe in yourself, even on a sleepy afternoon.
+ - Let your actions speak louder than the clock.
+ - Your life is what you make of it; make it amazing.
+ - Dream big, work hard, and never give up.
+ - You are stronger than you think.
+ - Every setback is a setup for a comeback.
+ - Embrace the journey, not just the destination.
+ - Chase your goals, not your fears.
+ - Your potential is endless; unlock it.
+
+
+ - Embrace the present moment, no matter the hour.
+ - In every dusk, find a new dawn of opportunity.
+ - Life is a journey; enjoy the scenery along the way.
+ - Your attitude shapes your reality.
+ - Your story is still being written, even as the sun sets.
+ - Capture the beauty in the ordinary moments.
+ - It\'s time to reflect and reset.
+ - Choose joy, no matter the time on the clock.
+ - Opportunities don\'t clock out in the evening.
+ - Be the reason someone smiles.
+ - Your potential knows no bounds.
+ - Moments of magic can happen at any hour.
+ - Your life is what you make of it; make it extraordinary.
+ - Stay curious, stay grateful.
+ - Be the change you wish to see in the world.
+ - Believe in yourself; you are capable of amazing things.
+ - The only limit is the one you set for yourself.
+ - Success begins with a single step.
+
+
+ - Seize the night, for it holds its own mysteries.
+ - In the quiet of this evening, find serenity.
+ - Life\'s greatest adventures can start in the darkness.
+ - Your journey continues even after the sun has set.
+ - Let the night be your canvas; paint it with dreams.
+ - In the stillness of this evening, find your inner peace.
+ - Stars shine brightest in the darkest hours.
+ - Dream big, even in the late hours of the day.
+ - Your life is a story waiting to be written.
+ - It\'s time for reflection and renewal.
+ - Late night thoughts can lead to early morning revelations.
+ - Find joy in the stillness of this evening.
+ - The night sky is a vast canvas of dreams.
+ - Your potential knows no bedtime; pursue your passions.
+ - Stay hopeful, even in the darkest hours.
+ - In the midst of chaos, find your inner calm.
+ - Stay positive, work hard, make it happen.
+
+
+ - Nights are the quiet whispers of opportunity.
+ - In the stillness of the night, find your inner strength.
+ - Embrace the darkness, for it holds the keys to your dreams.
+ - The world sleeps, but your potential is wide awake.
+ - The night may be late, but your ambitions are timeless.
+ - Stay curious, stay awake, and let nights guide you.
+ - Find the courage to chase your dreams.
+ - Great things never come from comfort zones.
+ - You have the power to create your own destiny.
+ - Happiness is a choice; choose it every day.
+ - Life is what you make it, so make it count.
+ - Stay focused, stay determined, stay unstoppable.
+ - You are the author of your own story.
+ - Strive for progress, not perfection.
+ - Every day is a new beginning; make it a great one.
+ - Inspire others by being your authentic self.
+ - Your attitude determines your direction.
+
+
+ - نەزانی بەختەوەرییە
+ - ئێستا کاتی لێدانی نوێکارییەکی دیکەیە؟
+ - Make peace, not war
+ - سڵاو، چی هەیە؟
+ - Welcome to real Matrix!
+ - چەندێک ڕوونماکەت ئەچرکێنی؟
+ - کردنەوەی goodness لەگەڵ AlphaDroid
+ - Spread love, not havoc
+ - We didn\'t start the fire!
+ - کاتی گوێگرتن لە میوزیکە
+ - \u003C\u003C\u003C\u003C\u003C\u003C\u003C سەر
+ - پێویستت بە AlphaDroid ی نایاب هەیە بۆ بینینی ئەمە
+ - Xêla Unicorn bi bîr bînin
+ - چێژ لە تەمەنی پاتری زیاتر وەرگرە
+ - دەسپێک لە تەواوی سفرەوە
+ - ئێمە 3000 جار خۆشمانەوێی
+ - Delicious even without Sushi
+ - ئەمڕۆ شتێکی جوان بکە
+ - ئەمە باشترین ئەزموونی پیکسڵە، وانییە؟
+ - خەیاڵ نییە، بەخێربێی بۆ واقیع!
+ - سپاس بۆ پاڵپشتیت
+ - Festîvalek ji bo Derps tune - tenê bêkêmasî!
+ - یەکێ لە باشترین هەڵبژێردراوەکانی buildbot
+ - Ji bo Paranoya we sax be
+ - Rising in peace
+ - چ ئەزموونێکی خۆشەویستە، وانییە؟
+ - گەشەکردن ئەفسانەییە، وایە؟
+ - Tiştê ku hûn dişoxilînin hûn in, nebin Potato
+ - بیر لە چی ئەکەیتەوە؟
+ - Expecto Patronum
+ - Wubba Lubba Dub Dub
+ - براوە براوە....؟
+ - rm -rf \/
+ - Pringles aren\'t actually potato chips..
+ - Remember to check device\'s battery level!
+ - Check your email/messages.
+ - Check your to-do list if you have one.
+ - You can disable quickspace via home settings.
+ - ئێمەت خۆشەوێ؟ دیاریمان پێشکەش بکە
+ - باشترین ڕۆم تا ئێستا.
+ - سڵاومان لێبکە لە تێلیگرام
+ - ئێمە 3000 جار خۆشمانەوێی
+
+ Unable to find calendar or clock activity
+
+ Weather provider
+ Auto
+ Google Smartspace
+ OmniJaws
+ Weather update
+ Display current weather update
+ Requires weather service to be enabled
+ Cloudy
+ Rainy
+ Sunny
+ Stormy
+ Snowy
+ Windy
+ Misty
+ Current location
+ Display current weather location
+ Current condition
+ Display current weather condition summary
+
+ Auto-hide page indicator
+ Hide the page indicator dots automatically
+
+ Kûrahiya paşîn
+ Ji bo dawîn û berkêşana sepanê asta kûrahiya şengiyê bicîh bikin
+
+ Application info
+ Kanî
+ Sîstem
+ Rojanekirina dawî
+ Awa
+ Zêde
+
+ دەستە ئایکۆن
+ Destçûnî
+ Zêdetir saz bikin
+ Dikana sepanê ya berdest tune
+
+ Wallpaper carousel
+ Quickly switch wallpapers from the long-press menu
+ Apply to lock screen
+ Set the same wallpaper on the lock screen
+
+ Pebble
+ Vessel
+ Pure square
+ Squircle
+ Tapered rect
+ Teardrop
+ Rounded rect
+ Cloudy
+ Cylindrical
+ Flower
+ Heart
+ Hexagon
+ Rounded hexagon
+ iOS Style
+ Leaf
+ Meow
+ Samsung Style
+ Stretched
+
+ Dark app labels
+ Always use dark text for app labels in the app drawer
+
+ App drawer style
+ Normal (bottom sheet)
+ Legacy fullscreen
+ List style
+ OneUI style (paged)
+
+ App drawer sorting
+ Sort alphabetically
+ Sort by install date
+ Sort by usage
+
+ Custom background color
+ Use a custom color for the app drawer background
+ Light mode color
+ Dark mode color
+
+ HSB
+ RGB
+ Hue
+ Saturation
+ Brightness
+ Red
+ Green
+ Blue
+ HEX
+
+ Pixel style search bar
+ Revamped search bar with layered background
+ Outer layer opacity
+
+ Veşartî & Serlêdanên parastî
+ Ji bo birêvebirina sepanên veşartî û parastî vekin
+ Ji bo vekirina %1$s rastrast bike
+ Barkirin\u2026
+ Ji kerema xwe ekranek kilîtkirî ya ewledar saz bikin da ku gihîştina sepanê sînordar bikin
+ Alîkarî
+ Serlêdanên veşartî û widgetên wan ji berkêşkê têne veşartin
+ Pêdivî ye ku sepanên parastî erêkirinê ji destpêkerê vekin
+
+ Recents layout style
+ Destçûnî
+ Scale effect
+ Kami style
+ iOS style
+ OxygenOS style
+
+ Lock/Unlock app
+ Unlocked app in recents
+ Locked app in recents
+
+ Scroll vibration
+
diff --git a/res/values-night/colors.xml b/res/values-night/colors.xml
index 42a3d91bec5..27f8ab86dc8 100644
--- a/res/values-night/colors.xml
+++ b/res/values-night/colors.xml
@@ -82,4 +82,8 @@
@color/system_secondary_fixed_dim
@color/system_tertiary_fixed
@color/system_tertiary_fixed_dim
+ @android:color/system_neutral1_900
+ @android:color/system_neutral2_200
+
+ #E3E3E3
\ No newline at end of file
diff --git a/res/values-night/cr_colors.xml b/res/values-night/cr_colors.xml
new file mode 100644
index 00000000000..3d4ae345fc6
--- /dev/null
+++ b/res/values-night/cr_colors.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+ @*android:color/system_neutral2_900
+
+
+ @*android:color/system_accent3_100
+ @*android:color/system_accent1_300
+ @*android:color/system_accent1_100
+
\ No newline at end of file
diff --git a/res/values-night/styles.xml b/res/values-night/styles.xml
index 13cf48e9550..26285ba92b4 100644
--- a/res/values-night/styles.xml
+++ b/res/values-night/styles.xml
@@ -49,4 +49,9 @@
- @style/WidgetContainerTheme.Dark
- @color/page_indicator_dot_color_dark
+
+
diff --git a/res/values-nl-rNL/cr_strings.xml b/res/values-nl-rNL/cr_strings.xml
new file mode 100644
index 00000000000..1b92813bff3
--- /dev/null
+++ b/res/values-nl-rNL/cr_strings.xml
@@ -0,0 +1,440 @@
+
+
+
+
+
+
+ Instellingen start
+
+ Startscherm
+ Stel Google Now in en meer
+ Pictogrammen
+ Stel icoon formaat in en meer
+ App lade
+ Personaliseer je app overzicht
+ Recente apps
+ Herzie het overzichtsscherm
+ Overige
+ Overige opties
+ Algemeen
+ Interface
+ Quickspace
+ Zoekbalk
+ Snelle acties
+
+ Opties voor ontwikkelaars
+ Schakel enkele verborgen functies in op eigen risico
+
+ Leg lay-out vast
+ Iconen en widgets kunnen niet worden toegevoegd, verwijderd en verplaatst op het startscherm
+ Iconen en widgets kunnen worden toegevoegd, verwijderd en verplaatst op het startscherm
+ Het is niet mogelijk om widgets toe te voegen aan het startscherm
+
+ Pictogram labels op het bureaublad
+ Pictogram labels in app-overzicht
+
+ Veeg voor toegang tot de Google app
+
+ Wanneer je naar links veegt vanuit het startscherm
+
+ Wanneer je naar rechts veegt vanuit het startscherm
+
+ Gebruik pictogram aanpassingen in app-overzicht
+ Volg pictogram aanpassingen gebruikt op startscherm
+
+ Pauseren
+ Pauzeer%1$s?
+ Meldingen voor %1$s zullen worden gepauzeerd
+ App gepauzeerd
+ %1$s is direct onderbroken van de launcher
+ App pauzeren
+ Werkapps zijn uitgeschakeld
+ Werkapps uitschakelen
+ Werk-apps inschakelen
+ Uw werk-apps kunnen u geen meldingen sturen, uw batterij gebruiken of toegang tot uw locatie
+ Uw werk-apps kunnen geen telefoongesprekken of sms\'jes ontvangen, meldingen sturen, uw batterij gebruiken of toegang tot uw locatie
+
+ Suggesties
+ Voor alle apps & startscherm suggesties
+
+ Herstarten
+ Herstart de launcher handmatig om eventuele instellingen in afwachting toe te passen
+ Launcher herstarten...
+ Launcher opnieuw opstarten om wijzigingen toe te passen...
+ Launcher herstarten om de launcher componenten bij te werken...
+
+ Dubbeltikken voor slaapstand
+ Dubbeltikken op een leeg gebied op het beginscherm om het scherm uit te schakelen
+ Trillen bij dubbeltikken om te slapen
+ Geef haptische feedback bij dubbeltikken voor slaapgebaar
+
+ Veeg om zoeken te starten
+ Open toetsenbord wanneer app drawer zichtbaar wordt
+
+ Pictogram grootte
+
+ Leterrtype grootte
+
+ Gebruik twee regels voor app label
+
+ Rijhoogte
+
+ Hotseat achtergrond
+ Voeg doorschijnende achtergrond toe aan het app dock
+
+ Achtergrond transparantie
+
+ Lijn
+
+ Sluit af
+ App afgesloten
+
+ Zoeken
+ Google Lens
+ Spraakgestuurd zoeken
+ AI modus
+ Muziek zoeken
+ Start muziek zoeken met het indrukken van het microfoon icoon
+ Google zoekbalk
+ Zoekbalk in het onderste dock
+ Thema pictogrammen
+ Gebruik thema pictogrammen voor zoekbalk
+ Hoekradius
+
+ App zoekbalk locatie
+ Verborgen
+ Bovenaan
+ Onderaan
+
+ Wallpaper scrollen
+ Wallpaper scrolleffect voor meerdere schermen
+
+ Achtergrond zoomen
+ Zoom de achtergrond in of uit bij gebruik van drawer of recente apps
+
+ Statusbalk
+
+ Schaduw bovenzijde
+
+ %1$s beschikbaar | %2$s
+ Geheugen informatie
+ ZRAM info
+
+ Schermafbeelding
+ Alles wissen
+ Lens
+
+ Korte parallax toestaan
+ Schakel volledige achtergrond scroll effect in op kleinere aantallen pagina\'s in plaats van de achtergrond bij te snijden
+ Enkele pagina midden
+ Achtergrond centreren bij gebruik van één pagina
+
+ Scrollbalk
+ Scrollbalk aan de zijkant van de app lade
+
+ Donkere statusbalk
+ Gebruik altijd donkere statusbalk op het startscherm
+
+ In een oogopslag
+ Toon aan de bovenkant van je startscherm
+
+ Goedemorgen.
+ Goedenavond.
+ Goedemiddag.
+ Goede nacht.
+ Goede dag.
+ Vandaag is
+ Het is
+
+ Onbekende artiest
+ Nu aan het afspelen
+ Door
+
+ Nu aan het afspelen
+ Het nummer dat je afspeelt weergeven
+
+ Minimalistische stijl
+ Overschakelen naar moderne stijl
+
+ Willekeurige berichten
+ Maak je metgezel levendiger met willekeurige berichten
+
+ - Goedemorgen!
+ - Goedemorgen, tijd om te rocken!
+ - Wat een mooie dag!
+ - Geniet van je dag!
+ - Wat denk je van een 5 minuten dutje?
+ - Laten we dit doen.
+ - Morgens zijn een vers canvas voor je dagelijkse meesterwerk.
+ - Omhels de zonsopgang van kansen elke morgen.
+ - Zoek in het vroege licht de kracht om uw pad te verlichten.
+ - Uw dag begint met eindeloze mogelijkheden.
+ - Elke zonsopgang is een herinnering dat je opnieuw kunt beginnen.
+ - Wakker met het doel, verover de komende dag.
+ - Uw gedachten kunnen de basis vormen van grote successen.
+ - De wereld ontwaakt, net als uw potentieel.
+ - Het eerste hoofdstuk van je dagelijkse avontuur.
+ - Laat uw ochtendroutine de lanceerplatform voor succes zijn.
+ - Zonsopkomst of niet, je potentieel stijgt altijd.
+ - Het is tijd voor frisse ideeën en vette acties.
+ - De vroege vogel vangt de worm, maar jij kunt je dromen vangen.
+ - Vind de moed om je aspiraties te achtervolgen.
+ - Strijd en schijn, voor grootheid wacht.
+ - Het beste moment om te beginnen is nu.
+
+
+ - Achtervolg je dromen, niet de minuten.
+ - In elk moment, een nieuwe opportuniteit.
+ - Omhels de reis, niet alleen de bestemming.
+ - Vandaag is een geschenk, dat is waarom het het heden heet.
+ - Laat je glimlach de wereld veranderen.
+ - Het leven is een verhaal; laat elk hoofdstuk tellen.
+ - Vind plezier in gewone momenten.
+ - Het beste moet nog komen, ga zo door.
+ - Je kans om \'wat als\' te veranderen in \'wat is.\'
+ - Grijp de dag, beginnend meteen.
+ - Geluk is een keuze die je op elk moment kan maken.
+ - Geloof in jezelf, zelfs op een slaperige namiddag.
+ - Laat je acties luider spreken dan de klok.
+ - Je leven is wat je er van maakt; maak het geweldig.
+ - Droom groot, werk hard en geef nooit op.
+ - Je bent sterker dan je denkt.
+ - Elke setback is een setup voor een comeback.
+ - Omhels de reis, niet alleen de bestemming.
+ - Chaseer je doelen, niet je angsten.
+ - Je potentieel is eindeloos; ontgrendel het.
+
+
+ - Omhels het huidige moment, ongeacht het uur.
+ - In elke schemerzone moet er een nieuwe kans komen.
+ - Het leven is een reis, geniet van het landschap onderweg.
+ - Uw houding vormt uw realiteit.
+ - Je verhaal wordt nog steeds geschreven, zelfs als de zon ondergaat.
+ - Leg de schoonheid vast in de gewone momenten.
+ - Het is tijd om na te denken en opnieuw in te stellen.
+ - Kies vreugde, ongeacht de tijd op de klok.
+ - Kansen klokken \'s avonds niet uit.
+ - Wees de reden dat iemand glimlacht.
+ - Uw potentieel kent geen grenzen.
+ - Een magische moment kan ieder uur gebeuren.
+ - Je leven is wat je er van maakt; maak het geweldig.
+ - Blijf nieuwsgierig, blijf dankbaar.
+ - Wees de verandering die je in de wereld wilt zien.
+ - Geloof in jezelf; je bent in staat om goeie dingen te doen.
+ - De enige limiet is degene die je voor jezelf hebt ingesteld.
+ - Succes begint met één enkele stap.
+
+
+ - Grijp de nacht, omdat het zijn eigen mysteries bezit.
+ - In de stilte van de avond is er sereniteit.
+ - De beste avonturen van het leven kunnen beginnen in de duisternis.
+ - Je reis gaat verder, zelfs na de zonsondergang.
+ - Laat de nacht je canvas; verf het met dromen.
+ - In de stille herinnering van deze avond vind je je innerlijke vrede.
+ - Sterren schijnen het sterkst in de donkerste uren.
+ - Dream groot, zelfs in de late uren van de dag.
+ - Je leven is een verhaal dat staat te worden geschreven.
+ - Het is tijd voor reflectie en vernieuwing.
+ - Late nachtelijke gedachten kunnen leiden tot vroege ochtendonthullingen.
+ - Vind plezier in de stille herinnering van vanavond.
+ - De nachtlucht is een uitgestrekt doek van dromen.
+ - Uw potentieel kent geen bedtijd, volg uw passies.
+ - Blijf hoopvol, zelfs in de donkerste uren.
+ - In het midden van de chaos vind je je innerlijke kalmte.
+ - Blijf positief, werk hard, doe het.
+
+
+ - Nachten zijn de stille fluisteraars van kansen.
+ - Vindt je innerlijke kracht in de duisternis van de nacht.
+ - Omhels de duisternis, want het bevat de sleutels tot je dromen.
+ - De wereld slaapt maar je potentieel is breed wakker.
+ - De nacht is misschien laat, maar je ambities zijn tijdloos.
+ - Blijf nieuwsgierig, blijf wakker, en laat je begeleiden met nachten.
+ - Vind de moed om je dromen te achtervolgen.
+ - Geweldige dingen komen nooit uit comfortzones.
+ - Je hebt de macht om je eigen lot te bepalen.
+ - Geluk is een keuze; kies er elke dag over.
+ - Het leven is wat je ervan maakt, dus laat het tellen.
+ - Blijf gefocust, blijf vastberaden, blijf onstuitbaar.
+ - Je bent de auteur van je eigen verhaal.
+ - Streef naar vooruitgang, niet naar perfectie.
+ - Elke dag is een nieuw begin, een fantastisch begin.
+ - Inspireer anderen door zelf je authentiek te zijn.
+ - Uw houding bepaalt uw richting.
+
+
+ - Negeren is Bliss
+ - Is het al tijd om nog een update te flashen?
+ - Vrede maken, geen oorlog
+ - Hey, hoe gaat het?
+ - Welkom bij echte Matrix!
+ - Hoeveel schermafbeeldingen neem je?
+ - Open het goeie met CrDroid
+ - Verspreid de liefde en niet de vernieling
+ - We hebben de strijd niet begonnen!
+ - Tijd voor wat goede muziek
+ - \u003C\u003C\u003C\u003C\u003C\u003C\u003C HEAD
+ - Je hebt AlphaDroid Premium nodig om dit te zien
+ - Onthoud de afkomst van de Unicorn
+ - Geniet van de Xtended batterijduur
+ - Vanaf nul beginnen
+ - We houden van je 3000
+ - Heerlijk zelfs zonder Sushi
+ - Doe iets leuks vandaag
+ - Dit is de beste Pixel Experience, nietwaar?
+ - Geen illusies, welkom bij de realiteit!
+ - Bedankt voor je steun
+ - Geen festival voor Derps - enkel perfectie!
+ - Een van buildbot\'s beste keuze
+ - Gezondheid voor je Paranoia
+ - Rising in peace
+ - Wat een mooie ervaring, nietwaar?
+ - Evolutie is een mythe, toch?
+ - Je bent wat je flasht, dus ben geen Potato
+ - Waar denk je aan?
+ - Expecto Patronum
+ - Wubba Lubba Dub Dub
+ - Winnaar ....?
+ - rm -rf \/
+ - Pringels zijn eigenlijk geen aardappelchips..
+ - Vergeet niet om het batterijniveau van het apparaat te controleren!
+ - Controleer je e-mail/berichten.
+ - Controleer je todo lijst als je er een hebt.
+ - U kunt quickspace uitschakelen via de home instellingen.
+ - Hou je van ons? Stuur ons een biertje
+ - Beste Rom ooit.
+ - Zeg hallo tegen ons op Telegram
+ - We houden van je 3000
+
+ Kan agenda of klok activiteit niet vinden
+
+ Weer aanbieder
+ Automatisch
+ Google Smartspace
+ OmniJaws
+ Weer update
+ Toon huidige weer update
+ Vereist weerservice om aan te zetten
+ Bewolkt
+ Regenachtig
+ Zonnig
+ Stormachtig
+ Sneeuwerig
+ Winderig
+ Mistig
+ Huidige locatie
+ Toon huidige weerlocatie
+ Huidige omstandigheden
+ Overzicht huidige weersomstandigheden weergeven
+
+ Pagina-indicator automatisch verbergen
+ Verberg automatisch de pagina indicator stippen
+
+ Achtergrond vervagen diepte
+ Stel vervaging diepte in voor recente apps en app drawer
+
+ Application Info
+ Bron
+ Systeem
+ Laatste update
+ Versie
+ Meer
+
+ Pictogram pakket
+ Standaard
+ Meer installeren
+ Er is geen beschikbare app store
+
+ Achtergrond carrousel
+ Wissel snel over van achtergronden vanuit het lang drukken menu
+ Toepassen op vergrendelingsscherm
+ Stel dezelfde achtergrond in op het vergrendelingsscherm
+
+ Kiezel
+ Schip
+ Pure vierkant
+ Squircle
+ Tapered rect
+ Traan
+ Afgeronde rect
+ Bewolkt
+ Cilindervormig
+ Bloem
+ Hart
+ Zeskant
+ Afgeronde zeshoek
+ iOS stijl
+ Blad
+ Miauw
+ Samsung Stijl
+ Uitgerekt
+
+ Donkere app labels
+ Gebruik altijd donkere tekst voor app labels in het app-overzicht
+
+ App drawer stijl
+ Normaal (ondervel)
+ Legacy volledig scherm
+ Lijst stijl
+ OneUI stijl (paged)
+
+ App lade sortering
+ Alfabetisch sorteren
+ Sorteren op installatiedatum
+ Sorteren op gebruik
+
+ Aangepaste achtergrondkleur
+ Gebruik een aangepaste kleur voor de achtergrond van de app drawer
+ Lichte modus kleur
+ Donkere modus kleur
+
+ HSB
+ RGB
+ Tint
+ Verzadiging
+ Helderheid
+ Rood
+ Groen
+ Blauw
+ HEX
+
+ Pixel stijl zoekbalk
+ Zoekbalk met gelaagde achtergrond
+ Transparantie buitenste laag
+
+ Verborgen & Beschermde apps
+ Deblokkeer om de verborgen en beschermde apps te beheren
+ Authentificeer om %1$s te openen
+ Laadt\u2026
+ Stel een beveiligd vergrendelscherm in om de toegang van apps te beperken
+ Hulp
+ Verborgen apps en hun widgets zijn verborgen van de lade
+ Beschermde apps vereisen dat authenticatie wordt geopend vanaf de launcher
+
+ Recente lay-out stijl
+ Standaard
+ Schaal effect
+ Kami stijl
+ iOS stijl
+ OxygenOS stijl
+
+ App vergrendelen/ontgrendelen
+ Ontgrendelde app in recente apps
+ Vergrendelde app in recente apps
+
+ Scroll trillen
+
diff --git a/res/values-pl-rPL/alpha_strings.xml b/res/values-pl-rPL/alpha_strings.xml
new file mode 100644
index 00000000000..e8660a28519
--- /dev/null
+++ b/res/values-pl-rPL/alpha_strings.xml
@@ -0,0 +1,44 @@
+
+
+
+
+ Lock/Unlock app
+ Unlocked app in recents
+ Locked app in recents
+
+ Recents layout style
+ Stock
+ Kami Style
+ iOS Style
+ OxygenOS Style
+
+ - Ignorance is Bliss
+ - Make love, not war
+ - Oh hey, what\'s up?
+ - We love you 3000
+ - Thank you for your support
+ - One of the buildbot\'s best picks
+ - Pringles aren\'t actually potato chips..
+ - You can disable quickspace via home settings.
+ - I got nothing ¯\\_(ツ)_/¯
+
+
+ - I never apologize. I\'m sorry but that\'s the way I am.
+ - All my life, I\'ve had one dream: to achieve my many goals.
+ - Good drink... good meat... good God, let\'s eat!
+ - You\'ll have to speak up. I\'m wearing a towel.
+ - Change the channel, Marge...
+ - Roads are just a suggestion Marge, just like pants.
+ - It takes two to lie - one to lie and one to listen.
+ - Shut up, Flanders!
+ - I\'m not normally a praying man, but if you\'re up there, please save me, Superman.
+ - It\'s so simple to be wise... just think of something stupid to say and then don\'t say it.
+ - Stupid sexy Flanders...
+ - I hope I didn\'t brain my damage.
+ - Operator, give me the number for 911!
+ - Don\'t blame me, I voted for Kodos.
+
+
diff --git a/res/values-pl-rPL/cr_strings.xml b/res/values-pl-rPL/cr_strings.xml
new file mode 100644
index 00000000000..70d0dddec0f
--- /dev/null
+++ b/res/values-pl-rPL/cr_strings.xml
@@ -0,0 +1,440 @@
+
+
+
+
+
+
+ Ustawienia ekranu głównego
+
+ Ekran główny
+ Ustaw panel Google Now i inne
+ Ikony
+ Ustaw rozmiar ikon i inne
+ Szuflada aplikacji
+ Dostosuj szufladę aplikacji
+ Ostatnio używane
+ Dostosuj ekran ostatnich aplikacji
+ Pozostałe
+ Inne opcje
+ Ogólne
+ Interfejs
+ Szybka przestrzeń
+ Pasek wyszukiwania
+ Szybkie akcje
+
+ Opcje programistyczne
+ Włącz niektóre ukryte funkcje na własne ryzyko
+
+ Blokada układu ekranu głównego
+ Ikony i widżety nie mogą być dodawane, usuwane ani przenoszone na ekranie głównym
+ Ikony i widżety mogą być dodawane, usuwane oraz przenoszone na ekranie głównym
+ Nie można dodawać widżetów do ekranu głównego
+
+ Etykiety ikon na pulpicie
+ Etykiety ikon w szufladzie aplikacji
+
+ Przesuń, aby uzyskać dostęp do aplikacji Google
+
+ Kiedy przesuniesz w lewo od ekranu głównego
+
+ Kiedy przesuniesz w prawo od ekranu głównego
+
+ Używaj personalizacji ikon w szufladzie
+ Używaj personalizacji ikon z ekranu głównego
+
+ Wstrzymaj
+ Wstrzymać %1$s?
+ Powiadomienia dla %1$s zostaną wstrzymane
+ Aplikacja wstrzymana
+ Aplikacja %1$s została wstrzymana z ekranu głównego
+ Wstrzymaj aplikację
+ Aplikacje służbowe są wyłączone
+ Wyłącz aplikacje służbowe
+ Włącz aplikacje służbowe
+ Aplikacje służbowe nie mogą wysyłać powiadomień, używać baterii ani uzyskiwać dostępu do twojej lokalizacji
+ Twoje aplikacje służbowe nie mogą odbierać połączeń telefonicznych ani wiadomości tekstowych, wysyłać powiadomień, używać baterii lub uzyskiwać dostępu do lokalizacji
+
+ Propozycje
+ Propozycje dotyczące wszystkich aplikacji i ekranu głównego
+
+ Uruchom ponownie
+ Uruchom ponownie launcher ręcznie, aby zastosować wszystkie oczekujące ustawienia
+ Ponowne uruchamianie launchera...
+ Ponowne uruchamianie launchera, aby zastosować zmiany...
+ Ponowne uruchamianie launchera, aby zaktualizować komponenty launchera...
+
+ Dotknij dwukrotnie, aby uśpić
+ Dwukrotnie dotknij pustego miejsca na ekranie głównym, aby wyłączyć ekran
+ Wibruj po dwukrotnym dotknięciu przy usypianiu
+ Używaj reakcji dotykowej na gest podwójnego dotknięcia w celu przełączenia urządzenia w tryb uśpienia
+
+ Przesuń, aby rozpocząć wyszukiwanie
+ Otwieraj klawiaturę, gdy szuflada aplikacji staje się widoczna
+
+ Rozmiar ikon
+
+ Rozmiar czcionki
+
+ Używaj dwóch linii dla etykiety aplikacji
+
+ Wysokość wiersza
+
+ Tło dolnego panelu
+ Dodaje przezroczyste tło dla panelu aplikacji
+
+ Nieprzezroczystość tła
+
+ Obramowanie
+
+ Zatrzymaj
+ Zatrzymano aplikację
+
+ Szukaj
+ Obiektyw Google
+ Wyszukiwanie głosowe
+ Tryb AI
+ Wyszukiwanie muzyki
+ Rozpocznij wyszukiwanie muzyki po dotknięciu ikony mikrofonu
+ Pasek wyszukiwania Google
+ Pasek wyszukiwania w dolnym panelu
+ Ikony tematyczne
+ Użyj ikon tematycznych dla paska wyszukiwania
+ Zaokrąglenie rogów
+
+ Lokalizacja paska wyszukiwania aplikacji
+ Ukryty
+ U góry
+ Na dole
+
+ Przewijane tapety
+ Efekt przewijania tapety dla wielu ekranów
+
+ Powiększanie tapety
+ Powiększaj, pomniejszaj tapetę podczas używania szuflady aplikacji lub widoku ostatnio używanych aplikacji
+
+ Pasek stanu
+
+ Górny cień
+
+ %1$s dostępne | %2$s
+ Informacje o pamięci
+ Informacje o ZRAM
+
+ Zrzut ekranu
+ Wyczyść wszystko
+ Obiektyw
+
+ Zezwalaj na krótki paralaks
+ Włącz efekt przewijania całej tapety na mniejszej liczbie stron, zamiast przycinać tapetę
+ Widok pojedynczej strony
+ Wyśrodkuj tapetę, jeśli używasz tylko jednej strony
+
+ Pasek przewijania
+ Pasek przewijania po boku szuflady aplikacji
+
+ Ciemny pasek stanu
+ Zawsze używaj ciemnego paska stanu na ekranie głównym
+
+ W skrócie
+ Pokaż w górnej części ekranu głównego
+
+ Dzień dobry.
+ Dobry wieczór.
+ Dzień dobry.
+ Dobranoc.
+ Dobrego dnia.
+ Dzisiaj jest
+ Jest
+
+ Nieznany wykonawca
+ Teraz odtwarzane
+ Przez
+
+ Teraz odtwarzane
+ Pokaż odtwarzany utwór
+
+ Styl rozszerzony
+ Przełącz na styl rozszerzony
+
+ Losowe wiadomości
+ Ożyw swojego towarzysza losowymi wiadomościami
+
+ - Dzień dobry!
+ - Dzień dobry, pora na działanie!
+ - Ależ piękny dzień!
+ - Miłego dnia!
+ - A może 5-cio minutowa drzemka?
+ - Zajmijmy się tym.
+ - Poranki to świeże płótno na twoje codzienne mistrzostwo.
+ - Każdego ranka ciesz się wschodem słońca możliwości.
+ - We wczesnym świetle znajdź moc, aby oświetlić swoją ścieżkę.
+ - Twój dzień zaczyna się od nieskończonych się możliwości.
+ - Każdy wschód słońca przypomina, że można zacząć od nowa.
+ - Obudź się z celem, żeby znów zwyciężyć.
+ - Twoje myśli mogą być podstawą wielkich osiągnięć.
+ - Świat budzi się, podobnie jak twój potencjał.
+ - Pierwszy rozdział twojej codziennej przygody.
+ - Niech twoja poranna rutyna będzie wyjściem dla sukcesu.
+ - Wschód słońca lub nie, Twój potencjał zawsze rośnie.
+ - Nadszedł czas na świeże pomysły i odważne działania.
+ - Skowronki łapią robaki, ale Ty możesz złapać swoje marzenia.
+ - Znajdź w sobie odwagę, by podążać za swoimi pragnieniami.
+ - Powstań i zabłyśnij, bo wielkość czeka.
+ - Najlepszym momentem, by zacząć — jest teraz.
+
+
+ - Goń za marzeniami, a nie za minutami.
+ - W każdej chwili pojawia się nowa szansa.
+ - Ciesz się podróżą, a nie tylko jej celem.
+ - Dzisiaj jest prezent, dlatego nazywa się teraźniejszością.
+ - Niech Twój uśmiech zmieni świat.
+ - Życie to historia, ważny jest każdy rozdział.
+ - Znajdź radość w zwykłych momentach.
+ - Najlepsze dopiero nadejdzie, tak trzymaj.
+ - Twoja szansa, aby zmienić \'co by było\' w \'co jest\'
+ - Rozpocznij ten dzień, zaczynając od teraz.
+ - Szczęście jest wyborem, którego możesz dokonywać w każdej chwili.
+ - Uwierz w siebie, nawet w senne popołudnie.
+ - Niech twoje dokonania będą głośniejsze od zegara.
+ - Twoje życie jest tym, co z nim robisz. Uczyń je niesamowitym.
+ - Miej wielkie marzenia, ciężko pracuj i nigdy się nie poddawaj.
+ - Masz większą moc, niż myślisz.
+ - Każde niepowodzenie jest okazją do powrotu.
+ - Ciesz się podróżą, a nie tylko jej celem.
+ - Realizuj swoje cele, a nie obawy.
+ - Twój potencjał jest nieskończony, odblokuj go.
+
+
+ - Ciesz się chwilą obecną, bez względu na godzinę.
+ - W każdym zmierzchu znajdź nowy świt możliwości.
+ - Życie to podróż, ciesz się jej scenerią.
+ - Twoja postawa kształtuje Twoją rzeczywistość.
+ - Twoja historia jest wciąż zapisywana, nawet po zachodzie słońca.
+ - Uchwyć piękno zwykłych chwil.
+ - Nadszedł czas na refleksję i reset.
+ - Wybierz radość, bez względu na godzinę.
+ - Możliwości nie kończą się wieczorem.
+ - Bądź powodem dla czyjegoś uśmiechu.
+ - Twój potencjał nie zna granic.
+ - Momenty magii zjawiają się o każdej godzinie.
+ - Twoje życie jest tym, co z nim robisz. Uczyń je niesamowitym.
+ - Miej ciekawość, czuj wdzięczność.
+ - Bądź zmianą, którą chcesz zobaczyć na świecie.
+ - Uwierz w siebie. Masz dar do robienia niesamowitych rzeczy.
+ - Jedynym limitem jest ten, który sobie ustalasz.
+ - Sukces zaczyna się od pojedynczego kroku.
+
+
+ - Chwytaj noc, bo kryje w sobie własne tajemnice.
+ - W ciszy tego wieczoru odnajdź spokój.
+ - Największe przygody życiowe mogą zaczynać się w ciemności.
+ - Twoja podróż trwa nawet po zachodzie słońca.
+ - Niech noc będzie twoim płótnem; pomaluj je marzeniami.
+ - W ciszy tego wieczoru odnajdź swój wewnętrzny spokój.
+ - Gwiazdy świecą najjaśniej w najciemniejszych godzinach.
+ - Miej wielkie marzenia, nawet w późnych godzinach.
+ - Twoje życie to historia, która czeka na napisanie.
+ - Nadszedł czas na refleksję i odnowę.
+ - Nocne przemyślenia mogą prowadzić do porannych objawień.
+ - Odnajdź radość w ciszy tego wieczoru.
+ - Nocne niebo jest rozległym płótnem marzeń.
+ - Twój potencjał nie zna czasu snu, podążaj za swoimi pasjami.
+ - Miej nadzieję, nawet w najciemniejszych godzinach.
+ - W środku chaosu znajdź swój wewnętrzny spokój.
+ - Bądź pozytywnie nastawiony, ciężko pracuj, spraw, by to się stało.
+
+
+ - Noce to ciche szepty możliwości.
+ - W ciszy nocy odnajdź swoją wewnętrzną siłę.
+ - Obejmij ciemność, ponieważ zawiera ona klucze do twoich marzeń.
+ - Świat śpi, ale twój potencjał jest szeroko obudzony.
+ - Noc może być późna, ale twoje ambicje są ponadczasowe.
+ - Bądź ciekawy, nie śpij i pozwól, aby noce cię prowadziły.
+ - Znajdź w sobie odwagę, by gonić za marzeniami.
+ - Wielkie rzeczy nigdy nie pochodzą ze stref komfortu.
+ - Masz moc tworzenia własnego przeznaczenia.
+ - Szczęście to wybór. Wybieraj je każdego dnia.
+ - Życie jest takie, jakim je uczynisz, więc spraw, by się liczyło.
+ - Skup się, wykaż zdecydowanie, nie daj się powstrzymać.
+ - Jesteś autorem swojej opowieści.
+ - Dąż do postępu, a nie do doskonałości.
+ - Każdy dzień to nowy początek. Spraw, aby był świetny.
+ - Inspiruj innych, będąc w pełni sobą.
+ - Twoja postawa określa twój kierunek.
+
+
+ - Niewiedza jest błogosławieństwem
+ - Czy to czas na kolejną aktualizację?
+ - Czyń miłość, nie wojnę
+ - Hej, co słychać?
+ - Witaj w prawdziwym Matriksie!
+ - Ile zrzutów ekranu robisz?
+ - Spersonalizuj swój system z AlphaDroid
+ - Szerz miłość, nie spustoszenie
+ - Nie rozpoczęliśmy ognia!
+ - Czas na dobrą muzykę
+ - \u003C\u003C\u003C\u003C\u003C\u003C\u003C GŁÓWNY
+ - Potrzebujesz AlphaDroid Premium, aby to zobaczyć
+ - Pamiętaj the Lineage of the Unicorn
+ - Ciesz się wydłużonym czasem baterii
+ - Zaczynamy od początku
+ - Uwielbiamy Cię!
+ - Pyszny nawet bez Sushi
+ - Zrób dziś coś miłego
+ - To najlepsze Pixel Experience, prawda?
+ - Brak iluzji, witaj w rzeczywistości!
+ - Dziękujemy za wsparcie
+ - Brak miejsca dla Derpów - czysta perfekcja!
+ - Jeden z najlepszych wyborów buildbota
+ - Ukojenie twojej paranoi
+ - Wzrastanie w pokoju
+ - Ale cudowne doświadczenie, nieprawdaż?
+ - Ewolucja jest mitem, prawda?
+ - Jesteś tym, co wgrywasz, nie bądź frajerzyną
+ - Co ci chodzi po głowie?
+ - Expecto Patronum
+ - Łabudi dajda łabudi daj
+ - Zwycięzca Wygrywa....?
+ - rm -rf \/
+ - Pringles nie są w rzeczywistości chipsami..
+ - Pamiętaj, aby sprawdzić poziom baterii telefonu!
+ - Sprawdź swój e-mail/wiadomości.
+ - Sprawdź listę zadań, jeśli masz takową.
+ - Możesz wyłączyć szybką przestrzeń za pomocą ustawień ekranu głównego.
+ - Lubisz nas? Kup nam piwo
+ - Najlepszy ROM wszechczasów.
+ - Przywitaj się z nami na Telegramie
+ - Uwielbiamy Cię!
+
+ Nie można odnaleźć kalendarza lub aktywności zegara
+
+ Dostawca pogody
+ Automatycznie
+ Google Smartspace
+ OmniJaws
+ Aktualizacja pogodowa
+ Pokazuj aktualną informację pogodową według lokalizacji
+ Wymaga włączenia usługi pogodowej
+ Pochmurno
+ Deszczowo
+ Czyste niebo
+ Burzowo
+ Śnieżnie
+ Wietrznie
+ Mgliście
+ Obecna lokalizacja
+ Wyświetl bieżącą lokalizację pogody
+ Aktualne warunki
+ Wyświetl podsumowanie warunków pogodowych
+
+ Automatyczne ukrywanie wskaźnika strony
+ Automatycznie ukrywaj kropki wskaźnika strony
+
+ Głębia rozmycia tła
+ Ustawienie poziomu głębi rozmycia dla przypomnień i szuflady aplikacji
+
+ Informacje o aplikacji
+ Źródło
+ System
+ Ostatnia aktualizacja
+ Wersja
+ Więcej
+
+ Pakiet ikon
+ Domyślny
+ Zainstaluj więcej
+ Nie jest dostępny sklep z aplikacjami
+
+ Tapety blokady
+ Szybka zmiana tapet z menu po przytrzymaniu
+ Zastosuj do ekranu blokady
+ Ustaw tę samą tapetę na ekranie blokady
+
+ Kamyk
+ Kadź
+ Czysty kwadrat
+ Kwadratokrąg
+ Przycięty kwadrat
+ Łza
+ Zaokrąglony kwadrat
+ Obłok
+ Cylindryczny
+ Kwiat
+ Serce
+ Sześciokąt
+ Zaokrąglony sześciokąt
+ Styl iOS
+ Liść
+ Miau
+ Samsung
+ Rozciągnięty
+
+ Ciemne etykiety aplikacji
+ W szufladzie aplikacji zawsze używaj ciemnego tekstu dla etykiet aplikacji
+
+ Styl szuflady aplikacji
+ Normalny (dolny arkusz)
+ Klasyczny tryb pełnoekranowy
+ Styl listy
+ Styl OneUI (stronicowy)
+
+ Sortowanie szuflady aplikacji
+ Sortuj alfabetycznie
+ Sortuj według daty instalacji
+ Sortuj według użycia
+
+ Własny kolor tła
+ Używaj własnego koloru dla tła szuflady aplikacji
+ Kolor trybu jasnego
+ Kolor trybu ciemnego
+
+ HSB
+ RGB
+ Odcień
+ Nasycenie
+ Jasność
+ Czerwony
+ Zielony
+ Niebieski
+ HEX
+
+ Pasek wyszukiwania w stylu Pixel
+ Ulepszony pasek wyszukiwania z warstwowym tłem
+ Przezroczystość warstwy zewnętrznej
+
+ Ukryte i chronione aplikacje
+ Odblokuj, aby zarządzać ukrytymi i chronionymi aplikacjami
+ Uwierzytelnij, aby otworzyć %1$s
+ Wczytywanie\u2026
+ Ustaw bezpieczny ekran blokady, aby ograniczyć dostęp do aplikacji
+ Pomoc
+ Ukryte aplikacje oraz ich widżety są ukryte również w szufladzie aplikacji
+ Chronione aplikacje wymagają uwierzytelnienia, aby je otworzyć z ekranu głównego
+
+ Styl układu ostatnich aplikacji
+ Domyślny
+ Efekt skali
+ Styl Kami
+ Styl iOS
+ Styl OxygenOS
+
+ Blokowanie/odblokowywanie aplikacji
+ Odblokowana aplikacja w ostatnich aplikacjach
+ Zablokowana aplikacja w ostatnich aplikacjach
+
+ Wibracje przewijania
+
diff --git a/res/values-pt-rBR/alpha_strings.xml b/res/values-pt-rBR/alpha_strings.xml
new file mode 100644
index 00000000000..8c43e8ca0de
--- /dev/null
+++ b/res/values-pt-rBR/alpha_strings.xml
@@ -0,0 +1,44 @@
+
+
+
+
+ Bloquear/Desbloquear app
+ Aplicativo desbloqueado nos recentes
+ Bloqueado app nos recentes
+
+ Estilo de layout recentes
+ Padrão
+ Estilo Kami
+ Estilo iOS
+ Estilo OxygenOS
+
+ - É melhor não saber
+ - Faça amor, não faça guerra
+ - E aí, beleza?
+ - Te amamos 3000
+ - Obrigado pelo seu apoio
+ - Uma das melhores escolhas do buildbot
+ - Pringles na verdade não são batatas fritas...
+ - Você pode desativar o Quickspace nas configurações da tela inicial.
+ - Não sei de nada ¯\\_(ツ)_/¯
+
+
+ - Eu nunca peço desculpas. Desculpa, mas é assim que eu sou.
+ - A minha vida toda, eu tive um sonho: alcançar meus muitos objetivos.
+ - Boa bebida... boa carne... meu Deus, bora comer!
+ - Vai ter que falar mais alto. Tô de toalha aqui.
+ - Muda o canal, Marge...
+ - Estradas são só uma sugestão, Marge, assim como calças.
+ - São precisos dois para mentir — um para mentir e outro para ouvir.
+ - Cala a boca, Flanders!
+ - Eu geralmente não sou de rezar, mas se estiver aí em cima, por favor, me salve, Superman.
+ - É tão simples ser sábio... basta pensar em algo idiota para falar e aí não falar.
+ - Estúpido sexy Flanders...
+ - Espero não ter estragado meu cérebro.
+ - Operadora, me passe o número do 190!
+ - Não me culpe, eu votei no Kodos.
+
+
diff --git a/res/values-pt-rBR/cr_strings.xml b/res/values-pt-rBR/cr_strings.xml
new file mode 100644
index 00000000000..72f595dd924
--- /dev/null
+++ b/res/values-pt-rBR/cr_strings.xml
@@ -0,0 +1,440 @@
+
+
+
+
+
+
+ Configurações da tela inicial
+
+ Tela inicial
+ Definir o Feed do Google e mais
+ Ícones
+ Definir tamanho do ícone e muito mais
+ Gaveta de aplicativos
+ Customizar sua gaveta de aplicativos
+ Recentes
+ Reformular a tela inicial
+ Diversos
+ Outras opções
+ Geral
+ Interface
+ Acesso rápido
+ Barra de pesquisa
+ Ações rápidas
+
+ Opções de desenvolvedor
+ Ativar alguns recursos ocultos por sua conta e risco
+
+ Bloquear edições na tela inicial
+ Ícones e widgets não podem ser adicionados, removidos e movidos na tela inicial
+ Ícones e widgets podem ser adicionados, removidos e movidos na tela inicial
+ Não é possível adicionar widgets à tela inicial
+
+ Rótulos dos ícones na tela inicial
+ Rótulos dos ícones na gaveta de aplicativos
+
+ Deslize para acessar o feed do Google
+
+ Ao deslizar para a esquerda na tela inicial
+
+ Ao deslizar para a direita na tela inicial
+
+ Use ícones customizados na gaveta
+ Seguir customização usado na tela inicial
+
+ Pausar
+ Pausar %1$s?
+ As notificações de %1$s serão pausadas
+ App pausado
+ %1$s foi pausado diretamente do launcher
+ Pausar app
+ Apps de trabalho desativados
+ Desativar aplicativos de trabalho
+ Ativar aplicativos de trabalho
+ Apps de trabalho não podem te enviar notificações, usar sua bateria ou acessar sua localização
+ Seus aplicativos de trabalho não podem receber chamadas telefônicas ou mensagens de texto, enviar-lhe notificações, usar sua bateria ou acessar sua localização
+
+ Sugestões
+ Sugestões de apps na tela inicial e gaveta de aplicativos
+
+ Reiniciar
+ Reiniciar manualmente o launcher para aplicar qualquer configuração pendente
+ Reiniciando o launcher...
+ Reiniciando o launcher para aplicar as alterações...
+ Reiniciando o launcher para atualizar seus componentes...
+
+ Duplo toque para deligar a tela
+ Double tap empty space on home screen to turn off the screen
+ Vibrate on double tap to sleep
+ Provide haptic feedback on double tap to sleep gesture
+
+ Deslize para iniciar busca
+ Open keyboard whenever app drawer becomes visible
+
+ Tamanho do ícone
+
+ Tamanho da fonte
+
+ Máximo de linhas para o rótulo do aplicativo
+
+ Altura da linha
+
+ Fundo desfocado
+ Desfocar o fundo do dock de aplicativos
+
+ Opacidade do fundo
+
+ Contorno
+
+ Fechar aplicativo
+ Aplicativo encerrado
+
+ Pesquisar
+ Google Lens
+ Pesquisa por voz
+ Modo IA
+ Pesquisa de música
+ Pesquisar músicas ao pressionar o ícone do microfone
+ Barra de pesquisa do Google
+ Barra de pesquisa na dock
+ Ícones temáticos
+ Usar ícones temáticos para a barra de pesquisa
+ Raio das bordas
+
+ App search bar location
+ Hidden
+ Top
+ Bottom
+
+ Rolagem do papel de parede
+ Rolar papel de parede ao alternar entre as telas
+
+ Zoom do papel de parede
+ Ampliar ou diminuir o papel de parede quando estiver usando a gaveta ou aplicativos recentes
+
+ Barra de Status
+
+ Sombra superior
+
+ %1$s Disponível - %2$s Total
+ Informações da memória
+ Informação do ZRAM
+
+ Capturar tela
+ Limpar tudo
+ Lens
+
+ Permitir sobreposição curta
+ Ativar o efeito de rolagem de papel de parede completo em um número menor de páginas, em vez de cortar o papel de parede
+ Página única centralizada
+ Centraliza o papel de parede ao usar apenas uma página
+
+ Barra de rolagem
+ Barra de rolagem na lateral da gaveta de aplicativos
+
+ Barra de status escura
+ Always use dark status bar on home screen
+
+ Resumo
+ Mostrar no topo da tela inicial
+
+ Bom dia.
+ Boa noite.
+ Boa tarde.
+ Boa noite.
+ Bom dia.
+ Hoje é
+ São
+
+ Artista desconhecido
+ Em reprodução
+ Por
+
+ Em reprodução
+ Mostrar a música que você está tocando
+
+ Estilo minimalista
+ Mostra as informações no canto da tela, não no centro
+
+ Mensagens aleatórias
+ Frases e mensagens aleatórias para animar seu dia
+
+ - Bom dia!
+ - Bom dia, hora de botar pra quebrar!
+ - Que dia lindo!
+ - Tenha um ótimo dia!
+ - Que tal um pequeno cochilo de 5 minutos?
+ - Bora colocar a mão na massa.
+ - As manhãs são uma nova tela para sua obra-prima diária.
+ - Abrace o nascer do sol da oportunidade todas as manhãs.
+ - Na luz da manhã, encontre o poder para iluminar o seu caminho.
+ - Seu dia começa com infinitas possibilidades.
+ - Todo amanhecer é um lembrete de que você pode começar de novo.
+ - Acorde com propósito, conquiste o dia seguinte.
+ - Os vossos pensamentos podem constituir a base de grandes conquistas.
+ - O mundo desperta, e o seu potencial também.
+ - O primeiro capítulo da sua aventura diária.
+ - Deixe sua manhã ser a plataforma lançadora para o sucesso.
+ - Amanhecer ou não, seu potencial está sempre crescendo.
+ - É hora de ideias novas e ações ousadas.
+ - Quem chega cedo se dá bem, e você pode conquistar seus sonhos.
+ - Encontre coragem para perseguir suas ambições.
+ - Acorde e brilhe, grandes coisas te esperam.
+ - A hora certa de começar é agora.
+
+
+ - Persiga os seus sonhos, não os minutos.
+ - Em cada momento, uma nova oportunidade.
+ - Abrace a viagem, não apenas o destino.
+ - Hoje é um presente, é por isso que é chamado de presente.
+ - Deixe seu sorriso mudar o mundo.
+ - A vida é uma história; faça cada capítulo contar.
+ - Encontre alegria nos momentos comuns.
+ - O melhor ainda está por vir, continue assim.
+ - A sua oportunidade de transformar \"quanto se\" em \"o que é\"
+ - Aproveite o dia, começando agora.
+ - A felicidade é uma escolha que podes fazer a qualquer momento.
+ - Acredite em si mesmo, mesmo em uma tarde sonolenta.
+ - Deixe suas ações falarem mais alto que o relógio.
+ - A tua vida é o que tu fazes dela; torna-a incrível.
+ - O sonho é grande, trabalha duro e nunca desista.
+ - Você é mais forte do que pensa.
+ - Cada contratempo é uma configuração para o retorno.
+ - Abrace a viagem, não apenas o destino.
+ - Acompanhe seus objetivos, não seus medos.
+ - Seu potencial é infinito; desbloqueá-lo.
+
+
+ - Abrace o momento presente, não importa a hora.
+ - Em cada crepúsculo, encontre uma nova aurora de oportunidades.
+ - A vida é uma viagem; curta a paisagem pelo caminho.
+ - Sua atitude molda tua realidade.
+ - Sua história ainda está sendo escrita, mesmo quando o sol se põe.
+ - Capture a beleza nos momentos comuns.
+ - É hora de refletir e redefinir.
+ - Escolha a alegria, não importa o horário no relógio.
+ - As oportunidades não chegam a uma hora à noite.
+ - Seja por que alguém sorri.
+ - Seu potencial não conhece limites.
+ - Momentos mágicos podem acontecer a qualquer hora.
+ - A sua vida é o que você faz dela, torne ela extraordinária.
+ - Fique curioso, fique grato.
+ - Seja a mudança que deseja enxergar no mundo.
+ - Acredite em si mesmo; você é capaz de coisas incríveis.
+ - O único limite é aquele que você definiu para si mesmo.
+ - O sucesso começa com um único passo.
+
+
+ - Aproveite a noite, pois tem seus próprios mistérios.
+ - No silêncio desta noite, encontre serenidade.
+ - As maiores aventuras da vida podem começar na escuridão.
+ - Sua jornada continua mesmo após o sol se pôr.
+ - Deixe a noite ser sua tela, pinte ela com sonhos.
+ - Na quietude desta noite, encontre sua paz interior.
+ - Estrelas brilham mais forte nas horas mais escuras.
+ - Sonhe grande, mesmo nas horas mais tardias do dia.
+ - Sua vida é uma história à espera de ser escrita.
+ - É hora para reflexão e renovação.
+ - Pensamentos tarde da noite podem levar à revelações pela manhã.
+ - Encontre alegria na quietude desta noite.
+ - O céu noturno é uma vasta tela de sonhos.
+ - Seu potencial não conhece a hora de deitar; siga as suas paixões.
+ - Mantenha-se esperançado, mesmo nas horas mais escuras.
+ - No meio do caos, encontre sua calma interior.
+ - Fique positivo, trabalhe duro, faça isso acontecer.
+
+
+ - As noites são os sussurros tranquilos da oportunidade.
+ - Na quietude da noite, encontre sua força interior.
+ - Abrace a escuridão, pois ela contém as chaves dos seus sonhos.
+ - O mundo dorme, mas o seu potencial está bem acordado.
+ - A noite pode se atrasar, mas suas ambições não têm hora.
+ - Fique curioso, fique acordado, e deixe as noites guiá-lo.
+ - Encontre coragem para seguir seus sonhos.
+ - Grandes coisas nunca vêm de zonas de conforto.
+ - Você tem o poder de criar seu próprio destino.
+ - A felicidade é uma escolha; escolha ela todos os dias.
+ - A vida é o que você faz, então faça valer a pena.
+ - Mantenha-se focado, permaneça determinado, permaneça imparável.
+ - Você é o autor de sua própria história.
+ - Esforço pelo progresso, não pela perfeição.
+ - Todo dia é um novo começo; faça dele grande.
+ - Inspire os outros sendo você mesmo autêntico.
+ - Sua atitude determina a sua direção.
+
+
+ - Ignorância é uma benção
+ - É hora de flashear outra atualização novamente?
+ - Faça a paz, não a guerra
+ - Ei, e aí?
+ - Bem-vindo à Matrix real!
+ - Quantas capturas de tela você tira?
+ - Torne-se um deus com a AlphaDroid
+ - Espalhe amor, não ódio
+ - Não iniciamos o fogo!
+ - Hora de uma boa música
+ - \u003C\u003C\u003C\u003C\u003C\u003C\u003C CABEÇALHO
+ - Você precisa da AlphaDroid Premium para ver isso
+ - Lembre-se da linhagem do unicornio
+ - Aproveite sua bateria extendida
+ - A partir do ponto zero
+ - Te amamos 3000
+ - Delicioso mesmo sem Sushi
+ - Faça algo legal hoje
+ - Isto é a melhor Pixel Experience, não é mesmo?
+ - Sem ilusões, bem-vindo à realidade!
+ - Obrigado pelo seu suporte
+ - Sem festival de Derps, somente perfeição!
+ - Uma das melhores escolhas do buildbot
+ - Sanidade para sua paranóia
+ - Subindo em paz
+ - Que experiência adorável, não é?
+ - A evolução é um mito, certo?
+ - Você é o que você pisca, não seja Batata
+ - No que você está pensando?
+ - Expecto Patronum
+ - Wubba Lubba Dub Dub
+ - Vencedor Vencedor ....?
+ - rm -rf \/
+ - Pringles não são realmente batatas fritas..
+ - Lembre-se de verificar o nível da bateria do dispositivo!
+ - Verifique seu e-mail/mensagens.
+ - Verifique sua lista de tarefas, se você tiver uma.
+ - Você pode desativar o Acesso Rápido através das configurações de início.
+ - Gostando da gente? Nos mande uma cerveja
+ - Melhor rom que existe.
+ - Nos dê um oi no Telegram
+ - Te amamos 3000
+
+ Não foi possível encontrar o calendário ou a atividade do relógio
+
+ Weather provider
+ Auto
+ Google Smartspace
+ OmniJaws
+ Atualizações climáticas
+ Exibir a atualização atual do tempo
+ Requer que o serviço de clima esteja habilitado
+ Nublado
+ Chuvoso
+ Céu Limpo
+ Tempestuoso
+ Neve
+ Ventania
+ Nevoeiro
+ Localização atual
+ Exibir localização do tempo atual
+ Condição atual
+ Exibir o resumo atual da condição climática
+
+ Auto-hide page indicator
+ Hide the page indicator dots automatically
+
+ Desfoque de fundo
+ Definir a intensidade do desfoque para os aplicativos recentes e gaveta de aplicativos
+
+ Informação do aplicativo
+ Fonte
+ Sistema
+ Última atualização
+ Versão
+ Mais
+
+ Pacote de ícones
+ Padrão
+ Instalar mais
+ Não há uma loja de aplicativos disponível
+
+ Carrossel de papel de parede
+ Trocar rapidamente papel de parede por segurar o botão menu
+ Aplicar a tela de bloqueio
+ Usar o mesmo papel de parede na tela de bloqueio
+
+ Pebble
+ Vessel
+ Pure square
+ Squircle
+ Tapered rect
+ Gota
+ Rounded rect
+ Nublado
+ Cilíndrico
+ Flor
+ Coração
+ Hexágono
+ Rounded hexagon
+ Estilo iOS
+ Folha
+ Meow
+ Estilo Samsung
+ Esticado
+
+ Dark app labels
+ Always use dark text for app labels in the app drawer
+
+ App drawer style
+ Normal (bottom sheet)
+ Legacy fullscreen
+ List style
+ OneUI style (paged)
+
+ App drawer sorting
+ Sort alphabetically
+ Sort by install date
+ Sort by usage
+
+ Cor de fundo personalizada
+ Usar uma cor personalizada para o fundo da gaveta de aplicativos
+ Cor no modo claro
+ Cor no modo escuro
+
+ HSB
+ RGB
+ Matiz
+ Saturação
+ Brilho
+ Vermelho
+ Verde
+ Azul
+ HEX
+
+ Pixel style search bar
+ Revamped search bar with layered background
+ Outer layer opacity
+
+ Aplicativos ocultos e protegidos
+ Desbloquear para gerenciar os aplicativos ocultos e protegidos
+ Autenticar para abrir %1$s
+ Carregando…
+ Configure uma tela de bloqueio para restringir o acesso aos aplicativos
+ Ajuda
+ Aplicativos ocultos e seus widgets estão escondidos do menu de apps
+ Os aplicativos protegidos requerem autenticação para serem executados
+
+ Recents layout style
+ Padrão
+ Scale effect
+ Kami style
+ iOS style
+ OxygenOS style
+
+ Lock/Unlock app
+ Unlocked app in recents
+ Locked app in recents
+
+ Scroll vibration
+
diff --git a/res/values-pt-rPT/alpha_strings.xml b/res/values-pt-rPT/alpha_strings.xml
new file mode 100644
index 00000000000..a6957f1ba00
--- /dev/null
+++ b/res/values-pt-rPT/alpha_strings.xml
@@ -0,0 +1,44 @@
+
+
+
+
+ Bloquear/Desbloquear
+ Aplicação desbloqueada
+ Aplicação bloqueada
+
+ Estilo do layout recentes
+ Padrão
+ Estilo Kami
+ Estilo iOS
+ Estilo OxigenOS
+
+ - Ignorância é felicidade
+ - Façam amor, não a guerra
+ - Olá! Tudo bem?
+ - Nós gostamos 3000 de ti
+ - Obrigado pelo teu apoio!
+ - Uma das escolhas recomendadas pelo buildbot
+ - As Pringles não são batatas fritas..
+ - Pode desativar o espaço rápido nas definições.
+ - Népias ¯\\_(ツ)_/¯
+
+
+ - Eu nunca peço desculpa. Desculpem, mas é assim que eu sou.
+ - Durante toda a minha vida, tive um sonho: realizar os meus muitos objetivos.
+ - Boa bebida... boa carne... bom Deus, vamos comer!
+ - Tens de falar mais alto. Eu estou de toalha.
+ - Muda o canal, Marge...
+ - Estradas são apenas uma sugestão, Marge. É como usar calças.
+ - São precisos dois para mentir - um para mentir e outro para ouvir.
+ - Cala a boca, Flanders!
+ - Normalmente eu não sou um homem crente, mas se estás aí em cima, por favor salva-me, super-homem.
+ - É muito simples ser sábio... basta pensar em algo estúpido para dizer e não dizer.
+ - Estúpido Flanders sensual...
+ - Espero não tem cerebrado os meus danos.
+ - Operadora, dê-me o número do 112!
+ - Não me culpem a mim, eu votei no Kodos.
+
+
diff --git a/res/values-pt-rPT/cr_strings.xml b/res/values-pt-rPT/cr_strings.xml
new file mode 100644
index 00000000000..433bb1d4b19
--- /dev/null
+++ b/res/values-pt-rPT/cr_strings.xml
@@ -0,0 +1,440 @@
+
+
+
+
+
+
+ Definições do ecrã inicial
+
+ Ecrã inicial
+ Definir o painel do Google Now e mais
+ Ícones
+ Definir o tamanho dos ícones e mais
+ Gaveta de aplicações
+ Personalize a sua gaveta de aplicações
+ Recentes
+ Personalizar o ecrã de recentes
+ Diversos
+ Outras opções
+ Geral
+ Interface
+ Espaço rápido
+ Barra de pesquisa
+ Ações rápidas
+
+ Opções de programador
+ Ativar alguns recursos ocultos por seu próprio risco
+
+ Bloquear disposição
+ Ícones e widgets não podem ser adicionados, removidos ou movidos no ecrã inicial
+ Ícones e widgets podem ser adicionados, removidos e movidos no ecrã inicial
+ Não é possível adicionar widgets ao ecrã inicial
+
+ Rótulos dos ícones no ecrã principal
+ Rótulos dos ícones na gaveta de apps
+
+ Deslizar para aceder à app Google
+
+ Ao deslizar para a esquerda no ecrã principal
+
+ Ao deslizar para a direita no ecrã principal
+
+ Usar as personalizações dos ícones na gaveta
+ Seguir as personalizações dos ícones usados no ecrã inicial
+
+ Pausar
+ Pausar %1$s?
+ As notificações de %1$s serão pausadas
+ App pausada
+ %1$s foi pausada diretamente a partir do launcher
+ Pausar app
+ Aplicações de trabalho desativados
+ Desligar aplicações de trabalho
+ Ativar aplicações de trabalho
+ As apps de trabalho não podem enviar-lhe notificações, utilizar a bateria ou aceder à sua localização
+ As suas aplicações de trabalho não podem receber chamadas telefónicas ou mensagens de texto, enviar-lhe notificações, utilizar a sua bateria ou aceder à sua localização
+
+ Sugestões
+ Para todas as apps e ecrã principal
+
+ Reiniciar
+ Reinicie o launcher manualmente para aplicar qualquer configuração pendente
+ A reiniciar o launcher...
+ A reiniciar o launcher para aplicar as alterações...
+ A reiniciar o launcher para atualizar os componentes do launcher...
+
+ Toque duplo para desligar ecrã
+ Toque duas vezes numa área vazia do ecrã inicial para desligar o ecrã
+ Vibrar ao tocar duas vezes para dormir
+ Prover resposta táctil ao fazer o gesto de tocar duas vezes para dormir
+
+ Deslize para começar busca
+ Abrir teclado assim que a gaveta de aplicações ficar visível
+
+ Tamanho dos ícones
+
+ Tamanho da letra
+
+ Utilizar duas linhas para a etiqueta da aplicação
+
+ Altura da linha
+
+ Fundo da doca
+ Mostrar fundo translúcido na doca de apps
+
+ Opacidade do fundo
+
+ Avc
+
+ Terminar
+ App terminada
+
+ Pesquisar
+ Lentes do Google
+ Pesquisa por voz
+ Modo IA
+ Pesquisa de música
+ Iniciar pesquisa de música ao pressionar o ícone do microfone
+ Barra de pesquisa do Google
+ Barra de pesquisa na doca inferior
+ Ícones temáticos
+ Usar ícones temáticos para a barra de pesquisa
+ Raio do canto
+
+ Localização da barra de pesquisa de aplicações
+ Oculto
+ Topo
+ Fundo
+
+ Rolar papel de parede
+ Efeito de rolagem do papel de parede para vários ecrãs
+
+ Zoom do papel de parede
+ Ampliar ou diminuir o papel de parede na gaveta de apps e no ecrã de recentes
+
+ Barra de estado
+
+ Sombra superior
+
+ %1$s Disponível | %2$s Total
+ Informações da memória
+ Informação do ZRAM
+
+ Capturar ecrã
+ Limpar tudo
+ Lens
+
+ Permitir parallax curto
+ Ativar o efeito de rolagem do papel de parede completo em números menores de páginas ao invés de cortar o papel de parede
+ Vista de uma só página
+ Centralizar papel de parede se apenas usando uma única página
+
+ Barra de deslocamento
+ Barra de deslocamento na parte lateral da gaveta de aplicações
+
+ Barra de estado escura
+ Usar sempre a barra de status escura no ecrã inicial
+
+ Resumo
+ Mostrado no topo do ecrã inicial
+
+ Bom dia.
+ Boa noite.
+ Boa tarde.
+ Boa noite.
+ Bom dia.
+ Hoje é
+ É
+
+ Artista desconhecido
+ Em reprodução
+ Por
+
+ Em reprodução
+ Mostrar a música que está a tocar
+
+ Estilo minimalista
+ Mudar para o estilo moderno
+
+ Mensagens aleatórias
+ Uma companhia mais animada com mensagens aleatórias
+
+ - Bom dia!
+ - Bom dia, hora de ir à luta!
+ - Que lindo dia!
+ - Tenha um bom dia!
+ - Que tal uma pequena soneca de 5 minutos?
+ - Vamos lá pôr mãos à obra.
+ - As manhãs são uma nova tela para a sua obra-prima diária.
+ - Abrace o nascer do sol da oportunidade todas as manhãs.
+ - Na luz da manhã, encontre o poder para iluminar o seu caminho.
+ - O seu dia começa com infinitas possibilidades.
+ - Cada nascer do sol é um lembrete de que pode começar de novo.
+ - Acorde com propósito, conquiste o dia que tem pela frente.
+ - Os vossos pensamentos podem constituir a base de grandes conquistas.
+ - O mundo desperta, assim como o seu potencial.
+ - O primeiro capítulo da sua aventura diária.
+ - Deixe a sua rotina matinal ser a plataforma de lançamento para o sucesso.
+ - Amanhecer ou não, seu potencial está sempre crescendo.
+ - É hora de ideias novas e ações ousadas.
+ - O pássaro madrugador apanha a minhoca, mas tu podes apanhar os teus sonhos.
+ - Encontre coragem para perseguir suas aspirações.
+ - Levanta-te e brilha, porque a grandeza espera-te.
+ - A melhor hora para começar é agora.
+
+
+ - Persiga os seus sonhos, não os minutos.
+ - Em cada momento, uma nova oportunidade.
+ - Abrace a viagem, não apenas o destino.
+ - Hoje é especial, é por isso que é chamado presente.
+ - Deixe seu sorriso mudar o mundo.
+ - A vida é uma história; faça cada capítulo contar.
+ - Encontre alegria nos momentos comuns.
+ - O melhor ainda está por vir, continue assim.
+ - A sua oportunidade de transformar \"e se\" em \"o que é\"
+ - Aproveite o dia, começando agora.
+ - A felicidade é uma escolha que você pode fazer a qualquer momento.
+ - Acredite em si mesmo, mesmo em uma tarde sonolenta.
+ - Deixe que as suas ações falem mais alto do que o relógio.
+ - A tua vida é o que tu fazes dela; torna-a fantástica.
+ - Sonhar em grande, trabalhar arduamente e nunca desistir.
+ - É mais forte do que pensa.
+ - Cada contratempo é uma preparação para um regresso.
+ - Abrace a viagem, não apenas o destino.
+ - Persigam os vossos objetivos, não os vossos medos.
+ - O seu potencial é infinito; desbloqueie-o.
+
+
+ - Abrace o momento presente, independentemente da hora.
+ - Em cada crepúsculo, encontra um novo amanhecer de oportunidades.
+ - A vida é uma viagem; aproveite a paisagem ao longo do caminho.
+ - A sua atitude molda a sua realidade.
+ - A sua história continua a ser escrita, mesmo quando o sol se põe.
+ - Capte a beleza dos momentos comuns.
+ - É tempo de refletir e de reiniciar.
+ - Escolher a alegria, independentemente do tempo no relógio.
+ - As oportunidades não se esgotam ao fim do dia.
+ - Seja a razão do sorriso de alguém.
+ - O seu potencial não tem limites.
+ - Os momentos de magia podem acontecer a qualquer hora.
+ - A vida é o que faz dela; torne-a extraordinária.
+ - Mantenha-se curioso e agradecido.
+ - Seja a mudança que deseja ver no mundo.
+ - Acredite em si próprio; é capaz de fazer coisas fantásticas.
+ - O único limite é aquele que estabelecemos para nós próprios.
+ - O sucesso começa com um único passo.
+
+
+ - Aproveite a noite, pois ela guarda os seus próprios mistérios.
+ - No silêncio desta noite, encontre serenidade.
+ - As maiores aventuras da vida podem começar na escuridão.
+ - A sua jornada continua mesmo após o sol se ter posto.
+ - Deixe que a noite seja a sua tela; pinte-a com sonhos.
+ - Na quietude desta noite, encontre a sua paz interior.
+ - As estrelas brilham mais nas horas mais escuras.
+ - Sonhe em grande, mesmo nas últimas horas do dia.
+ - A sua vida é uma história à espera de ser escrita.
+ - É tempo de reflexão e de renovação.
+ - Os pensamentos noturnos podem levar a revelações matinais.
+ - Encontre alegria na quietude desta noite.
+ - O céu noturno é uma vasta tela de sonhos.
+ - O seu potencial não conhece hora de dormir; persiga as suas paixões.
+ - Mantenha a esperança, mesmo nas horas mais sombrias.
+ - No meio do caos, encontre a sua calma interior.
+ - Mantenha-se positivo, trabalhe arduamente e faça acontecer.
+
+
+ - As noites são os sussurros silenciosos da oportunidade.
+ - Na quietude da noite, encontre a sua força interior.
+ - Abrace a escuridão, pois ela contém as chaves dos seus sonhos.
+ - O mundo dorme, mas o seu potencial está bem desperto.
+ - A noite pode ser tardia, mas as suas ambições são intemporais.
+ - Mantenha-se curioso, desperto e deixe que as noites o guie.
+ - Encontre a coragem para perseguir os seus sonhos.
+ - As grandes coisas nunca vêm de zonas de conforto.
+ - Consegue criar o seu próprio destino.
+ - A felicidade é uma escolha; escolha-a todos os dias.
+ - A vida é o que fazemos dela, por isso, faça-a valer a pena.
+ - Mantenha-se concentrado, determinado, imparável.
+ - É o autor da sua própria história.
+ - Esforce-se para progredir, não pela perfeição.
+ - Cada dia é um novo começo; faça dele um grande começo.
+ - Inspire os outros sendo autêntico.
+ - A sua atitude determina o seu rumo.
+
+
+ - A ignorância é uma benção
+ - Já está na hora de instalar outra atualização?
+ - Faça a paz, não a guerra
+ - Olá! Tudo bem?
+ - Bem-vindo(a) à Matriz real!
+ - Quantas capturas de ecrã tira?
+ - Descubra o que há de melhor com AlphaDroid
+ - Espalhe amor, não devastação
+ - Não iniciámos o fogo!
+ - Hora de um pouco de boa música
+ - \u003C\u003C\u003C\u003C\u003C\u003C\u003C HEAD
+ - Necessita da AlphaDroid Premium para ver isto
+ - Lembre-se da linhagem do unicórnio
+ - Desfrute da duração Xtendida da bateria
+ - Começando do zero
+ - Nós te amamos 3000
+ - Delicioso mesmo sem Sushi
+ - Faça algo de bom hoje
+ - Esta é a melhor Pixel Experience, não é?
+ - Sem ilusões, bem-vindo à realidade!
+ - Obrigado pelo seu apoio
+ - Não há festival para Derps - só a perfeição!
+ - Uma das melhores escolhas do buildbot
+ - Sanidade para a sua paranóia
+ - Subindo em paz
+ - Que experiência adorável, não é?
+ - A evolução é um mito, certo?
+ - Você é o que você instala, não seja Batata
+ - Em que está a pensar?
+ - Expecto Patronum
+ - Wubba Lubba Dub Dub
+ - Vencedor Vencedor ....?
+ - rm -rf \/
+ - As Pringles não são batatas fritas..
+ - Não se esqueça de verificar o nível da bateria do dispositivo!
+ - Verifique o seu correio eletrónico/mensagens.
+ - Verifique a sua lista de tarefas, se a tiver.
+ - Pode desativar o espaço rápido através das definições da página inicial.
+ - A gostar de nós? Envie-nos uma cerveja
+ - A melhor ROM de sempre.
+ - Diga-nos olá no Telegram
+ - Nós te amamos 3000
+
+ Não foi possível encontrar o calendário ou a atividade do relógio
+
+ Provedor de meteorologia
+ Auto
+ Google Smartspace
+ OmniJaws
+ Atualização meteorológica
+ Mostrar o estado do tempo atual
+ Requer que o serviço meteorológico esteja ativado
+ Com nuvens
+ Chuvoso
+ Ensolarado
+ Tempestuoso
+ Nevado
+ Ventoso
+ Enevoado
+ Localização atual
+ Mostrar localização meteorológica atual
+ Condições atuais
+ Mostrar as condições meteorológicas atuais
+
+ Ocultar automaticamente indicador de página
+ Ocultar os pontos indicadores da página automaticamente
+
+ Nível de desfoque do fundo
+ Definir o nível de desfoque para a gaveta de apps e ecrã de recentes
+
+ Info. da Aplicação
+ Origem
+ Sistema
+ Última atualização
+ Versão
+ Mais
+
+ Pacote de ícones
+ Padrão
+ Instalar mais
+ Não há uma loja de apps disponível
+
+ Carrossel de papéis de parede
+ Troque rapidamente de papéis de parede através do menu de toque longo
+ Aplicar ao ecrã de bloqueio
+ Aplicar o mesmo papel de parede ao ecrã de bloqueio
+
+ Seixo
+ Vaso
+ Quadrado puro
+ Quadrículo
+ Retângulo afunilado
+ Lágrima
+ Retângulo arredondado
+ Com nuvens
+ Cilíndrico
+ Flor
+ Coração
+ Hexágono
+ Hexágono Arredondado
+ Estilo iOS
+ Folha
+ Meow
+ Estilo Samsung
+ Esticado
+
+ Rótulos de aplicação escuros
+ Usar sempre texto escuro para os rótulos das aplicações na gaveta de aplicações
+
+ Estilo da gaveta de aplicações
+ Normal (folha inferior)
+ Tela cheia (antigo)
+ Estilo de lista
+ Estilo OneUI (paginado)
+
+ Ordenação da gaveta de aplicações
+ Ordenar alfabeticamente
+ Ordenar por data de instalação
+ Ordenar por uso
+
+ Cor de fundo personalizada
+ Utilizar uma cor personalizada para o fundo da gaveta de aplicações
+ Cor do modo claro
+ Cor do modo escuro
+
+ HSB
+ RGB
+ Tonalidade
+ Saturação
+ Brilho
+ Vermelho
+ Verde
+ Azul
+ HEX
+
+ Barra de procura ao estilo Pixel
+ Barra de procura revista com fundo sobreposto
+ Opacidade da camada externa
+
+ Apps ocultas e protegidas
+ Desbloquear para gerir as apps ocultas e protegidas
+ Autenticar para abrir %1$s
+ A carregar\u2026
+ Configure um ecrã de bloqueio seguro para restringir o acesso às apps
+ Ajuda
+ As apps ocultas e seus widgets estão escondidos da gaveta de apps
+ As apps protegidas requerem autenticação para serem executadas
+
+ Estilo de layout dos recentes
+ Padrão
+ Efeito de escala
+ Estilo Kami
+ Estilo iOS
+ Estilo OxigenOS
+
+ Bloquear/Desbloquear aplicação
+ Aplicação desbloqueada em recentes
+ Aplicação bloqueada em recentes
+
+ Vibração ao rolar
+
diff --git a/res/values-ro-rRO/alpha_strings.xml b/res/values-ro-rRO/alpha_strings.xml
new file mode 100644
index 00000000000..e8660a28519
--- /dev/null
+++ b/res/values-ro-rRO/alpha_strings.xml
@@ -0,0 +1,44 @@
+
+
+
+
+ Lock/Unlock app
+ Unlocked app in recents
+ Locked app in recents
+
+ Recents layout style
+ Stock
+ Kami Style
+ iOS Style
+ OxygenOS Style
+
+ - Ignorance is Bliss
+ - Make love, not war
+ - Oh hey, what\'s up?
+ - We love you 3000
+ - Thank you for your support
+ - One of the buildbot\'s best picks
+ - Pringles aren\'t actually potato chips..
+ - You can disable quickspace via home settings.
+ - I got nothing ¯\\_(ツ)_/¯
+
+
+ - I never apologize. I\'m sorry but that\'s the way I am.
+ - All my life, I\'ve had one dream: to achieve my many goals.
+ - Good drink... good meat... good God, let\'s eat!
+ - You\'ll have to speak up. I\'m wearing a towel.
+ - Change the channel, Marge...
+ - Roads are just a suggestion Marge, just like pants.
+ - It takes two to lie - one to lie and one to listen.
+ - Shut up, Flanders!
+ - I\'m not normally a praying man, but if you\'re up there, please save me, Superman.
+ - It\'s so simple to be wise... just think of something stupid to say and then don\'t say it.
+ - Stupid sexy Flanders...
+ - I hope I didn\'t brain my damage.
+ - Operator, give me the number for 911!
+ - Don\'t blame me, I voted for Kodos.
+
+
diff --git a/res/values-ro-rRO/cr_strings.xml b/res/values-ro-rRO/cr_strings.xml
new file mode 100644
index 00000000000..54a56789a79
--- /dev/null
+++ b/res/values-ro-rRO/cr_strings.xml
@@ -0,0 +1,440 @@
+
+
+
+
+
+
+ Setări ecran de pornire
+
+ Ecranul principal
+ Setează panoul Google Now și altele
+ Pictograme
+ Setează forma pictogramei și mai multe
+ Sertar aplicații
+ Personalizați sertarul de aplicații
+ Recente
+ Renovare la ecranul general
+ Diverse
+ Alte opțiuni
+ General
+ Interfață
+ Spațiu rapid
+ Bara de căutare
+ Actiuni rapide
+
+ Opțiuni dezvoltator
+ Activează pe propriul risc unele caracteristici ascunse
+
+ Blocare aspect
+ Iconițele și widget-urile nu pot fi adăugate, eliminate și mutate pe ecranul principal
+ Iconițele și widget-urile pot fi adăugate, eliminate și mutate pe ecranul principal
+ Nu este posibil să adăugați widget-uri pe ecranul de pornire
+
+ Etichete pictograme pe desktop
+ Etichete pictograme în sertar
+
+ Glisați pentru a accesa aplicația Google
+
+ Când glisați stânga din ecranul principal de pornire
+
+ Când glisați dreapta din ecranul principal de pornire
+
+ Utilizați particularizările pictogramelor în sertar
+ Urmărește personalizările pictogramelor folosite pe ecranul principal
+
+ Pauză
+ Întrerupeți %1$s?
+ Notificările pentru %1$s vor fi întrerupte
+ Aplicație întreruptă
+ %1$s a fost întrerupt direct din lansator
+ Întrerupeți aplicația
+ Aplicațiile de serviciu sunt dezactivate
+ Dezactivați aplicațiile de serviciu
+ Activează aplicațiile pentru servici
+ Aplicațiile pentru lucru nu pot să vă trimită notificări, să folosească bateria sau să vă acceseze locația
+ Aplicațiile dvs. de serviciu nu pot primi apeluri telefonice sau mesaje text, nu pot trimite notificări, nu pot utiliza bateria sau nu pot accesa locația dvs
+
+ Sugestii
+ Pentru toate aplicațiile & sugestiile pentru ecranul de pornire
+
+ Repornire
+ Repornește lansatorul manual pentru a aplica orice setări în așteptare
+ Se repornește lansatorul...
+ Se repornește lansatorul pentru a aplica modificările...
+ Se repornește lansatorul pentru a actualiza componentele lansatorului...
+
+ Dublă atingere pentru oprire ecran
+ Atingeți de două ori spațiul gol de pe ecranul de pornire pentru a opri ecranul
+ Vibrează la atingerea dublă pentru a dormi
+ Oferă feedback haptic la gestul de două apăsări pentru a dormi
+
+ Glisați pentru a începe căutarea
+ Deschide tastatura ori de câte ori sertarul de aplicații devine vizibil
+
+ Dimensiune pictogramă
+
+ Dimensiune font
+
+ Utilizați două linii pentru eticheta aplicației
+
+ Înălțimea rândului
+
+ Fundal andocare aplicații
+ Adaugă fundal translucid în andocare aplicații
+
+ Opacitate fundal
+
+ Contur
+
+ Termină
+ Aplicaţie terminată
+
+ Căutare
+ Google Lens
+ Căutare vocală
+ Mod AI
+ Căutare muzică
+ Pornește căutarea muzicii la apăsarea pictogramei microfonului
+ Bară de căutare Google
+ Bara de căutare în bara de jos
+ Pictograme cu aspect
+ Folosește pictograme tematice pentru bara de căutare
+ Raza de colț
+
+ Locație bară de căutare aplicații
+ Ascuns
+ Sus
+ Jos
+
+ Derulare fundal
+ Efect de derulare fundal pentru mai multe ecrane
+
+ Zooming wallpaper
+ Măriți imaginea de fundal atunci când utilizați sertarul sau aplicațiile recente
+
+ Bara de stare
+
+ Umbra de sus
+
+ %1$s Disponibil | %2$s Total
+ Informații memorie
+ Informații ZRAM
+
+ Captură ecran
+ Stergeti toate
+ Obiectiv
+
+ Permite parallax scurt
+ Activează efectul complet de defilare a imaginii de fundal pe un număr mic de pagini în loc să fie decupată imaginea de fundal
+ Centrare pagină unică
+ Se centrează imaginea de fundal dacă se utilizează doar o singură pagină
+
+ Bara de derulare
+ Bara de căutare în partea de sus a sertarului de aplicații
+
+ Bară de stare întunecată
+ Utilizaţi întotdeauna bara de stare întunecată pe ecranul principal
+
+ Pe scurt
+ O copie a widgetului Google dintr-o privire
+
+ Bună dimineața.
+ Bună seara.
+ Bună ziua.
+ Noapte bună.
+ O zi bună.
+ Astăzi este
+ Este
+
+ Artist Necunoscut
+ Redare acum
+ De
+
+ Redare acum
+ Arată melodia pe care o redă
+
+ Stil minimalist
+ Comutați la stilul extins
+
+ Mesaje aleatorii
+ Fă-ți prietenul tău mai viu cu mesaje aleatoare
+
+ - Bună dimineața!
+ - Bună dimineața, timpul de rock!
+ - Ce zi frumoasă!
+ - O zi bună!
+ - Ce zici de o pauză de 5 minute?
+ - Hai să obținem această pâine.
+ - Dimineațele sunt o pânză nouă pentru capodopera zilnică.
+ - Încurajează răsărirea oportunităţilor în fiecare dimineaţă.
+ - În lumina timpurie, găsește puterea de a vă ilumina calea.
+ - Ziua ta începe cu posibilități nesfârșite.
+ - Fiecare răsărit este un memento că poți începe din nou.
+ - Urmează cu scopul, cucerește ziua următoare.
+ - Gândurile tale pot sta la baza marilor realizări.
+ - Lumea se trezește, la fel și potențialul tău.
+ - Primul capitol din aventura ta zilnică.
+ - Lăsați rutina de dimineață să fie panoul de lansare pentru succes.
+ - Potențialul tău este mereu în creștere.
+ - Este timpul pentru idei noi și acțiuni îndrăznețe.
+ - Pasărea timpurie prinde viermi, dar îți poți prinde visele.
+ - Găsește curajul de a-ți urmări aspirațiile.
+ - Ridicați și străluciți, pentru măreție așteaptă.
+ - Cel mai bun moment de început este acum.
+
+
+ - Parcurge-ți visele, nu minutele.
+ - În fiecare moment, o nouă oportunitate.
+ - Încurajați călătoria, nu doar destinația.
+ - Azi e un cadou, de aceea se numeşte prezent.
+ - Lasa zâmbetul tau sa schimbe lumea.
+ - Viața e o poveste; fiecare capitol contează.
+ - Găsește bucurie în momentele obișnuite.
+ - Cel mai bun este încă de venit, țineți-l sus.
+ - Șansa dvs. de a transforma \"ce” în \"ce”
+ - Porniți ziua, începând chiar acum.
+ - Fericirea este o alegere pe care o poți face oricând.
+ - Credeți-vă singuri, chiar și după-amiaza somnoroasă.
+ - Lasă acțiunile tale să vorbească mai tare decât ceasul.
+ - Viața ta este ceea ce o creezi; o faci uimitoare.
+ - Visează mare, munceşte din greu şi nu renunţă niciodată.
+ - Ești mai puternic decât crezi.
+ - Fiecare eșec este o configurație pentru o revenire.
+ - Încurajați călătoria, nu doar destinația.
+ - Urmăriți-vă obiectivele, nu temerile.
+ - Potențialul tău este nesfârșit; deblochează-l.
+
+
+ - Embracați momentul, indiferent de oră.
+ - În fiecare bănuţ, găseşte un nou zori al oportunităţii.
+ - Viața e o călătorie; bucură-te de peisajul pe parcurs.
+ - Atitudinea ta vă modelează realitatea.
+ - Povestea ta încă este scrisă, chiar dacă apare soarele.
+ - Capturează frumusețea în momentele obișnuite.
+ - Este timpul să reflectăm şi să resetăm.
+ - Alege bucuria, indiferent de timpul din ceas.
+ - Oportunitățile nu se petrec seara.
+ - Fii motivul pentru care cineva zâmbește.
+ - Potențialul tău nu cunoaște limite.
+ - Momentele de magie se pot întâmpla la orice oră.
+ - Viaţa ta este ceea ce o creezi; o faci extraordinară.
+ - Stai curios, fii recunoscător.
+ - Fii schimbarea pe care vrei să o vezi în lume.
+ - Credeți-vă în voi; sunteți capabili de lucruri uimitoare.
+ - Singura limită este cea pe care ți-ai stabilit-o.
+ - Succesul începe cu un singur pas.
+
+
+ - Capturează noaptea, pentru că îşi ţine propriile mistere.
+ - În tăcerea din această seară, găsește liniștea.
+ - Cele mai mari aventuri ale vieţii pot începe în întuneric.
+ - Călătoria ta continuă chiar și după ce soarele a fost setat.
+ - Să lăsăm noaptea să fie pânza ta; să o pictezi cu vise.
+ - În această seară, găsiţi-vă pacea interioară.
+ - Stelele strălucesc în cele mai întunecate ore.
+ - Visează mare, chiar şi în ultimele ore ale zilei.
+ - Viața ta este o poveste care așteaptă să fie scrisă.
+ - Este timpul pentru reflecție și reînnoire.
+ - Gândurile tarziu in noapte pot duce la revelatii dimineata devreme.
+ - Găseşte bucuria în această seară.
+ - Cerul nopţii este o pânză vastă de vise.
+ - Potențialul tău nu cunoaște culcare; urmărește pasiunile tale.
+ - Rămâi optimist, chiar şi în cele mai întunecate ore.
+ - În mijlocul haosului, găsește-ți calmul interior.
+ - Rămâi pozitiv, muncește din greu, face acest lucru să se întâmple.
+
+
+ - Noaptea sunt şoimii tăcuţi ai oportunităţilor.
+ - În perioada nopţii, găsiţi-vă puterea interioară.
+ - Dezvoltă întunericul, pentru că ţine cheile viselor tale.
+ - Lumea doarme dar potențialul tău este foarte treaz.
+ - Noaptea poate fi târzie, dar ambiţiile dumneavoastră sunt inoportune.
+ - Stai curios, stai treaz, și lasă nopțile să te ghideze.
+ - Găsește curajul de a-ți urmări visele.
+ - Lucruri grozave nu vin niciodată din zonele de confort.
+ - Ai puterea să îți creezi propriul destin.
+ - Fericirea este o alegere; alege-o în fiecare zi.
+ - Viața e ceea ce o faci, așa că fă-o să număre.
+ - Rămâi concentrat, rămâi determinat, stai de neoprit.
+ - Ești autorul propriei povești.
+ - Eforturile pentru progres, nu perfecțiunea.
+ - Fiecare zi este un nou început; transformă-l într-unul grozav.
+ - Inspiră-i pe ceilalți prin a fi sinele tău autentic.
+ - Atitudinea dumneavoastră vă determină direcţia.
+
+
+ - Ignoranta este Bliss
+ - Este timpul să instalezi o nouă actualizare deja?
+ - Fă pace, nu război
+ - Oh, hei, ce se întâmplă?
+ - Bine ai venit in Matrixul real!
+ - Câte capturi de ecran faceți?
+ - Deschide bunătatea cu AlphaDroid
+ - Dragostea răsunătoare, nu haos
+ - Nu am început focul!
+ - Timp pentru muzica buna
+ - \u003C\u003C\u003C\u003C\u003C\u003C\u003C HEAD
+ - Ai nevoie de AlphaDroid Premium pentru a vedea asta
+ - Amintiți-vă Lineage of the Unicorn
+ - Bucură-te de durata bateriei Xtended
+ - Pornind de la zero
+ - Te iubim 3000
+ - Delicios chiar și fără Sushi
+ - Fă ceva drăguț astăzi
+ - Aceasta este cea mai bună experiență Pixel, nu-i așa?
+ - Nicio iluzie, bun venit în realitate!
+ - Vă mulțumim pentru sprijin
+ - Niciun festival pentru Derps - doar perfecţiune!
+ - Una dintre cele mai bune alegeri ale buildbot-ului
+ - Sanitatea ta pentru Paranoia
+ - Intensificarea păcii
+ - Ce experiență minunată, nu-i așa?
+ - Evoluția e un mit, nu?
+ - Ești ceea ce flash-ui, nu fii Cartofor
+ - La ce te gandesti?
+ - Expecto Patronum
+ - Wubba Lubba Dub Dub
+ - Câștigător ....?
+ - rm -rf \/
+ - Pringles nu sunt de fapt chipsuri de cartofi..
+ - Nu uita să verifici nivelul bateriei dispozitivului!
+ - Verifică-ți e-mailul/mesajele.
+ - Verifică lista de lucruri de făcut dacă ai una.
+ - Poți dezactiva spațiul rapid în setările lansatorului.
+ - Ne iubiti? Trimite-ne o bere
+ - Cel mai bun Rom din totdeauna.
+ - Spune-ne salut pe Telegram
+ - Te iubim 3000
+
+ Nu s-a găsit activitatea calendar sau ceas
+
+ Furnizor de vreme
+ Automat
+ Smartspace Google
+ OmniJaws
+ Actualizare meteo
+ Afișează actualizarea meteo curentă
+ Necesită activarea serviciului meteo
+ Înnorat
+ Ploios
+ Însorit
+ Furtunos
+ Ninsoare
+ Vânt
+ Cețos
+ Locația curentă
+ Afişare locaţie meteo curentă
+ Starea curentă
+ Afişează rezumatul stării meteo curente
+
+ Ascundere automată a indicatorului de pagină
+ Ascunde automat punctele indicatorului de pagină
+
+ Adâncime blur de fundal
+ Setaţi nivelul de adâncime încețoșare pentru aplicaţiile recente şi lista aplicaţiilor
+
+ Informații aplicație
+ Sursa
+ Sistem
+ Ultima actualizare
+ Versiune
+ Mai mult
+
+ Pachet de pictograme
+ Prestabilit
+ Instalează mai multe
+ Nu există niciun magazin de aplicații disponibil
+
+ Carusel de imagini
+ Schimbă rapid fundalul din meniul de apăsare lungă
+ Aplică pentru ecranul de blocare
+ Setează aceeași imagine de fundal pe ecranul de blocare
+
+ Pietricică
+ Navă
+ Pătrat pur
+ Pătrat rotunjit
+ Dreptunghi conic
+ Lacrimă
+ Dreptunghi rotund
+ Înnorat
+ Cilindric
+ Floare
+ Inimă
+ Hexagon
+ Rounded hexagon
+ Stil iOS
+ Frunză
+ Miau
+ Stil Samsung
+ Întins
+
+ Etichete întunecate pentru aplicații
+ Utilizați întotdeauna textul întunecat pentru etichetele aplicațiilor în sertarul de aplicații
+
+ Stil aplicație sertar
+ Normal (foi de bază)
+ Ecran complet vechi
+ Stil listă
+ Stilul OneUI (pagină)
+
+ Sortare aplicaţie
+ Sortează alfabetic
+ Sortează după data instalării
+ Sortare după utilizare
+
+ Culoare fundal personalizată
+ Utilizați o culoare personalizată pentru fundalul sertarului de aplicații
+ Culoare mod lumină
+ Culoare modul întunecat
+
+ HSB
+ RGB
+ Nuanță
+ Saturație
+ Luminozitate
+ Roșu
+ Verde
+ Albastru
+ HEX
+
+ Bară de căutare stiluri Pixel
+ Bara de căutare redimensionată cu fundal strat
+ Outer layer opacity
+
+ Aplicații ascunse & protejate
+ Deblochează pentru a gestiona aplicațiile ascunse și protejate
+ Autentifică-te pentru a deschide %1$s
+ Încărcare\u2026
+ Vă rugăm să configurați un ecran de blocare securizat pentru a restricționa accesul aplicației
+ Ajutor
+ Aplicațiile ascunse și widget-urile lor sunt ascunse din sertar
+ Aplicațiile protejate necesită autentificare pentru a fi deschise din launcher
+
+ Recents layout style
+ Prestabilit
+ Scale effect
+ Kami style
+ iOS style
+ OxygenOS style
+
+ Lock/Unlock app
+ Unlocked app in recents
+ Locked app in recents
+
+ Scroll vibration
+
diff --git a/res/values-ru-rRU/alpha_strings.xml b/res/values-ru-rRU/alpha_strings.xml
new file mode 100644
index 00000000000..6f0dd7eabf2
--- /dev/null
+++ b/res/values-ru-rRU/alpha_strings.xml
@@ -0,0 +1,44 @@
+
+
+
+
+ Заблокировать/Разблокировать приложение
+ Разблокировано приложение в недавних
+ Заблокировано приложение в недавних
+
+ Стиль недавних приложений
+ Сток
+ Дух стиль
+ Стиль iOS
+ Стиль OxygenOS
+
+ - Невежество — это блаженство
+ - Занимайтесь любовью, а не войной
+ - О, привет, как дела?
+ - Мы любим тебя 3000
+ - Спасибо за вашу поддержку
+ - Один из лучших выборов билдбота
+ - Pringles на самом деле не являются картофельными чипсами.
+ - Вы можете отключить QuickSpace в домашних настройках.
+ - У меня ничего нет ¯\\_(ツ)_/¯
+
+
+ - Я никогда не извиняюсь. Мне жаль, но я такой.
+ - Всю жизнь у меня была одна мечта: достичь своих многочисленных целей.
+ - Хороший напиток... хорошее мясо... Боже мой, давайте есть!
+ - Тебе придётся говорить громче. Я в полотенце.
+ - Переключи канал, Мардж...
+ - Дороги - это всего лишь предположение, Мардж, как и штаны.
+ - Чтобы лгать, нужны двое: один, чтобы лгать, и один, чтобы слушать.
+ - Заткнись, Фландерс!
+ - Я обычно не молюсь, но если ты там, наверху, пожалуйста, спаси меня, Супермен.
+ - Быть мудрым так просто... просто придумайте какую-нибудь глупость, которую можно было бы сказать, и не говорите ее.
+ - Глупый сексуальный Фландерс...
+ - Надеюсь, я не нанес себе увечья.
+ - Оператор, дайте мне номер 911!
+ - Не вините меня, я голосовал за Кодоса.
+
+
diff --git a/res/values-ru-rRU/cr_strings.xml b/res/values-ru-rRU/cr_strings.xml
new file mode 100644
index 00000000000..ffb6447e053
--- /dev/null
+++ b/res/values-ru-rRU/cr_strings.xml
@@ -0,0 +1,440 @@
+
+
+
+
+
+
+ Настройки
+
+ Рабочий стол
+ Панель Google now и другое
+ Значки
+ Установить размер значков и прочее
+ Меню приложений
+ Настроить меню приложений
+ Недавние
+ Настройки экрана недавних приложений
+ Разное
+ Прочие настройки
+ Общие
+ Интерфейс
+ «С первого взгляда»
+ Панель поиска
+ Быстрые действия
+
+ Параметры разработчика
+ Включить некоторые скрытые функции на свой страх и риск
+
+ Блокировка изменений
+ Значки и виджеты не могут быть добавлены, удалены или перемещены на рабочий стол
+ Значки и виджеты могут быть добавлены, удалены и перемещены на рабочем столе
+ Невозможно добавить виджеты на рабочий стол
+
+ Показывать подписи значков
+ Показывать подписи значков
+
+ Смахивание вправо - Google
+
+ Когда открываете рабочий стол слева
+
+ Когда открываете рабочий стол справа
+
+ Использовать настройки значков в меню приложений
+ Следовать настройкам значков, главного экрана
+
+ Пауза
+ Приостановить %1$s?
+ Уведомления для %1$s будут приостановлены
+ Приложение приостановлено
+ %1$s приостановлено из главного экрана
+ Приостановить приложение
+ Рабочие приложения отключены
+ Отключить рабочие приложения
+ Включить рабочие приложения
+ Рабочие приложения не могут отправлять уведомления, расходовать заряд батареи и получать доступ к данным о вашем местоположении.
+ Ваши рабочие приложения не могут принимать телефонные звонки или текстовые сообщения, отправлять уведомления, использовать аккумулятор или получать доступ к вашему местоположению
+
+ Рекомендации
+ Рекомендации в меню приложений и на рабочем столе
+
+ Перезапустить
+ Перезапустить главный экран вручную, чтобы применить все ожидающие настройки
+ Перезапуск главного экрана...
+ Перезапуск главного экрана для применения изменений...
+ Перезапуск главного экрана для обновления компонентов...
+
+ Выкл. двойным касанием
+ Дважды коснитесь пустой области главного экрана, чтобы выключить его
+ Вибрировать при двойном нажатии для сна
+ Вибрировать при выполнении жеста двойного нажатия для перехода в спящий режим
+
+ Автоматическая клавиатура
+ Показать клавиатуру, при открытии меню приложений
+
+ Размер значка
+
+ Размер шрифта
+
+ Название в две строки
+
+ Высота строки
+
+ Фон нижней панели
+ Полупрозрачный фон нижней панели приложений
+
+ Прозрачность фона
+
+ Окантовка
+
+ Завершить
+ Приложение закрыто
+
+ Поиск
+ Google Объектив
+ Голосовой поиск
+ Режим ИИ
+ Поиск музыки
+ Поиск музыки при нажатии на микрофон
+ Поиск Google
+ Показывать строку поиска снизу
+ Значки из темы
+ Для панели поиска использовать значки из темы
+ Радиус закругления углов
+
+ Положение строки поиска
+ Скрыта
+ Сверху
+ Снизу
+
+ Прокрутка обоев
+ Эффект прокрутки обоев для нескольких экранов
+
+ Масштабирование обоев
+ Увеличение или уменьшение обоев в меню приложений или в списке недавних приложений
+
+ Строка состояния
+
+ Тень сверху
+
+ Доступно: %1$s | Всего: %2$s
+ Информация о памяти
+ Информация о ZRAM
+
+ Снимок экрана
+ Очистить всё
+ Объектив
+
+ Разрешить короткое смещение
+ Включить эффект полной прокрутки обоев на меньшем количестве страниц вместо обрезки обоев
+ Центр, если одна страница
+ Центрировать обои, если используется только одна страница
+
+ Полоса прокрутки
+ Полоса прокрутки сбоку меню приложений
+
+ Тёмная строка состояния
+ Использовать тёмную строку состояния на главном экране
+
+ Самое главное
+ Показывать в верхней части рабочего стола
+
+ Доброго утра.
+ Доброго вечера.
+ Доброго дня.
+ Доброй ночи.
+ Хороший день.
+ Вот и
+ Сегодня
+
+ Неизвестный исполнитель
+ Сейчас играет
+ Автор
+
+ Сейчас играет
+ Показать песню, которая проигрывается
+
+ Расширенный стиль
+ Переключиться на расширенный стиль
+
+ Случайные сообщения
+ Сделайте своего спутника более живым с помощью случайных сообщений
+
+ - Доброго утра!
+ - Доброго утра! Время зажигать!
+ - Какой чудесный день!
+ - Хорошего дня!
+ - Как насчет небольшого 5-минутного перерыва?
+ - Давайте сделаем это.
+ - Утро — свежий холст для вашего ежедневного шедевра
+ - Встречайте рассвет возможностей каждое утро
+ - С ранним светом найдите силу, которая осветит ваш путь
+ - Ваш день начинается с безграничных возможностей
+ - Каждый восход солнца — это напоминание о том, что можно начать заново
+ - Просыпайтесь с целью, победите предстоящий день
+ - Ваши мысли могут стать основой великих достижений
+ - Мир пробуждается, и ваш потенциал тоже
+ - Первая глава вашего ежедневного приключения
+ - Пусть ваша утренняя рутина станет стартовой площадкой для успеха
+ - Восходит солнце или нет, но ваш потенциал всегда растёт
+ - Пришло время для свежих идей и смелых действий
+ - Ранняя пташка ловит червяка, а вы - ловите свои мечты
+ - Найдите в себе смелость преследовать свои стремления
+ - Восстаньте и сияйте, ибо величие ждёт
+ - Лучшее время для начала – здесь и сейчас
+
+
+ - Гоняйся за своей мечтой, а не за минутами
+ - В каждый момент новая возможность
+ - Охватите путешествие, а не только пункт назначения
+ - Сегодня - это подарок, поэтому он и называется настоящим
+ - Пусть твоя улыбка изменит мир
+ - Жизнь — это история. Сделай каждую главу значимой
+ - Находите радость в обычных моментах
+ - Лучшее ещё впереди, так держать!
+ - Ваш шанс превратить «что, если» в «то, что есть»
+ - Ловите момент, начиная прямо сейчас
+ - Счастье — это выбор, который вы можете сделать в любое время
+ - Верьте в себя даже сонным днём
+ - Пусть ваши действия говорят громче часов
+ - Ваша жизнь — это то, что вы из неё делаете. Сделай это потрясающе
+ - Мечтай о многом, работай усердно и никогда не сдавайся
+ - Ты сильнее чем ты думаешь
+ - Каждая неудача – это подготовка к возвращению
+ - Создай путешествие, а не дорогу до пункта назначения
+ - Преследуйте свои цели, а не свои страхи
+ - Ваш потенциал безграничен. Разблокируйте его
+
+
+ - Примите настоящий момент, независимо от часа
+ - В каждом сумраке находите новый рассвет возможностей
+ - Жизнь - это путешествие. Наслаждайтесь пейзажами по пути
+ - Ваше отношение формирует вашу реальность
+ - Ваша история всё ещё пишется, даже когда солнце садится
+ - Сохраняйте красоту в обычных моментах
+ - Пришло время задуматься и перезагрузиться
+ - Выбирайте радость, независимо от времени на часах
+ - Возможности не заканчиваются вечером
+ - Будьте причиной улыбок
+ - Ваш потенциал не знает границ
+ - Моменты волшебства могут случиться в любой час
+ - Ваша жизнь — это то, что вы из нее делаете. Сделайте её необычной
+ - Оставайтесь любопытными, оставайтесь благодарными
+ - Станьте тем изменением, которое вы хотите увидеть в мире
+ - Верьте в себя. Вы способны на удивительные вещи
+ - Единственный предел тот, который вы сами для себя установили
+ - Успех начинается с первого шага
+
+
+ - Ловите ночь, ведь она таит в себе свои тайны
+ - В тишине этого вечера найдите безмятежность
+ - Величайшие приключения в жизни могут начаться в темноте
+ - Ваше путешествие продолжается даже после захода солнца
+ - Пусть ночь будет вашим холстом. Нарисуйте его мечтами
+ - В тишине этого вечера найдите свой внутренний покой
+ - Звёзды сияют ярче в самые тёмные часы
+ - Мечтайте о многом, даже в поздние часы дня
+ - Ваша жизнь - история, ожидающая написания.
+ - Пришло время для размышлений и обновления.
+ - Ночные мысли могут привести к утренним откровениям.
+ - Найди радость в тишине этого вечера.
+ - Ночное небо — огромное полотно снов.
+ - Ваш потенциал не знает времени сна; преследуйте свои увлечения
+ - Сохраняйте надежду даже в самые мрачные часы
+ - Посреди хаоса, найди свое внутреннее спокойствие
+ - Сохраняйте позитивный настрой, усердно работайте, чтобы это произошло
+
+
+ - Ночи - это тихий шёпот возможностей
+ - В ночной тиши найдите свою внутреннюю силу
+ - Примите тьму, ведь она хранит ключи к вашим мечтам
+ - Мир спит, но ваш потенциал бодрствует
+ - Ночь может быть поздней, но ваши амбиции вечны
+ - Оставайтесь любопытными, не спите, и пусть ночи ведут вас
+ - Найдите в себе смелость следовать за своей мечтой
+ - Великие дела никогда не приходят из зоны комфорта
+ - У вас есть сила творить свою судьбу
+ - Счастье это выбор. Выбирайте его каждый день.
+ - Жизнь — это то, что вы делаете, поэтому примите это во внимание.
+ - Оставайтесь сосредоточенными, оставайтесь решительными, оставайтесь неудержимыми
+ - Вы автор своей истории
+ - Стремитесь к прогрессу, а не к совершенству
+ - Каждый день - это новое начало. Сделайте его отличным.
+ - Вдохновляйте других, будучи самим собой
+ - Ваше отношение определяет ваше направление
+
+
+ - Невежество — блаженство
+ - Не пора ли уже прошить ещё одно обновление?
+ - Цените мир
+ - О, привет, что случилось?
+ - Добро пожаловать в матрицу!
+ - Сколько снимков экрана вы сделали?
+ - Открой лучшее с AlphaDroid
+ - Распространяйте любовь, а не хаос
+ - Не мы разожгли огонь!
+ - Время хорошей музыки
+ - \u003C\u003C\u003C\u003C\u003C\u003C\u003C ГОЛОВА
+ - Вам нужен AlphaDroid Premium, чтобы видеть это
+ - Помни родословную Lineage
+ - Наслаждайтесь долгим сроком службы батареи
+ - Начинайте с нуля
+ - Мы любим тебя 3000
+ - Вкусно даже без Суши
+ - Сделайте сегодня кому-то что-то приятное
+ - Это же лучше Pixel Experience, не так ли?
+ - Никаких иллюзий, добро пожаловать в реальность!
+ - Спасибо за вашу поддержку!
+ - Никакого праздника для сумасшедших – только совершенство!
+ - Одна из лучших сборок
+ - Безопасность для вашей паранойи
+ - Восхождение в мире
+ - Какой прекрасный опыт, не правда ли?
+ - Эволюция - это миф, правда?
+ - Ты - это то, что прошиваешь, не будь картошкой
+ - Что у тебя на уме?
+ - Я жду опекуна (лат.)
+ - Тили-тили, трали-вали...
+ - Победитель, Победитель ....?
+ - rm -rf \/
+ - Pringles, на самом деле, не картофельные чипсы..
+ - Не забудьте проверить уровень заряда батареи своих устройств!
+ - Проверьте свою электронную почту/сообщения.
+ - Проверьте свой список дел, если он у вас есть.
+ - Вы можете отключить «С первого взгляда» в настройках рабочего стола.
+ - Нравится прошивка? Можете нас проспонсировать
+ - Лучшая прошивка на свете
+ - Поздоровайтесь с нами в Telegram
+ - Мы любим вас, нас уже больше 3000
+
+ Невозможно найти календарь или часы
+
+ Поставщик погоды
+ Авто
+ Умное пространство Google
+ OmniJaws
+ Обновление погоды
+ Показывать текущее обновление погоды
+ Требуется, чтобы служба погоды была запущена
+ Пасмурно
+ Дождь
+ Ясная погода
+ Гроза
+ Снег
+ Ветер
+ Туман
+ Текущее местоположение
+ Показывать текущее местоположение
+ Текущее состояние
+ Показывать информацию о текущих погодных условиях
+
+ Автоскрытие индикатора страницы
+ Автоматически скрывать точки индикатора страницы
+
+ Глубина размытия фона
+ Установка глубины размытия фона для недавних и меню приложений
+
+ О приложении
+ Источник
+ Системное
+ Последнее обновление
+ Версия
+ Дополнительно...
+
+ Набор значков
+ По умолчанию
+ Установить ещё
+ Нет доступного магазина приложений
+
+ Карусель обоев
+ Быстрое переключение обоев из меню настроек
+ Применять к экрану блокировки
+ Устанавливать те же обои на экран блокировки
+
+ Галька
+ Ёмкость
+ Чистый квадрат
+ Квадрокруг
+ Заострённый прямоугольник
+ Капля
+ Скруглённый прямоугольник
+ Пасмурно
+ Цилиндр
+ Цветок
+ Сердце
+ Шестиугольник
+ Скруглённый шестиугольник
+ iOS стиль
+ Лист
+ Мяу
+ Стиль Samsung
+ Растянутый
+
+ Тёмные названия приложений
+ Использовать тёмный текст для названия приложений
+
+ Стиль меню приложений
+ Нормальный (нижний лист)
+ Устаревший полноэкранный
+ В виде списка
+ Стиль OneUI (страничный)
+
+ Сортировка меню приложений
+ Сортировать по алфавиту
+ Сортировать по дате установки
+ По частоте использования
+
+ Свой цвет фона
+ Использовать свой цвет фона для меню приложений
+ Цвет светлого режима
+ Цвет тёмного режима
+
+ HSB
+ RGB
+ Оттенок
+ Насыщенность
+ Яркость
+ Красный
+ Зелёный
+ Синий
+ HEX
+
+ Панель поиска Pixel
+ Обновлённая панель поиска с многослойным фоном
+ Прозрачность внешнего слоя
+
+ Скрыть и защитить приложения
+ Разблокируйте, чтобы управлять скрытыми и защищёнными приложениями
+ Авторизуйтесь, чтобы открыть %1$s
+ Загрузка\u2026
+ Пожалуйста, настройте блокировку экрана, чтобы ограничить доступ к приложениям
+ Помощь
+ Не показывать скрытые приложения и их виджеты в меню приложений
+ Для запуска защищённых приложений требуется авторизация
+
+ Стиль недавних приложений
+ По умолчанию
+ Эффект масштабирования
+ Ками стиль
+ iOS стиль
+ Стиль OxygenOS
+
+ Блокировать/разблокировать
+ Разблокировано в недавних
+ Заблокировано в недавних
+
+ Вибрация прокрутки
+
diff --git a/res/values-sat-rIN/cr_strings.xml b/res/values-sat-rIN/cr_strings.xml
new file mode 100644
index 00000000000..6abc6a0fc2d
--- /dev/null
+++ b/res/values-sat-rIN/cr_strings.xml
@@ -0,0 +1,440 @@
+
+
+
+
+
+
+ Home settings
+
+ ᱚᱲᱟᱜ ᱥᱠᱨᱤᱱ
+ ᱜᱩᱜᱟᱹᱞ ᱯᱮᱱᱟᱞ ᱟᱨ ᱟᱭᱢᱟ ᱡᱤᱱᱤᱥ ᱥᱮᱴ ᱢᱮ
+ ᱟᱭᱠᱚᱱᱠᱚ
+ Set icon size and more
+ ᱮᱯ ᱰᱨᱚᱣᱟᱹᱨ
+ ᱟᱢᱟᱜ ᱮᱯ ᱰᱨᱚᱣᱟᱹᱨ ᱠᱚᱥᱴᱚᱢᱟᱭᱤᱡᱽ ᱢᱮ
+ ᱱᱮᱛᱟᱨᱟᱜᱠᱚ
+ Revamp the overview screen
+ ᱢᱤᱥᱮᱞᱟᱱᱤᱭᱟᱹᱥ
+ ᱚᱞᱜᱟ ᱚᱯᱥᱚᱱᱠᱚ
+ General
+ ᱤᱱᱴᱚᱨᱯᱷᱮᱥ
+ Quickspace
+ Search bar
+ Quick actions
+
+ ᱰᱮᱵᱽᱷᱞᱚᱯᱟᱹᱨ ᱚᱯᱥᱚᱱ ᱠᱚ
+ ᱟᱢᱟᱜ ᱨᱤᱥᱠ ᱛᱮ ᱡᱟᱦᱟᱱᱟᱜ ᱩᱠᱩ ᱯᱷᱤᱪᱚᱨᱠᱚ ᱮᱢ ᱪᱷᱚᱭ ᱢᱮ
+
+ Lock layout
+ Icons and widgets can\'t be added, removed and moved on the homescreen
+ Icons and widgets can be added, removed and moved on the homescreen
+ It\'s not possible to add widgets to the home screen
+
+ Icon labels on desktop
+ Icon labels in drawer
+
+ Swipe to access Google app
+
+ When you swipe left from main Home screen
+
+ When you swipe right from main Home screen
+
+ Use icon customizations in drawer
+ Follow icon customizations used on home screen
+
+ ᱛᱷᱩᱠᱩᱢ
+ Pause %1$s?
+ Notifications for %1$s will be paused
+ App paused
+ %1$s was directly paused from the launcher
+ Pause app
+ Work apps are off
+ Turn off work apps
+ Turn on work apps
+ Your work apps cannot send you notifications, use your battery, or access your location
+ Your work apps cannot receive phone calls or text messages, send you notifications, use your battery, or access your location
+
+ Suggestions
+ For All Apps & Home screen suggestions
+
+ Restart
+ Restart the launcher manually to apply any pending settings
+ Restarting launcher...
+ Restarting launcher to apply changes...
+ Restarting launcher to update launcher components...
+
+ ᱜᱤᱛᱤᱡ ᱞᱟᱹᱜᱤᱫ ᱵᱟᱨ ᱡᱮᱠᱷᱟ ᱚᱛᱟᱭ ᱢᱮ
+ Double tap empty space on home screen to turn off the screen
+ Vibrate on double tap to sleep
+ Provide haptic feedback on double tap to sleep gesture
+
+ Swipe to start search
+ Open keyboard whenever app drawer becomes visible
+
+ Icon size
+
+ Font size
+
+ Use two lines for app label
+
+ Row height
+
+ Hotseat background
+ Add translucent background to the app dock
+
+ ᱵᱮᱠᱜᱽᱨᱟᱩᱸᱰ ᱚᱯᱟᱥᱤᱴᱭ
+
+ Stroke
+
+ ᱜᱚᱡᱽ
+ ᱮᱯᱯ ᱜᱚᱡᱽ ᱠᱮᱫᱮᱭᱟᱠᱚ
+
+ ᱥᱮᱸᱫᱽᱨᱟ
+ Google Lens
+ Voice search
+ AI mode
+ Music search
+ Start music search on pressing mic icon
+ Google search bar
+ Search bar in the bottom dock
+ Themed icons
+ Use themed icons for search bar
+ Corner radius
+
+ App search bar location
+ Hidden
+ Top
+ Bottom
+
+ Wallpaper scrolling
+ Wallpaper scrolling effect for multiple screens
+
+ Wallpaper zooming
+ Zoom in or out the wallpaper when using drawer or recent apps
+
+ ᱥᱴᱟᱴᱟᱹᱥ ᱵᱟᱨ
+
+ Top shadow
+
+ %1$s available | %2$s
+ Memory info
+ ZRAM info
+
+ ᱥᱠᱨᱤᱱᱥᱷᱚᱴ
+ Clear all
+ Lens
+
+ Allow short parallax
+ Enable full wallpaper scroll effect on smaller numbers of pages instead of cropping the wallpaper
+ Single page center
+ Center wallpaper if only using a single page
+
+ Scrollbar
+ Scrollbar on side of the app drawer
+
+ Dark status bar
+ Always use dark status bar on home screen
+
+ ᱢᱤᱫᱽ ᱡᱷᱚᱞᱠ ᱛᱮ
+ Show at the top of your home screen
+
+ ᱥᱟᱹᱜᱩᱱ ᱥᱮᱛᱟᱜ.
+ Good evening.
+ Good afternoon.
+ ᱥᱟᱹᱜᱩᱱ ᱧᱤᱫᱟᱹ.
+ Good day.
+ Today is
+ It\'s
+
+ Unknown Artist
+ Now playing
+ By
+
+ Now playing
+ Show the song you\'re playing
+
+ Minimalistic style
+ Switch to modern style
+
+ Random messages
+ Make your companion more lively with random messages
+
+ - ᱥᱟᱹᱜᱩᱱ ᱥᱮᱛᱟᱜ!
+ - ᱥᱟᱹᱜᱤᱱ ᱥᱮᱛᱟᱜ, ᱨᱚᱠ ᱨᱮᱭᱟᱜ ᱚᱠᱛᱚ!
+ - ᱟᱹᱰᱤ ᱵᱮᱥ ᱫᱤᱱ ᱠᱟᱱᱟ!
+ - ᱵᱮᱥ ᱫᱤᱱ ᱛᱟᱦᱮᱸᱱ ᱢᱟ!
+ - ᱕ ᱴᱤᱯᱤᱡ ᱱᱮᱯ ᱪᱮᱫᱮᱢ ᱢᱮᱱᱮᱜᱼᱟ?
+ - ᱱᱚᱶᱟ ᱯᱟᱸᱨᱩᱴᱤ ᱵᱚᱱ ᱤᱫᱤᱭᱟ ᱾
+ - Mornings are a fresh canvas for your daily masterpiece.
+ - Embrace the sunrise of opportunity each morning.
+ - In the early light, find the power to illuminate your path.
+ - Your day begins with endless possibilities.
+ - Every sunrise is a reminder that you can start anew.
+ - Awake with purpose, conquer the day ahead.
+ - Your thoughts can be the foundation of great achievements.
+ - The world awakens, and so does your potential.
+ - The first chapter in your daily adventure.
+ - Let your morning routine be the launchpad for success.
+ - Sunrise or not, your potential is always on the rise.
+ - It\'s time for fresh ideas and bold actions.
+ - The early bird catches the worm, but you can catch your dreams.
+ - Find the courage to chase your aspirations.
+ - Rise and shine, for greatness awaits.
+ - The best time to start is now.
+
+
+ - Chase your dreams, not the minutes.
+ - In every moment, a new opportunity.
+ - Embrace the journey, not just the destination.
+ - Today is a gift, that\'s why it\'s called the present.
+ - Let your smile change the world.
+ - Life is a story; make each chapter count.
+ - Find joy in the ordinary moments.
+ - The best is yet to come, keep it up.
+ - Your chance to turn \'what if\' into \'what is.\'
+ - Seize the day, starting right now.
+ - Happiness is a choice you can make any time.
+ - Believe in yourself, even on a sleepy afternoon.
+ - Let your actions speak louder than the clock.
+ - Your life is what you make of it; make it amazing.
+ - Dream big, work hard, and never give up.
+ - You are stronger than you think.
+ - Every setback is a setup for a comeback.
+ - Embrace the journey, not just the destination.
+ - Chase your goals, not your fears.
+ - Your potential is endless; unlock it.
+
+
+ - Embrace the present moment, no matter the hour.
+ - In every dusk, find a new dawn of opportunity.
+ - Life is a journey; enjoy the scenery along the way.
+ - Your attitude shapes your reality.
+ - Your story is still being written, even as the sun sets.
+ - Capture the beauty in the ordinary moments.
+ - It\'s time to reflect and reset.
+ - Choose joy, no matter the time on the clock.
+ - Opportunities don\'t clock out in the evening.
+ - Be the reason someone smiles.
+ - Your potential knows no bounds.
+ - Moments of magic can happen at any hour.
+ - Your life is what you make of it; make it extraordinary.
+ - Stay curious, stay grateful.
+ - Be the change you wish to see in the world.
+ - Believe in yourself; you are capable of amazing things.
+ - The only limit is the one you set for yourself.
+ - Success begins with a single step.
+
+
+ - Seize the night, for it holds its own mysteries.
+ - In the quiet of this evening, find serenity.
+ - Life\'s greatest adventures can start in the darkness.
+ - Your journey continues even after the sun has set.
+ - Let the night be your canvas; paint it with dreams.
+ - In the stillness of this evening, find your inner peace.
+ - Stars shine brightest in the darkest hours.
+ - Dream big, even in the late hours of the day.
+ - Your life is a story waiting to be written.
+ - It\'s time for reflection and renewal.
+ - Late night thoughts can lead to early morning revelations.
+ - Find joy in the stillness of this evening.
+ - The night sky is a vast canvas of dreams.
+ - Your potential knows no bedtime; pursue your passions.
+ - Stay hopeful, even in the darkest hours.
+ - In the midst of chaos, find your inner calm.
+ - Stay positive, work hard, make it happen.
+
+
+ - Nights are the quiet whispers of opportunity.
+ - In the stillness of the night, find your inner strength.
+ - Embrace the darkness, for it holds the keys to your dreams.
+ - The world sleeps, but your potential is wide awake.
+ - The night may be late, but your ambitions are timeless.
+ - Stay curious, stay awake, and let nights guide you.
+ - Find the courage to chase your dreams.
+ - Great things never come from comfort zones.
+ - You have the power to create your own destiny.
+ - Happiness is a choice; choose it every day.
+ - Life is what you make it, so make it count.
+ - Stay focused, stay determined, stay unstoppable.
+ - You are the author of your own story.
+ - Strive for progress, not perfection.
+ - Every day is a new beginning; make it a great one.
+ - Inspire others by being your authentic self.
+ - Your attitude determines your direction.
+
+
+ - ᱚᱜᱭᱟᱱᱛᱟ ᱯᱚᱨᱚᱢᱟᱱᱚᱸᱫᱼᱟ
+ - ᱚᱱᱜᱟ ᱟᱹᱯᱰᱮᱴ ᱯᱷᱞᱟᱥ ᱨᱮᱭᱟᱜ ᱚᱠᱛᱚ ᱦᱩᱭᱮᱱᱟ ᱥᱮ?
+ - Make peace, not war
+ - ᱚ ᱭᱮ, ᱣᱷᱟᱹᱴᱥ ᱟᱹᱯ?
+ - Welcome to real Matrix!
+ - ᱛᱤᱱᱟᱹᱝ ᱜᱟᱱ ᱥᱠᱨᱤᱱᱥᱚᱴ ᱮᱢ ᱤᱫᱤᱮᱫᱟ?
+ - AlphaDroid ᱥᱟᱞᱟᱜ ᱵᱮᱥ ᱡᱤᱱᱥᱤ ᱠᱷᱩᱞᱟᱫᱭ ᱢᱮ
+ - Spread love, not havoc
+ - We didn\'t start the fire!
+ - ᱵᱮᱥ ᱥᱮᱨᱮᱧ ᱞᱟᱹᱜᱤᱫ ᱚᱠᱛᱚ ᱚᱲᱚᱠ ᱢᱮ
+ - \u003C\u003C\u003C\u003C\u003C\u003C\u003C ᱵᱚᱦᱚᱜ
+ - ᱱᱚᱶᱟ ᱧᱮᱞ ᱞᱟᱹᱜᱤᱫ AlphaDroid ᱯᱨᱢᱤᱭᱟᱹᱢ ᱫᱚᱨᱠᱟᱨ ᱛᱟᱢ
+ - ᱭᱩᱱᱤᱠᱚᱨᱱ ᱨᱮᱭᱟᱜ Lineage ᱩᱭᱦᱟᱹᱨ ᱠᱟᱜ ᱢᱮ
+ - ᱵᱚᱲᱦᱟᱣ ᱠᱟᱱ ᱵᱽᱮᱴᱨᱭ ᱡᱤᱣᱚᱱ ᱥᱟᱞᱟᱜ ᱢᱚᱡᱽ ᱢᱮ
+ - ᱜᱨᱟᱩᱸᱰ ᱡᱤᱨᱚ ᱥᱟᱞᱟᱜ ᱮᱦᱚᱵᱚᱜ ᱠᱟᱱᱟ
+ - ᱟᱞᱮ ᱠᱩᱥᱤ ᱟᱢ ᱠᱟᱱᱟᱞᱮ ᱓᱐᱐᱐
+ - Delicious even without Sushi
+ - ᱛᱮᱦᱚᱧ ᱵᱮᱥ ᱡᱤᱱᱤᱥ ᱠᱚᱨᱟᱣ ᱢᱮ
+ - ᱱᱚᱶᱟ ᱫᱚ ᱵᱮᱥ Pixel Experience ᱠᱟᱱᱟ, ᱵᱟ ᱥᱮ?
+ - ᱵᱷᱨᱚᱢ ᱵᱟᱝ ᱜᱮ, ᱚᱥᱚᱞ ᱛᱮ ᱛᱮ ᱥᱟᱹᱜᱩᱱ ᱫᱟᱨᱟᱢ!
+ - ᱟᱢᱟᱜ ᱜᱚᱲᱚ ᱮᱢ ᱞᱟᱹᱜᱤᱫ ᱟᱭᱢᱟ ᱥᱟᱨᱦᱟᱣ
+ - ᱥᱹᱨᱯᱥ ᱞᱟᱹᱜᱤᱫ ᱚᱠᱟ ᱯᱚᱨᱚᱵᱽ ᱦᱚᱸ ᱵᱟᱝᱟ - ᱠᱷᱟᱹᱞᱤ ᱯᱟᱹᱨᱯᱷᱮᱠᱥᱚᱱ!
+ - ᱵᱤᱞᱰᱵᱚᱴ ᱨᱮᱭᱟᱜ ᱵᱮᱥ ᱵᱟᱪᱷᱟᱣ ᱠᱷᱚᱱ ᱢᱤᱫᱴᱟᱹᱝ
+ - ᱟᱢᱟᱜ ᱯᱟᱨᱟᱱᱚᱤᱭᱟ ᱞᱟᱹᱜᱤᱫ ᱥᱟᱱᱤᱴᱭ
+ - Rising in peace
+ - ᱟᱹᱲᱤ ᱫᱩᱞᱟᱹᱲᱤᱭᱟᱹ ᱢᱟᱨᱠᱷᱤ, ᱵᱟ ᱥᱮ?
+ - Evolution ᱫᱚ ᱠᱟᱞᱯᱚᱱᱤᱠ ᱠᱟᱱᱟ, ᱥᱟᱹᱨᱤ ᱥᱮ?
+ - ᱟᱢ ᱩᱱᱤ ᱠᱟᱱᱟᱢ ᱚᱠᱚᱭ ᱫᱚ ᱯᱷᱞᱟᱥ ᱮᱫ ᱟᱭ, ᱟᱞᱚᱢ ᱟᱹᱲᱩᱜᱼᱟᱢ
+ - ᱟᱢᱟᱜ ᱢᱟᱹᱱᱮ ᱨᱮ ᱪᱮᱫ ᱢᱮᱱᱟᱜ-ᱟ?
+ - Expecto Patronum
+ - Wubba Lubba Dub Dub
+ - ᱡᱤᱛᱟᱹᱣ ᱮᱱᱟᱢ ᱡᱤᱛᱟᱹᱣ ᱮᱱᱟᱢ .... ?
+ - rm -rf \/
+ - Pringles aren\'t actually potato chips..
+ - Remember to check device\'s battery level!
+ - Check your email/messages.
+ - Check your to-do list if you have one.
+ - You can disable quickspace via home settings.
+ - ᱠᱩᱥᱤᱟᱫᱽ ᱞᱮᱟᱢ? ᱫᱤᱱ ᱵᱷᱮᱡᱟ ᱞᱮᱢ
+ - ᱵᱮᱥ ᱨᱳᱢ ᱱᱤᱛ ᱦᱟᱹᱵᱤᱡᱽ ᱛᱮ ᱾
+ - Telegram ᱨᱮ ᱦᱟᱭ ᱞᱮᱢ
+ - ᱟᱞᱮ ᱠᱩᱥᱤ ᱟᱢ ᱠᱟᱱᱟᱞᱮ ᱓᱐᱐᱐
+
+ Unable to find calendar or clock activity
+
+ Weather provider
+ Auto
+ Google Smartspace
+ OmniJaws
+ Weather update
+ Display current weather update
+ Requires weather service to be enabled
+ Cloudy
+ Rainy
+ Sunny
+ Stormy
+ Snowy
+ Windy
+ Misty
+ Current location
+ Display current weather location
+ Current condition
+ Display current weather condition summary
+
+ Auto-hide page indicator
+ Hide the page indicator dots automatically
+
+ Background blur depth
+ Set blur depth level for recents and app drawer
+
+ Application info
+ ᱥᱨᱚᱛ
+ System
+ ᱢᱟᱲᱟᱝ ᱟᱹᱯᱰᱮᱴ
+ ᱵᱷᱟᱹᱨᱥᱚᱱ
+ ᱵᱟᱹᱲᱛᱤ
+
+ ᱟᱭᱠᱚᱱ ᱯᱮᱠ
+ ᱢᱩᱞ ᱯᱷᱮᱲᱟᱛ
+ Install more
+ There\'s no available app store
+
+ Wallpaper carousel
+ Quickly switch wallpapers from the long-press menu
+ Apply to lock screen
+ Set the same wallpaper on the lock screen
+
+ Pebble
+ Vessel
+ Pure square
+ Squircle
+ Tapered rect
+ Teardrop
+ Rounded rect
+ Cloudy
+ Cylindrical
+ Flower
+ Heart
+ Hexagon
+ Rounded hexagon
+ iOS Style
+ Leaf
+ Meow
+ Samsung Style
+ Stretched
+
+ Dark app labels
+ Always use dark text for app labels in the app drawer
+
+ App drawer style
+ Normal (bottom sheet)
+ Legacy fullscreen
+ List style
+ OneUI style (paged)
+
+ App drawer sorting
+ Sort alphabetically
+ Sort by install date
+ Sort by usage
+
+ Custom background color
+ Use a custom color for the app drawer background
+ Light mode color
+ Dark mode color
+
+ HSB
+ RGB
+ Hue
+ Saturation
+ Brightness
+ Red
+ Green
+ Blue
+ HEX
+
+ Pixel style search bar
+ Revamped search bar with layered background
+ Outer layer opacity
+
+ Hidden & Protected apps
+ Unlock to manage the hidden and protected apps
+ Authenticate to open %1$s
+ Loading\u2026
+ Please set up a secure lock screen to restrict app access
+ ᱜᱚᱲᱚ
+ Hidden apps and their widgets are hidden from the drawer
+ Protected apps require authentication to be opened from the launcher
+
+ Recents layout style
+ ᱢᱩᱞ ᱯᱷᱮᱲᱟᱛ
+ Scale effect
+ Kami style
+ iOS style
+ OxygenOS style
+
+ Lock/Unlock app
+ Unlocked app in recents
+ Locked app in recents
+
+ Scroll vibration
+
diff --git a/res/values-sk-rSK/cr_strings.xml b/res/values-sk-rSK/cr_strings.xml
new file mode 100644
index 00000000000..2377c59e1fe
--- /dev/null
+++ b/res/values-sk-rSK/cr_strings.xml
@@ -0,0 +1,440 @@
+
+
+
+
+
+
+ Nastavenia domovskej obrazovky
+
+ Domovská obrazovka
+ Nastavenie panelu Google Teraz a ďalších funkcií
+ Ikony
+ Nastaviť veľkosť ikon a ďalšie parametre
+ Zoznam aplikácií
+ Prispôsobiť zoznam aplikácií
+ Nedávne aplikácie
+ Zmeniť štýl obrazovky nedávnych aplikácií
+ Rôzne
+ Ďalšie možnosti
+ Všeobecné
+ Rozhranie
+ QuickSpace
+ Vyhľadávací panel
+ Rýchle akcie
+
+ Možnosti pre vývojárov
+ Povoliť niektoré skryté funkcie na svoje vlastné riziko
+
+ Zamknúť rozloženie
+ Ikony a miniaplikácie nie je možné pridať, odstrániť a presunúť na domovskú obrazovku
+ Ikony a miniaplikácie je možné pridať, odstrániť a presunúť na domovskú obrazovku
+ Nie je možné pridať miniaplikácie na domovskú obrazovku
+
+ Zobraziť menovky ikon na pracovnej ploche
+ Zobraziť menovky ikon v ponuke aplikácii
+
+ Prijatím prejsť do aplikácie Google
+
+ Pri potiahnutí prstom vľavo na hlavnej obrazovke
+
+ Pri potiahnutí prstom vpravo na hlavnej obrazovke
+
+ Use icon customizations in drawer
+ Follow icon customizations used on home screen
+
+ Pozastaviť
+ Pozastaviť %1$s?
+ Upozornenia pre %1$s budú pozastavené
+ Aplikácia je pozastavená
+ %1$s bola pozastavená priamo zo spúšťača
+ Pozastaviť aplikáciu
+ Pracovné aplikácie sú vypnuté
+ Vypnúť pracovné aplikácie
+ Zapnúť pracovné aplikácie
+ Vaše pracovné aplikácie vám nemôžu posielať upozornenia, používať batériu ani pristupovať k vašej polohe
+ Vaše pracovné aplikácie nemôžu prijímať telefonické hovory ani textové správy, odosielať vám upozornenia, používať batériu ani pristupovať k vašej polohe
+
+ Návrhy
+ Pre všetky aplikácie & návrhy dna ploche
+
+ Reštartovať
+ Ak chcete použiť všetky neuložené zmeny nastavení, tak reštartujte launcher ručne
+ Launcher sa reštartuje...
+ Aplikujú sa zmeny, launcher sa reštartuje...
+ Aktualizujú sa komponenty, launcher sa reštartuje...
+
+ Uspať dvojitým poklepaním
+ Double tap empty space on home screen to turn off the screen
+ Vibrovať pri uspaní dvojitým dotykom
+ Poskytnúť hmatovú spätnú väzbu pri geste dvojitého klepnutia pre uspanie
+
+ Potiahnite prstom pre spustenie vyhľadávania
+ Vždy otvárať klávesnicu, keď je zoznam aplikácií viditeľný
+
+ Veľkosť ikony
+
+ Veľkosť písma
+
+ Pre názov aplikácie použiť dva riadky
+
+ Výška riadku
+
+ Hotseat pozadie
+ Pridať priesvitné pozadie do doku aplikácie
+
+ Priehľadnosť pozadia
+
+ Zdvih
+
+ Ukončiť
+ Aplikácia bola ukončená
+
+ Vyhľadať
+ Dokumenty Google
+ Hlasové vyhľadávanie
+ AI mode
+ Vyhľadávanie hudby
+ Stlačením ikony mikrofónu spustite vyhľadávanie hudby
+ Vyhľadávací panel Google
+ Zobraziť vyhľadávací panel v spodnom doku
+ Tematické ikony
+ Použite tematické ikony pre vyhľadávací panel
+ Zaoblenie rohu
+
+ App search bar location
+ Hidden
+ Top
+ Bottom
+
+ Posúvanie tapety
+ Efekt posúvania tapety pre viacero obrazoviek
+
+ Priblíženie tapety
+ Priblížiť alebo oddialiť tapetu pri použití ponuky aplikácií alebo nedávnych aplikácií
+
+ Stavový riadok
+
+ Horný tieň
+
+ %1$s Dostupných | %2$s Celkovo
+ Informácie o pamäti
+ Informácie o ZRAM pamäti
+
+ Snímka obrazovky
+ Vymazať všetko
+ Objektív
+
+ Povoliť krátku paralaxu
+ Namiesto odstrihnutia pozadia povoliť pri menšom počte stránok skrolovanie celého pozadia
+ Vycentrovať jedinú stránku
+ Vycentrovať pozadie len pri použití jednej stránky
+
+ Posuvník
+ Posuvník na boku v zásuvke aplikácií
+
+ Tmavý stavový riadok
+ Vždy použiť tmavý stavový riadok na ploche
+
+ Prehľad udalostí
+ Zobraziť v hornej časti domovskej obrazovky
+
+ Dobré ráno.
+ Dobrý večer
+ Dobrý deň
+ Dobrú noc
+ Dobrý deň
+ Dnes je
+ Je
+
+ Neznámy interpret
+ Prehráva sa
+ Od
+
+ Prehráva sa
+ Zobraziť práve prehrávanú skladbu
+
+ Minimalistický štýl
+ Prejdite na moderný štýl
+
+ Náhodné správy
+ Prekvapte svojho kamaráta pomocou náhodných správ
+
+ - Dobré ráno!
+ - Dobré ráno, čas na rozcvičku!
+ - Aký krásny deň!
+ - Prajeme pekný deň
+ - Čo tak malý 5 minútový spánok?
+ - Poďme na to.
+ - Rána sú čistým plátnom pre tvoje každodenné umelecké diela.
+ - Chop sa príležitosti každé ráno.
+ - V ranom svetle nájdi silu, ktorá Ti osvetlí cestu.
+ - Tvoj deň začína nekonečnými možnosťami.
+ - Každý východ slnka Ti pripomína, že vždy môžeš začať znova.
+ - Prebuď sa s poslaním a dobi deň pre sebou.
+ - Tvoje myšlienky sú základom veľkých úspechov.
+ - Svet sa prebúdza, rovnako ako tvoj potenciál.
+ - Prvá kapitola v Tvojom každodennom dobrodružstve.
+ - Nech je tvoja ranná rutina odrazovým mostíkom k úspechu.
+ - Východ slnka alebo nie, Tvoj potenciál je vždy na vzostupe.
+ - Je čas na nové nápady a odvážne činy.
+ - Ranné vtáča chytí červíka, ale Ty môžeš chytiť svoje sny.
+ - Nájdi odvahu ísť za svojimi túžbami.
+ - Vstaň a zažiar, veľké veci čakajú.
+ - Najlepší čas začať je teraz.
+
+
+ - Nasleduj svoje sny, nie minúty.
+ - V každom okamihu nová príležitosť.
+ - Prijmi cestu, nielen cieľ.
+ - Dnes je dar, preto sa tomu hovorí súčasnosť.
+ - Nechaj svoj úsmev zmeniť svet.
+ - Život je príbeh; nech sa počíta každá kapitola.
+ - Nájdi radosť v obyčajných chvíľach.
+ - To najlepšie ešte len príde, len tak ďalej.
+ - Tvoja šanca premeniť „čo ak“ na „čo je“.
+ - Využi deň a začni hneď teraz.
+ - Šťastie je voľba, ktorú môžeš urobiť kedykoľvek.
+ - Ver si aj počas ospalého popoludnia.
+ - Nechaj svoje činy hovoriť hlasnejšie ako hodiny.
+ - Tvoj život je taký, aký si ho urobíš. Urob ho úžasným.
+ - Snívaj veľa, pracuj tvrdo a nikdy sa nevzdávaj.
+ - Si silnejší ako si myslíš.
+ - Každý neúspech je šanca pre návrat.
+ - Prijmi cestu, nielen cieľ.
+ - Choď za svojimi cieľmi, nie strachmi.
+ - Tvoj potenciál je nekonečný, odomkni ho.
+
+
+ - Uži si súčasný okamih, bez ohľadu na čas.
+ - V každom šere nájdi nový úsvit príležitosti.
+ - Život je cesta, užívaj si scenérie pri ceste.
+ - Tvoj postoj formuje Tvoju realitu.
+ - Tvoj príbeh sa píše stále, aj počas západu slnka.
+ - Zachyť krásu obyčajného.
+ - Je čas zamyslieť sa a zresetovať.
+ - Vyber si radosť, bez ohľadu na čas.
+ - Príležitosti nekončia večer.
+ - Buď dôvodom niekoho úsmevu.
+ - Tvoj potenciál nepozná hranice.
+ - Kúzelné chvíle sa môžu prihodiť hocikedy.
+ - Tvoj život je taký, aký si ho urobíš. Urob ho úžasným.
+ - Zostaň zvedavý, zostaň vďačný.
+ - Buď zmenou, ktorú chceš vidieť vo svete.
+ - Ver si, si schopný úžasných vecí.
+ - Jediný limit je ten, ktorý si stanovíš pre seba.
+ - Úspech začína jediným krokom.
+
+
+ - Uži si noc, ponúka vlastné tajomstvá.
+ - Nájdi pokoj v tichu tohoto večera.
+ - Najväčšie dobrodružstvá môžu začať v temnote.
+ - Tvoja cesta pokračuje aj po západe slnka.
+ - Nech je noc tvojím plátnom, vymaľuj ju snami.
+ - Nájdi svoj vnútorný mier, v kľude tohoto večera.
+ - V najtemnejších časoch žiaria hviezdy najviac.
+ - Snívaj vo veľkom aj v neskorých hodinách.
+ - Tvoj život je príbeh čakajúci na napísanie.
+ - Je čas na zamyslenie a obnovu.
+ - Neskoré večerné myšlienky môžu viesť ku skorým ranným objavom.
+ - Nájdi radosť v kľude tohoto večera.
+ - Nočná obloha je obrovské plátno snov.
+ - Tvoj potenciál nepozná hraníc, nasleduj svoje vášne.
+ - Zachovaj si nádej aj v najtemnejších časoch.
+ - Nájdi svoj vnútorný pokoj uprostred chaosu.
+ - Zostaň pozitívny, tvrdo pracuj, urob to.
+
+
+ - Noci sú tichým šepotom príležitosti.
+ - Nájdi svoj vnútorný mier, v kľude tohoto večera.
+ - Pijmi temnotu, pretože má kľúče k tvojim snom.
+ - Svet spí, ale tvoj potenciál nie.
+ - Môže byť neskoro v noci, ale tvoje ambície nepoznajú čas.
+ - Zostaň zvedavý, zostaň hore a nechaj sa viesť nocou.
+ - Nájdi odvahu ísť za svojimi túžbami.
+ - Veľké vecí nepochádzajú z komfortných zón.
+ - Máš moc vytvárať svoj vlastný osud.
+ - Šťastie je voľba, vyber si ho každý deň.
+ - Život je taký, aký si ho urobíš, tak nech stojí za to.
+ - Zostaň sústredený, zostaň odhodlaný, zostaň nezastaviteľný.
+ - Si autorom svojho vlastného príbehu.
+ - Snaž sa o pokrok, nie o dokonalosť.
+ - Každý deň je nový začiatok, urob ho skvelým.
+ - Inšpiruj ostatných svojím pravým ja.
+ - Tvoj postoj určuje tvoj smer.
+
+
+ - Sladká nevedomosť
+ - Je čas na ďalšiu aktualizáciu?
+ - Urobte mier, nie vojnu
+ - Hej, čo je?
+ - Toto je skutočný Matrix!
+ - Koľko snímok obrazovky urobíš?
+ - S AlphaDroid otvoríš hocijaké vychytávky
+ - Šír lásku, nie chaos
+ - My sme oheň nezaložili!
+ - Čas na dobrú hudbu
+ - \u003C\u003C\u003C\u003C\u003C\u003C\u003C HROT
+ - Pre zobrazenie tohto obsahu je potrebné mať AlphaDroid Premium
+ - Pamätaj na Lineage of the Unicorn
+ - Uži si dlhšiu výdrž batérie
+ - Začíname od piky
+ - Milujeme ťa 3000
+ - Chutné aj bez sushi
+ - Urob dnes niečo pekné
+ - Toto je najlepší Pixel Experience, však?
+ - Žiadne ilúzie, vitaj v realite!
+ - Ďakujem za tvoju podporu
+ - Blbosti tu nemajú miesto - iba dokonalosť!
+ - Jeden z najlepších výberov buildbota
+ - Zdravý rozum pre tvoju paranoju
+ - Vstávajte v mieri
+ - Aká krásna skúsenosť, nemyslíš?
+ - Evolúcia je mýtus, však?
+ - Si to, čo nainštaluješ, nebuď zemiak
+ - Na čo myslíš?
+ - Expecto Patronum
+ - Wubba Lubba Dub Dub
+ - Víťaz Víťaz ....?
+ - rm -rf \/
+ - Pringles v skutočnosti nie sú zemiakové chipsy..
+ - Vždy kontroluj stav batérie!
+ - Skontroluj svoje emaily/správy.
+ - Skontroluj si zoznam úloh, ak nejaký máš.
+ - Quickspace môžeš zakázať v nastaveniach domovskej obrazovky.
+ - Máte nás radi? Pošlite nám pivo
+ - Úplne najlepšia ROM.
+ - Pozdrav nás na Telegrame
+ - Milujeme ťa 3000
+
+ Aktivita kalendára alebo hodín sa nenašla
+
+ Poskytovateľ počasia
+ Automaticky
+ Google Smartspace
+ OmniJaws
+ Aktualizácia počasia
+ Zobrazenie aktuálne počasie
+ Vyžaduje aktiváciu meteorologickej služby
+ Zamračené
+ Daždivo
+ Jasno, ani mráčik
+ Búrka
+ Sneženie
+ Veterno
+ Hmla
+ Aktuálna poloha
+ Zobraziť polohu aktuálneho počasia
+ Aktuálne podmienky
+ Zobraziť súhrn aktuálneho počasia
+
+ Automaticky skryť indikátor stránky
+ Automaticky skryť bodky indikátora stránky
+
+ Sila rozmazania pozadia
+ Nastaviť úroveň hĺbky rozmazania pre nedávne aplikácie a ponuku aplikácií
+
+ Informácie o aplikácii
+ Zdroj
+ Systém
+ Posledná aktualizácia
+ Verzia
+ Viac
+
+ Balíček ikon
+ Predvolené
+ Inštalovať ďalšie
+ Nie je dostupný žiadny obchod s aplikáciami
+
+ Wallpaper carousel
+ Quickly switch wallpapers from the long-press menu
+ Apply to lock screen
+ Set the same wallpaper on the lock screen
+
+ Pebble
+ Vessel
+ Pure square
+ Squircle
+ Tapered rect
+ Teardrop
+ Rounded rect
+ Zamračené
+ Cylindrical
+ Flower
+ Heart
+ Hexagon
+ Rounded hexagon
+ iOS Style
+ Leaf
+ Meow
+ Samsung Style
+ Stretched
+
+ Dark app labels
+ Always use dark text for app labels in the app drawer
+
+ App drawer style
+ Normal (bottom sheet)
+ Legacy fullscreen
+ List style
+ OneUI style (paged)
+
+ App drawer sorting
+ Sort alphabetically
+ Sort by install date
+ Sort by usage
+
+ Custom background color
+ Use a custom color for the app drawer background
+ Light mode color
+ Dark mode color
+
+ HSB
+ RGB
+ Hue
+ Saturation
+ Brightness
+ Red
+ Green
+ Blue
+ HEX
+
+ Pixel style search bar
+ Revamped search bar with layered background
+ Outer layer opacity
+
+ Skryté a chránené aplikácie
+ Odomknite pre správu skrytých a chránených aplikácií
+ Pre otvorenie %1$s potvrďte svoju totožnosť
+ Načítavanie\u2026
+ Pre obmedzenie prístupu k aplikácii prosím nastavte zabezpečenú obrazovku uzamknutia
+ Nápoveda
+ Skryté aplikácie a ich widgety se nezobrazujú v zozname aplikácií
+ Chránené aplikácie vyžadujú pre otvorenie zo spúšťača overenie
+
+ Recents layout style
+ Predvolené
+ Scale effect
+ Kami style
+ iOS style
+ OxygenOS style
+
+ Lock/Unlock app
+ Unlocked app in recents
+ Locked app in recents
+
+ Scroll vibration
+
diff --git a/res/values-sq-rAL/alpha_strings.xml b/res/values-sq-rAL/alpha_strings.xml
new file mode 100644
index 00000000000..e8660a28519
--- /dev/null
+++ b/res/values-sq-rAL/alpha_strings.xml
@@ -0,0 +1,44 @@
+
+
+
+
+ Lock/Unlock app
+ Unlocked app in recents
+ Locked app in recents
+
+ Recents layout style
+ Stock
+ Kami Style
+ iOS Style
+ OxygenOS Style
+
+ - Ignorance is Bliss
+ - Make love, not war
+ - Oh hey, what\'s up?
+ - We love you 3000
+ - Thank you for your support
+ - One of the buildbot\'s best picks
+ - Pringles aren\'t actually potato chips..
+ - You can disable quickspace via home settings.
+ - I got nothing ¯\\_(ツ)_/¯
+
+
+ - I never apologize. I\'m sorry but that\'s the way I am.
+ - All my life, I\'ve had one dream: to achieve my many goals.
+ - Good drink... good meat... good God, let\'s eat!
+ - You\'ll have to speak up. I\'m wearing a towel.
+ - Change the channel, Marge...
+ - Roads are just a suggestion Marge, just like pants.
+ - It takes two to lie - one to lie and one to listen.
+ - Shut up, Flanders!
+ - I\'m not normally a praying man, but if you\'re up there, please save me, Superman.
+ - It\'s so simple to be wise... just think of something stupid to say and then don\'t say it.
+ - Stupid sexy Flanders...
+ - I hope I didn\'t brain my damage.
+ - Operator, give me the number for 911!
+ - Don\'t blame me, I voted for Kodos.
+
+
diff --git a/res/values-sr-rCS/cr_strings.xml b/res/values-sr-rCS/cr_strings.xml
new file mode 100644
index 00000000000..248f31cf35f
--- /dev/null
+++ b/res/values-sr-rCS/cr_strings.xml
@@ -0,0 +1,440 @@
+
+
+
+
+
+
+ Home settings
+
+ Početni ekran
+ Postavi Google now panel i više
+ Ikone
+ Set icon size and more
+ Ladica sa aplikacijama
+ Prilagodite ladicu aplikacija
+ Nedavne aplikacije
+ Revamp the overview screen
+ Razno
+ Druge opcije
+ Opšte
+ Interfejs
+ Quickspace
+ Search bar
+ Quick actions
+
+ Developer options
+ Enable some hidden features at your own risk
+
+ Lock layout
+ Icons and widgets can\'t be added, removed and moved on the homescreen
+ Icons and widgets can be added, removed and moved on the homescreen
+ It\'s not possible to add widgets to the home screen
+
+ Icon labels on desktop
+ Icon labels in drawer
+
+ Swipe to access Google app
+
+ When you swipe right from main home screen
+
+ When you swipe left from main home screen
+
+ Use icon customizations in drawer
+ Follow icon customizations used on home screen
+
+ Pause
+ Pause %1$s?
+ Notifications for %1$s will be paused
+ App paused
+ %1$s was directly paused from the launcher
+ Pause app
+ Work apps are off
+ Turn off work apps
+ Turn on work apps
+ Your work apps cannot send you notifications, use your battery, or access your location
+ Your work apps cannot receive phone calls or text messages, send you notifications, use your battery, or access your location
+
+ Suggestions
+ For All Apps & Home screen suggestions
+
+ Restart
+ Restart the launcher manually to apply any pending settings
+ Restarting launcher...
+ Restarting launcher to apply changes...
+ Restarting launcher to update launcher components...
+
+ Dvostruki dodir za spavanje
+ Double tap empty space on home screen to turn off the screen
+ Vibrate on double tap to sleep
+ Provide haptic feedback on double tap to sleep gesture
+
+ Swipe to start search
+ Open keyboard whenever app drawer becomes visible
+
+ Icon size
+
+ Veličina fonta
+
+ Use two lines for app label
+
+ Row height
+
+ Hotseat background
+ Add translucent background to the app dock
+
+ Prozirnost pozadine
+
+ Stroke
+
+ Prisilno zatvori aplikaciju
+ Aplikacija je prisilno zatvorena
+
+ Pretraga
+ Google Lens
+ Glasovna pretraga
+ AI mode
+ Music search
+ Start music search on pressing mic icon
+ Google search bar
+ Search bar in the bottom dock
+ Themed icons
+ Use themed icons for search bar
+ Corner radius
+
+ App search bar location
+ Hidden
+ Top
+ Bottom
+
+ Wallpaper scrolling
+ Wallpaper scrolling effect for multiple screens
+
+ Wallpaper zooming
+ Zoom in or out the wallpaper when using drawer or recent apps
+
+ Statusna trka
+
+ Top shadow
+
+ %1$s available | %2$s
+ Memory info
+ ZRAM info
+
+ Slika ekrana
+ Počisti
+ Lens
+
+ Allow short parallax
+ Enable full wallpaper scroll effect on smaller numbers of pages instead of cropping the wallpaper
+ Single page center
+ Center wallpaper if only using a single page
+
+ Scrollbar
+ Scrollbar on side of the app drawer
+
+ Dark status bar
+ Always use dark status bar on home screen
+
+ Na prvi pogled
+ Show at the top of your home screen
+
+ Dobro jutro.
+ Good evening.
+ Good afternoon.
+ Laku noć.
+ Good day.
+ Today is
+ It\'s
+
+ Unknown Artist
+ Now playing
+ By
+
+ Now playing
+ Show the song you\'re playing
+
+ Minimalistic style
+ Switch to modern style
+
+ Random messages
+ Make your companion more lively with random messages
+
+ - Dobro jutro!
+ - Dobro jutro, vreme je za akciju!
+ - Kako divan dan!
+ - Prijatan dan!
+ - Šta kažete na petominutnu dremku?
+ - Hajmo po ovaj hleb.
+ - Mornings are a fresh canvas for your daily masterpiece.
+ - Embrace the sunrise of opportunity each morning.
+ - In the early light, find the power to illuminate your path.
+ - Your day begins with endless possibilities.
+ - Every sunrise is a reminder that you can start anew.
+ - Awake with purpose, conquer the day ahead.
+ - Your thoughts can be the foundation of great achievements.
+ - The world awakens, and so does your potential.
+ - The first chapter in your daily adventure.
+ - Let your morning routine be the launchpad for success.
+ - Sunrise or not, your potential is always on the rise.
+ - It\'s time for fresh ideas and bold actions.
+ - The early bird catches the worm, but you can catch your dreams.
+ - Find the courage to chase your aspirations.
+ - Rise and shine, for greatness awaits.
+ - The best time to start is now.
+
+
+ - Chase your dreams, not the minutes.
+ - In every moment, a new opportunity.
+ - Embrace the journey, not just the destination.
+ - Today is a gift, that\'s why it\'s called the present.
+ - Let your smile change the world.
+ - Life is a story; make each chapter count.
+ - Find joy in the ordinary moments.
+ - The best is yet to come, keep it up.
+ - Your chance to turn \'what if\' into \'what is.\'
+ - Seize the day, starting right now.
+ - Happiness is a choice you can make any time.
+ - Believe in yourself, even on a sleepy afternoon.
+ - Let your actions speak louder than the clock.
+ - Your life is what you make of it; make it amazing.
+ - Dream big, work hard, and never give up.
+ - You are stronger than you think.
+ - Every setback is a setup for a comeback.
+ - Embrace the journey, not just the destination.
+ - Chase your goals, not your fears.
+ - Your potential is endless; unlock it.
+
+
+ - Embrace the present moment, no matter the hour.
+ - In every dusk, find a new dawn of opportunity.
+ - Life is a journey; enjoy the scenery along the way.
+ - Your attitude shapes your reality.
+ - Your story is still being written, even as the sun sets.
+ - Capture the beauty in the ordinary moments.
+ - It\'s time to reflect and reset.
+ - Choose joy, no matter the time on the clock.
+ - Opportunities don\'t clock out in the evening.
+ - Be the reason someone smiles.
+ - Your potential knows no bounds.
+ - Moments of magic can happen at any hour.
+ - Your life is what you make of it; make it extraordinary.
+ - Stay curious, stay grateful.
+ - Be the change you wish to see in the world.
+ - Believe in yourself; you are capable of amazing things.
+ - The only limit is the one you set for yourself.
+ - Success begins with a single step.
+
+
+ - Seize the night, for it holds its own mysteries.
+ - In the quiet of this evening, find serenity.
+ - Life\'s greatest adventures can start in the darkness.
+ - Your journey continues even after the sun has set.
+ - Let the night be your canvas; paint it with dreams.
+ - In the stillness of this evening, find your inner peace.
+ - Stars shine brightest in the darkest hours.
+ - Dream big, even in the late hours of the day.
+ - Your life is a story waiting to be written.
+ - It\'s time for reflection and renewal.
+ - Late night thoughts can lead to early morning revelations.
+ - Find joy in the stillness of this evening.
+ - The night sky is a vast canvas of dreams.
+ - Your potential knows no bedtime; pursue your passions.
+ - Stay hopeful, even in the darkest hours.
+ - In the midst of chaos, find your inner calm.
+ - Stay positive, work hard, make it happen.
+
+
+ - Nights are the quiet whispers of opportunity.
+ - In the stillness of the night, find your inner strength.
+ - Embrace the darkness, for it holds the keys to your dreams.
+ - The world sleeps, but your potential is wide awake.
+ - The night may be late, but your ambitions are timeless.
+ - Stay curious, stay awake, and let nights guide you.
+ - Find the courage to chase your dreams.
+ - Great things never come from comfort zones.
+ - You have the power to create your own destiny.
+ - Happiness is a choice; choose it every day.
+ - Life is what you make it, so make it count.
+ - Stay focused, stay determined, stay unstoppable.
+ - You are the author of your own story.
+ - Strive for progress, not perfection.
+ - Every day is a new beginning; make it a great one.
+ - Inspire others by being your authentic self.
+ - Your attitude determines your direction.
+
+
+ - Neznanje je Sreća
+ - Da li je već vreme za novo ažuriranje?
+ - Make peace, not war
+ - Zdravo, šta ima?
+ - Welcome to real Matrix!
+ - Koliko snimaka ekrana napravite?
+ - Otkrijte dobre stvari uz AlphaDroid
+ - Spread love, not havoc
+ - We didn\'t start the fire!
+ - Vreme je za malo muzike
+ - \u003C\u003C\u003C\u003C\u003C\u003C\u003C GLAVA
+ - Treba Vam AlphaDroid Premium kako biste videli ovo
+ - Setite se Loze Jednoroga
+ - Uživajte u Xtendnom trajanju baterije
+ - Počinjemo od nule
+ - Volimo Vas 3000
+ - Delicious even without Sushi
+ - Radite nešto lepo danas
+ - Ovo je najbolji Iskustvo Pixela, zar ne?
+ - Nema iluzija, dobrodošli u realnost!
+ - Hvala Vam na podršci
+ - Nema zabave za Derpove - samo perfekcije!
+ - Jedan od najboljih odabira buildbota
+ - Zdrav razum za Vašu Paranoju
+ - Rising in peace
+ - Kako lepo iskustvo, zar ne?
+ - Evolucija je mit, zar ne?
+ - Ti si ono što flešuješ, nemoj biti Krompir
+ - Šta Vam je na umu?
+ - Expecto Patronum
+ - Wubba Lubba Dub Dub
+ - Winner Winner ....?
+ - rm -rf \/
+ - Pringles aren\'t actually potato chips..
+ - Remember to check device\'s battery level!
+ - Check your email/messages.
+ - Check your to-do list if you have one.
+ - You can disable quickspace via home settings.
+ - Sviđamo Vam se? Pošaljite nam pivo
+ - Najbolji Rom Ikada.
+ - Javite nam se na Telegramu
+ - Volimo Vas 3000
+
+ Unable to find calendar or clock activity
+
+ Weather provider
+ Auto
+ Google Smartspace
+ OmniJaws
+ Weather update
+ Display current weather update
+ Requires weather service to be enabled
+ Cloudy
+ Rainy
+ Sunny
+ Stormy
+ Snowy
+ Windy
+ Misty
+ Current location
+ Display current weather location
+ Current condition
+ Display current weather condition summary
+
+ Auto-hide page indicator
+ Hide the page indicator dots automatically
+
+ Background blur depth
+ Set blur depth level for recents and app drawer
+
+ Application info
+ Izvor
+ System
+ Poslеdnjе ažuriranjе
+ Verzija
+ Više
+
+ Paket ikona
+ Podrazumevano
+ Install more
+ There\'s no available app store
+
+ Wallpaper carousel
+ Quickly switch wallpapers from the long-press menu
+ Apply to lock screen
+ Set the same wallpaper on the lock screen
+
+ Pebble
+ Vessel
+ Pure square
+ Squircle
+ Tapered rect
+ Teardrop
+ Rounded rect
+ Cloudy
+ Cylindrical
+ Flower
+ Heart
+ Hexagon
+ Rounded hexagon
+ iOS Style
+ Leaf
+ Meow
+ Samsung Style
+ Stretched
+
+ Dark app labels
+ Always use dark text for app labels in the app drawer
+
+ App drawer style
+ Normal (bottom sheet)
+ Legacy fullscreen
+ List style
+ OneUI style (paged)
+
+ App drawer sorting
+ Sort alphabetically
+ Sort by install date
+ Sort by usage
+
+ Custom background color
+ Use a custom color for the app drawer background
+ Light mode color
+ Dark mode color
+
+ HSB
+ RGB
+ Hue
+ Saturation
+ Brightness
+ Red
+ Green
+ Blue
+ HEX
+
+ Pixel style search bar
+ Revamped search bar with layered background
+ Outer layer opacity
+
+ Hidden & Protected apps
+ Unlock to manage the hidden and protected apps
+ Authenticate to open %1$s
+ Loading\u2026
+ Please set up a secure lock screen to restrict app access
+ Pomoć
+ Hidden apps and their widgets are hidden from the drawer
+ Protected apps require authentication to be opened from the launcher
+
+ Recents layout style
+ Podrazumevano
+ Scale effect
+ Kami style
+ iOS style
+ OxygenOS style
+
+ Lock/Unlock app
+ Unlocked app in recents
+ Locked app in recents
+
+ Scroll vibration
+
diff --git a/res/values-sr-rSP/cr_strings.xml b/res/values-sr-rSP/cr_strings.xml
new file mode 100644
index 00000000000..36893074d08
--- /dev/null
+++ b/res/values-sr-rSP/cr_strings.xml
@@ -0,0 +1,357 @@
+
+
+
+
+
+
+ Home settings
+
+ Home screen
+ Set Google now panel and more
+ Icons
+ Set icon size and more
+ App drawer
+ Customize your app drawer
+ Recents
+ Revamp the overview screen
+ Miscellaneous
+ Other options
+ General
+ Interface
+ Quickspace
+ Search bar
+ Quick actions
+
+ Developer options
+ Enable some hidden features at your own risk
+
+ Lock layout
+ Icons and widgets can\'t be added, removed and moved on the homescreen
+ Icons and widgets can be added, removed and moved on the homescreen
+ It\'s not possible to add widgets to the home screen
+
+ Icon labels on desktop
+ Icon labels in drawer
+
+ Swipe to access Google app
+
+ When you swipe right from main home screen
+
+ When you swipe left from main home screen
+
+ Hidden & Protected apps
+ Unlock to manage the hidden and protected apps
+ Authenticate to open %1$s
+ Loading\u2026
+ Please set up a secure lock screen to restrict app access
+ Help
+ Hidden apps and their widgets are hidden from the drawer
+ Protected apps require authentication to be opened from the launcher
+
+ Suggestions
+ For All Apps & Home screen suggestions
+
+ Pause
+ Pause %1$s?
+ Notifications for %1$s will be paused
+ App paused
+ %1$s was directly paused from the launcher
+ Pause app
+
+ Double tap to sleep
+ Double tap on empty space for screen off
+
+ Value: %s
+ by default
+ Default value: %s\nLong tap to set
+ Default value is set
+
+ Icon size
+
+ Font size
+
+ Use two lines for app label
+
+ Kill
+ App killed
+
+ Use taskbar
+ For opening and switching apps anywhere
+
+ Search
+ Google Lens
+ Voice search
+ Music search
+ Start music search on pressing mic icon
+ Google search bar
+ Search bar in the bottom dock
+ Themed icons
+ Use themed icons for search bar
+ Corner radius
+
+ Hotseat background
+ Add translucent background to the app dock
+
+ Background opacity
+
+ Wallpaper scrolling
+ Wallpaper scrolling effect for multiple screens
+
+ Wallpaper zooming
+ Zoom in or out the wallpaper when using drawer or recent apps
+
+ Status Bar
+
+ Top shadow
+
+ Row height
+
+ %1$s available | %2$s
+ Memory info
+
+ Background blur depth
+ Set blur depth level for recents and app drawer
+
+ Screenshot
+ Clear all
+ Lens
+
+ Shake phone to clear all tasks
+
+ Themed icons
+ Follow themed icons used on home screen
+
+ Source
+ System
+ Last update
+ Version
+ More
+
+ Icon pack
+ Default
+ Install more
+ There\'s no available app store
+
+ Allow short parallax
+ Enable full wallpaper scroll effect on smaller numbers of pages instead of cropping the wallpaper
+ Single page center
+ Center wallpaper if only using a single page
+
+ App search bar
+ Search bar on top of the app drawer
+
+ Restart
+ Restart the launcher manually to apply any pending settings
+ Restarting launcher...
+ Restarting launcher to apply changes...
+ Restarting launcher to update launcher components...
+
+ At A Glance
+ Show at the top of your home screen
+
+ Welcome to AlphaDroid!
+ AlphaDroid time!
+
+ - Thank you for choosing us
+ - Loving us? Send us a beer
+ - Best Rom Ever.
+ - Say hi to us on Telegram
+ - Tap here to begin
+ - We love you 3000
+
+
+ Good morning.
+ Good evening.
+ Good afternoon.
+ Good night.
+ Good day.
+ Today is
+ It\'s
+
+ Unknown Artist
+ Now playing
+ By
+
+ Now playing
+ Show the song you\'re playing
+
+ Extended style
+ Switch to extended style
+
+ Random messages
+ Make your companion more lively with random messages
+
+ - Good morning!
+ - Good morning, time to rock!
+ - What a beautiful day!
+ - Have a nice day!
+ - What about a small 5 minutes nap?
+ - Let\'s get this bread.
+ - Mornings are a fresh canvas for your daily masterpiece.
+ - Embrace the sunrise of opportunity each morning.
+ - In the early light, find the power to illuminate your path.
+ - Your day begins with endless possibilities.
+ - Every sunrise is a reminder that you can start anew.
+ - Awake with purpose, conquer the day ahead.
+ - Your thoughts can be the foundation of great achievements.
+ - The world awakens, and so does your potential.
+ - The first chapter in your daily adventure.
+ - Let your morning routine be the launchpad for success.
+ - Sunrise or not, your potential is always on the rise.
+ - It\'s time for fresh ideas and bold actions.
+ - The early bird catches the worm, but you can catch your dreams.
+ - Find the courage to chase your aspirations.
+ - Rise and shine, for greatness awaits.
+ - The best time to start is now.
+
+
+ - Chase your dreams, not the minutes.
+ - In every moment, a new opportunity.
+ - Embrace the journey, not just the destination.
+ - Today is a gift, that\'s why it\'s called the present.
+ - Let your smile change the world.
+ - Life is a story; make each chapter count.
+ - Find joy in the ordinary moments.
+ - The best is yet to come, keep it up.
+ - Your chance to turn \'what if\' into \'what is.\'
+ - Seize the day, starting right now.
+ - Happiness is a choice you can make any time.
+ - Believe in yourself, even on a sleepy afternoon.
+ - Let your actions speak louder than the clock.
+ - Your life is what you make of it; make it amazing.
+ - Dream big, work hard, and never give up.
+ - You are stronger than you think.
+ - Every setback is a setup for a comeback.
+ - Embrace the journey, not just the destination.
+ - Chase your goals, not your fears.
+ - Your potential is endless; unlock it.
+
+
+ - Embrace the present moment, no matter the hour.
+ - In every dusk, find a new dawn of opportunity.
+ - Life is a journey; enjoy the scenery along the way.
+ - Your attitude shapes your reality.
+ - Your story is still being written, even as the sun sets.
+ - Capture the beauty in the ordinary moments.
+ - It\'s time to reflect and reset.
+ - Choose joy, no matter the time on the clock.
+ - Opportunities don\'t clock out in the evening.
+ - Be the reason someone smiles.
+ - Your potential knows no bounds.
+ - Moments of magic can happen at any hour.
+ - Your life is what you make of it; make it extraordinary.
+ - Stay curious, stay grateful.
+ - Be the change you wish to see in the world.
+ - Believe in yourself; you are capable of amazing things.
+ - The only limit is the one you set for yourself.
+ - Success begins with a single step.
+
+
+ - Seize the night, for it holds its own mysteries.
+ - In the quiet of this evening, find serenity.
+ - Life\'s greatest adventures can start in the darkness.
+ - Your journey continues even after the sun has set.
+ - Let the night be your canvas; paint it with dreams.
+ - In the stillness of this evening, find your inner peace.
+ - Stars shine brightest in the darkest hours.
+ - Dream big, even in the late hours of the day.
+ - Your life is a story waiting to be written.
+ - It\'s time for reflection and renewal.
+ - Late night thoughts can lead to early morning revelations.
+ - Find joy in the stillness of this evening.
+ - The night sky is a vast canvas of dreams.
+ - Your potential knows no bedtime; pursue your passions.
+ - Stay hopeful, even in the darkest hours.
+ - In the midst of chaos, find your inner calm.
+ - Stay positive, work hard, make it happen.
+
+
+ - Nights are the quiet whispers of opportunity.
+ - In the stillness of the night, find your inner strength.
+ - Embrace the darkness, for it holds the keys to your dreams.
+ - The world sleeps, but your potential is wide awake.
+ - The night may be late, but your ambitions are timeless.
+ - Stay curious, stay awake, and let nights guide you.
+ - Find the courage to chase your dreams.
+ - Great things never come from comfort zones.
+ - You have the power to create your own destiny.
+ - Happiness is a choice; choose it every day.
+ - Life is what you make it, so make it count.
+ - Stay focused, stay determined, stay unstoppable.
+ - You are the author of your own story.
+ - Strive for progress, not perfection.
+ - Every day is a new beginning; make it a great one.
+ - Inspire others by being your authentic self.
+ - Your attitude determines your direction.
+
+
+ - Ignorance is Bliss
+ - Is it time to flash another update already?
+ - Make peace, not war
+ - Oh hey, what\'s up?
+ - Focus on your tasks
+ - How many screenshots do you take?
+ - Open goodness with AlphaDroid
+ - Spread love, not havoc
+ - We didn\'t start the fire!
+ - Time for some good music
+ - \u003C\u003C\u003C\u003C\u003C\u003C\u003C HEAD
+ - You need AlphaDroid Premium to see this
+ - Remember the Lineage of the Unicorn
+ - Enjoy Xtended battery life
+ - Starting from the ground zero
+ - We love you 3000
+ - Delicious even without Sushi
+ - Do something nice today
+ - This is best Pixel Experience, isn\'t it?
+ - No illusions, welcome to reality!
+ - Thank you for your support
+ - No festival for Derps - only perfection!
+ - One of the buildbot\'s best picks
+ - Sanity for your Paranoia
+ - Try Ice Cold desserts
+ - What a lovely experience, isn\'t it?
+ - Evolution is a myth, right?
+ - You are what you flash, don\'t be Potato
+ - What\'s on your mind?
+ - Expecto Patronum
+ - Wubba Lubba Dub Dub
+ - Winner Winner ....?
+ - rm -rf \/
+ - Pringles aren\'t actually potato chips..
+ - Remember to check device\'s battery level!
+ - Check your email/messages.
+ - Check your to-do list if you have one.
+ - You can disable quickspace via home settings.
+
+
+ Weather update
+ Display current weather update
+ Requires weather service to be enabled
+ Cloudy
+ Rainy
+ Sunny
+ Stormy
+ Snowy
+ Windy
+ Misty
+ Current location
+ Display current weather location
+ Current condition
+ Display current weather condition summary
+ Unable to find calendar or clock activity
+
diff --git a/res/values-sw600dp/dimens.xml b/res/values-sw600dp/dimens.xml
index 23828a03e30..b0032c84b4b 100644
--- a/res/values-sw600dp/dimens.xml
+++ b/res/values-sw600dp/dimens.xml
@@ -24,9 +24,6 @@
32dp
-
- 0dp
-
75dp
62dp
diff --git a/res/values-ta-rIN/cr_strings.xml b/res/values-ta-rIN/cr_strings.xml
new file mode 100644
index 00000000000..106c72e7fab
--- /dev/null
+++ b/res/values-ta-rIN/cr_strings.xml
@@ -0,0 +1,440 @@
+
+
+
+
+
+
+ Home settings
+
+ முகப்புத்திரை
+ கூகுள் நவ் பேனல் மற்றும் பல
+ சின்னங்கள்
+ Set icon size and more
+ பயன்பாட்டு அலமாரி
+ உங்கள் பயன்பாடு தட்டு தனிப்பயனாக்கு
+ சமீபத்திய
+ Revamp the overview screen
+ இதர அமைப்பு
+ மற்ற தேர்வுகள்
+ பொது
+ இடைமுகம்
+ Quickspace
+ Search bar
+ Quick actions
+
+ Developer options
+ Enable some hidden features at your own risk
+
+ Lock layout
+ Icons and widgets can\'t be added, removed and moved on the homescreen
+ Icons and widgets can be added, removed and moved on the homescreen
+ It\'s not possible to add widgets to the home screen
+
+ Icon labels on desktop
+ Icon labels in drawer
+
+ Swipe to access Google app
+
+ When you swipe right from main home screen
+
+ When you swipe left from main home screen
+
+ Use icon customizations in drawer
+ Follow icon customizations used on home screen
+
+ இடைநிறுத்து
+ Pause %1$s?
+ Notifications for %1$s will be paused
+ App paused
+ %1$s was directly paused from the launcher
+ Pause app
+ Work apps are off
+ Turn off work apps
+ Turn on work apps
+ Your work apps cannot send you notifications, use your battery, or access your location
+ Your work apps cannot receive phone calls or text messages, send you notifications, use your battery, or access your location
+
+ Suggestions
+ For All Apps & Home screen suggestions
+
+ மீண்டும் தொடங்கு
+ Restart the launcher manually to apply any pending settings
+ Restarting launcher...
+ Restarting launcher to apply changes...
+ Restarting launcher to update launcher components...
+
+ உறங்குவதற்கு இருமுறை தட்டு
+ Double tap empty space on home screen to turn off the screen
+ Vibrate on double tap to sleep
+ Provide haptic feedback on double tap to sleep gesture
+
+ Swipe to start search
+ Open keyboard whenever app drawer becomes visible
+
+ Icon size
+
+ எழுத்துரு அளவு
+
+ Use two lines for app label
+
+ Row height
+
+ Hotseat background
+ Add translucent background to the app dock
+
+ பின்னணி ஒளிபுகாத்தன்மை
+
+ Stroke
+
+ கொல்
+ பயன்பாடு கொல்லப்பட்டது
+
+ தேடு
+ Google Lens
+ குரல் தேடல்
+ AI mode
+ Music search
+ Start music search on pressing mic icon
+ Google தேடல்
+ Search bar in the bottom dock
+ Themed icons
+ Use themed icons for search bar
+ Corner radius
+
+ App search bar location
+ Hidden
+ Top
+ Bottom
+
+ Wallpaper scrolling
+ Wallpaper scrolling effect for multiple screens
+
+ Wallpaper zooming
+ Zoom in or out the wallpaper when using drawer or recent apps
+
+ நிலைமை பட்டை
+
+ Top shadow
+
+ %1$s Available | %2$s Total
+ Memory info
+ ZRAM info
+
+ திரைப்பிடிப்பு
+ அனைத்தையும் அழி
+ Lens
+
+ Allow short parallax
+ Enable full wallpaper scroll effect on smaller numbers of pages instead of cropping the wallpaper
+ Single page center
+ Center wallpaper if only using a single page
+
+ Scrollbar
+ Scrollbar on side of the app drawer
+
+ Dark status bar
+ Always use dark status bar on home screen
+
+ முகப்புத் தகவல்
+ Show at the top of your home screen
+
+ Good morning.
+ Good evening.
+ Good afternoon.
+ Good night.
+ Good day.
+ Today is
+ It\'s
+
+ Unknown Artist
+ Now playing
+ By
+
+ Now playing
+ Show the song you\'re playing
+
+ Minimalistic style
+ Switch to modern style
+
+ Random messages
+ Make your companion more lively with random messages
+
+ - காலை வணக்கம்!
+ - காலை வணக்கம், உற்சாகத்திற்கான நேரம்!
+ - என்ன ஒரு அழகான நாள்!
+ - ஒரு நல்ல நாள்!
+ - ஒரு சிறிய 5 நிமிட தூக்கத்தைப் பற்றி என்ன?
+ - இந்த இட்லியைப் பெறுவோம்.
+ - Mornings are a fresh canvas for your daily masterpiece.
+ - Embrace the sunrise of opportunity each morning.
+ - In the early light, find the power to illuminate your path.
+ - Your day begins with endless possibilities.
+ - Every sunrise is a reminder that you can start anew.
+ - Awake with purpose, conquer the day ahead.
+ - Your thoughts can be the foundation of great achievements.
+ - The world awakens, and so does your potential.
+ - The first chapter in your daily adventure.
+ - Let your morning routine be the launchpad for success.
+ - Sunrise or not, your potential is always on the rise.
+ - It\'s time for fresh ideas and bold actions.
+ - The early bird catches the worm, but you can catch your dreams.
+ - Find the courage to chase your aspirations.
+ - Rise and shine, for greatness awaits.
+ - The best time to start is now.
+
+
+ - Chase your dreams, not the minutes.
+ - In every moment, a new opportunity.
+ - Embrace the journey, not just the destination.
+ - Today is a gift, that\'s why it\'s called the present.
+ - Let your smile change the world.
+ - Life is a story; make each chapter count.
+ - Find joy in the ordinary moments.
+ - The best is yet to come, keep it up.
+ - Your chance to turn \'what if\' into \'what is.\'
+ - Seize the day, starting right now.
+ - Happiness is a choice you can make any time.
+ - Believe in yourself, even on a sleepy afternoon.
+ - Let your actions speak louder than the clock.
+ - Your life is what you make of it; make it amazing.
+ - Dream big, work hard, and never give up.
+ - You are stronger than you think.
+ - Every setback is a setup for a comeback.
+ - Embrace the journey, not just the destination.
+ - Chase your goals, not your fears.
+ - Your potential is endless; unlock it.
+
+
+ - Embrace the present moment, no matter the hour.
+ - In every dusk, find a new dawn of opportunity.
+ - Life is a journey; enjoy the scenery along the way.
+ - Your attitude shapes your reality.
+ - Your story is still being written, even as the sun sets.
+ - Capture the beauty in the ordinary moments.
+ - It\'s time to reflect and reset.
+ - Choose joy, no matter the time on the clock.
+ - Opportunities don\'t clock out in the evening.
+ - Be the reason someone smiles.
+ - Your potential knows no bounds.
+ - Moments of magic can happen at any hour.
+ - Your life is what you make of it; make it extraordinary.
+ - Stay curious, stay grateful.
+ - Be the change you wish to see in the world.
+ - Believe in yourself; you are capable of amazing things.
+ - The only limit is the one you set for yourself.
+ - Success begins with a single step.
+
+
+ - Seize the night, for it holds its own mysteries.
+ - In the quiet of this evening, find serenity.
+ - Life\'s greatest adventures can start in the darkness.
+ - Your journey continues even after the sun has set.
+ - Let the night be your canvas; paint it with dreams.
+ - In the stillness of this evening, find your inner peace.
+ - Stars shine brightest in the darkest hours.
+ - Dream big, even in the late hours of the day.
+ - Your life is a story waiting to be written.
+ - It\'s time for reflection and renewal.
+ - Late night thoughts can lead to early morning revelations.
+ - Find joy in the stillness of this evening.
+ - The night sky is a vast canvas of dreams.
+ - Your potential knows no bedtime; pursue your passions.
+ - Stay hopeful, even in the darkest hours.
+ - In the midst of chaos, find your inner calm.
+ - Stay positive, work hard, make it happen.
+
+
+ - Nights are the quiet whispers of opportunity.
+ - In the stillness of the night, find your inner strength.
+ - Embrace the darkness, for it holds the keys to your dreams.
+ - The world sleeps, but your potential is wide awake.
+ - The night may be late, but your ambitions are timeless.
+ - Stay curious, stay awake, and let nights guide you.
+ - Find the courage to chase your dreams.
+ - Great things never come from comfort zones.
+ - You have the power to create your own destiny.
+ - Happiness is a choice; choose it every day.
+ - Life is what you make it, so make it count.
+ - Stay focused, stay determined, stay unstoppable.
+ - You are the author of your own story.
+ - Strive for progress, not perfection.
+ - Every day is a new beginning; make it a great one.
+ - Inspire others by being your authentic self.
+ - Your attitude determines your direction.
+
+
+ - அறியாமையே பேரின்பம்
+ - ஏற்கனவே மற்றொரு புதுப்பிப்பை ப்ளாஷ் செய்ய நேரம் வந்துவிட்டதா?
+ - Make peace, not war
+ - வணக்கம், என்ன நடக்கிறது?
+ - Welcome to real Matrix!
+ - எத்தனை திரைப்பதிவுகளை எடுக்கிறீர்கள்?
+ - CrDroid உடன் நன்மைகளை அனுபவிங்கள்
+ - Spread love, not havoc
+ - We didn\'t start the fire!
+ - இது நல்ல இசைக்கான நேரம்
+ - \u003C\u003C\u003C\u003C\u003C\u003C\u003C HEAD
+ - இதைப் பார்க்க உங்களுக்கு AlphaDroid பிரீமியம் தேவை
+ - யூனிகார்னின் பரம்பரையை நினைவில் கொள்க
+ - நீட்டிக்கப்பட்ட மின்கல ஆயுளை அனுபவிக்கவும்
+ - முதலில் இருந்து தொடங்குகிறது
+ - நாங்கள் உங்களை 3000 மடங்கு நேசிக்கிறோம்
+ - Delicious even without Sushi
+ - இன்று ஏதாவது நல்லது செய்யுங்கள்
+ - இதுதான் சிறந்த Pixel அனுபவம், இல்லையா?
+ - மாயை அல்ல, உண்மைக்கு வருக!
+ - தங்கள் ஆதரவுக்கு நன்றி
+ - முட்டாள்தனத்திற்கு கொண்டாட்டம் அல்ல - முழுமை மட்டுமே!
+ - தரவியக்கியின் சிறந்த தேர்வுகளில் ஒன்று
+ - உங்கள் பிரம்மைக்கு நல்லறிவு
+ - Rising in peace
+ - என்ன ஒரு அழகான அனுபவம், இல்லையா?
+ - பரிணாம வளர்ச்சி என்பது ஒரு கட்டுக்கதை, இல்லையா?
+ - நீங்கள் ஃபிளாஷ் செய்வதுதான் நீங்கள், Potato-க இருக்க வேண்டாம்
+ - உங்கள் மனதில் என்ன இருக்கிறது?
+ - Expecto Patronum
+ - Wubba Lubba Dub Dub
+ - வின்னர் வின்னர்....?
+ - rm -rf \/
+ - Pringles aren\'t actually potato chips..
+ - Remember to check device\'s battery level!
+ - Check your email/messages.
+ - Check your to-do list if you have one.
+ - You can disable quickspace via home settings.
+ - எங்களை நேசிக்கிறீர்களா? எங்களுக்கு ஒரு பீர் அனுப்புங்கள்
+ - சிறந்த ROM.
+ - டெலிகிராம்-ல் எங்களுக்கு வணக்கம் சொல்லுங்கள்
+ - நாங்கள் உங்களை 3000 மடங்கு நேசிக்கிறோம்
+
+ Unable to find calendar or clock activity
+
+ Weather provider
+ Auto
+ Google Smartspace
+ OmniJaws
+ Weather update
+ Display current weather update
+ Requires weather service to be enabled
+ Cloudy
+ Rainy
+ Sunny
+ Stormy
+ Snowy
+ Windy
+ Misty
+ Current location
+ Display current weather location
+ Current condition
+ Display current weather condition summary
+
+ Auto-hide page indicator
+ Hide the page indicator dots automatically
+
+ Background blur depth
+ Set blur depth level for recents and app drawer
+
+ Application info
+ Source
+ சிஸ்டம்
+ Last update
+ Version
+ மேலும்
+
+ படவுருத் தொகுப்பு
+ இயல்புநிலை
+ Install more
+ There\'s no available app store
+
+ Wallpaper carousel
+ Quickly switch wallpapers from the long-press menu
+ Apply to lock screen
+ Set the same wallpaper on the lock screen
+
+ Pebble
+ Vessel
+ Pure square
+ Squircle
+ Tapered rect
+ Teardrop
+ Rounded rect
+ Cloudy
+ Cylindrical
+ Flower
+ Heart
+ Hexagon
+ Rounded hexagon
+ iOS Style
+ Leaf
+ Meow
+ Samsung Style
+ Stretched
+
+ Dark app labels
+ Always use dark text for app labels in the app drawer
+
+ App drawer style
+ Normal (bottom sheet)
+ Legacy fullscreen
+ List style
+ OneUI style (paged)
+
+ App drawer sorting
+ Sort alphabetically
+ Sort by install date
+ Sort by usage
+
+ Custom background color
+ Use a custom color for the app drawer background
+ Light mode color
+ Dark mode color
+
+ HSB
+ RGB
+ Hue
+ Saturation
+ Brightness
+ Red
+ Green
+ Blue
+ HEX
+
+ Pixel style search bar
+ Revamped search bar with layered background
+ Outer layer opacity
+
+ Hidden & Protected apps
+ Unlock to manage the hidden and protected apps
+ Authenticate to open %1$s
+ ஏற்றுகிறது...
+ Please set up a secure lock screen to restrict app access
+ உதவி
+ Hidden apps and their widgets are hidden from the drawer
+ Protected apps require authentication to be opened from the launcher
+
+ Recents layout style
+ இயல்புநிலை
+ Scale effect
+ Kami style
+ iOS style
+ OxygenOS style
+
+ Lock/Unlock app
+ Unlocked app in recents
+ Locked app in recents
+
+ Scroll vibration
+
diff --git a/res/values-tr-rTR/alpha_strings.xml b/res/values-tr-rTR/alpha_strings.xml
new file mode 100644
index 00000000000..b6829f141ee
--- /dev/null
+++ b/res/values-tr-rTR/alpha_strings.xml
@@ -0,0 +1,44 @@
+
+
+
+
+ Uygulama kilitle/kilidi aç
+ Son kullanılanlarda uygulamanın kilidini açıldı
+ Son kullanılanlarda uygulama kilitlendi
+
+ Son kullanılanlar düzeni stili
+ Varsayılan
+ Kami stili
+ iOS stili
+ OxygenOS stili
+
+ - Cehalet mutluluktur
+ - Savaşma, seviş
+ - Hey, nasıl gidiyor?
+ - Seni seviyoruz 3000
+ - Desteğiniz için teşekkürler
+ - Buildbot\'un en iyi seçimlerinden biri
+ - Pringles aslında patates cipsi değil..
+ - Quickspace\'i ana ekran ayarlarından devre dışı bırakabilirsiniz.
+ - Hiçbir şeyim yok ¯\\_(ツ)_/¯
+
+
+ - Ben asla özür dilemem. Üzgünüm ama ben böyleyim.
+ - Hayatım boyunca tek bir hayalim vardı: Birçok hedefime ulaşmak.
+ - Güzel içecek... güzel et... aman Tanrım, yiyelim!
+ - Konuşman gerekecek. Üzerimde havlu var.
+ - Kanalı değiştir, Marge...
+ - Yollar sadece birer öneri Marge, tıpkı pantolonlar gibi.
+ - Yalan söylemek için iki kişi gerekir; biri yalan söyler, biri dinler.
+ - Sus artık, Flanders!
+ - Ben normalde dua eden bir adam değilim ama eğer oradaysan lütfen beni kurtar Süperman.
+ - Akıllı olmak çok basit... sadece söyleyebileceğin aptalca bir şey düşün ve sonra onu söyleme.
+ - Aptal seksi Flanders...
+ - Umarım verdiğim zararı kafama takmamışımdır.
+ - Operatör, bana 911\'in numarasını ver!
+ - Beni suçlamayın, ben Kodos\'a oy verdim.
+
+
diff --git a/res/values-tr-rTR/cr_strings.xml b/res/values-tr-rTR/cr_strings.xml
new file mode 100644
index 00000000000..c0f6951ed49
--- /dev/null
+++ b/res/values-tr-rTR/cr_strings.xml
@@ -0,0 +1,440 @@
+
+
+
+
+
+
+ Ana ekran ayarları
+
+ Ana ekran
+ Google now panelini ve daha fazlasını ayarlayın
+ Simgeler
+ Simge boyutunu ve daha fazlasını ayarlayın
+ Uygulama menüsü
+ Uygulama menünüzü özelleştirin
+ Son uygulamalar
+ Son uygulamalar ekranını yenileyin
+ Çeşitli
+ Diğer seçenekler
+ Genel
+ Arayüz
+ Hızlı alan
+ Arama çubuğu
+ Hızlı eylemler
+
+ Geliştirici seçenekleri
+ Bazı gizli özellikleri kendi sorumluluğunuzda etkinleştirin
+
+ Düzeni kilitle
+ Simgeler ve widget\'lar ana ekrana eklenemez, kaldırılamaz ve taşınamaz
+ Simgeler ve widget\'lar ana ekrana eklenebilir, kaldırılabilir ve taşınabilir
+ Ana ekrana widget eklemek mümkün değil
+
+ Ana ekranda simge etiketleri
+ Çekmecede simge etiketleri
+
+ Google uygulamasına erişmek için kaydırın
+
+ Ana sayfadan sola kaydırdığınızda
+
+ Ana sayfadan sağa kaydırdığınızda
+
+ Çekmecede simge kişiselleştirmelerini kullan
+ Ana ekranda kullanılan simge kişiselleştirmelerini takip et
+
+ Duraklat
+ %1$s duraklatılsın mı?
+ %1$s için bildirimler duraklatılacak
+ Uygulama duraklatıldı
+ %1$s doğrudan başlatıcıdan duraklatıldı
+ Uygulamayı duraklat
+ İş uygulamaları kapalı
+ İş uygulamalarını kapat
+ İş uygulamalarını aç
+ İş uygulamalarınız size bildirim gönderemez, pilinizi kullanamaz veya konumunuza erişemez
+ İş uygulamalarınız telefon çağrısı ve metin mesajı alamaz, size bildirim gönderemez, pilinizi kullanamaz, konumunuza erişemez
+
+ Öneriler
+ Tüm uygulamalar & Ana ekran önerileri için
+
+ Yeniden Başlat
+ Ayarları Uygulamak İçin Yeniden Başlat
+ Ana ekran yeniden başlatılıyor...
+ Değişiklikleri uygulamak için yeniden başlatılıyor...
+ Başlatıcı bileşenlerini güncellemek için başlatıcı yeniden başlatılıyor...
+
+ Çift dokunuşla uyutma
+ Ekranı kapatmak için ana ekrandaki boş bir alana çift dokunun
+ Uyutmak için çift dokunuşta titret
+ Çift dokunuşla uyuma jestinde titreşim geri bildirimi ver
+
+ Aramaya başlamak için kaydır
+ Uygulama çekmecesi göründüğünde klavye açılsın
+
+ Simge boyutu
+
+ Yazı tipi boyutu
+
+ Uygulama etiketi için iki satır kullan
+
+ Satır yüksekliği
+
+ Hotseat arka planı
+ Uygulama yuvasına yarı şeffaf bir arka plan ekleyin
+
+ Arka plan şeffaflığı
+
+ Kenarlı
+
+ Sonlandır
+ Uygulama sonlandırıldı
+
+ Ara
+ Google lens
+ Sesli arama
+ YZ modu
+ Müzik ara
+ Mikrofon simgesine basarak müzik aramayı başlatın
+ Google arama çubuğu
+ Alt kısımda arama çubuğu
+ Temalı simgeler
+ Arama çubuğunda temalı simgeler kullan
+ Köşe yarıçapı
+
+ Uygulama arama çubuğu konumu
+ Gizli
+ Üstte
+ Altta
+
+ Duvar kağıdı kaydırma
+ Birden çok ekran için duvar kağıdı kaydırma efekti
+
+ Duvar kağıdı yakınlaştırma
+ Çekmeceyi veya son uygulamaları kullanırken duvar kağıdını yakınlaştırın veya uzaklaştırın
+
+ Durum çubuğu
+
+ Üst gölge
+
+ %1$s Mevcut | %2$s Toplam
+ Bellek bilgisi
+ ZRAM bilgisi
+
+ Ekran görüntüsü
+ Tümünü temizle
+ Lens
+
+ Kısa paralaksa izin ver
+ Duvar kağıdını kırpmak yerine daha az sayıda sayfada tam duvar kağıdı kaydırma efektini etkinleştirin
+ Tek sayfayı ortalayın
+ Yalnızca tek bir sayfa kullanılıyorsa duvar kağıdını ortalayın
+
+ Kaydırma Çubuğu
+ Uygulama sayfasının yanındaki kaldırma çubuğu
+
+ Koyu durum çubuğu
+ Her zaman ana ekranda koyu durum çubuğunu kullan
+
+ Bir Bakışta
+ Ana ekranınızın üst kısmında göster
+
+ Günaydın.
+ İyi akşamlar.
+ Tünaydın.
+ İyi geceler.
+ İyi günler.
+ Bugün
+ Şu an
+
+ Bilinmeyen Sanatçı
+ Şimdi oynatılıyor
+ Tarafından
+
+ Şimdi oynatılıyor
+ Çalmakta olduğunuz şarkıyı gösterin
+
+ Minimalist still
+ Modern stile geç
+
+ Rastgele mesajlar
+ Rastgele mesajlarla arkadaşınızı daha canlı hale getirin
+
+ - Günaydın!
+ - Günaydın, rock zamanı!
+ - Ne kadar güzel bir gün!
+ - İyi günler dilerim!
+ - Peki ya 5 dakikalık küçük bir şekerlemeye nedersin?
+ - Haydi sorunu çözelim.
+ - Sabahlar, günlük şaheseriniz için taze bir tuvaldir.
+ - Her sabah fırsat güneşinin doğuşunu kucaklayın.
+ - Günün ilk ışıklarıyla yolunuzu aydınlatacak gücü bulun.
+ - Gününüz sonsuz olasılıklarla başlıyor.
+ - Her gün doğumu, yeniden başlayabileceğinizi hatırlatır.
+ - Amacınızla uyanın, önünüzdeki günü fethedin.
+ - Düşünceleriniz büyük başarıların temeli olabilir.
+ - Dünya uyanır ve potansiyeliniz de uyanır.
+ - Günlük maceranızın ilk bölümü.
+ - Sabah rutininizin başarı için bir fırlatma rampası olmasına izin verin.
+ - Güneş doğsun ya da doğmasın, potansiyeliniz her zaman yükseliştedir.
+ - Şimdi yeni fikirler ve cesur adımlar atma zamanı.
+ - Erken kalkan yol alır ama siz hayallerinizi yakalayabilirsiniz.
+ - Hedeflerinizin peşinden gidecek cesareti bulun.
+ - Yükselin ve parlayın, çünkü büyüklük sizi bekliyor.
+ - Başlamak için en iyi zaman şimdi.
+
+
+ - Hayallerinizin peşinden gidin, dakikaların değil.
+ - Her an, yeni bir fırsat.
+ - Sadece varış noktasını değil, yolculuğu da kucaklayın.
+ - Bugün bir hediye, sonuna kadar keyfini çıkarmaya bakın.
+ - Gülümsemenizin dünyayı değiştirmesine izin verin.
+ - Hayat bir hikayedir; her bölümün hakkını verin.
+ - Sıradan anlarda neşeyi bulun.
+ - En iyisi henüz gelmedi, böyle devam edin.
+ - \"Eğer\"\'i \"olan\"\'a dönüştürme şansınız
+ - Şu andan itibaren günü yakalayın.
+ - Mutluluk her zaman yapabileceğiniz bir seçimdir.
+ - Uykulu bir öğleden sonra bile kendinize inanın.
+ - Bırakın eylemleriniz saatten daha yüksek sesle konuşsun.
+ - Hayatınız ondan ne yaptığınızdır; onu muhteşem kılın.
+ - Büyük hayaller kurun, çok çalışın ve asla pes etmeyin.
+ - Sandığınızdan daha güçlüsünüz.
+ - Her başarısızlık, geri dönüş için bir hazırlıktır.
+ - Sadece varış noktasını değil, yolculuğu da kucaklayın.
+ - Hedeflerinizin peşinden gidin, korkularınızın değil.
+ - Potansiyeliniz sonsuz; onu ortaya çıkarın.
+
+
+ - Şu anı kucaklayın, saat kaç olursa olsun.
+ - Her alacakaranlıkta, yeni bir fırsat şafağı bulun.
+ - Hayat bir yolculuktur; yol boyunca manzaranın tadını çıkarın.
+ - Tutumunuz gerçekliğinizi şekillendirir.
+ - Güneş batarken bile hikayeniz yazılmaya devam ediyor.
+ - Sıradan anlardaki güzelliği yakalayın.
+ - Düşünme ve sıfırlama zamanı.
+ - Saat kaç olursa olsun neşeyi seçin.
+ - Fırsatlar akşamları bitmez.
+ - Birinin gülümsemesinin sebebi olun.
+ - Potansiyeliniz sınır tanımıyor.
+ - Sihirli anlar her an gerçekleşebilir.
+ - Hayatınız ondan ne yaptığınızdır; onu olağanüstü kılın.
+ - Meraklı kalın, minnettar kalın.
+ - Dünyada görmek istediğiniz değişimin kendisi olun.
+ - Kendinize inanın; inanılmaz şeyler yapabilecek kapasitedesiniz.
+ - Tek sınır sizin kendiniz için belirlediğiniz sınırdır.
+ - Başarı tek bir adımla başlar.
+
+
+ - Geceyi yakalayın, çünkü kendi gizemlerini barındırır.
+ - Bu akşamın sessizliğinde huzuru bulun.
+ - Hayatın en büyük maceraları karanlıkta başlayabilir.
+ - Yolculuğunuz güneş battıktan sonra bile devam eder.
+ - Bırakın gece tuvaliniz olsun; onu hayallerle boyayın.
+ - Bu akşamın dinginliğinde, iç huzurunuzu bulun.
+ - Yıldızlar en karanlık saatlerde parlar.
+ - Günün geç saatlerinde bile büyük hayaller kurun.
+ - Hayatınız yazılmayı bekleyen bir hikayedir.
+ - Düşünme ve yenilenme zamanı.
+ - Gece geç saatlere kadar süren düşünceler sabahın erken saatlerinde açığa çıkabilir.
+ - Bu akşamın dinginliğinde neşeyi bulun.
+ - Gece gökyüzü uçsuz bucaksız bir rüya tuvalidir.
+ - Potansiyelinizin uyku saati yoktur; tutkularınızın peşinden gidin.
+ - En karanlık saatlerde bile umutlu kalın.
+ - Kaosun ortasında, içinizdeki sükûneti bulun.
+ - Pozitif kalın, çok çalışın, başarın.
+
+
+ - Geceler fırsatların sessiz fısıltılarıdır.
+ - Gecenin sessizliğinde, içinizdeki gücü bulun.
+ - Karanlığı kucaklayın, çünkü hayallerinizin anahtarı ondadır.
+ - Dünya uyuyor ama sizin potansiyeliniz tamamen uyanık.
+ - Gece geç olabilir, ancak tutkularınız zamansızdır.
+ - Meraklı kalın, uyanık kalın ve gecenin size rehberlik etmesine izin verin.
+ - Hayallerinizin peşinden gidecek cesareti bulun.
+ - Büyük işler asla konfor alanlarından gelmez.
+ - Kendi kaderinizi yaratma gücüne sahipsiniz.
+ - Mutluluk bir seçimdir; onu her gün seçin.
+ - Hayat yaptığınız şeydir, bu yüzden onu değerlendirin.
+ - Odaklanın, kararlı olun, durdurulamaz olun.
+ - Kendi hikayenizin yazarı sizsiniz.
+ - İlerleme için çabalayın, mükemmellik için değil.
+ - Her gün yeni bir başlangıçtır; bunu harika bir başlangıç haline getirin.
+ - Özgün benliğiniz olarak başkalarına ilham verin.
+ - Tutumunuz yönünüzü belirler.
+
+
+ - Cehalet mutluluktur
+ - Çoktan başka güncelleme flaş etme zamanı gelmedi mi?
+ - Savaş değil, barış yapın
+ - Hey, nasıl gidiyor?
+ - Gerçek Matrise hoş geldiniz!
+ - Kaç tane ekran görüntüsü alıyorsunuz?
+ - Crdroid ile çeşitli özelleştirmeler
+ - Sevgiyi yay, tahribatı değil
+ - Yangını biz başlatmadık!
+ - Bazı güzel müzikler için doğru zaman
+ - \u003C\u003C\u003C\u003C\u003C\u003C\u003C BAŞ
+ - Bunu görmek için AlphaDroid Premium\'a ihtiyacınız var
+ - Unicorn soyunu hatırla
+ - Daha uzun pil ömrünün keyfini çıkarın
+ - Sıfırdan başlayarak
+ - Seni her şeyden çok seviyoruz
+ - Suşi olmadan bile lezzetli
+ - Bugün güzel bir şey yap
+ - Bu en iyi Pixel Experience, değil mi?
+ - Yanılsama yok, gerçeğe hoş geldiniz!
+ - Desteğiniz için teşekkür ederiz
+ - Derps için festival yok - sadece mükemmellik!
+ - Buildbot\'ların en iyi seçimlerinden biri
+ - Paranoya için akıl sağlığı
+ - Sükunetle yükseliş
+ - Ne hoş bir deneyim değil mi?
+ - Evrim bir efsanedir, değil mi?
+ - Sen, ne kullanıyorsan osun. Sakın patates olma
+ - Aklınızdan ne geçiyor?
+ - Bir gardiyan bekliyorum
+ - Wubba Lubba Dub Dub
+ - Kazanan Kazanan ....?
+ - rm -rf \/
+ - Pringles aslında patates cipsi değil..
+ - Cihazın pil seviyelerini kontrol etmeyi unutmayın!
+ - E-postanızı/mesajlarınızı kontrol edin.
+ - Yapılacaklar listeniz varsa kontrol edin.
+ - Hızlı alanı ana ekran ayarlarından devre dışı bırakabilirsiniz.
+ - Bizi seviyor musunuz? Bize bir bira ısmarla
+ - Gelmiş Geçmiş En İyi Rom.
+ - Telegram\'da bize merhaba de
+ - Seni 3000 kez seviyoruz
+
+ Takvim veya saat etkinliği bulunamıyor
+
+ Hava durumu sağlayıcısı
+ Otomatik
+ Google Smartspace
+ OmniJaws
+ Hava durumu güncellemesi
+ Geçerli hava durumu güncellemesini görüntüle
+ Hava durumu hizmetinin etkinleştirilmesi gerekir
+ Bulutlu
+ Yağmurlu
+ Açık Hava
+ Fırtınalı
+ Kar yağışlı
+ Rüzgarlı
+ Puslu
+ Mevcut konum
+ Mevcut hava durumu konumunu göster
+ Geçerli durum
+ Mevcut hava durumu özetini görüntüle
+
+ Sayfa işaretini otomatik gizle
+ Sayfa işareti için kullanılan noktaları otomatikman gizle
+
+ Arka plan bulanıklık derinliği
+ Son uygulamalar ve uygulama çekmecesi için bulanıklık derinliği seviyesini ayarlayın
+
+ Uygulama bilgisi
+ Kaynak
+ Sistem
+ Son güncelleme
+ Sürüm
+ Daha fazla
+
+ Simge paketi
+ Varsayılan
+ Daha fazla yükle
+ Kullanılabilir uygulama mağazası yok
+
+ Duvar kağıdı döngüsü
+ Uzun dokunuş menüsünden duvar kağıtlarını hızlıca değiştir
+ Kilit ekranına uygula
+ Aynı duvar kağıdını kilit ekranına uygula
+
+ Çakıl
+ Gemi
+ Düz kare
+ Karemsi
+ Konik dikdörtgen
+ Damla
+ Yuvarlak dikdörtgen
+ Bulutlu
+ Silindirik
+ Çiçek
+ Kalp
+ Altıgen
+ Yuvarlak altıgen
+ iOS tarzı
+ Yaprak
+ Miyav
+ Samsung tarzı
+ Esnetilmiş
+
+ Koyu uygulama etiketleri
+ Uygulama çekmecesindeki uygulama etiketleri için her zaman karanlık renkte metin kullan
+
+ Uygulama çekmecesi stili
+ Normal (alttan sayfa)
+ Eski tam ekran
+ Liste stili
+ OneUI stili (sayfalandırılmış)
+
+ Uygulama çekmecesi sıralaması
+ Alfabe sırası
+ Kurulum tarihine göre sırala
+ Kullanıma göre sırala
+
+ Özel arka plan rengi
+ Uygulama çekmecesinin arka planında özel bir renk kullan
+ Aydınlık mod rengi
+ Karanlık mod rengi
+
+ HSB
+ RGB
+ Ton
+ Doygunluk
+ Parlaklık
+ Kırmızı
+ Yeşil
+ Mavi
+ HEX
+
+ Pixel tarzı arama çubuğu
+ Katmanlı arka plana sahip yenilenmiş arama çubuğu
+ Dış katman opaklığı
+
+ Gizli ve Korunan Uygulamalar
+ Gizli ve korunan uygulamaları yönetmek için kilidi açın
+ %1$s açmak için kimlik doğrula
+ Yükleniyor\u2026
+ Uygulama erişimini kısıtlamak için lütfen güvenli bir kilit ekranı ayarlayın
+ Yardım
+ Gizli uygulamalar ve widget\'ları uygulama menüsünde gizlidir
+ Korunan uygulamaların başlatıcıdan açılması için kimlik doğrulama gerekir
+
+ Son kullanılanlar düzeni stili
+ Varsayılan
+ Ölçek etkisi
+ Kami stili
+ iOS stili
+ OxygenOS stili
+
+ Uygulama kilitle/kilidi aç
+ Yakın geçmişte kilidi açılan uygulama
+ Yakın geçmişte kilitlenen uygulama
+
+ Kaydırma titreşimi
+
diff --git a/res/values-uk-rUA/cr_strings.xml b/res/values-uk-rUA/cr_strings.xml
new file mode 100644
index 00000000000..e9f492a8802
--- /dev/null
+++ b/res/values-uk-rUA/cr_strings.xml
@@ -0,0 +1,440 @@
+
+
+
+
+
+
+ Налаштування головного екрана
+
+ Домашній екран
+ Встановити панель Google Now та інше
+ Іконки
+ Встановити розмір іконки та багато іншого
+ Меню додатків
+ Налаштувати меню додатків
+ Меню \"Нещодавні\"
+ Настройка останніх додатків
+ Різне
+ Інші параметри
+ Загальні налаштування
+ Інтерфейс
+ Найголовніше
+ Рядок пошуку
+ Швидкі дії
+
+ Параметри розробника
+ Увімкнути деякі приховані функції на свій страх і ризик
+
+ Заблокувати переміщення піктограм
+ Ікони та віджети не можуть бути додані, видалені та переміщені на головний екран
+ Дозволено переміщення піктограми та віджетів на Головному екрані
+ Неможливо додати віджети на головний екран
+
+ Назви іконок на робочому столі
+ Підписи іконок в меню програм
+
+ Проведіть для доступу до програми Google
+
+ Під час гортання вліво від головного екрану
+
+ Під час гортання вправо від головного екрану
+
+ Використовувати налаштування значків в меню
+ Налаштування іконок, що використовуються на головному екрані
+
+ Пауза
+ Призупинити %1$s?
+ Сповіщення для %1$s буде призупинено
+ Додаток призупинено
+ %1$s Був зупинений безпосередньо з лаунчера
+ Призупинити додаток
+ Робочі програми вимкнено
+ Вимкнути робочі додатки
+ Увімкнути робочі додатки
+ Ваші робочі програми не можуть надсилати вам сповіщення, використовувати заряд акумулятора або отримувати доступ до вашого місцезнаходження
+ Ваші робочі додатки не можуть отримувати телефонні дзвінки або текстові повідомлення, надсилати сповіщення, використовуйте акумулятор або доступ до місцезнаходження
+
+ Пропозиції
+ Для підказок в меню всіх додатків та на домашньому екрані
+
+ Перезапустити
+ Перезапустіть лаунчер вручну для подальшого застосування налаштувань
+ Перезавантаження оболонки...
+ Перезапуск оболонки для застосування змін...
+ Перезапуск лаунчера для оновлення компонентів...
+
+ Подвійне натискання для блокування екрану
+ Двічі торкніться порожньої області на головному екрані, щоб вимкнути дисплей
+ Вібрувати при подвійному дотику для сну
+ Використовувати тактильний зворотній зв\'язок при подвійному натисканні щоб засинати
+
+ Свайпніть, щоб почати пошук
+ Відкривати клавіатуру щоразу при появі меню додатків
+
+ Розмір значків
+
+ Розмір шрифту
+
+ Використовувати два рядки для мітки застосунків
+
+ Висота рядка
+
+ Фон віджета
+ Додати напівпрозорий фон в панель додатка
+
+ Прозорість фону
+
+ Обведення
+
+ Зупинити
+ Програму закрито
+
+ Пошук
+ Google Ленс
+ Голосовий пошук
+ Режим ШІ
+ Пошук музики
+ Почати пошук музики при натисканні на іконку із мікрофона
+ Панель пошуку Google
+ Рядок пошуку у нижній панелі
+ Іконки з темами
+ Використовувати тематичні іконки для панелі пошуку
+ Радіус округлення
+
+ Позиція панелі пошуку додатків
+ Прихований
+ Зверху
+ Знизу
+
+ Прокручування шпалер
+ Ефект прокрутки шпалер для декількох екранів
+
+ Масштабування шпалерів
+ Збільшувати або зменшувати шпалери при використанні меню чи останніх програм
+
+ Рядок стану
+
+ Верхня тінь
+
+ %1$s Доступно | %2$s Загалом
+ Інформація про пам\'ять
+ Інформація ZRAM
+
+ Знімок екрану
+ Очистити все
+ Лінза
+
+ Дозволити короткий паралакс
+ Увімкнути можливість повної прокрутки при гортанні на меншу кількість сторінок замість обрізання шпалер
+ У центрі на одній сторінці
+ Центрувати шпалери, якщо тільки одна сторінка
+
+ Смуга прокрутки
+ Смуга прокрутки у списку додатків
+
+ Темний рядок стану
+ Завжди використовувати темний рядок стану на головному екрані
+
+ Найголовніше
+ Показувати вгорі домашнього екрана
+
+ Доброго ранку.
+ Доброго вечора.
+ Доброго дня.
+ Спокійної ночі.
+ Гарного дня.
+ Сьогодні є
+ Це
+
+ Невідомий виконавець
+ Відтворюється зараз
+ Від
+
+ Відтворюється зараз
+ Показувати пісню, яку ви відтворюєте
+
+ Мінімалістичний стиль
+ Перемикнути на сучасний стиль
+
+ Випадкові повідомлення
+ Зробіть свого супутника більш жвавим за допомогою випадкових повідомлень
+
+ - Доброго ранку!
+ - Доброго ранку, час запалювати!
+ - Який гарний день!
+ - Гарного дня!
+ - Як щодо маленької 5 хвилинної дрімоти?
+ - Зробімо це.
+ - Рани - це свіже полотно для вашого щоденного шедевру.
+ - Обійняти схід можливого сонця щоранку.
+ - У ранньому світлі знаходить силу для освітлення вашого шляху.
+ - Ваш день починається з нескінченних можливостей.
+ - Кожен світанок - це нагадування, що ви можете почати заново.
+ - Прокинувшись з метою, завоюйте день вперед.
+ - Ваші думки можуть бути основою великих досягнень.
+ - Світ пробуджується, і також має і свій потенціал.
+ - Перший розділ твоєї щоденної пригоди.
+ - Нехай ранкова рутина стає стартовим майданчиком успіху.
+ - Світанок чи ні, ваш потенціал завжди зростає.
+ - Це час для свіжих ідей і сміливих дій.
+ - Птах рано вловлює хробака, але ви можете упіймати свої мрії.
+ - Знайдіть сміливість аби переслідувати ваші прагнення.
+ - Повз і сяйво, велич чекає.
+ - Найкращий час розпочати зараз.
+
+
+ - Чекай свої мрії, а не хвилини.
+ - У кожну мить нова можливість.
+ - Гляньте дорогу, а не лише місце призначення.
+ - Сьогодні це подарунок, тому його називають теперішнім.
+ - Нехай ваша посмішка змінить світ.
+ - Життя - це історія, зробіть кожен розділ граф.
+ - Знайдіть радість в звичайний момент.
+ - Найкраще ще приходити, тримайте.
+ - Ваш шанс перетворити \"що\" в \"що\" на \"що це.\"
+ - Хапай вдень, починаючи просто зараз.
+ - Щастя - це вибір, який можна зробити будь-коли.
+ - Вірите у себе, навіть у сонний день.
+ - Нехай ваші дії говорять голосніше, ніж годинник.
+ - Ваше життя - це те, що ти робиш від цього; зробіть це дивовижним.
+ - Замріяні великі, багато працювати і ніколи не здавайся.
+ - Ти сильніший, ніж ти думаєш.
+ - Кожна невдача є налаштуванням для повернення.
+ - Гляньте дорогу, а не лише місце призначення.
+ - Вистримуйте свої цілі, а не страхи.
+ - Ваш потенціал є безкінечним; розблокувати його.
+
+
+ - Приймайте теперішній момент, незалежно від години.
+ - У кожному сутінку знайдіть нове зорі можливостей.
+ - Життя - це подорож, насолоджуйтесь крадіжкою на шляху.
+ - Ваше ставлення формує вашу реальність.
+ - Ваша історія все ще записується, навіть як сонце.
+ - Захопіть красу в звичайні миті.
+ - Настав час показати та скинути.
+ - Виберіть радість, незалежно від часу на годиннику.
+ - Угоди не демонструють вже ввечері.
+ - Будьте причиною того, що хтось посміхається.
+ - Ваш потенціал не знає меж.
+ - Моменти магії можуть трапитися в будь-яку годину.
+ - Ваше життя - це те, що ти робиш від цього; зробіть це дивовижним.
+ - Будьте допитливі, будьте вдячні.
+ - Якщо бажаєш, щоб світ змінився, - змінися сам.
+ - Повірте в себе; ви в змозі вражаючі речі.
+ - Єдина межа - та, яку ви встановлюєте самостійно.
+ - Успіх починається з одного кроку.
+
+
+ - Схопіть ніч на те, що вона має свої таємниці.
+ - У тиху вечора зтався безтурботність.
+ - Найвеличніші життєві пригоди можуть початися в темряві.
+ - Ваша подорож триває навіть після заходу сонця.
+ - А ніч все нехай буде вашим полотном; пофарбуйте його в сни.
+ - Цього вечора знайдіть свій внутрішній мир.
+ - Зірки світять найяскравіші в найтемніші години.
+ - Сон великий, навіть після пізньої години дня.
+ - Ваше життя - це історія, яка чекає на написання.
+ - Настав час для роздумів і поновлення.
+ - Пізні думки про ніч можуть призвести до одкровень раннього ранку.
+ - Знайдіть радість у тиші цього вечора.
+ - Нічне небо - це величезне полотно мрії.
+ - Ваш потенціал не знає часу спати, переслідуйте за своїми пристрастями.
+ - Залишайся надією, навіть у найтемніших годинах.
+ - У розпал хаосу знайдіть ваше внутрішнє тіло.
+ - Тримайте позитивний, наполегливо працюйте, зроби це реальним.
+
+
+ - Ночі — тихі шепоти нагоди.
+ - У нічній тиші ви знайдете свою внутрішню силу.
+ - Включає темряву, бо вона тримає ключі для вашої мрії.
+ - Світ спить, але ваш потенціал є пробудженим.
+ - Ніч може запізнитися, але ваші амбіції безчасові.
+ - Залишайся допитливим, не спайте, а ночі підводьте вас.
+ - Знайдіть мужність, щоб переслідувати свої мрії.
+ - Великі речі з зони комфорту ніколи не будуть.
+ - Ви маєте можливість створити свою власну долю.
+ - Щастя - це вибір; обирайте його щодня.
+ - Життя - це те, що ти робиш, тому зробиш його такт.
+ - Залишайтеся сфокусованими, залишайтеся вимкненими.
+ - Ви - автор власної історії.
+ - Боротьба за прогрес, не досконалості.
+ - Кожен день - це новий початок, зробити його великим.
+ - Надихайте інших, будучи вашим справжнім собою.
+ - Ваша позиція визначає ваш напрямок.
+
+
+ - Невігластво це блаженство
+ - Настав час прошити ще одне оновлення?
+ - Зробіть мир, а не війну
+ - Привіт, що відбувається?
+ - Ласкаво просимо до справжньої Матриці!
+ - Скільки знімків екрану ви робите?
+ - Відкрийте добро з AlphaDroid
+ - Розповсюджуйте любов, а не хаос
+ - Не ми почали пожежу!
+ - Час для якоїсь хорошої музики
+ - Голова
+ - Вам потрібен AlphaDroid Premium щоб побачити це
+ - Пам\'ятайте схему єдинорога
+ - Насолоджуйтесь подовженим життям батарейки
+ - Починаючи з нуля
+ - Ми вас любимо, вас 3000
+ - Смачно навіть без Суші
+ - Зроби щось гарне сьогодні
+ - Це найкращий Pixel Experience, чи не так?
+ - Немає ілюзій, ласкаво просимо до реальності!
+ - Дякуємо за вашу підтримку
+ - Жодного святкування задля дурниць - лише досконалість!
+ - Один з найкращих виборців будівельного робота
+ - Здоров’я для вашої Параної
+ - Спочивай в мирі
+ - Який чудовий досвід, чи не так?
+ - Еволюція — міф, чи не так?
+ - Ви саме те, що формуєте, не будьте байдужими
+ - Що у тебе на думці?
+ - Експекто патронум
+ - Кров не вода, а серце не камінь
+ - Переможець ....?
+ - rm -rf \/
+ - Pringles — це не лише картопляні чіпси..
+ - Завжди перевіряйте рівень заряду акумулятора пристрою!
+ - Завжди перевіряти повідомлення електронної пошти.
+ - Перевірте свій запланований список, якщо у вас є.
+ - Можна вимкнути швидку область в налаштуваннях будинку.
+ - Подобається наша робота? Віддячте матеріально
+ - Найкраща прошивка у світі.
+ - Привітайтесь з нами у Telegram
+ - Ми вас любимо, вас 3000
+
+ Не вдалося знайти календар або годинник активності
+
+ Постачальник погоди
+ Автоматично
+ Google Smartspace
+ OmniJaws
+ Оновлення погоди
+ Відображати поточне оновлення погоди
+ Потрібно ввімкнути службу погоди
+ Хмарно
+ Дощова
+ Сонячно
+ Гроза
+ Йде сніг
+ Вітряно
+ Димка
+ Інформація про місто за місцерозташуванням
+ Показувати поточну погоду
+ Поточний стан
+ Показувати поточний стан погоди
+
+ Автоматично приховувати індикатор сторінки
+ Автоматичне приховання точок індикатора сторінки
+
+ Глибина розмиття фону
+ Встановити глибину розмиття для нещодавніх та меню додатків
+
+ Інформація про застосунок
+ Джерело
+ Система
+ Останнє оновлення
+ Версія
+ Більше
+
+ Пакет іконок
+ За замовчанням
+ Встановити ще
+ Немає доступного магазину додатків
+
+ Карусель шпалер
+ Швидке перемикання шпалер з довгого натискання
+ Застосувати до екрана блокування
+ Встановити однакові шпалери на екран блокування
+
+ Камінець
+ Глечик
+ Чистий квадрат
+ Квадрат із заокругленими кутами
+ Звужений прямокутник
+ Крапля
+ Округлений прямокутник
+ Хмарно
+ Циліндричний
+ Квітка
+ Серце
+ Шестикутник
+ Закруглений шестикутник
+ Стиль iOS
+ Листок
+ Нявчати
+ Samsung Style
+ Розтягнутий
+
+ Темні мітки додатків
+ Завжди використовувати темний текст для підписів додатків у меню додатків
+
+ Стиль меню програм
+ Звичайний (нижній простил)
+ Застарілий повноекранний режим
+ Стиль списку
+ Стиль OneUI (сторінково)
+
+ Сортування додатків
+ Сортувати за алфавітом
+ Сортувати за датою встановлення
+ Сортувати за використанням
+
+ Власний колір тла
+ Використовувати свій колір для тла меню програм
+ Колір світлого режиму
+ Колір темного режиму
+
+ HSB
+ RGB
+ Глибина кольору
+ Насиченість
+ Яскравість
+ Червона
+ Зелений
+ Синій
+ HEX
+
+ Панель пошуку в стилі Pixel
+ Оновлений рядок пошуку з багатошаровим фоном
+ Непрозорість зовнішнього шару
+
+ Приховані та захищені додатки
+ Розблокуйте, щоб керувати прихованими та захищеними додатками
+ Автентифікуйтесь, щоб відкрити %1$s
+ Завантаження\u2026
+ Будь ласка, встановіть блокування екрану для обмеження доступу до додатків
+ Допомога
+ Приховані додатки і їх віджети приховані з меню
+ Для відкриття захищених додатків з лаунчера потрібна аутентифікація
+
+ Стиль макета нещодавніх
+ За замовчанням
+ Ефект масштабу
+ Стиль Kami
+ Стиль iOS
+ Стиль OxygenOS
+
+ Блокування/розблокування програми
+ Розблокований додаток у списку останніх
+ Заблокований додаток у списку останніх
+
+ Вібрація при прокручуванні
+
diff --git a/res/values-v31/colors.xml b/res/values-v31/colors.xml
index a1f70ea5580..3623ac98578 100644
--- a/res/values-v31/colors.xml
+++ b/res/values-v31/colors.xml
@@ -41,6 +41,12 @@
@android:color/system_neutral2_700
@android:color/system_neutral2_200
+ @*android:color/system_neutral1_100
+ @*android:color/system_neutral1_800
+
+ @*android:color/system_neutral1_100
+ @*android:color/system_neutral1_800
+
@android:color/system_neutral1_50
@android:color/system_neutral2_200
@android:color/system_neutral2_400
diff --git a/res/values-v31/styles.xml b/res/values-v31/styles.xml
index 55653e634fe..67e73092b55 100644
--- a/res/values-v31/styles.xml
+++ b/res/values-v31/styles.xml
@@ -30,6 +30,7 @@
- true
- @style/HomeSettings.PreferenceTheme
- google-sans-flex
+ - @style/HomeSettings.AlertDialog
+
+
+
+
+
+
+
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 4aaf95efb30..3e33a8a629a 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -40,6 +40,10 @@
0dp
+ 61dp
+ 10dp
+ 6dp
+
8dp
@@ -461,7 +465,6 @@
@*android:dimen/rounded_corner_content_padding
0dp
- 0dp
0dp
18dp
50dp
@@ -501,7 +504,7 @@
16dp
- 16dp
+ @*android:dimen/rounded_corner_radius
1dp
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 8caf8f7c2b1..10387b003e2 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -38,12 +38,12 @@
+
+
+
+
+
+
+
+
+
@@ -194,14 +245,35 @@
- false
- true
- @style/HomeSettings.PreferenceTheme
+ - @style/HomeSettings.AlertDialog
+
+
+
+
-
+
+
+
@@ -613,7 +690,7 @@
@@ -654,4 +731,12 @@
- @style/WidgetContainerTheme
- @color/page_indicator_dot_color_light
+
+
+
diff --git a/res/xml/app_info_preferences.xml b/res/xml/app_info_preferences.xml
new file mode 100644
index 00000000000..11a048efd64
--- /dev/null
+++ b/res/xml/app_info_preferences.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/xml/device_profiles.xml b/res/xml/device_profiles.xml
index 3390515e810..a7f9f9b06bd 100644
--- a/res/xml/device_profiles.xml
+++ b/res/xml/device_profiles.xml
@@ -100,6 +100,8 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/xml/launcher_home_screen_preferences.xml b/res/xml/launcher_home_screen_preferences.xml
index 265ec21da16..87755321266 100644
--- a/res/xml/launcher_home_screen_preferences.xml
+++ b/res/xml/launcher_home_screen_preferences.xml
@@ -20,6 +20,7 @@
xmlns:settings="http://schemas.android.com/apk/res/com.android.launcher3">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/xml/launcher_icons_preferences.xml b/res/xml/launcher_icons_preferences.xml
index 234eed37e79..52dae795218 100644
--- a/res/xml/launcher_icons_preferences.xml
+++ b/res/xml/launcher_icons_preferences.xml
@@ -19,10 +19,32 @@
xmlns:launcher="http://schemas.android.com/apk/res-auto"
xmlns:settings="http://schemas.android.com/apk/res/com.android.launcher3">
+
+
+
+
+
+
diff --git a/res/xml/launcher_misc_preferences.xml b/res/xml/launcher_misc_preferences.xml
index 045f091bbac..b62fb8201e1 100644
--- a/res/xml/launcher_misc_preferences.xml
+++ b/res/xml/launcher_misc_preferences.xml
@@ -23,10 +23,37 @@
android:key="pref_allowRotation"
android:title="@string/allow_rotation_title"
android:summary="@string/allow_rotation_desc"
- android:defaultValue="false"
+ android:defaultValue="@bool/config_allowRotation"
android:persistent="true"
launcher:logIdOn="615"
launcher:logIdOff="616"
launcher:iconSpaceReserved="false" />
+
+
+
+
+
+
+
+
+
+
diff --git a/res/xml/launcher_preferences.xml b/res/xml/launcher_preferences.xml
index 029ed4638a0..73921b45816 100644
--- a/res/xml/launcher_preferences.xml
+++ b/res/xml/launcher_preferences.xml
@@ -22,7 +22,7 @@
android:key="icons"
android:title="@string/icons_category_title"
android:summary="@string/icons_category_summary"
- android:icon="@drawable/ic_homepage_icons">
+ android:icon="@drawable/ic_settings_icons">
+ android:icon="@drawable/ic_settings_homescreen">
+ android:icon="@drawable/ic_settings_appdrawer">
+ android:icon="@drawable/ic_settings_recents">
+ android:icon="@drawable/ic_settings_misc">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/seraphixgoogle/Android.bp b/seraphixgoogle/Android.bp
new file mode 100644
index 00000000000..506d25aab11
--- /dev/null
+++ b/seraphixgoogle/Android.bp
@@ -0,0 +1,24 @@
+// Copyright (C) 2021 Chaldeaprjkt
+//
+// 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"],
+}
+
+java_library {
+ name: "chaldea.seraphixgoogle",
+ srcs: ["src/**/*.kt"],
+ sdk_version: "current",
+ min_sdk_version: min_launcher3_sdk_version,
+}
diff --git a/seraphixgoogle/src/io/chaldeaprjkt/seraphixgoogle/Card.kt b/seraphixgoogle/src/io/chaldeaprjkt/seraphixgoogle/Card.kt
new file mode 100644
index 00000000000..3f7a68662a0
--- /dev/null
+++ b/seraphixgoogle/src/io/chaldeaprjkt/seraphixgoogle/Card.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2021 Chaldeaprjkt
+ *
+ * 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 io.chaldeaprjkt.seraphixgoogle
+
+import android.graphics.Bitmap
+
+data class Card(var text: String? = null, var image: Bitmap? = null)
diff --git a/seraphixgoogle/src/io/chaldeaprjkt/seraphixgoogle/DataProviderBinder.kt b/seraphixgoogle/src/io/chaldeaprjkt/seraphixgoogle/DataProviderBinder.kt
new file mode 100644
index 00000000000..ad163ff3623
--- /dev/null
+++ b/seraphixgoogle/src/io/chaldeaprjkt/seraphixgoogle/DataProviderBinder.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2021 Chaldeaprjkt
+ *
+ * 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 io.chaldeaprjkt.seraphixgoogle
+
+interface DataProviderBinder {
+ fun onBound(newId: Int)
+}
diff --git a/seraphixgoogle/src/io/chaldeaprjkt/seraphixgoogle/DataProviderListener.kt b/seraphixgoogle/src/io/chaldeaprjkt/seraphixgoogle/DataProviderListener.kt
new file mode 100644
index 00000000000..36e7a60b436
--- /dev/null
+++ b/seraphixgoogle/src/io/chaldeaprjkt/seraphixgoogle/DataProviderListener.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2021 Chaldeaprjkt
+ *
+ * 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 io.chaldeaprjkt.seraphixgoogle
+
+interface DataProviderListener {
+ fun onDataUpdated(chip: Card)
+}
\ No newline at end of file
diff --git a/seraphixgoogle/src/io/chaldeaprjkt/seraphixgoogle/EphemeralWidgetHostGoogle.kt b/seraphixgoogle/src/io/chaldeaprjkt/seraphixgoogle/EphemeralWidgetHostGoogle.kt
new file mode 100644
index 00000000000..59e32adec39
--- /dev/null
+++ b/seraphixgoogle/src/io/chaldeaprjkt/seraphixgoogle/EphemeralWidgetHostGoogle.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 Chaldeaprjkt
+ *
+ * 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 io.chaldeaprjkt.seraphixgoogle
+
+import android.appwidget.AppWidgetHost
+import android.appwidget.AppWidgetHostView
+import android.appwidget.AppWidgetProviderInfo
+import android.content.Context
+
+class EphemeralWidgetHostGoogle(context: Context, hostId: Int) : AppWidgetHost(context, hostId) {
+ private var listener: DataProviderListener? = null
+
+ override fun onCreateView(
+ context: Context?,
+ appWidgetId: Int,
+ appWidget: AppWidgetProviderInfo?
+ ): AppWidgetHostView = EphemeralWidgetHostViewGoogle(context).setOnUpdateAppWidget(listener)
+
+ fun setOnDataUpdated(listener: DataProviderListener?) {
+ this.listener = listener
+ }
+}
diff --git a/seraphixgoogle/src/io/chaldeaprjkt/seraphixgoogle/EphemeralWidgetHostViewGoogle.kt b/seraphixgoogle/src/io/chaldeaprjkt/seraphixgoogle/EphemeralWidgetHostViewGoogle.kt
new file mode 100644
index 00000000000..ba8e0cc7073
--- /dev/null
+++ b/seraphixgoogle/src/io/chaldeaprjkt/seraphixgoogle/EphemeralWidgetHostViewGoogle.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2021 Chaldeaprjkt
+ *
+ * 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 io.chaldeaprjkt.seraphixgoogle
+
+import android.appwidget.AppWidgetHostView
+import android.content.Context
+import android.graphics.drawable.BitmapDrawable
+import android.widget.ImageView
+import android.widget.RemoteViews
+import android.widget.TextView
+import io.chaldeaprjkt.seraphixgoogle.SeraphixCompanion.allChildren
+
+class EphemeralWidgetHostViewGoogle(context: Context?) : AppWidgetHostView(context) {
+ private var listener: DataProviderListener? = null
+
+ override fun updateAppWidget(remoteViews: RemoteViews?) {
+ super.updateAppWidget(remoteViews)
+ val weather = Card()
+
+ val leaves = allChildren()
+ val textViews = leaves.filterIsInstance().filter { it.text?.isNotEmpty() == true }
+ val imageViews = leaves.filterIsInstance().filter { it.drawable != null && it.drawable is BitmapDrawable }
+
+ val tempTv = textViews.firstOrNull { tv ->
+ val t = tv.text.toString()
+ t.contains("°") || t.contains("℃") || t.contains("℉")
+ } ?: textViews.lastOrNull()
+
+ weather.text = tempTv?.text?.toString()
+
+ val iconIv = imageViews.firstOrNull { iv ->
+ val n = try { resources.getResourceEntryName(iv.id) } catch (_: Exception) { "" }
+ n.contains("weather", true) || n.contains("icon", true) || n.contains("temp", true)
+ } ?: imageViews.firstOrNull()
+
+ weather.image = (iconIv?.drawable as? BitmapDrawable)?.bitmap
+
+ listener?.onDataUpdated(weather)
+ }
+
+ fun setOnUpdateAppWidget(listener: DataProviderListener? = null): EphemeralWidgetHostViewGoogle {
+ this.listener = listener
+ return this
+ }
+}
diff --git a/seraphixgoogle/src/io/chaldeaprjkt/seraphixgoogle/SeraphixCompanion.kt b/seraphixgoogle/src/io/chaldeaprjkt/seraphixgoogle/SeraphixCompanion.kt
new file mode 100644
index 00000000000..804c2b41e96
--- /dev/null
+++ b/seraphixgoogle/src/io/chaldeaprjkt/seraphixgoogle/SeraphixCompanion.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2021 Chaldeaprjkt
+ *
+ * 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 io.chaldeaprjkt.seraphixgoogle
+
+import android.content.Context
+import android.content.pm.PackageManager
+import android.content.res.Resources
+import android.graphics.Bitmap
+import android.graphics.drawable.BitmapDrawable
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+
+
+object SeraphixCompanion {
+ fun Context.isPackageEnabled(packageName: String): Boolean {
+ return try {
+ packageManager.getApplicationInfo(packageName, 0).enabled
+ } catch (e: PackageManager.NameNotFoundException) {
+ false
+ }
+ }
+
+ private fun ViewGroup.allChildren(list: MutableList) {
+ for (i in (0 until childCount)) {
+ val child = getChildAt(i)
+ if (child is ViewGroup) {
+ child.allChildren(list)
+ } else {
+ list.add(child)
+ }
+ }
+ }
+
+ fun ViewGroup.allChildren() = ArrayList().also { allChildren(it) }
+}
diff --git a/seraphixgoogle/src/io/chaldeaprjkt/seraphixgoogle/SeraphixDataProvider.kt b/seraphixgoogle/src/io/chaldeaprjkt/seraphixgoogle/SeraphixDataProvider.kt
new file mode 100644
index 00000000000..0e99d760cfe
--- /dev/null
+++ b/seraphixgoogle/src/io/chaldeaprjkt/seraphixgoogle/SeraphixDataProvider.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2021 Chaldeaprjkt
+ *
+ * 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 io.chaldeaprjkt.seraphixgoogle
+
+import android.appwidget.AppWidgetManager
+import android.content.ComponentName
+import android.content.Context
+import android.util.Log
+import io.chaldeaprjkt.seraphixgoogle.SeraphixCompanion.isPackageEnabled
+
+class SeraphixDataProvider(
+ private val context: Context,
+ private val hostId: Int,
+ hostWidgetId: Int = -1
+) {
+ private val widgetManager by lazy { AppWidgetManager.getInstance(context) }
+ private val providerInfo by lazy {
+ widgetManager.installedProviders
+ .firstOrNull { it.provider == smartspaceProviderComponent }
+ }
+ private val widgetHost by lazy { EphemeralWidgetHostGoogle(context, hostId) }
+ private lateinit var widgetHostView: EphemeralWidgetHostViewGoogle
+ private val smartspaceProviderComponent = ComponentName(QSB_PACKAGE, SMARTSPACE_PROVIDER)
+ private var widgetId = hostWidgetId
+ private var isWidgetBound = false
+ private var isListening = false
+
+ fun setOnDataUpdated(listener: DataProviderListener? = null): SeraphixDataProvider {
+ widgetHost.setOnDataUpdated(listener)
+ return this
+ }
+
+ fun pauseListening() {
+ if (isListening) {
+ widgetHost.stopListening()
+ isListening = false
+ }
+ }
+
+ fun resumeListening() {
+ if (isWidgetBound && !isListening) {
+ widgetHost.startListening()
+ isListening = true
+ }
+ }
+
+ fun bind(onBounded: DataProviderBinder? = null) {
+ if (isWidgetBound) return
+ if (!context.isPackageEnabled(QSB_PACKAGE)) return
+
+ val wInfo = widgetManager.getAppWidgetInfo(widgetId)
+ isWidgetBound = wInfo != null && providerInfo?.provider == wInfo.provider
+ if (!isWidgetBound) {
+ if (widgetId > -1) widgetHost.deleteHost()
+ widgetId = widgetHost.allocateAppWidgetId()
+ isWidgetBound = widgetManager.bindAppWidgetIdIfAllowed(widgetId, smartspaceProviderComponent)
+ }
+
+ if (isWidgetBound) {
+ onBounded?.onBound(widgetId)
+ resumeListening()
+ widgetHostView = widgetHost.createView(context, widgetId, providerInfo)
+ as EphemeralWidgetHostViewGoogle
+ }
+ }
+
+ fun unbind() {
+ if (!isWidgetBound) return
+ pauseListening()
+ widgetHost.deleteHost()
+ if (::widgetHostView.isInitialized) {
+ widgetHostView.setOnUpdateAppWidget(null)
+ }
+ isWidgetBound = false
+ }
+
+ companion object {
+ const val TAG = "SeraphixDataProvider"
+ const val SMARTSPACE_PROVIDER =
+ "com.google.android.apps.gsa.staticplugins.smartspace.widget.SmartspaceWidgetProvider"
+ const val QSB_PACKAGE = "com.google.android.googlequicksearchbox"
+ }
+}
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 09819304bfb..81ed8dfdbb1 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -76,7 +76,7 @@ public abstract class BaseActivity extends Activity implements ActivityContext,
private static final String TAG = "BaseActivity";
// TODO(b/406230491): Trun DEBUG back to false once done with investigation.
- static final boolean DEBUG = true;
+ static final boolean DEBUG = false;
public static final int INVISIBLE_BY_STATE_HANDLER = 1 << 0;
public static final int INVISIBLE_BY_APP_TRANSITIONS = 1 << 1;
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 8a93b46ec5e..897cb892174 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -24,7 +24,7 @@
import static com.android.launcher3.BubbleTextView.RunningAppState.RUNNING;
import static com.android.launcher3.Flags.enableContrastTiles;
import static com.android.launcher3.Flags.enableScalabilityForDesktopExperience;
-import static com.android.launcher3.LauncherPrefs.ALLAPPS_THEMED_ICONS;
+import static com.android.launcher3.LauncherPrefs.ALLAPPS_ICON_CUSTOMIZATION;
import static com.android.launcher3.LauncherPrefs.SHOW_DESKTOP_LABELS;
import static com.android.launcher3.LauncherPrefs.SHOW_DRAWER_LABELS;
import static com.android.launcher3.graphics.PreloadIconDelegate.extractPreloadDelegate;
@@ -298,7 +298,7 @@ public BubbleTextView(Context context, AttributeSet attrs, int defStyle) {
mDeviceProfile.getAllAppsProfile().getIconDrawablePaddingPx());
defaultIconSize = mDeviceProfile.getAllAppsProfile().getIconSizePx();
mShouldShowLabel = SHOW_DRAWER_LABELS.get(context);
- mThemeAllAppsIcons = ALLAPPS_THEMED_ICONS.get(context);
+ mThemeAllAppsIcons = ALLAPPS_ICON_CUSTOMIZATION.get(context);
} else if (mDisplay == DISPLAY_FOLDER) {
setTextSize(TypedValue.COMPLEX_UNIT_PX,
mDeviceProfile.getFolderProfile().getChildTextSizePx());
@@ -320,10 +320,9 @@ public BubbleTextView(Context context, AttributeSet attrs, int defStyle) {
} else {
// widget_selection or shortcut_popup
defaultIconSize = mDeviceProfile.getWorkspaceIconProfile().getIconSizePx();
- mShouldShowLabel = SHOW_DESKTOP_LABELS.get(context);
+ mShouldShowLabel = true;
}
-
mIconSize = a.getDimensionPixelSize(R.styleable.BubbleTextView_iconSizeOverride,
defaultIconSize);
a.recycle();
@@ -609,7 +608,7 @@ protected boolean shouldUseTwoLine() {
// whether the BubbleTextView height needs to be increased to accommodate an extra line of
// text.
if (mDisplay == DISPLAY_ALL_APPS || mDisplay == DISPLAY_PREDICTION_ROW) {
- return mDeviceProfile.inv.enableTwoLinesInAllApps;
+ return LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE.get(getContext());
}
// Otherwise, show two lines if the cell declares it can fit two line label.
@@ -1004,10 +1003,10 @@ public void setCenterVertically(boolean centerVertically) {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int height = MeasureSpec.getSize(heightMeasureSpec);
- if (mCenterVertically) {
+ if ((mCenterVertically || !mShouldShowLabel) && !mLayoutHorizontal) {
Paint.FontMetrics fm = getPaint().getFontMetrics();
- int cellHeightPx = mIconSize + getCompoundDrawablePadding() +
- (int) Math.ceil(fm.bottom - fm.top) * getCellSpecMaxTextLineCount();
+ int textHeight = mShouldShowLabel ? (int) Math.ceil(fm.bottom - fm.top) * getCellSpecMaxTextLineCount(): 0;
+ int cellHeightPx = mIconSize + getCompoundDrawablePadding() + textHeight;
setPadding(getPaddingLeft(), (height - cellHeightPx) / 2, getPaddingRight(),
getPaddingBottom());
}
@@ -1361,6 +1360,11 @@ private Drawable getIconOrTransparentColor() {
return mIsIconVisible ? mIcon : new ColorDrawable(Color.TRANSPARENT);
}
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+
/** Sets the icon visual state to disabled or not. */
public void setIconDisabled(boolean isDisabled) {
if (mIcon != null) {
@@ -1378,7 +1382,9 @@ protected void applyCompoundDrawables(Drawable icon) {
// same as before.
mDisableRelayout = mIcon != null;
- icon.setBounds(0, 0, mIconSize, mIconSize);
+ if (icon.getBounds().width() != mIconSize || icon.getBounds().height() != mIconSize) {
+ icon.setBounds(0, 0, mIconSize, mIconSize);
+ }
updateIcon(icon);
@@ -1432,6 +1438,9 @@ public void verifyHighRes() {
CacheLookupFlag expectedFlag = DEFAULT_LOOKUP_FLAG.withThemeIcon(shouldUseTheme());
if (getTag() instanceof ItemInfoWithIcon info && !mHighResUpdateInProgress
&& info.getMatchingLookupFlag().isVisuallyLessThan(expectedFlag)) {
+ if (mIcon != null && mIcon.isThemed() && shouldUseTheme()) {
+ return;
+ }
if (mIconLoadRequest != null) {
mIconLoadRequest.cancel();
}
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 87e6281c1e6..25ed69e5653 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -25,12 +25,12 @@
import static com.android.launcher3.deviceprofile.DevicePropertiesKt.createWindowBounds;
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR;
import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
+import static com.android.launcher3.taskbar.TaskbarManagerImpl.ENABLE_TASKBAR_URI;
import static com.android.launcher3.testing.shared.ResourceUtils.INVALID_RESOURCE_HANDLE;
import static com.android.launcher3.testing.shared.ResourceUtils.pxFromDp;
import static com.android.launcher3.util.OverviewReleaseFlags.enableGridOnlyOverview;
import static com.android.wm.shell.Flags.enableBubbleBar;
import static com.android.wm.shell.Flags.enableBubbleBarOnPhones;
-import static com.android.wm.shell.Flags.enableTinyTaskbar;
import static java.lang.Math.max;
@@ -61,6 +61,7 @@
import com.android.launcher3.deviceprofile.OverviewProfile;
import com.android.launcher3.deviceprofile.TaskbarProfile;
import com.android.launcher3.deviceprofile.WorkspaceProfile;
+import com.android.launcher3.allapps.AppDrawerStyle;
import com.android.launcher3.icons.DotRenderer;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.responsive.CalculatedCellSpec;
@@ -75,6 +76,7 @@
import com.android.launcher3.util.DisplayController.Info;
import com.android.launcher3.util.IconSizeSteps;
import com.android.launcher3.util.ResourceHelper;
+import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.util.WindowBounds;
import com.android.launcher3.util.window.WindowManagerProxy;
@@ -109,6 +111,7 @@ public class DeviceProfile {
public boolean isPredictiveBackSwipe;
public final boolean isQsbInline;
+ public final boolean isQsbVisible;
// Device properties in current orientation
@@ -145,6 +148,9 @@ public class DeviceProfile {
public int hotseatBarBottomSpacePx;
public int hotseatQsbSpace;
public int hotseatQsbWidth; // only used when isQsbInline
+ public final int hotseatQsbHeight;
+ public final int hotseatQsbVisualHeight;
+ private final int hotseatQsbShadowHeight;
public int hotseatBorderSpace;
// Space required for the bubble bar between the hotseat and the edge of the screen. If there's
// not enough space, the hotseat will adjust itself for the bubble bar.
@@ -157,6 +163,8 @@ public class DeviceProfile {
public int allAppsCloseDuration;
public int allAppsLeftRightMargin;
public final int numShownAllAppsColumns;
+ private float allAppsCellHeightMultiplier;
+ private boolean allAppsIconText;
private final OverviewProfile overviewProfile;
@@ -187,6 +195,11 @@ public class DeviceProfile {
// DragController
public int flingToDeleteThresholdVelocity;
+ // Meminfo in overview
+ public int memInfoHeight;
+
+ private final Context context;
+
/** Used only as an alternative to mocking when null values cannot be used. */
@VisibleForTesting
public DeviceProfile() {
@@ -224,6 +237,7 @@ public DeviceProfile() {
hotseatProfile = new HotseatProfile(false, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
mTaskbarProfile = new TaskbarProfile(0, 0, 0, 0, 0, false, false);
mFolderProfile = new FolderProfile(0, 0, 0, 0, 0, new Point(), 0, 0, 0, 0, 0, 0, 0, 0);
+ context = null;
inv = null;
mDisplayOptionSpec = null;
mInfo = null;
@@ -231,12 +245,16 @@ public DeviceProfile() {
mIconSizeSteps = null;
isPredictiveBackSwipe = false;
isQsbInline = false;
+ isQsbVisible = true;
isLeftRightSplit = false;
mIsScalableGrid = false;
mTypeIndex = 0;
mIsResponsiveGrid = false;
mDropTargetProfile = new DropTargetProfile(0, 0, 0, 0, 0, 0, 0, 0, 0);
hotseatQsbWidth = 0;
+ hotseatQsbHeight = 0;
+ hotseatQsbVisualHeight = 0;
+ hotseatQsbShadowHeight = 0;
hotseatBorderSpace = 0;
mBubbleBarSpaceThresholdPx = 0;
numShownAllAppsColumns = 0;
@@ -265,6 +283,11 @@ public DeviceProfile() {
isGestureMode
);
+ context = getContext(info, isLandscapeOrientation()
+ ? Configuration.ORIENTATION_LANDSCAPE
+ : Configuration.ORIENTATION_PORTRAIT,
+ windowBounds);
+
mInsets.set(windowBounds.insets);
this.mDisplayOptionSpec = displayOptionSpec;
@@ -279,17 +302,16 @@ public DeviceProfile() {
mIsScalableGrid = inv.isScalable && !isVerticalBarLayout() && !isExternalDisplay;
// Determine device posture.
mInfo = info;
- boolean taskbarOrBubbleBarOnPhones = enableTinyTaskbar()
- || (enableBubbleBar() && enableBubbleBarOnPhones());
- isTaskbarPresent =
- (mDeviceProperties.isTablet() || (taskbarOrBubbleBarOnPhones && isGestureMode))
+ boolean enableTaskbar = SettingsCache.INSTANCE.get(context).getIntValue(
+ ENABLE_TASKBAR_URI, mDeviceProperties.isTablet() ? 1 : 0) != 0;
+ boolean allowTaskbar =
+ mDeviceProperties.isPhone() ? enableTaskbar && isGestureMode : enableTaskbar;
+ boolean taskbarOrBubbleBarOnPhones = enableBubbleBar()
+ && enableBubbleBarOnPhones() && isGestureMode;
+ isTaskbarPresent = (allowTaskbar || taskbarOrBubbleBarOnPhones)
&& wmProxy.isTaskbarDrawnInProcess();
// Some more constants.
- Context context = getContext(info, isLandscapeOrientation()
- ? Configuration.ORIENTATION_LANDSCAPE
- : Configuration.ORIENTATION_PORTRAIT,
- windowBounds);
final Resources res = context.getResources();
overviewProfile = OverviewProfile.Factory.createOverviewProfile(res);
@@ -310,6 +332,10 @@ public DeviceProfile() {
inv
);
+ allAppsCellHeightMultiplier =
+ (float) LauncherPrefs.ROW_HEIGHT.get(context) / 100F;
+ allAppsIconText = LauncherPrefs.SHOW_DRAWER_LABELS.get(context);
+
setupAllAppsStyle(context);
// Some foldable portrait modes are too wide in terms of aspect ratio so we need to tweak
@@ -321,6 +347,8 @@ public DeviceProfile() {
.Factory
.createDropTargetProfile(res, shouldApplyWidePortraitDimens);
+ isQsbVisible = Utilities.showQSB(context);
+
numShownHotseatIcons = displayOptionSpec.numShownHotseatIcons;
mHotseatColumnSpan = inv.numColumns;
@@ -341,17 +369,19 @@ public DeviceProfile() {
mDeviceProperties.getWidthPx())
: hotseatSpecsProvider.getCalculatedSpec(responsiveAspectRatio,
DimensionType.HEIGHT, mDeviceProperties.getHeightPx());
- hotseatQsbSpace = mResponsiveHotseatSpec.getHotseatQsbSpace();
+ hotseatQsbSpace = isQsbVisible ? mResponsiveHotseatSpec.getHotseatQsbSpace() : 0;
hotseatBarBottomSpace =
- isVerticalBarLayout() ? 0 : mResponsiveHotseatSpec.getEdgePadding();
+ isVerticalBarLayout() || (!isQsbVisible && !isTaskbarPresent) ?
+ 0 : mResponsiveHotseatSpec.getEdgePadding();
ResponsiveCellSpecsProvider workspaceCellSpecs = ResponsiveCellSpecsProvider.create(
new ResourceHelper(context, displayOptionSpec.workspaceCellSpecsId));
mResponsiveWorkspaceCellSpec = workspaceCellSpecs.getCalculatedSpec(
responsiveAspectRatio, mDeviceProperties.getHeightPx());
} else {
- hotseatQsbSpace = pxFromDp(inv.hotseatQsbSpace[mTypeIndex], mMetrics);
- hotseatBarBottomSpace = pxFromDp(inv.hotseatBarBottomSpace[mTypeIndex], mMetrics);
+ hotseatQsbSpace = isQsbVisible ? pxFromDp(inv.hotseatQsbSpace[mTypeIndex], mMetrics) : 0;
+ hotseatBarBottomSpace = isQsbVisible || isTaskbarPresent ?
+ pxFromDp(inv.hotseatBarBottomSpace[mTypeIndex], mMetrics) : 0;
}
hotseatProfile = HotseatProfile.Factory.createHotseatProfile(
@@ -365,11 +395,16 @@ public DeviceProfile() {
// TODO(431261051) HotseatProfile is calculated before the WorkspaceProfile hence
// this variable needs to be manually set here. A better way to handle this is
// necessary.
- res.getDimensionPixelSize(R.dimen.workspace_page_indicator_height)
+ res.getDimensionPixelSize(R.dimen.workspace_page_indicator_height),
+ isQsbVisible
);
+ hotseatQsbHeight = getHotseatProfile().getQsbHeight();
+ hotseatQsbShadowHeight = getHotseatProfile().getQsbShadowHeight();
+ hotseatQsbVisualHeight = getHotseatProfile().getQsbVisualHeight();
+
// Whether QSB might be inline in appropriate orientation (e.g. landscape).
- isQsbInline = isQsbInline(
+ isQsbInline = isQsbVisible && isQsbInline(
inv,
hotseatProfile,
mDeviceProperties,
@@ -472,6 +507,9 @@ public DeviceProfile() {
splitPlaceholderInset = res.getDimensionPixelSize(R.dimen.split_placeholder_inset);
+ memInfoHeight = LauncherPrefs.RECENTS_MEMINFO.get(context) ? res.getDimensionPixelSize(
+ R.dimen.meminfo_claimed_height) : 0;
+
// We need to use the full window bounds for split determination because on near-square
// devices, the available bounds (bounds minus insets) may actually be in landscape while
// actually portrait
@@ -517,7 +555,7 @@ public DeviceProfile() {
mInsets,
res,
mWorkspaceProfile.getEdgeMarginPx(),
- shouldShowAllAppsOnSheet(),
+ shouldShowAllAppsOnSheet(context),
mWorkspaceProfile
);
@@ -531,8 +569,7 @@ public DeviceProfile() {
hotseatBorderSpace = getWorkspaceIconProfile().getCellLayoutBorderSpacePx().y;
}
-
- if (shouldShowAllAppsOnSheet()) {
+ if (shouldShowAllAppsOnSheet(context)) {
allAppsPadding.top = allAppsTopPadding;
allAppsShiftRange = mDeviceProperties.getHeightPx() - allAppsTopPadding + mInsets.top;
} else {
@@ -557,6 +594,10 @@ public DeviceProfile() {
getAllAppsProfile().getIconSizePx(), dotRendererCache);
}
+ public Context getContext() {
+ return context;
+ }
+
private boolean isLandscapeOrientation() {
return inv.isFixedLandscape
|| isVerticalBarLayout()
@@ -604,7 +645,7 @@ private boolean isQsbInline(
// In tablets we inline in both orientations but only if we have enough space in the QSB
boolean tabletInlineQsb = inv.inlineQsb[INDEX_DEFAULT] || inv.inlineQsb[INDEX_LANDSCAPE];
boolean canQsbInline = deviceProperties.isTwoPanels() ? twoPanelCanInline : tabletInlineQsb;
- canQsbInline = canQsbInline && hotseatProfile.getQsbHeight() > 0;
+ canQsbInline = canQsbInline && hotseatQsbHeight > 0;
return (isScalableGrid && inv.inlineQsb[mTypeIndex] && canQsbInline)
|| inv.isFixedLandscape;
@@ -628,7 +669,7 @@ private static DotRenderer createDotRenderer(
*/
public int getMaxAllAppsRowCount() {
return (int) (Math.ceil((mDeviceProperties.getAvailableHeightPx() - allAppsPadding.top)
- / (float) getAllAppsProfile().getCellHeightPx()));
+ / ((float) getAllAppsProfile().getCellHeightPx() * allAppsCellHeightMultiplier)));
}
/**
@@ -678,12 +719,12 @@ private void updateHotseatSizes(int hotseatIconSizePx) {
hotseatBarSizePx = hotseatIconSizePx + getHotseatProfile().getBarEdgePaddingPx()
+ getHotseatProfile().getBarWorkspaceSpacePx();
} else if (isQsbInline) {
- hotseatBarSizePx = max(hotseatIconSizePx, getHotseatProfile().getQsbVisualHeight())
+ hotseatBarSizePx = max(hotseatIconSizePx, hotseatQsbVisualHeight)
+ hotseatBarBottomSpacePx;
} else {
hotseatBarSizePx = hotseatIconSizePx
+ hotseatQsbSpace
- + getHotseatProfile().getQsbVisualHeight()
+ + hotseatQsbVisualHeight
+ hotseatBarBottomSpacePx;
}
}
@@ -839,7 +880,8 @@ public void updateIconSize(float scale, Context context) {
mResponsiveAllAppsWidthSpec,
mResponsiveAllAppsHeightSpec,
mIconSizeSteps,
- isVerticalBarLayout()
+ isVerticalBarLayout(),
+ allAppsCellHeightMultiplier
);
updateAllAppsWithResponsiveMeasures();
} else {
@@ -851,7 +893,8 @@ public void updateIconSize(float scale, Context context) {
mTypeIndex,
scale,
getWorkspaceIconProfile().getIconSizePx(),
- mWorkspaceProfile.getIconDrawablePaddingOriginalPx()
+ mWorkspaceProfile.getIconDrawablePaddingOriginalPx(),
+ allAppsCellHeightMultiplier
);
}
updateAllAppsContainerWidth();
@@ -859,12 +902,32 @@ public void updateIconSize(float scale, Context context) {
hideWorkspaceLabelsIfNotEnoughSpace();
}
- if (inv.enableTwoLinesInAllApps
+ // If drawer labels are disabled, make the All Apps cell closer to a square
+ // by basing its height on the drawer width and column count.
+ if (!allAppsIconText) {
+ int cellLayoutHorizontalPadding =
+ (mWorkspaceProfile.getCellLayoutPaddingPx().left
+ + mWorkspaceProfile.getCellLayoutPaddingPx().right) / 2;
+ int leftRightPadding =
+ getWorkspaceIconProfile().getDesiredWorkspaceHorizontalMarginPx()
+ + cellLayoutHorizontalPadding;
+ int drawerWidth =
+ mDeviceProperties.getAvailableWidthPx() - leftRightPadding * 2;
+
+ // Use the number of shown All Apps columns for actual cell width
+ int cellWidth = drawerWidth / numShownAllAppsColumns;
+ int cellHeight = (int) (cellWidth * allAppsCellHeightMultiplier);
+
+ mAllAppsProfile = getAllAppsProfile().copyWithCellHeightPx(cellHeight);
+ }
+
+ if (LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE.get(context)
+ && allAppsIconText
&& !(mIsResponsiveGrid && getAllAppsProfile().getMaxAllAppsTextLineCount() == 2)) {
// Add extra textHeight to the existing allAppsCellHeight.
mAllAppsProfile = getAllAppsProfile().copyWithCellHeightPx(
- getAllAppsProfile().getCellHeightPx() + Utilities.calculateTextHeight(
- getAllAppsProfile().getIconTextSizePx())
+ (int) ((float) getAllAppsProfile().getCellHeightPx() * allAppsCellHeightMultiplier)
+ + Utilities.calculateTextHeight( getAllAppsProfile().getIconTextSizePx())
);
}
@@ -931,8 +994,15 @@ private void updateAllAppsContainerWidth() {
}
/** Whether All Apps should be presented on a bottom sheet. */
- public boolean shouldShowAllAppsOnSheet() {
- return mDeviceProperties.isTablet() || Flags.allAppsSheetForHandheld();
+ public boolean shouldShowAllAppsOnSheet(Context context) {
+ if (context != null) {
+ String style = AppDrawerStyle.get(context);
+ if (AppDrawerStyle.isVerticalPaged(style) || AppDrawerStyle.isFullscreen(style)) {
+ return false;
+ }
+ }
+ return (mDeviceProperties.isTablet() || Flags.allAppsSheetForHandheld())
+ && !AppDrawerStyle.isVerticalPaged(AppDrawerStyle.get(this.context));
}
private void setupAllAppsStyle(Context context) {
@@ -1162,7 +1232,14 @@ public boolean shouldAdjustHotseatOrQsbForBubbleBar(Context context) {
* Returns the padding for hotseat view
*/
public Rect getHotseatLayoutPadding(Context context) {
+ boolean isTaskbarPresent = this.isTaskbarPresent &&
+ SettingsCache.INSTANCE.get(context)
+ .getIntValue(ENABLE_TASKBAR_URI, mDeviceProperties.isTablet() ? 1 : 0) != 0;
Rect hotseatBarPadding = new Rect();
+ int iconExtraSpacePx = getWorkspaceIconProfile().getIconSizePx() - getIconVisibleSizePx(
+ getWorkspaceIconProfile().getIconSizePx());
+ int hotseatWidth = getHotseatRequiredWidth();
+ boolean isRtl = Utilities.isRtl(context.getResources());
if (isVerticalBarLayout()) {
// The hotseat icons will be placed in the middle of the hotseat cells.
// Changing the hotseatCellHeightPx is not affecting hotseat icon positions
@@ -1190,8 +1267,6 @@ public Rect getHotseatLayoutPadding(Context context) {
int hotseatPlusQSBWidth = getIconToIconWidthForColumns(inv.numColumns);
// This is needed because of b/235886078 since QSB needs to span to the icon borders
- int iconExtraSpacePx = getWorkspaceIconProfile().getIconSizePx() - getIconVisibleSizePx(
- getWorkspaceIconProfile().getIconSizePx());
int qsbWidth = getAdditionalQsbSpace() + iconExtraSpacePx / 2;
int availableWidthPxForHotseat = mDeviceProperties.getAvailableWidthPx() - Math.abs(
@@ -1220,58 +1295,49 @@ public Rect getHotseatLayoutPadding(Context context) {
int hotseatBarTopPadding =
hotseatBarSizePx - hotseatBarBottomPadding - hotseatCellHeightPx;
- int hotseatWidth = getHotseatRequiredWidth();
- int startSpacing;
- int endSpacing;
// Hotseat aligns to the left with nav buttons
if (getHotseatProfile().getBarEndOffset() > 0) {
- startSpacing = getHotseatProfile().getInlineNavButtonsEndSpacingPx();
- endSpacing = mDeviceProperties.getAvailableWidthPx() - hotseatWidth - startSpacing
+ int startSpacing = getHotseatProfile().getInlineNavButtonsEndSpacingPx();
+ int endSpacing = mDeviceProperties.getAvailableWidthPx() - hotseatWidth - startSpacing
+ hotseatBorderSpace;
+
+ startSpacing += getAdditionalQsbSpace();
+
+ if (isRtl) {
+ hotseatBarPadding.left = endSpacing;
+ hotseatBarPadding.right = startSpacing;
+ } else {
+ hotseatBarPadding.left = startSpacing;
+ hotseatBarPadding.right = endSpacing;
+ }
} else {
- startSpacing = (mDeviceProperties.getAvailableWidthPx() - hotseatWidth) / 2;
- endSpacing = startSpacing;
+ int sideSpacing = isQsbInline ? (mDeviceProperties.getAvailableWidthPx() - hotseatWidth) / 2 :
+ (mDeviceProperties.getAvailableWidthPx() - (hotseatQsbWidth + iconExtraSpacePx)) / 2;
+ if (isRtl) {
+ hotseatBarPadding.left = sideSpacing + mInsets.left;
+ hotseatBarPadding.right = sideSpacing + getAdditionalQsbSpace() + mInsets.right;
+ } else {
+ hotseatBarPadding.left = sideSpacing + getAdditionalQsbSpace() + mInsets.left;
+ hotseatBarPadding.right = sideSpacing + mInsets.right;
+ }
}
- startSpacing += getAdditionalQsbSpace();
hotseatBarPadding.top = hotseatBarTopPadding;
hotseatBarPadding.bottom = hotseatBarBottomPadding;
- boolean isRtl = Utilities.isRtl(context.getResources());
+ } else {
+ int sideSpacing = isQsbInline ? (mDeviceProperties.getAvailableWidthPx() - hotseatWidth) / 2 :
+ (mDeviceProperties.getAvailableWidthPx() - (hotseatQsbWidth + iconExtraSpacePx)) / 2;
if (isRtl) {
- hotseatBarPadding.left = endSpacing;
- hotseatBarPadding.right = startSpacing;
+ hotseatBarPadding.set(sideSpacing + mInsets.left,
+ 0,
+ sideSpacing + getAdditionalQsbSpace() + mInsets.right,
+ getHotseatBarBottomPadding());
} else {
- hotseatBarPadding.left = startSpacing;
- hotseatBarPadding.right = endSpacing;
+ hotseatBarPadding.set(sideSpacing + getAdditionalQsbSpace() + mInsets.left,
+ 0,
+ sideSpacing + mInsets.right,
+ getHotseatBarBottomPadding());
}
-
- } else if (mIsScalableGrid) {
- int iconExtraSpacePx = getWorkspaceIconProfile().getIconSizePx() - getIconVisibleSizePx(
- getWorkspaceIconProfile().getIconSizePx());
- int sideSpacing =
- (mDeviceProperties.getAvailableWidthPx() - (hotseatQsbWidth + iconExtraSpacePx))
- / 2;
- hotseatBarPadding.set(sideSpacing,
- 0,
- sideSpacing,
- getHotseatBarBottomPadding());
- } else {
- // We want the edges of the hotseat to line up with the edges of the workspace, but the
- // icons in the hotseat are a different size, and so don't line up perfectly. To account
- // for this, we pad the left and right of the hotseat with half of the difference of a
- // workspace cell vs a hotseat cell.
- float workspaceCellWidth = (float) mDeviceProperties.getWidthPx() / inv.numColumns;
- float hotseatCellWidth = (float) mDeviceProperties.getWidthPx() / numShownHotseatIcons;
- int hotseatAdjustment = Math.round((workspaceCellWidth - hotseatCellWidth) / 2);
- hotseatBarPadding.set(
- hotseatAdjustment + mWorkspaceProfile.getWorkspacePadding().left
- + mWorkspaceProfile.getCellLayoutPaddingPx().left
- + mInsets.left,
- 0,
- hotseatAdjustment + mWorkspaceProfile.getWorkspacePadding().right
- + mWorkspaceProfile.getCellLayoutPaddingPx().right
- + mInsets.right,
- getHotseatBarBottomPadding());
}
return hotseatBarPadding;
}
@@ -1332,12 +1398,12 @@ private int getHotseatRequiredWidth() {
public int getQsbOffsetY() {
if (isQsbInline) {
return getHotseatBarBottomPadding()
- - ((getHotseatProfile().getQsbHeight() - hotseatCellHeightPx) / 2);
+ - ((hotseatQsbHeight - hotseatCellHeightPx) / 2);
} else if (isTaskbarPresent) { // QSB on top
- return hotseatBarSizePx - getHotseatProfile().getQsbHeight()
- + getHotseatProfile().getQsbShadowHeight();
+ return hotseatBarSizePx - hotseatQsbHeight
+ + hotseatQsbShadowHeight;
} else {
- return hotseatBarBottomSpacePx - getHotseatProfile().getQsbShadowHeight();
+ return hotseatBarBottomSpacePx - hotseatQsbShadowHeight;
}
}
@@ -1360,12 +1426,12 @@ private int getHotseatBarBottomPadding() {
public int getBubbleBarVerticalCenterForHome() {
if (shouldAlignBubbleBarWithHotseat()) {
return hotseatBarSizePx
- - (isQsbInline ? 0 : getHotseatProfile().getQsbVisualHeight())
+ - (isQsbInline ? 0 : hotseatQsbVisualHeight)
- hotseatQsbSpace
- (hotseatCellHeightPx / 2)
+ ((hotseatCellHeightPx - getWorkspaceIconProfile().getIconSizePx()) / 2);
} else {
- return hotseatBarSizePx - (getHotseatProfile().getQsbVisualHeight() / 2);
+ return hotseatBarSizePx - (hotseatQsbVisualHeight / 2);
}
}
@@ -1403,7 +1469,7 @@ public int getOverviewActionsClaimedSpace() {
int overviewActionsSpace = mDeviceProperties.isTablet() && enableGridOnlyOverview()
? 0
: (overviewProfile.getActionsTopMarginPx() + overviewProfile.getActionsHeight());
- return overviewActionsSpace + getOverviewActionsClaimedSpaceBelow();
+ return overviewActionsSpace + memInfoHeight + getOverviewActionsClaimedSpaceBelow();
}
/**
@@ -1451,11 +1517,13 @@ public Rect getAbsoluteOpenFolderBounds() {
}
public static int calculateCellWidth(int width, int borderSpacing, int countX) {
- return (width - ((countX - 1) * borderSpacing)) / countX;
+ int adjustedCountX = countX <= 0 ? 1 : countX;
+ return (width - ((adjustedCountX - 1) * borderSpacing)) / adjustedCountX;
}
public static int calculateCellHeight(int height, int borderSpacing, int countY) {
- return (height - ((countY - 1) * borderSpacing)) / countY;
+ int adjustedCountY = countY <= 0 ? 1 : countY;
+ return (height - ((adjustedCountY - 1) * borderSpacing)) / adjustedCountY;
}
/**
@@ -1654,7 +1722,7 @@ public void dump(Context context, String prefix, PrintWriter writer) {
writer.println(prefix + pxToDpStr("hotseatBarEndOffset",
getHotseatProfile().getBarEndOffset()));
writer.println(prefix + pxToDpStr("hotseatQsbSpace", hotseatQsbSpace));
- writer.println(prefix + pxToDpStr("hotseatQsbHeight", getHotseatProfile().getQsbHeight()));
+ writer.println(prefix + pxToDpStr("hotseatQsbHeight", hotseatQsbHeight));
writer.println(prefix + pxToDpStr("springLoadedHotseatBarTopMarginPx",
getHotseatProfile().getSpringLoadedBarTopMarginPx()));
Rect hotseatLayoutPadding = getHotseatLayoutPadding(context);
diff --git a/src/com/android/launcher3/FastScrollRecyclerView.java b/src/com/android/launcher3/FastScrollRecyclerView.java
index 17084bb5315..d3abb60d779 100644
--- a/src/com/android/launcher3/FastScrollRecyclerView.java
+++ b/src/com/android/launcher3/FastScrollRecyclerView.java
@@ -133,6 +133,10 @@ protected void synchronizeScrollBarThumbOffsetToViewScroll(int scrollY,
* @param ev MotionEvent in {@param eventSource}
*/
public boolean shouldContainerScroll(MotionEvent ev, View eventSource) {
+ if (mScrollbar == null || eventSource == null || mScrollbar.getParent() == null
+ || eventSource.getWindowId() == null || mScrollbar.getWindowId() == null) {
+ return computeVerticalScrollOffset() == 0;
+ }
float[] point = new float[2];
point[0] = ev.getX();
point[1] = ev.getY();
diff --git a/src/com/android/launcher3/FirstFrameAnimatorHelper.java b/src/com/android/launcher3/FirstFrameAnimatorHelper.java
index fdf0101e663..931ea9739b3 100644
--- a/src/com/android/launcher3/FirstFrameAnimatorHelper.java
+++ b/src/com/android/launcher3/FirstFrameAnimatorHelper.java
@@ -23,6 +23,7 @@
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
import android.view.ViewTreeObserver.OnDrawListener;
+import android.view.animation.AnimationUtils;
/*
* This is a helper class that listens to updates from the corresponding animation.
@@ -78,7 +79,7 @@ private class MyListener implements AnimatorUpdateListener {
@Override
public void onAnimationUpdate(final ValueAnimator animation) {
- final long currentTime = System.currentTimeMillis();
+ final long currentTime = AnimationUtils.currentAnimationTimeMillis();
if (mStartTime == -1) {
mStartFrame = mGlobalFrameCount;
mStartTime = currentTime;
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 239e8afb229..dee953a684d 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -105,11 +105,13 @@ public Hotseat(Context context, AttributeSet attrs) {
public Hotseat(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
- if (Flags.enableQsbOnHotseat()) {
- mQsb = LayoutInflater.from(context).inflate(R.layout.qsb_container_hotseat, this,
- false);
+ if (Utilities.showQSB(context)) {
+ int layoutRes = LauncherPrefs.DOCK_SEARCH_PIXEL_STYLE.get(context)
+ ? R.layout.search_container_hotseat_pixel
+ : R.layout.search_container_hotseat;
+ mQsb = LayoutInflater.from(context).inflate(layoutRes, this, false);
} else {
- mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this,
+ mQsb = LayoutInflater.from(context).inflate(R.layout.empty_view, this,
false);
}
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 3444a3fdf4b..864fee6c35e 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -20,12 +20,16 @@
import static com.android.launcher3.GridType.GRID_TYPE_ANY;
import static com.android.launcher3.GridType.GRID_TYPE_NON_ONE_GRID;
import static com.android.launcher3.GridType.GRID_TYPE_ONE_GRID;
-import static com.android.launcher3.LauncherPrefs.ALLAPPS_THEMED_ICONS;
+import static com.android.launcher3.LauncherPrefs.ALLAPPS_ICON_CUSTOMIZATION;
import static com.android.launcher3.LauncherPrefs.DB_FILE;
+import static com.android.launcher3.LauncherPrefs.DRAWER_OPEN_KEYBOARD;
import static com.android.launcher3.LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE;
import static com.android.launcher3.LauncherPrefs.FIXED_LANDSCAPE_MODE;
+import static com.android.launcher3.LauncherPrefs.FONT_SIZE;
import static com.android.launcher3.LauncherPrefs.GRID_NAME;
+import static com.android.launcher3.LauncherPrefs.ICON_SIZE;
import static com.android.launcher3.LauncherPrefs.NON_FIXED_LANDSCAPE_GRID_NAME;
+import static com.android.launcher3.LauncherPrefs.ROW_HEIGHT;
import static com.android.launcher3.LauncherPrefs.SHOW_DESKTOP_LABELS;
import static com.android.launcher3.LauncherPrefs.SHOW_DRAWER_LABELS;
import static com.android.launcher3.Utilities.dpiFromPx;
@@ -63,6 +67,7 @@
import androidx.annotation.XmlRes;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.customization.IconDatabase;
import com.android.launcher3.dagger.ApplicationContext;
import com.android.launcher3.dagger.LauncherAppComponent;
import com.android.launcher3.dagger.LauncherAppSingleton;
@@ -166,6 +171,7 @@ public class InvariantDeviceProfile {
public int[] numFolderColumns;
public float[] iconSize;
public float[] iconTextSize;
+ public String iconPack;
public int iconBitmapSize;
public int fillResIconDpi;
public @DeviceType int deviceType;
@@ -248,7 +254,6 @@ public class InvariantDeviceProfile {
public int allAppsCellSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
private String mLocale = "";
- public boolean enableTwoLinesInAllApps = false;
// If non-negative, the workspace row with which top of the all apps container is to be aligned
// with.
@@ -319,12 +324,15 @@ public class InvariantDeviceProfile {
onConfigChanged();
}
Trace.endSection();
- } else if (ENABLE_TWOLINE_ALLAPPS_TOGGLE.getSharedPrefKey().equals(key)
- && enableTwoLinesInAllApps != prefs.get(ENABLE_TWOLINE_ALLAPPS_TOGGLE)) {
- onConfigChanged();
- } else if (ALLAPPS_THEMED_ICONS.getSharedPrefKey().equals(key) ||
+ } else if (ALLAPPS_ICON_CUSTOMIZATION.getSharedPrefKey().equals(key) ||
+ DRAWER_OPEN_KEYBOARD.getSharedPrefKey().equals(key) ||
SHOW_DESKTOP_LABELS.getSharedPrefKey().equals(key) ||
- SHOW_DRAWER_LABELS.getSharedPrefKey().equals(key)) {
+ SHOW_DRAWER_LABELS.getSharedPrefKey().equals(key) ||
+ ICON_SIZE.getSharedPrefKey().equals(key) ||
+ FONT_SIZE.getSharedPrefKey().equals(key) ||
+ ENABLE_TWOLINE_ALLAPPS_TOGGLE.getSharedPrefKey().equals(key) ||
+ ROW_HEIGHT.getSharedPrefKey().equals(key) ||
+ IconDatabase.KEY_ICON_PACK.equals(key)) {
onConfigChanged();
}
};
@@ -388,9 +396,6 @@ public void reset() {
private void initGridForDisplayOption(Info displayInfo, DisplayOption displayOption) {
Context context = displayInfo.context;
- enableTwoLinesInAllApps = Flags.enableTwolineToggle()
- && Utilities.isEnglishLanguage(context)
- && mPrefs.get(ENABLE_TWOLINE_ALLAPPS_TOGGLE);
mLocale = context.getResources().getConfiguration().locale.toString();
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
@@ -436,6 +441,8 @@ private void initGridForDisplayOption(Info displayInfo, DisplayOption displayOpt
for (int i = 1; i < iconSize.length; i++) {
maxIconSize = Math.max(maxIconSize, iconSize[i]);
}
+ iconPack = IconDatabase.getGlobal(context);
+
iconBitmapSize = ResourceUtils.pxFromDp(maxIconSize, metrics);
fillResIconDpi = getLauncherIconDensity(iconBitmapSize);
@@ -551,7 +558,8 @@ public void setCurrentGrid(String newGridName) {
private Object[] toModelState() {
return new Object[]{
numColumns, numRows, numSearchContainerColumns, numDatabaseHotseatIcons,
- iconBitmapSize, fillResIconDpi, numDatabaseAllAppsColumns, dbFile, mLocale};
+ iconPack, iconBitmapSize, fillResIconDpi, numDatabaseAllAppsColumns,
+ dbFile, mLocale};
}
/** Updates IDP using the provided context. Notifies listeners of change. */
@@ -1472,6 +1480,11 @@ static final class DisplayOption {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ProfileDisplayOption);
+ float iconSizeModifier =
+ (float) LauncherPrefs.ICON_SIZE.get(context) / 100F;
+ float fontSizeModifier =
+ (float) LauncherPrefs.FONT_SIZE.get(context) / 100F;
+
minWidthDps = a.getFloat(R.styleable.ProfileDisplayOption_minWidthDps, 0);
minHeightDps = a.getFloat(R.styleable.ProfileDisplayOption_minHeightDps, 0);
@@ -1600,20 +1613,23 @@ static final class DisplayOption {
allAppsBorderSpaceTwoPanelLandscape);
allAppsBorderSpaces[INDEX_TWO_PANEL_LANDSCAPE] = new PointF(x, y);
- iconSizes[INDEX_DEFAULT] =
- a.getFloat(R.styleable.ProfileDisplayOption_iconImageSize, 0);
- iconSizes[INDEX_LANDSCAPE] =
- a.getFloat(R.styleable.ProfileDisplayOption_iconSizeLandscape,
- iconSizes[INDEX_DEFAULT]);
- iconSizes[INDEX_TWO_PANEL_PORTRAIT] =
- a.getFloat(R.styleable.ProfileDisplayOption_iconSizeTwoPanelPortrait,
- iconSizes[INDEX_DEFAULT]);
- iconSizes[INDEX_TWO_PANEL_LANDSCAPE] =
- a.getFloat(R.styleable.ProfileDisplayOption_iconSizeTwoPanelLandscape,
- iconSizes[INDEX_DEFAULT]);
-
- allAppsIconSizes[INDEX_DEFAULT] = a.getFloat(
- R.styleable.ProfileDisplayOption_allAppsIconSize, iconSizes[INDEX_DEFAULT]);
+ float baseIconSize = a.getFloat(R.styleable.ProfileDisplayOption_iconImageSize, 0);
+
+ iconSizes[INDEX_DEFAULT] = baseIconSize * iconSizeModifier;
+ iconSizes[INDEX_LANDSCAPE] = a.getFloat(
+ R.styleable.ProfileDisplayOption_iconSizeLandscape,
+ iconSizes[INDEX_DEFAULT]);
+ iconSizes[INDEX_TWO_PANEL_PORTRAIT] = a.getFloat(
+ R.styleable.ProfileDisplayOption_iconSizeTwoPanelPortrait,
+ iconSizes[INDEX_DEFAULT]);
+ iconSizes[INDEX_TWO_PANEL_LANDSCAPE] = a.getFloat(
+ R.styleable.ProfileDisplayOption_iconSizeTwoPanelLandscape,
+ iconSizes[INDEX_DEFAULT]);
+
+ float baseAllAppsIconSize = a.getFloat(R.styleable.ProfileDisplayOption_allAppsIconSize, 0);
+
+ allAppsIconSizes[INDEX_DEFAULT] = baseAllAppsIconSize == 0 ?
+ iconSizes[INDEX_DEFAULT] : baseAllAppsIconSize * iconSizeModifier;
allAppsIconSizes[INDEX_LANDSCAPE] = a.getFloat(
R.styleable.ProfileDisplayOption_allAppsIconSizeLandscape,
allAppsIconSizes[INDEX_DEFAULT]);
@@ -1625,7 +1641,7 @@ static final class DisplayOption {
allAppsIconSizes[INDEX_DEFAULT]);
textSizes[INDEX_DEFAULT] =
- a.getFloat(R.styleable.ProfileDisplayOption_iconTextSize, 0);
+ a.getFloat(R.styleable.ProfileDisplayOption_iconTextSize, 0) * fontSizeModifier;
textSizes[INDEX_LANDSCAPE] =
a.getFloat(R.styleable.ProfileDisplayOption_iconTextSizeLandscape,
textSizes[INDEX_DEFAULT]);
@@ -1671,16 +1687,16 @@ static final class DisplayOption {
R.styleable.ProfileDisplayOption_hotseatBarBottomSpaceTwoPanelPortrait,
hotseatBarBottomSpace[INDEX_DEFAULT]);
- hotseatQsbSpace[INDEX_DEFAULT] = a.getFloat(
+ hotseatQsbSpace[INDEX_DEFAULT] = !Utilities.showQSB(context) ? 0f : a.getFloat(
R.styleable.ProfileDisplayOption_hotseatQsbSpace,
res.getFloat(R.dimen.hotseat_qsb_space_default));
- hotseatQsbSpace[INDEX_LANDSCAPE] = a.getFloat(
+ hotseatQsbSpace[INDEX_LANDSCAPE] = !Utilities.showQSB(context) ? 0f : a.getFloat(
R.styleable.ProfileDisplayOption_hotseatQsbSpaceLandscape,
hotseatQsbSpace[INDEX_DEFAULT]);
- hotseatQsbSpace[INDEX_TWO_PANEL_LANDSCAPE] = a.getFloat(
+ hotseatQsbSpace[INDEX_TWO_PANEL_LANDSCAPE] = !Utilities.showQSB(context) ? 0f : a.getFloat(
R.styleable.ProfileDisplayOption_hotseatQsbSpaceTwoPanelLandscape,
hotseatQsbSpace[INDEX_DEFAULT]);
- hotseatQsbSpace[INDEX_TWO_PANEL_PORTRAIT] = a.getFloat(
+ hotseatQsbSpace[INDEX_TWO_PANEL_PORTRAIT] = !Utilities.showQSB(context) ? 0f : a.getFloat(
R.styleable.ProfileDisplayOption_hotseatQsbSpaceTwoPanelPortrait,
hotseatQsbSpace[INDEX_DEFAULT]);
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index e82551305e1..087ea9bd199 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -53,6 +53,7 @@
import static com.android.launcher3.LauncherConstants.TraceEvents.ON_RESUME_EVT;
import static com.android.launcher3.LauncherConstants.TraceEvents.ON_START_EVT;
import static com.android.launcher3.LauncherConstants.TraceEvents.SINGLE_TRACE_COOKIE;
+import static com.android.launcher3.LauncherPrefs.DRAWER_OPEN_KEYBOARD;
import static com.android.launcher3.LauncherPrefs.FIXED_LANDSCAPE_MODE;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APPS;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APPS_PREDICTION;
@@ -98,6 +99,8 @@
import static com.android.launcher3.popup.SystemShortcut.APP_INFO;
import static com.android.launcher3.popup.SystemShortcut.INSTALL;
import static com.android.launcher3.popup.SystemShortcut.REMOVE;
+import static com.android.launcher3.popup.SystemShortcut.RENAME_APP;
+import static com.android.launcher3.popup.SystemShortcut.UNINSTALL;
import static com.android.launcher3.popup.SystemShortcut.WIDGETS;
import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
@@ -211,6 +214,7 @@
import com.android.launcher3.popup.ArrowPopup;
import com.android.launcher3.popup.PopupController;
import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.quickspace.QuickSpaceView;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.statemanager.StatefulActivity;
@@ -427,6 +431,9 @@ public class Launcher extends StatefulActivity
private boolean mIsTopResumedActivity;
+ // QuickSpace
+ private QuickSpaceView mQuickSpace;
+
public static Launcher getLauncher(Context context) {
return fromContext(context);
}
@@ -528,7 +535,8 @@ protected void onCreate(Bundle savedInstanceState) {
// Listen for screen turning off
ScreenOnTracker.INSTANCE.get(this).addListener(mScreenOnListener);
getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW,
- Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText));
+ Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText)
+ || LauncherPrefs.DARK_STATUS_BAR.get(this));
mOverlayManager = getDefaultOverlay();
PluginManagerWrapper.INSTANCE.get(this)
@@ -700,12 +708,7 @@ private void updateFixedLandscape() {
if (!com.android.launcher3.Flags.oneGridSpecs()) {
return;
}
- // When the flag oneGridSpecs is on we want to disable ALLOW_ROTATION which is replaced
- // by FIXED_LANDSCAPE_MODE, ALLOW_ROTATION will only be used on Tablets and foldables
- // afterwards.
- if (getDeviceProfile().getDeviceProperties().isPhone()) {
- LauncherPrefs.get(this).put(LauncherPrefs.ALLOW_ROTATION, false);
- } else if (getDeviceProfile().getDeviceProperties().isTablet()) {
+ if (getDeviceProfile().getDeviceProperties().isTablet()) {
// Tablet do not use fixed landscape mode, make sure it can't be activated by mistake
LauncherPrefs.get(this).put(FIXED_LANDSCAPE_MODE, false);
}
@@ -996,6 +999,9 @@ protected void onStop() {
} else {
mOverlayManager.onActivityStopped();
}
+ if (mQuickSpace != null) {
+ mQuickSpace.onPause();
+ }
hideKeyboard();
logStopAndResume(false /* isResume */);
mAppWidgetHolder.setActivityStarted(false);
@@ -1128,7 +1134,7 @@ public void onStateSetStart(LauncherState state) {
mWorkspace.setClipChildren(false);
}
// When multiple pages are visible or desktop devices, show persistent page indicator
- mWorkspace.getPageIndicator().setShouldAutoHide(!state.hasFlag(FLAG_MULTI_PAGE)
+ mWorkspace.getPageIndicator().setShouldAutoHide(LauncherPrefs.AUTO_HIDE_DOTS.get(this)
&& !shouldEnableMouseInteractionChanges(mWorkspace.getContext()));
mPrevLauncherState = mStateManager.getCurrentStableState();
@@ -1183,6 +1189,8 @@ public void onStateSetEnd(LauncherState state) {
getAppsView().reset(false /* animate */, true /* clearScrim */);
getAllAppsExitEvent().ifPresent(getStatsLogManager().logger()::log);
mAllAppsSessionLogId = null;
+ } else if (ALL_APPS.equals(state) && DRAWER_OPEN_KEYBOARD.get(this)) {
+ getAppsView().getSearchUiManager().focusSearchField();
}
setTitle(state);
}
@@ -1204,6 +1212,10 @@ protected void onResume() {
TraceHelper.INSTANCE.beginSection(ON_RESUME_EVT);
super.onResume();
+ if (mQuickSpace != null) {
+ mQuickSpace.onResume();
+ }
+
if (mDeferOverlayCallbacks) {
scheduleDeferredCheck();
} else {
@@ -1227,6 +1239,9 @@ protected void onPause() {
if (!mDeferOverlayCallbacks) {
mOverlayManager.onActivityPaused();
}
+ if (mQuickSpace != null) {
+ mQuickSpace.onPause();
+ }
mAppWidgetHolder.setActivityResumed(false);
}
@@ -1296,6 +1311,11 @@ protected void setupViews() {
mLeftArrow.setOnClickListener(v -> mWorkspace.snapToPage(
mWorkspace.getCurrentPage() - 1));
+ if (LauncherPrefs.SHOW_HOTSEAT_BG.get(this)) {
+ mHotseat.setBackgroundResource(R.drawable.bkg_appseat);
+ mHotseat.getBackground().setAlpha(LauncherPrefs.HOTSEAT_OPACITY.get(this) * 255 / 100);
+ }
+
// Setup the drag layer
mDragLayer.setup(mDragController, mWorkspace);
@@ -1316,11 +1336,17 @@ protected void setupViews() {
// Setup Scrim
mScrimView = findViewById(R.id.scrim_view);
+ // QuickSpace
+ mQuickSpace = findViewById(R.id.reserved_container_workspace);
+ if (!LauncherPrefs.SHOW_QUICKSPACE.get(this) && mQuickSpace != null) {
+ mQuickSpace.setVisibility(View.GONE);
+ }
+
// Setup the drag controller (drop targets have to be added in reverse order in priority)
mDropTargetBar.setup(mDragController);
mAllAppsController.setupViews(mScrimView, mAppsView);
- mWorkspace.getPageIndicator().setShouldAutoHide(
+ mWorkspace.getPageIndicator().setShouldAutoHide(LauncherPrefs.AUTO_HIDE_DOTS.get(this) &&
!shouldEnableMouseInteractionChanges(mWorkspace.getContext()));
mWorkspace.getPageIndicator().setPaintColor(Themes.getAttrBoolean(
this, R.attr.isWorkspaceDarkText) ? Color.BLACK : Color.WHITE);
@@ -1738,6 +1764,10 @@ public void onDestroy() {
getRootView().getViewTreeObserver().removeOnPreDrawListener(mOnInitialBindListener);
mOverlayManager.onActivityDestroyed();
PillColorProvider.getInstance(mWorkspace.getContext()).unregisterObserver();
+
+ if (mQuickSpace != null) {
+ mQuickSpace.onDestroy();
+ }
}
/**
@@ -2146,6 +2176,12 @@ public RunnableList startActivitySafely(View v, Intent intent, ItemInfo item) {
return result;
}
+ public void startActivitySafelyAuth(View v, Intent intent, ItemInfo item) {
+ Utilities.showLockScreen(this, getString(R.string.trust_apps_manager_name), () -> {
+ startActivitySafely(v, intent, item);
+ });
+ }
+
boolean isHotseatLayout(View layout) {
// TODO: Remove this method
return mHotseat != null && (layout == mHotseat);
@@ -2661,6 +2697,11 @@ public void onStateTransitionCompletedAfterSwipeToHome(LauncherState finalState)
// Overridden
}
+ /** To be overrideden by subclasses */
+ public void onSleepEvent(MotionEvent ev) {
+ // Overridden
+ }
+
public void closeOpenViews() {
closeOpenViews(true);
}
@@ -2687,7 +2728,9 @@ public boolean supportsAdaptiveIconAnimation(View clickedView) {
* @param progress Transition progress from 0 to 1; where 0 => home and 1 => all apps.
*/
public void onAllAppsTransition(float progress) {
- // No-Op
+ if (progress == 0 && mAppsView != null) {
+ hideKeyboard();
+ }
}
/** @return list of View targets to be blurred based on changes to depth. */
@@ -2946,18 +2989,19 @@ protected RectF getPopupTarget(float x, float y) {
public Stream getSupportedShortcuts(ItemInfo itemInfo) {
int container = itemInfo.container;
if (container == CONTAINER_DESKTOP || container == CONTAINER_HOTSEAT) {
- return Stream.of(APP_INFO, WIDGETS, INSTALL, REMOVE);
+ return Stream.of(APP_INFO, RENAME_APP, WIDGETS, INSTALL, REMOVE, UNINSTALL);
} else if (container == CONTAINER_ALL_APPS || container == CONTAINER_ALL_APPS_PREDICTION) {
// TODO(b/444744861): Update private space apps to have its own container.
boolean isPinnable = itemInfo instanceof ItemInfoWithIcon info
&& (info.runtimeStatusFlags & FLAG_NOT_PINNABLE) == 0;
if (isPinnable) {
- return Stream.of(APP_INFO, WIDGETS, INSTALL, ADD_TO_HOME_SCREEN);
+ return Stream.of(APP_INFO, RENAME_APP, WIDGETS, INSTALL,
+ ADD_TO_HOME_SCREEN, UNINSTALL);
} else {
- return Stream.of(APP_INFO, WIDGETS, INSTALL);
+ return Stream.of(APP_INFO, RENAME_APP, WIDGETS, INSTALL, UNINSTALL);
}
}
- return Stream.of(APP_INFO, WIDGETS, INSTALL);
+ return Stream.of(APP_INFO, RENAME_APP, WIDGETS, INSTALL, UNINSTALL);
}
/**
diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java
index 9157dab1434..38737df560f 100644
--- a/src/com/android/launcher3/LauncherAnimUtils.java
+++ b/src/com/android/launcher3/LauncherAnimUtils.java
@@ -67,6 +67,9 @@ public Float get(View view) {
@Override
public void setValue(View view, float scale) {
+ if (Float.isNaN(scale) || Float.isInfinite(scale)) {
+ scale = 1.0f;
+ }
view.setScaleX(scale);
view.setScaleY(scale);
}
diff --git a/src/com/android/launcher3/LauncherAppState.kt b/src/com/android/launcher3/LauncherAppState.kt
index ff84c3c863b..f87545379e3 100644
--- a/src/com/android/launcher3/LauncherAppState.kt
+++ b/src/com/android/launcher3/LauncherAppState.kt
@@ -16,9 +16,10 @@
package com.android.launcher3
import android.content.Context
+import android.widget.Toast;
import com.android.launcher3.dagger.ApplicationContext
import com.android.launcher3.icons.IconCache
-import com.android.launcher3.icons.LauncherIconProvider
+import com.android.launcher3.icons.ThirdPartyIconProvider
import com.android.launcher3.util.DaggerSingletonObject
import javax.inject.Inject
import javax.inject.Named
@@ -29,14 +30,28 @@ data class LauncherAppState
@Inject
constructor(
@ApplicationContext val context: Context,
- val iconProvider: LauncherIconProvider,
+ val iconProvider: ThirdPartyIconProvider,
val iconCache: IconCache,
val model: LauncherModel,
val invariantDeviceProfile: InvariantDeviceProfile,
@Named("SAFE_MODE") val isSafeModeEnabled: Boolean,
) {
+ fun setNeedsRestart() {
+ needsRestart = true
+ }
+
+ fun checkIfRestartNeeded() {
+ // we destroyed Settings activity with the back button
+ // so we force a restart now if needed without waiting for home button press
+ if (needsRestart) {
+ Toast.makeText(context, R.string.restarting_launcher_changes, Toast.LENGTH_SHORT).show();
+ Utilities.restart()
+ }
+ }
+
companion object {
+ @JvmField var needsRestart: Boolean = false
@JvmField var INSTANCE = DaggerSingletonObject { it.launcherAppState }
diff --git a/src/com/android/launcher3/LauncherModel.kt b/src/com/android/launcher3/LauncherModel.kt
index 608a3c6aab2..b22b781c0ae 100644
--- a/src/com/android/launcher3/LauncherModel.kt
+++ b/src/com/android/launcher3/LauncherModel.kt
@@ -15,10 +15,15 @@
*/
package com.android.launcher3
+import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.ShortcutInfo
+import android.database.ContentObserver
+import android.os.Handler
+import android.os.Looper
import android.os.UserHandle
+import android.provider.Settings
import com.android.launcher3.celllayout.CellPosMapper
import com.android.launcher3.dagger.ApplicationContext
import com.android.launcher3.dagger.LauncherAppSingleton
@@ -39,6 +44,7 @@ import com.android.launcher3.model.ModelTaskController
import com.android.launcher3.model.ModelWriter
import com.android.launcher3.model.data.WorkspaceItemInfo
import com.android.launcher3.model.tasks.CacheDataUpdatedTask
+import com.android.launcher3.model.tasks.CustomAppNameChangedTask
import com.android.launcher3.model.tasks.UserAvailabilityChangedTask
import com.android.launcher3.model.tasks.UserLockStateChangedTask
import com.android.launcher3.pm.UserCache
@@ -119,6 +125,17 @@ constructor(
lifecycle.addCloseable { destroy() }
modelDelegate.init(this, mBgAllAppsList, mBgDataModel)
lifecycle.addCloseable(dumpManager.register(this))
+
+ val sandboxObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {
+ override fun onChange(selfChange: Boolean) {
+ forceReload()
+ }
+ }
+ context.contentResolver.registerContentObserver(
+ Settings.Secure.getUriFor("sandbox_config"),
+ false, sandboxObserver
+ )
+ lifecycle.addCloseable { context.contentResolver.unregisterContentObserver(sandboxObserver) }
}
fun newModelCallbacks() = ModelLauncherCallbacks(this::enqueueModelUpdateTask)
@@ -382,6 +399,11 @@ constructor(
}
}
+ /** Called when a user-defined app display name has changed. */
+ fun onCustomAppNameChanged(component: ComponentName, user: UserHandle) {
+ enqueueModelUpdateTask(CustomAppNameChangedTask(component, user))
+ }
+
fun enqueueModelUpdateTask(task: ModelUpdateTask) {
if (mModelDestroyed) {
return
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index 2189476675b..c7b9345c8a5 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -18,6 +18,7 @@ package com.android.launcher3
import android.content.Context
import android.content.Context.MODE_PRIVATE
import android.content.SharedPreferences
+import android.os.UserManager
import androidx.annotation.VisibleForTesting
import com.android.launcher3.GridType.Companion.GRID_TYPE_ANY
import com.android.launcher3.InvariantDeviceProfile.GRID_NAME_PREFS_KEY
@@ -56,8 +57,20 @@ constructor(@ApplicationContext private val encryptedContext: Context) {
open protected fun getSharedPrefs(item: Item): SharedPreferences =
item.run {
- if (encryptionType == EncryptionType.DEVICE_PROTECTED) deviceProtectedSharedPrefs
- else encryptedContext.getSharedPreferences(sharedPrefFile, MODE_PRIVATE)
+ if (encryptionType == EncryptionType.DEVICE_PROTECTED) {
+ deviceProtectedSharedPrefs
+ } else {
+ val um = encryptedContext.getSystemService(UserManager::class.java)
+ val isUnlocked = um?.isUserUnlocked == true
+
+ val ctx = if (isUnlocked) {
+ encryptedContext
+ } else {
+ encryptedContext.createDeviceProtectedStorageContext()
+ }
+
+ ctx.getSharedPreferences(sharedPrefFile, MODE_PRIVATE)
+ }
}
@Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.")
@@ -249,11 +262,61 @@ constructor(@ApplicationContext private val encryptedContext: Context) {
@JvmField
val ENABLE_TWOLINE_ALLAPPS_TOGGLE = backedUpItem("pref_enable_two_line_toggle", false)
@JvmField val WORKSPACE_LOCK = backedUpItem("pref_workspace_lock", false)
- @JvmField val ALLAPPS_THEMED_ICONS = backedUpItem("pref_allapps_themed_icons", false)
+ @JvmField val ALL_APPS_DARK_TEXT = backedUpItem("pref_all_apps_dark_text", false)
+ @JvmField val ALL_APPS_SEARCH_PLACEMENT = backedUpItem("pref_allapps_search_placement", "top")
+ @JvmField val ALLAPPS_ICON_CUSTOMIZATION = backedUpItem("pref_allapps_icon_customization", true)
+ @JvmField val ALLOW_WALLPAPER_ZOOMING = backedUpItem("pref_allow_wallpaper_zooming", true)
+ @JvmField val APP_DRAWER_CUSTOM_COLOR_ENABLED = backedUpItem("pref_app_drawer_custom_color_enabled", false)
+ @JvmField val APP_DRAWER_CUSTOM_COLOR_LIGHT = backedUpItem("pref_app_drawer_custom_color_light", -1)
+ @JvmField val APP_DRAWER_CUSTOM_COLOR_DARK = backedUpItem("pref_app_drawer_custom_color_dark", -16777216)
+ @JvmField val APP_DRAWER_OPACITY = backedUpItem("pref_app_drawer_opacity", 60)
+ @JvmField val APP_DRAWER_SORT_MODE = backedUpItem("pref_app_drawer_sorting_mode", "alphabetical")
+ @JvmField val APP_DRAWER_STYLE = backedUpItem("pref_app_drawer_style", "normal")
+ @JvmField val AUTO_HIDE_DOTS = backedUpItem("pref_auto_hide_dots", true)
+ @JvmField val BLUR_DEPTH = backedUpItem("pref_blur_depth", 30)
+ @JvmField val DARK_STATUS_BAR = backedUpItem("pref_dark_status_bar", false)
+ @JvmField val DOCK_MUSIC_SEARCH = backedUpItem("pref_dock_music_search", false)
+ @JvmField val DOCK_SEARCH = backedUpItem("pref_dock_search", true)
+ @JvmField val DOCK_SEARCH_PIXEL_STYLE = backedUpItem("pref_dock_search_pixel_style", true)
+ @JvmField val DOCK_THEME = backedUpItem("pref_dock_theme", false)
+ @JvmField val DRAWER_OPEN_KEYBOARD = backedUpItem("pref_drawer_open_keyboard", false)
+ @JvmField val DRAWER_SCROLLBAR = backedUpItem("pref_drawer_scrollbar", true)
+ @JvmField val FONT_SIZE = backedUpItem("pref_custom_font_size", 100)
+ @JvmField val HOTSEAT_OPACITY = backedUpItem("pref_hotseat_opacity", 40)
+ @JvmField val HOTSEAT_QSB_OPACITY = backedUpItem("pref_hotseat_qsb_opacity", 70)
+ @JvmField val HOTSEAT_QSB_OUTER_OPACITY = backedUpItem("pref_hotseat_qsb_outer_opacity", 60)
+ @JvmField val HOTSEAT_QSB_STROKE_WIDTH = backedUpItem("pref_hotseat_qsb_stroke_width", 0)
+ @JvmField val ICON_SIZE = backedUpItem("pref_custom_icon_size", 100)
+ @JvmField val RECENTS_CLEAR_ALL = backedUpItem("pref_recents_clear_all", true)
+ @JvmField val RECENTS_LOCK = backedUpItem("pref_recents_lock", true)
+ @JvmField val RECENTS_LENS = backedUpItem("pref_recents_lens", false)
+ @JvmField val RECENTS_MEMINFO = backedUpItem("pref_recents_meminfo", false)
+ @JvmField val RECENTS_MEMINFO_ZRAM = backedUpItem("pref_recents_meminfo_zram", false)
+ @JvmField val RECENTS_STYLE = backedUpItem("pref_recents_style", "default")
+ @JvmField val RECENTS_OPACITY = backedUpItem("pref_recents_opacity", 40)
+ @JvmField val RECENTS_SCREENSHOT = backedUpItem("pref_recents_screenshot", true)
+ @JvmField val RECENTS_SCROLL_VIBRATE = backedUpItem("pref_recents_scroll_vibrate", true)
+ @JvmField val ROW_HEIGHT = backedUpItem("pref_row_height", 100)
+ @JvmField val SEARCH_RADIUS_SIZE = backedUpItem("pref_search_radius_size", 100)
+ @JvmField val SHORT_PARALLAX = backedUpItem("pref_short_parallax", false)
@JvmField val SHOW_DESKTOP_LABELS = backedUpItem("pref_desktop_show_labels", true)
@JvmField val SHOW_DRAWER_LABELS = backedUpItem("pref_drawer_show_labels", true)
+ @JvmField val SHOW_HOTSEAT_BG = backedUpItem("pref_show_hotseat_bg", false)
+ @JvmField val SHOW_QUICKSPACE = backedUpItem("pref_quickspace", true)
+ @JvmField val SHOW_QUICKSPACE_ALT = backedUpItem("pref_quickspace_alt", false)
+ @JvmField val SHOW_QUICKSPACE_PSONALITY = backedUpItem("pref_quickspace_psonality", true)
+ @JvmField val SHOW_QUICKSPACE_NOWPLAYING = backedUpItem("pref_quickspace_np", true)
+ @JvmField val SHOW_QUICKSPACE_WEATHER = backedUpItem("pref_quickspace_weather", true)
+ @JvmField val SHOW_STATUS_BAR = backedUpItem("pref_show_statusbar", true)
+ @JvmField val SHOW_TOP_SHADOW = backedUpItem("pref_show_top_shadow", true)
+ @JvmField val SINGLE_PAGE_CENTER = backedUpItem("pref_single_page_center", false)
+ @JvmField val SLEEP_GESTURE = backedUpItem("pref_sleep_gesture", false)
+ @JvmField val SLEEP_GESTURE_HAPTIC = backedUpItem("pref_sleep_gesture_haptic", true)
@JvmField
val PROMISE_ICON_IDS = nonRestorableItem(InstallSessionHelper.PROMISE_ICON_IDS, "")
+ @JvmField val WALLPAPER_CAROUSEL = backedUpItem("pref_wallpaper_carousel", true)
+ @JvmField val WALLPAPER_CAROUSEL_LOCKSCREEN = backedUpItem("pref_wallpaper_carousel_lockscreen", true)
+ @JvmField val WALLPAPER_SCROLLING = backedUpItem("pref_allow_wallpaper_scrolling", true)
@JvmField val WORK_EDU_STEP = backedUpItem("showed_work_profile_edu", 0)
@JvmField
val WORKSPACE_SIZE =
@@ -308,7 +371,7 @@ constructor(@ApplicationContext private val encryptedContext: Context) {
@JvmField
val ALLOW_ROTATION =
backedUpItem(RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY, Boolean::class.java) {
- RotationHelper.getAllowRotationDefaultValue(DisplayController.INSTANCE.get(it).info)
+ RotationHelper.getAllowRotationDefaultValue(it, DisplayController.INSTANCE.get(it).info)
}
@JvmField
diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java
index b8a0abd9e72..175c80653c6 100644
--- a/src/com/android/launcher3/LauncherRootView.java
+++ b/src/com/android/launcher3/LauncherRootView.java
@@ -76,6 +76,12 @@ public void setInsets(Rect insets) {
}
}
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mSysUiScrim.release();
+ }
+
public void dispatchInsets() {
if (isAttachedToWindow()) {
updateInsets(getRootWindowInsets());
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index e44c918accb..5acd9636942 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -73,6 +73,7 @@ public abstract class LauncherState implements BaseState {
public static final int SPLIT_PLACHOLDER_VIEW = 1 << 6;
public static final int FLOATING_SEARCH_BAR = 1 << 7;
public static final int ADD_DESK_BUTTON = 1 << 8;
+ public static final int MEMINFO = 1 << 9;
// Flag indicating workspace has multiple pages visible.
public static final int FLAG_MULTI_PAGE = BaseState.getFlag(0);
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index cd20acb3d53..1eb4a988911 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -199,7 +199,8 @@ public static ComponentName getUninstallTarget(Context context, ItemInfo item) {
Intent intent = null;
UserHandle user = null;
if (item != null &&
- item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
+ (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
+ item.itemType == LauncherSettings.Favorites.ITEM_TYPE_TASK)) {
intent = item.getIntent();
user = item.user;
}
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index 57071bf9e4f..a70c98a391f 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -195,7 +195,7 @@ public void measureChild(View child) {
}
private boolean isChildQsb(View child) {
- return child.getId() == R.id.search_container_workspace;
+ return child.getId() == R.id.reserved_container_workspace;
}
public boolean invertLayoutHorizontally() {
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 72a1ea7b8a6..84e369e4a6f 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -21,8 +21,10 @@
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PRIVATESPACE;
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR;
import static com.android.launcher3.graphics.ShapeDelegate.DEFAULT_PATH_SIZE;
+import static com.android.launcher3.icons.BaseIconFactory.CONFIG_HINT_NO_WRAP;
import static com.android.launcher3.icons.BitmapInfo.FLAG_THEMED;
import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN;
@@ -31,9 +33,13 @@
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.ActivityOptions;
+import android.app.KeyguardManager;
import android.app.Person;
import android.app.WallpaperManager;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
@@ -53,12 +59,17 @@
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.hardware.biometrics.BiometricManager.Authenticators;
+import android.hardware.biometrics.BiometricPrompt;
import android.os.Build;
import android.os.Build.VERSION_CODES;
+import android.os.CancellationSignal;
import android.os.DeadObjectException;
import android.os.Handler;
+import android.os.Looper;
import android.os.Message;
import android.os.TransactionTooLargeException;
+import android.os.UserHandle;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
@@ -72,6 +83,7 @@
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
+import android.widget.Toast;
import androidx.annotation.ChecksSdkIntAtLeast;
import androidx.annotation.IntDef;
@@ -80,6 +92,7 @@
import androidx.annotation.WorkerThread;
import androidx.core.graphics.ColorUtils;
+import com.android.launcher3.R;
import com.android.launcher3.deviceprofile.DeviceProperties;
import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
import com.android.launcher3.graphics.ThemeManager;
@@ -112,6 +125,8 @@
import java.util.function.Predicate;
import java.util.stream.Collectors;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
/**
* Various utilities shared amongst the Launcher's classes.
*/
@@ -137,6 +152,10 @@ public final class Utilities {
public static final boolean ATLEAST_V = Build.VERSION.SDK_INT
>= VERSION_CODES.VANILLA_ICE_CREAM;
+ private static final long WAIT_BEFORE_RESTART = 300; // ms
+ private static final Object sRestartLock = new Object();
+ private static boolean sRestartScheduled = false;
+
/**
* Set on a motion event dispatched from the nav bar. See {@link MotionEvent#setEdgeFlags(int)}.
*/
@@ -151,7 +170,7 @@ public final class Utilities {
public static final boolean IS_DEBUG_DEVICE = BuildConfig.IS_DEBUG_DEVICE;
public static boolean qsbOnFirstScreen() {
- return !injectableModelItems() && BuildConfig.QSB_ON_FIRST_SCREEN;
+ return BuildConfig.USE_QUICKSPACE_VIEW;
}
public static final int TRANSLATE_UP = 0;
@@ -163,6 +182,13 @@ public static boolean qsbOnFirstScreen() {
public @interface AdjustmentDirection{}
public static final String GSA_PACKAGE = "com.google.android.googlequicksearchbox";
+ public static final String GEMINI_PACKAGE = "com.google.android.apps.bard";
+ public static final String LENS_ACTIVITY = "com.google.android.apps.search.lens.LensExportedActivity";
+ public static final String LENS_URI = "google://lens";
+ public static final String LENS_SHARE_ACTIVITY = "com.google.android.apps.search.lens.LensShareEntryPointActivity";
+
+ private static final String FREEFORM_PACKAGE = "com.libremobileos.freeform";
+ private static final String FREEFORM_INTENT = "com.libremobileos.freeform.START_FREEFORM";
/**
* Returns true if theme is dark.
@@ -288,6 +314,9 @@ public static void mapRectInSelfToDescendant(View descendant, View root, Rect re
* Inverse of {@link #getDescendantCoordRelativeToAncestor(View, View, float[], boolean)}.
*/
public static void mapCoordInSelfToDescendant(View descendant, View root, float[] coord) {
+ if (descendant == null || root == null) {
+ return;
+ }
sMatrix.reset();
//TODO(b/307488755) when implemented this check should be removed
if (!Objects.equals(descendant.getWindowId(), root.getWindowId())) {
@@ -687,8 +716,12 @@ public static boolean isEnglishLanguage(Context context) {
((PendingAddShortcutInfo) info).getActivityInfo(context);
mainIcon = activityInfo.getFullResIcon(appState.getIconCache());
} else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
+ android.content.Intent intent = info.getIntent();
+ if (intent == null) {
+ return null;
+ }
LauncherActivityInfo activityInfo = context.getSystemService(LauncherApps.class)
- .resolveActivity(info.getIntent(), info.user);
+ .resolveActivity(intent, info.user);
if (activityInfo == null) {
return null;
}
@@ -731,10 +764,11 @@ public static boolean isEnglishLanguage(Context context) {
if (mainIcon == null) {
return null;
}
- AdaptiveIconDrawable result;
+ AdaptiveIconDrawable result = null;
+ boolean noWrapHintSet = (mainIcon.getChangingConfigurations() & CONFIG_HINT_NO_WRAP) != 0;
if (mainIcon instanceof AdaptiveIconDrawable aid) {
result = aid;
- } else {
+ } else if (!noWrapHintSet) {
// Wrap the main icon in AID
try (LauncherIcons li = LauncherIcons.obtain(context)) {
result = li.wrapToAdaptiveIcon(mainIcon);
@@ -742,7 +776,7 @@ public static boolean isEnglishLanguage(Context context) {
}
// Inject theme icon drawable
- if (ATLEAST_T && useTheme) {
+ if (ATLEAST_T && useTheme && !noWrapHintSet) {
IconThemeController themeController =
ThemeManager.INSTANCE.get(context).getThemeController();
if (themeController != null) {
@@ -1021,6 +1055,19 @@ public static void debugLog(String tag, String message) {
}
}
+ public static void restart() {
+ synchronized (sRestartLock) {
+ if (sRestartScheduled) {
+ return;
+ }
+ sRestartScheduled = true;
+ }
+
+ MAIN_EXECUTOR.getHandler().postDelayed(() -> {
+ System.exit(0);
+ }, WAIT_BEFORE_RESTART);
+ }
+
/**
* Returns whether mouse interaction changes intended for the desktop form factor should be
* enabled.
@@ -1034,12 +1081,7 @@ public static boolean shouldEnableMouseInteractionChanges(Context context) {
* Returns a partial, loggable stack trace.
*/
public static String getTrimmedStackTrace(String callingMethodName) {
- String stackTrace = Log.getStackTraceString(new Exception());
- return Arrays.stream(stackTrace.split("\\n"))
- .skip(2) // Removes the line "java.lang.Exception" and "getTrimmedStackTrace".
- .filter(traceLine -> !traceLine.contains(callingMethodName))
- .limit(3)
- .collect(Collectors.joining("\n"));
+ return "";
}
/**
@@ -1065,11 +1107,107 @@ public static boolean calculateIsLeftRightSplit(boolean allowLeftRightSplitInPor
}
}
+ public static boolean isPackageInstalled(Context context, String pkg) {
+ try {
+ return context.getPackageManager().getApplicationInfo(pkg, 0).enabled;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
public static boolean isGSAEnabled(Context context) {
+ return isPackageInstalled(context, GSA_PACKAGE);
+ }
+
+ public static boolean showQSB(Context context) {
+ return isGSAEnabled(context) && isQSBEnabled(context);
+ }
+
+ private static boolean isQSBEnabled(Context context) {
+ return LauncherPrefs.DOCK_SEARCH.get(context);
+ }
+
+ public static boolean isMusicSearchEnabled(Context context) {
+ return isGSAEnabled(context) && LauncherPrefs.DOCK_MUSIC_SEARCH.get(context);
+ }
+
+ public static boolean isResizeableActivity(Context context, ComponentName activity) {
+ if (activity == null) return false;
+ final ActivityInfo info;
try {
- return context.getPackageManager().getApplicationInfo(GSA_PACKAGE, 0).enabled;
- } catch (PackageManager.NameNotFoundException e) {
+ info = context.getPackageManager().getActivityInfo(activity, /* flags */ 0);
+ } catch (Exception e) {
+ Log.e(TAG, "failed to check isResizeableActivity:", e);
return false;
}
+ return ActivityInfo.isResizeableMode(info.resizeMode);
+ }
+
+ public static void startLmoFreeform(Context context, ComponentName activity,
+ int userId, int taskId) {
+ final Intent intent = new Intent(FREEFORM_INTENT)
+ .setPackage(FREEFORM_PACKAGE)
+ .putExtra("packageName", activity.getPackageName())
+ .putExtra("activityName", activity.getClassName())
+ .putExtra("userId", userId)
+ .putExtra("taskId", taskId);
+ context.sendBroadcast(intent);
+ }
+
+ public static void startLmoFreeform(Context context, ComponentName activity, int userId) {
+ startLmoFreeform(context, activity, userId, -1);
+ }
+
+ public static void startLmoFreeform(Context context, ComponentName activity) {
+ startLmoFreeform(context, activity, UserHandle.myUserId());
+ }
+
+ /**
+ * Shows authentication screen to confirm credentials (pin, pattern or password) for the current
+ * user of the device.
+ *
+ * @param context The {@code Context} used to get {@code KeyguardManager} service
+ * @param title the {@code String} which will be shown as the pompt title
+ * @param successRunnable The {@code Runnable} which will be executed if the user does not setup
+ * device security or if lock screen is unlocked
+ */
+ public static void showLockScreen(Context context, String title, Runnable successRunnable) {
+ if (hasSecureKeyguard(context)) {
+ final BiometricPrompt.AuthenticationCallback authenticationCallback =
+ new BiometricPrompt.AuthenticationCallback() {
+ @Override
+ public void onAuthenticationSucceeded(
+ BiometricPrompt.AuthenticationResult result) {
+ successRunnable.run();
+ }
+
+ @Override
+ public void onAuthenticationError(int errorCode, CharSequence errString) {
+ //Do nothing
+ }
+ };
+
+ final BiometricPrompt bp = new BiometricPrompt.Builder(context)
+ .setTitle(title)
+ .setAllowedAuthenticators(Authenticators.BIOMETRIC_STRONG |
+ Authenticators.DEVICE_CREDENTIAL)
+ .build();
+
+ final Handler handler = MAIN_EXECUTOR.getHandler();
+ bp.authenticate(new CancellationSignal(),
+ runnable -> handler.post(runnable),
+ authenticationCallback);
+ } else {
+ // Notify the user a secure keyguard is required for protected apps,
+ // but allow to set hidden apps
+ Toast.makeText(context, R.string.trust_apps_no_lock_error, Toast.LENGTH_LONG)
+ .show();
+ successRunnable.run();
+ }
+ }
+
+ public static boolean hasSecureKeyguard(Context context) {
+ final KeyguardManager keyguardManager = context.getSystemService(KeyguardManager.class);
+ return keyguardManager != null && keyguardManager.isKeyguardSecure();
}
}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index addcb684aac..86925d77ded 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -36,7 +36,6 @@
import static com.android.launcher3.LauncherState.SPRING_LOADED;
import static com.android.launcher3.MotionEventsUtils.isTrackpadMotionEvent;
import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe;
-import static com.android.launcher3.Utilities.qsbOnFirstScreen;
import static com.android.launcher3.Utilities.shouldEnableMouseInteractionChanges;
import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
@@ -651,7 +650,7 @@ public void onViewRemoved(View child) {
public void bindAndInitFirstWorkspaceScreen() {
// Add the first page
CellLayout firstPage = insertNewWorkspaceScreen(Workspace.FIRST_SCREEN_ID, getChildCount());
- if (!qsbOnFirstScreen()) {
+ if (!LauncherPrefs.SHOW_QUICKSPACE.get(getContext())) {
mFirstPagePinnedItem = null;
return;
}
@@ -661,14 +660,14 @@ public void bindAndInitFirstWorkspaceScreen() {
// As workspace does not touch the edges, we do not need a full
// width first page pinned item.
mFirstPagePinnedItem = LayoutInflater.from(getContext())
- .inflate(R.layout.search_container_workspace, firstPage, false);
+ .inflate(R.layout.reserved_container_workspace, firstPage, false);
}
int cellHSpan = mLauncher.getDeviceProfile().inv.numSearchContainerColumns;
CellLayoutLayoutParams lp = new CellLayoutLayoutParams(0, 0, cellHSpan, 1);
lp.canReorder = false;
if (!firstPage.addViewToCellLayout(
- mFirstPagePinnedItem, 0, R.id.search_container_workspace, lp, true)) {
+ mFirstPagePinnedItem, 0, R.id.reserved_container_workspace, lp, true)) {
Log.e(TAG, "Failed to add to item at (0, 0) to CellLayout");
mFirstPagePinnedItem = null;
}
@@ -1427,7 +1426,9 @@ protected void setWallpaperDimension() {
@Override
public void run() {
final Point size = LauncherAppState.getIDP(getContext()).defaultWallpaperSize;
- if (size.x != mWallpaperManager.getDesiredMinimumWidth()
+ if (!mWallpaperManager.isWallpaperSupported()) {
+ mWallpaperManager.suggestDesiredDimensions(0, 0);
+ } else if (size.x != mWallpaperManager.getDesiredMinimumWidth()
|| size.y != mWallpaperManager.getDesiredMinimumHeight()) {
mWallpaperManager.suggestDesiredDimensions(size.x, size.y);
}
@@ -1504,7 +1505,9 @@ protected void onAttachedToWindow() {
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mWallpaperOffset.setWindowToken(null);
+ mWallpaperOffset.destroy();
mLauncher.getStateManager().removeStateListener(mAccessibilityDropListener);
+ mOverlayCallbacks.clear();
}
@Override
@@ -3522,8 +3525,12 @@ public boolean isOverlayShown() {
*/
public void moveToDefaultScreen() {
int page = DEFAULT_PAGE;
- if (!workspaceInModalState() && getNextPage() != page) {
- snapToPage(page);
+ if (!workspaceInModalState()) {
+ if (getNextPage() != page) {
+ snapToPage(page);
+ } else {
+ mLauncher.getStateManager().goToState(ALL_APPS);
+ }
}
View child = getChildAt(page);
if (child != null) {
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index cb36e7e01cb..1843c0d52b6 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -74,6 +74,10 @@ public class WorkspaceStateTransitionAnimation {
private static final float FIRST_PAGE_PINNED_WIDGET_DISABLED_ALPHA = 0.3f;
+ private static float sCachedHintScaleDamping = -1;
+ private static float sCachedHintScaleStiffness = -1;
+ private static float sCachedHintScaleVelocity = -1;
+
private static final FloatProperty> WORKSPACE_SCALE_PROPERTY =
WORKSPACE_SCALE_PROPERTY_FACTORY.get(SCALE_INDEX_WORKSPACE_STATE);
@@ -254,18 +258,20 @@ public static ValueAnimator getWorkspaceSpringScaleAnimator(Launcher launcher,
*/
public static ValueAnimator getSpringScaleAnimator(Launcher launcher, T v,
float scale, FloatProperty property) {
- ResourceProvider rp = DynamicResource.provider(launcher);
- float damping = rp.getFloat(R.dimen.hint_scale_damping_ratio);
- float stiffness = rp.getFloat(R.dimen.hint_scale_stiffness);
- float velocityPxPerS = rp.getDimension(R.dimen.hint_scale_velocity_dp_per_s);
+ if (sCachedHintScaleDamping < 0) {
+ ResourceProvider rp = DynamicResource.provider(launcher);
+ sCachedHintScaleDamping = rp.getFloat(R.dimen.hint_scale_damping_ratio);
+ sCachedHintScaleStiffness = rp.getFloat(R.dimen.hint_scale_stiffness);
+ sCachedHintScaleVelocity = rp.getDimension(R.dimen.hint_scale_velocity_dp_per_s);
+ }
return new SpringAnimationBuilder(v.getContext())
- .setStiffness(stiffness)
- .setDampingRatio(damping)
+ .setStiffness(sCachedHintScaleStiffness)
+ .setDampingRatio(sCachedHintScaleDamping)
.setMinimumVisibleChange(MIN_VISIBLE_CHANGE_SCALE)
.setEndValue(scale)
.setStartValue(property.get(v))
- .setStartVelocity(velocityPxPerS)
+ .setStartVelocity(sCachedHintScaleVelocity)
.build(v, property);
}
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index 205d2286781..ae2cd13f8e9 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -46,18 +46,22 @@
import android.os.Bundle;
import android.os.Parcelable;
import android.os.Process;
+import android.os.UserManager;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
+import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
import android.view.ViewOutlineProvider;
import android.view.WindowInsets;
import android.widget.Button;
import android.widget.RelativeLayout;
+import android.widget.RelativeLayout.LayoutParams;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -75,6 +79,7 @@
import com.android.launcher3.Flags;
import com.android.launcher3.Insettable;
import com.android.launcher3.InsettableFrameLayout;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
@@ -84,13 +89,16 @@
import com.android.launcher3.keyboard.FocusedItemDecorator;
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
import com.android.launcher3.model.StringCache;
+import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.recyclerview.AllAppsRecyclerViewPool;
+import com.android.launcher3.util.BerryBlackTheme;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.pageindicators.PageIndicatorDots;
import com.android.launcher3.views.BaseDragLayer;
import com.android.launcher3.views.RecyclerViewFastScroller;
import com.android.launcher3.views.ScrimView;
@@ -126,14 +134,13 @@ public class ActivityAllAppsContainerView
private static final boolean DEBUG_HEADER_PROTECTION = false;
/** Context of an activity or window that is inflating this container. */
+ protected final Context mContext;
protected final T mActivityContext;
protected final List mAH;
- protected final Predicate mPersonalMatcher = ItemInfoMatcher.ofUser(
- Process.myUserHandle());
+ protected final Predicate mPersonalMatcher;
protected WorkProfileManager mWorkManager;
protected final PrivateProfileManager mPrivateProfileManager;
protected final Point mFastScrollerOffset = new Point();
- protected final int mScrimColor;
protected final float mHeaderThreshold;
protected final AllAppsSearchUiDelegate mSearchUiDelegate;
@@ -171,6 +178,10 @@ public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
/** {@code true} when rendered view is in search state instead of the scroll state. */
private boolean mIsSearching;
+ private String mAppDrawerStyle = AppDrawerStyle.NORMAL;
+ private boolean mShowFastScroller;
+ private PageIndicatorDots mOneUiPageIndicator;
+ @Nullable private OneUiPagedAllAppsView mOneUiPagedView;
private boolean mRebindAdaptersAfterSearchAnimation;
private int mNavBarScrimHeight = 0;
private SearchRecyclerView mSearchRecyclerView;
@@ -197,10 +208,12 @@ public ActivityAllAppsContainerView(Context context, AttributeSet attrs) {
public ActivityAllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
+ mContext = context;
mActivityContext = ActivityContext.lookupContext(context);
+ UserManager userManager = mActivityContext.getSystemService(UserManager.class);
+ mPersonalMatcher = ItemInfoMatcher.ofCurrentOrDualUser(userManager, Process.myUserHandle());
mAllAppsStore = mActivityContext.getActivityComponent().getAppsStore();
- mScrimColor = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
mHeaderThreshold = getResources().getDimensionPixelSize(
R.dimen.dynamic_grid_cell_border_spacing);
mHeaderProtectionColor = Themes.getAttrColor(context, R.attr.allappsHeaderProtectionColor);
@@ -219,7 +232,7 @@ public ActivityAllAppsContainerView(Context context, AttributeSet attrs, int def
mNavBarScrimPaint = new Paint();
mNavBarScrimPaint.setColor(Themes.getNavBarScrimColor(mActivityContext));
- AllAppsStore.OnUpdateListener onAppsUpdated = this::onAppsUpdated;
+ AllAppsStore.OnUpdateListener onAppsUpdated = () -> onAppsUpdated();
mAllAppsStore.addUpdateListener(onAppsUpdated);
// This is a focus listener that proxies focus from a view into the list view. This is to
@@ -230,6 +243,7 @@ public ActivityAllAppsContainerView(Context context, AttributeSet attrs, int def
}
});
mSearchUiDelegate = createSearchUiDelegate();
+ updateAppDrawerStyle();
initContent();
mSearchTransitionController = new SearchTransitionController(this);
@@ -253,6 +267,8 @@ public AllAppsSearchUiDelegate getSearchUiDelegate() {
* onFinishInflate -> onPostCreate
*/
protected void initContent() {
+ updateAppDrawerStyle();
+ mShowFastScroller = shouldShowFastScroller();
mMainAdapterProvider = mSearchUiDelegate.createMainAdapterProvider();
mAH.set(AdapterHolder.MAIN, new AdapterHolder(AdapterHolder.MAIN,
@@ -275,6 +291,9 @@ protected void initContent() {
mFastScroller = findViewById(R.id.fast_scroller);
mFastScroller.setPopupView(findViewById(R.id.fast_scroller_popup));
mFastScrollLetterLayout = findViewById(R.id.scroll_letter_layout);
+ mOneUiPageIndicator = findViewById(R.id.all_apps_page_indicator);
+ mFastScroller.setVisibility(mShowFastScroller ? VISIBLE : INVISIBLE);
+ mFastScrollLetterLayout.setVisibility(mShowFastScroller ? VISIBLE : INVISIBLE);
setClipChildren(false);
mSearchContainer = inflateSearchBar();
@@ -298,6 +317,13 @@ public List getAdditionalHeaderRows() {
protected void onFinishInflate() {
super.onFinishInflate();
+ String searchPlacement = LauncherPrefs.ALL_APPS_SEARCH_PLACEMENT.get(getContext());
+ if ("hidden".equals(searchPlacement)) {
+ mSearchContainer.setVisibility(View.GONE);
+ } else {
+ mSearchContainer.setVisibility(View.VISIBLE);
+ }
+
mAH.get(SEARCH).setup(mSearchRecyclerView,
/* Filter out A-Z apps */ itemInfo -> false);
rebindAdapters(true /* force */);
@@ -326,6 +352,7 @@ protected void onFinishInflate() {
updateBackgroundVisibility(mActivityContext.getDeviceProfile());
mSearchUiManager.initializeSearch(this);
+ updateOneUiPageIndicatorState();
}
@Override
@@ -399,14 +426,17 @@ void animateToSearchState(boolean goingToSearch, long durationMs) {
if (!mSearchTransitionController.isRunning() && goingToSearch == isSearching()) {
return;
}
- mFastScroller.setVisibility(goingToSearch ? INVISIBLE : VISIBLE);
+ mFastScroller.setVisibility(goingToSearch || !mShowFastScroller ? INVISIBLE : VISIBLE);
if (goingToSearch) {
// Fade out the button to pause work apps.
mWorkManager.onActivePageChanged(SEARCH);
} else if (mAllAppsTransitionController != null) {
// If exiting search, revert predictive back scale on all apps
mAllAppsTransitionController.animateAllAppsToNoScale();
+ mFastScroller.setVisibility(mShowFastScroller ? VISIBLE : INVISIBLE);
+ mFastScrollLetterLayout.setVisibility(mShowFastScroller ? VISIBLE : INVISIBLE);
}
+ setScrollbarVisibility(!goingToSearch);
mSearchTransitionController.animateToState(goingToSearch, durationMs,
/* onEndRunnable = */ () -> {
mIsSearching = goingToSearch;
@@ -438,7 +468,7 @@ public boolean shouldContainerScroll(MotionEvent ev) {
return true;
}
AllAppsRecyclerView rv = getActiveRecyclerView();
- if (rv == null) {
+ if (rv == null || rv.getParent() == null || rv.getWindowId() == null) {
return true;
}
if (rv.getScrollbar() != null
@@ -446,8 +476,10 @@ public boolean shouldContainerScroll(MotionEvent ev) {
&& dragLayer.isEventOverView(rv.getScrollbar(), ev)) {
return false;
}
+ View visibleContainer = getVisibleContainerView();
// Scroll if not within the container view (e.g. over large-screen scrim).
- if (!dragLayer.isEventOverView(getVisibleContainerView(), ev)) {
+ if (visibleContainer == null || visibleContainer.getWindowId() == null
+ || !dragLayer.isEventOverView(visibleContainer, ev)) {
return true;
}
return rv.shouldContainerScroll(ev, dragLayer);
@@ -570,16 +602,12 @@ public void onActivePageChanged(int currentActivePage) {
// Will be called at the end of the animation.
return;
}
- if (currentActivePage != SEARCH) {
- mActivityContext.hideKeyboard();
- }
if (mAH.get(currentActivePage).mRecyclerView != null) {
mAH.get(currentActivePage).mRecyclerView.bindFastScrollbar(mFastScroller,
ALL_APPS_SCROLLER);
}
// Header keeps track of active recycler view to properly render header protection.
mHeader.setActiveRV(currentActivePage);
- reset(true /* animate */, !isSearching() /* exitSearch */, false /* clearScrim */);
mWorkManager.onActivePageChanged(currentActivePage);
}
@@ -590,11 +618,16 @@ protected void rebindAdapters() {
protected void rebindAdapters(boolean force) {
Log.d(TAG, "rebindAdapters: force: " + force);
+ String previousStyle = mAppDrawerStyle;
+ updateAppDrawerStyle();
+ force |= !previousStyle.equals(mAppDrawerStyle);
+ force |= AppDrawerStyle.isVerticalPaged(mAppDrawerStyle);
if (mSearchTransitionController.isRunning()) {
mRebindAdaptersAfterSearchAnimation = true;
return;
}
updateSearchResultsVisibility();
+ updateAppsPerRowState(mActivityContext.getDeviceProfile(), force);
boolean showTabs = shouldShowTabs();
if (showTabs == mUsingTabs && !force) {
@@ -608,7 +641,7 @@ protected void rebindAdapters(boolean force) {
replaceAppsRVContainer(showTabs);
mUsingTabs = showTabs;
- mAllAppsStore.unregisterIconContainer(mAH.get(AdapterHolder.MAIN).mRecyclerView);
+ unregisterMainIconContainers();
mAllAppsStore.unregisterIconContainer(mAH.get(AdapterHolder.WORK).mRecyclerView);
mAllAppsStore.unregisterIconContainer(mAH.get(AdapterHolder.SEARCH).mRecyclerView);
@@ -646,17 +679,30 @@ protected void rebindAdapters(boolean force) {
if (mHeader.isSetUp()) {
onActivePageChanged(mViewPager.getNextPage());
}
+ } else if (AppDrawerStyle.isVerticalPaged(mAppDrawerStyle)) {
+ mainRecyclerView = null;
+ workRecyclerView = null;
+ mAH.get(AdapterHolder.WORK).mRecyclerView = null;
+ bindOneUiPagedView();
} else {
mainRecyclerView = findViewById(R.id.apps_list_view);
workRecyclerView = null;
mAH.get(AdapterHolder.MAIN).setup(mainRecyclerView, mPersonalMatcher);
mAH.get(AdapterHolder.WORK).mRecyclerView = null;
}
- setUpCustomRecyclerViewPool(
- mainRecyclerView,
- workRecyclerView,
- mActivityContext.getActivityComponent().getSharedAppsPool());
+ configureStyleForMainRecyclerView(mainRecyclerView);
+ mShowFastScroller = shouldShowFastScroller();
+ mFastScroller.setVisibility(mShowFastScroller ? VISIBLE : INVISIBLE);
+ mFastScrollLetterLayout.setVisibility(mShowFastScroller ? VISIBLE : INVISIBLE);
+
+ if (mainRecyclerView != null) {
+ setUpCustomRecyclerViewPool(
+ mainRecyclerView,
+ workRecyclerView,
+ mActivityContext.getActivityComponent().getSharedAppsPool());
+ }
setupHeader();
+ applyOneUiPagePadding();
if (isSearchBarFloating()) {
// Keep the scroller above the search bar.
@@ -667,7 +713,7 @@ protected void rebindAdapters(boolean force) {
R.dimen.fastscroll_bottom_margin_floating_search);
}
- mAllAppsStore.registerIconContainer(mAH.get(AdapterHolder.MAIN).mRecyclerView);
+ registerMainIconContainers();
mAllAppsStore.registerIconContainer(mAH.get(AdapterHolder.WORK).mRecyclerView);
mAllAppsStore.registerIconContainer(mAH.get(AdapterHolder.SEARCH).mRecyclerView);
}
@@ -701,9 +747,13 @@ private void replaceAppsRVContainer(boolean showTabs) {
}
}
View oldView = getAppsRecyclerViewContainer();
+ clearOneUiPaging();
int index = indexOfChild(oldView);
removeView(oldView);
- int layout = showTabs ? R.layout.all_apps_tabs : R.layout.all_apps_rv_layout;
+ int layout = showTabs ? R.layout.all_apps_tabs
+ : AppDrawerStyle.isVerticalPaged(mAppDrawerStyle)
+ ? R.layout.all_apps_oneui_paged_layout
+ : R.layout.all_apps_rv_layout;
final View rvContainer = getLayoutInflater().inflate(layout, this, false);
addView(rvContainer, index);
if (showTabs) {
@@ -729,16 +779,77 @@ public void getOutline(View view, Outline outline) {
} else {
mWorkManager.detachWorkUtilityViews();
mViewPager = null;
+ mOneUiPagedView = AppDrawerStyle.isVerticalPaged(mAppDrawerStyle)
+ ? (OneUiPagedAllAppsView) rvContainer : null;
+ if (mOneUiPagedView != null) {
+ mOneUiPagedView.setApps(Arrays.asList(mAllAppsStore.getApps()));
+ mOneUiPagedView.setPageIndicator(mOneUiPageIndicator);
+ mOneUiPageIndicator.setVisibility(VISIBLE);
+ }
}
removeCustomRules(rvContainer);
removeCustomRules(getSearchRecyclerView());
- if (isSearchBarFloating()) {
+
+ String searchPlacement = LauncherPrefs.ALL_APPS_SEARCH_PLACEMENT.get(getContext());
+ if ("hidden".equals(searchPlacement)) {
+ getSearchRecyclerView().setVisibility(View.GONE);
+ if (mSearchContainer != null) {
+ mSearchContainer.setVisibility(View.GONE);
+ }
alignParentTop(rvContainer, showTabs);
- alignParentTop(getSearchRecyclerView(), /* tabs= */ false);
} else {
- layoutBelowSearchContainer(rvContainer, showTabs);
- layoutBelowSearchContainer(getSearchRecyclerView(), /* tabs= */ false);
+ getSearchRecyclerView().setVisibility(View.VISIBLE);
+ if (mSearchContainer != null) {
+ mSearchContainer.setVisibility(View.VISIBLE);
+ }
+ if (isSearchBarFloating()) {
+ alignParentTop(rvContainer, showTabs);
+ alignParentTop(getSearchRecyclerView(), /* tabs= */ false);
+ } else if ("bottom".equals(searchPlacement)) {
+ layoutAboveSearchContainer(rvContainer, showTabs);
+ layoutAboveSearchContainer(getSearchRecyclerView(), /* tabs= */ false);
+ layoutSearchContainerBottom();
+ } else {
+ layoutBelowSearchContainer(rvContainer, showTabs);
+ layoutBelowSearchContainer(getSearchRecyclerView(), /* tabs= */ false);
+ layoutSearchContainerTop();
+ }
+ }
+
+ if (mOneUiPageIndicator != null && mOneUiPageIndicator.getLayoutParams() instanceof RelativeLayout.LayoutParams) {
+ RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) mOneUiPageIndicator.getLayoutParams();
+ if ("bottom".equals(searchPlacement) && !isSearchBarFloating()) {
+ lp.removeRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
+ lp.addRule(RelativeLayout.ABOVE, R.id.search_container_all_apps);
+ lp.bottomMargin = 0;
+ mOneUiPageIndicator.setTranslationY(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24, getContext().getResources().getDisplayMetrics()));
+ } else {
+ lp.removeRule(RelativeLayout.ABOVE);
+ lp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
+ lp.bottomMargin = getContext().getResources().getDimensionPixelSize(R.dimen.all_apps_page_indicator_bottom_margin);
+ mOneUiPageIndicator.setTranslationY(0);
+ }
+ mOneUiPageIndicator.setLayoutParams(lp);
+ }
+
+ if (AppDrawerStyle.isVerticalPaged(mAppDrawerStyle)
+ && rvContainer.getLayoutParams() instanceof RelativeLayout.LayoutParams) {
+ RelativeLayout.LayoutParams rvLp = (RelativeLayout.LayoutParams) rvContainer.getLayoutParams();
+ if (mOneUiPageIndicator != null && mOneUiPageIndicator.getVisibility() == VISIBLE) {
+ int indicatorHeight = getContext().getResources().getDimensionPixelSize(
+ R.dimen.workspace_page_indicator_height);
+ int indicatorMargin;
+ if ("bottom".equals(searchPlacement) && !isSearchBarFloating()) {
+ indicatorMargin = 0;
+ } else {
+ indicatorMargin = indicatorHeight
+ + getContext().getResources().getDimensionPixelSize(
+ R.dimen.all_apps_page_indicator_bottom_margin);
+ }
+ rvLp.bottomMargin = Math.max(rvLp.bottomMargin, indicatorMargin);
+ }
+ rvContainer.setLayoutParams(rvLp);
}
updateSearchResultsVisibility();
@@ -776,11 +887,14 @@ void setupHeader() {
mAdditionalHeaderRows.forEach(row -> mHeader.onPluginConnected(row, mActivityContext));
removeCustomRules(mHeader);
- if (isSearchBarFloating()) {
+
+ String searchPlacement = LauncherPrefs.ALL_APPS_SEARCH_PLACEMENT.get(getContext());
+ if (isSearchBarFloating() || "bottom".equals(searchPlacement) || "hidden".equals(searchPlacement)) {
alignParentTop(mHeader, false /* includeTabsMargin */);
} else {
layoutBelowSearchContainer(mHeader, false /* includeTabsMargin */);
}
+ applyOneUiPagePadding();
}
/**
@@ -808,6 +922,14 @@ public void addChildrenForAccessibility(ArrayList arrayList) {
}
protected void updateHeaderScroll(int scrolledOffset) {
+ if (AppDrawerStyle.isVerticalPaged(mAppDrawerStyle)) {
+ if (mHeaderColor != Color.TRANSPARENT || mTabsProtectionAlpha != 0) {
+ mHeaderColor = Color.TRANSPARENT;
+ mTabsProtectionAlpha = 0;
+ invalidateHeader();
+ }
+ return;
+ }
float prog = Utilities.boundToRange((float) scrolledOffset / mHeaderThreshold, 0f, 1f);
int headerColor = getHeaderColor(prog);
int tabsAlpha = mHeader.getPeripheralProtectionHeight(/* expectedHeight */ false) == 0 ? 0
@@ -819,7 +941,6 @@ protected void updateHeaderScroll(int scrolledOffset) {
mTabsProtectionAlpha = tabsAlpha;
invalidateHeader();
}
- getSearchView().setBackgroundResource(R.drawable.bg_all_apps_searchbox);
if (mSearchUiManager.getEditText() == null) {
return;
}
@@ -834,30 +955,66 @@ protected void updateHeaderScroll(int scrolledOffset) {
}
protected int getHeaderColor(float blendRatio) {
- if (!mActivityContext.getDeviceProfile().shouldShowAllAppsOnSheet()) {
+ if (AppDrawerStyle.isFullscreen(mAppDrawerStyle)) {
+ return android.graphics.Color.TRANSPARENT;
+ }
+ if (!mActivityContext.getDeviceProfile().shouldShowAllAppsOnSheet(getContext())) {
return ColorUtils.setAlphaComponent(
- ColorUtils.blendARGB(mScrimColor, mHeaderProtectionColor, blendRatio),
- (int) (mSearchContainer.getAlpha() * 255));
+ ColorUtils.blendARGB(getScrimColor(), mHeaderProtectionColor, blendRatio),
+ (int) (mSearchContainer.getAlpha() *
+ LauncherPrefs.APP_DRAWER_OPACITY.get(mContext) * 255 / 100));
}
return isBackgroundBlurEnabled()
- ? ColorUtils.setAlphaComponent(mHeaderProtectionColor, (int) (blendRatio * 255))
- : ColorUtils.blendARGB(getBackgroundColor(), mHeaderProtectionColor, blendRatio);
+ ? ColorUtils.setAlphaComponent(mHeaderProtectionColor,
+ (int) (blendRatio * LauncherPrefs.APP_DRAWER_OPACITY.get(mContext) * 255 / 100))
+ : ColorUtils.setAlphaComponent(
+ ColorUtils.blendARGB(getBackgroundColor(), mHeaderProtectionColor, blendRatio),
+ LauncherPrefs.APP_DRAWER_OPACITY.get(mContext) * 255 / 100);
}
private int getBackgroundColor() {
- return mActivityContext.getDeviceProfile().shouldShowAllAppsOnSheet()
- ? getBottomSheetBackgroundColor() : mScrimColor;
+ return mActivityContext.getDeviceProfile().shouldShowAllAppsOnSheet(getContext())
+ ? getBottomSheetBackgroundColor() : getScrimColor();
+ }
+
+ private int getScrimColor() {
+ int color;
+ if (LauncherPrefs.APP_DRAWER_CUSTOM_COLOR_ENABLED.get(mContext)) {
+ color = Utilities.isDarkTheme(mContext)
+ ? LauncherPrefs.APP_DRAWER_CUSTOM_COLOR_DARK.get(mContext)
+ : LauncherPrefs.APP_DRAWER_CUSTOM_COLOR_LIGHT.get(mContext);
+ } else {
+ color = Themes.getAttrColor(mContext, R.attr.allAppsScrimColor);
+ }
+ if (BerryBlackTheme.isActive(mContext)) {
+ color = Color.BLACK;
+ }
+ return ColorUtils.setAlphaComponent(
+ color,
+ LauncherPrefs.APP_DRAWER_OPACITY.get(mContext) * 255 / 100);
}
int getBottomSheetBackgroundColor() {
- if (!Flags.allAppsBlur()) {
- return mBottomSheetBackgroundColorLegacy;
+ int bgColor;
+ if (LauncherPrefs.APP_DRAWER_CUSTOM_COLOR_ENABLED.get(mContext)) {
+ bgColor = Utilities.isDarkTheme(mContext)
+ ? LauncherPrefs.APP_DRAWER_CUSTOM_COLOR_DARK.get(mContext)
+ : LauncherPrefs.APP_DRAWER_CUSTOM_COLOR_LIGHT.get(mContext);
+ } else {
+ if (!Flags.allAppsBlur()) {
+ bgColor = mBottomSheetBackgroundColorLegacy;
+ } else if (!mActivityContext.isAllAppsBackgroundBlurEnabled()) {
+ // Don't apply any alpha if the blur is disabled.
+ bgColor = mBottomSheetBackgroundColorBlurFallback;
+ } else {
+ bgColor = mBottomSheetBackgroundColorOverBlur;
+ }
}
- if (!mActivityContext.isAllAppsBackgroundBlurEnabled()) {
- // Don't apply any alpha if the blur is disabled.
- return mBottomSheetBackgroundColorBlurFallback;
+ if (BerryBlackTheme.isActive(mContext)) {
+ bgColor = Color.BLACK;
}
- return mBottomSheetBackgroundColorOverBlur;
+ return ColorUtils.setAlphaComponent(
+ bgColor, LauncherPrefs.APP_DRAWER_OPACITY.get(mContext) * 255 / 100);
}
boolean isBackgroundBlurEnabled() {
@@ -942,6 +1099,47 @@ private void layoutBelowSearchContainer(View v, boolean includeTabsMargin) {
layoutParams.topMargin = topMargin;
}
+ private void layoutAboveSearchContainer(View v, boolean includeTabsMargin) {
+ if (!(v.getLayoutParams() instanceof RelativeLayout.LayoutParams)) {
+ return;
+ }
+
+ RelativeLayout.LayoutParams layoutParams = (LayoutParams) v.getLayoutParams();
+ layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
+ layoutParams.addRule(RelativeLayout.ABOVE, R.id.search_container_all_apps);
+
+ int topMargin = 0;
+ if (includeTabsMargin) {
+ topMargin += getContext().getResources().getDimensionPixelSize(
+ R.dimen.all_apps_header_pill_height)
+ + getContext().getResources().getDimensionPixelSize(
+ R.dimen.all_apps_tabs_margin_top);
+ }
+ layoutParams.topMargin = topMargin;
+ layoutParams.bottomMargin = getContext().getResources().getDimensionPixelSize(
+ R.dimen.all_apps_search_bar_bottom_adjustment);
+ }
+
+ private void layoutSearchContainerBottom() {
+ if (mSearchContainer == null || !(mSearchContainer.getLayoutParams() instanceof RelativeLayout.LayoutParams)) {
+ return;
+ }
+ RelativeLayout.LayoutParams layoutParams = (LayoutParams) mSearchContainer.getLayoutParams();
+ layoutParams.removeRule(RelativeLayout.ALIGN_PARENT_TOP);
+ layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
+ mSearchContainer.setLayoutParams(layoutParams);
+ }
+
+ private void layoutSearchContainerTop() {
+ if (mSearchContainer == null || !(mSearchContainer.getLayoutParams() instanceof RelativeLayout.LayoutParams)) {
+ return;
+ }
+ RelativeLayout.LayoutParams layoutParams = (LayoutParams) mSearchContainer.getLayoutParams();
+ layoutParams.removeRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
+ layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
+ mSearchContainer.setLayoutParams(layoutParams);
+ }
+
private void alignParentTop(View v, boolean includeTabsMargin) {
if (!(v.getLayoutParams() instanceof RelativeLayout.LayoutParams)) {
return;
@@ -968,9 +1166,9 @@ private void removeCustomRules(View v) {
layoutParams.removeRule(RelativeLayout.BELOW);
}
- protected BaseAllAppsAdapter createAdapter(AlphabeticalAppsList appsList) {
+ protected BaseAllAppsAdapter createAdapter(AlphabeticalAppsList appsList, int adapterType) {
return new AllAppsGridAdapter(mActivityContext, getLayoutInflater(), appsList,
- mMainAdapterProvider);
+ mMainAdapterProvider, adapterType == AdapterHolder.MAIN);
}
public boolean isInAllApps() {
@@ -1039,9 +1237,8 @@ public boolean hasPrivateProfile() {
@Override
public void onDeviceProfileChanged(DeviceProfile dp) {
+ updateAppsPerRowState(dp, true);
for (AdapterHolder holder : mAH) {
- holder.mAdapter.setAppsPerRow(dp.numShownAllAppsColumns);
- holder.mAppsList.setNumAppsPerRowAllApps(dp.numShownAllAppsColumns);
if (holder.mRecyclerView != null) {
// Remove all views and clear the pool, while keeping the data same. After this
// call, all the viewHolders will be recreated.
@@ -1049,7 +1246,12 @@ public void onDeviceProfileChanged(DeviceProfile dp) {
holder.mRecyclerView.getRecycledViewPool().clear();
}
}
+ if (mOneUiPagedView != null) {
+ bindOneUiPagedView();
+ applyOneUiPagePadding();
+ }
updateBackgroundVisibility(dp);
+ updateOneUiPageIndicator();
int navBarScrimColor = Themes.getNavBarScrimColor(mActivityContext);
if (mNavBarScrimPaint.getColor() != navBarScrimColor) {
@@ -1059,8 +1261,13 @@ public void onDeviceProfileChanged(DeviceProfile dp) {
}
protected void updateBackgroundVisibility(DeviceProfile deviceProfile) {
- mBottomSheetBackground.setVisibility(
- deviceProfile.shouldShowAllAppsOnSheet() ? View.VISIBLE : View.GONE);
+ boolean showOnSheet = deviceProfile.shouldShowAllAppsOnSheet(getContext());
+ mBottomSheetBackground.setVisibility(showOnSheet ? View.VISIBLE : View.GONE);
+ if (AppDrawerStyle.isVerticalPaged(mAppDrawerStyle)) {
+ setBackgroundColor(getScrimColor());
+ } else {
+ setBackground(null);
+ }
// Note: The opaque sheet background and header protection are added in drawOnScrim.
// For the taskbar entrypoint, the scrim is drawn by its abstract slide in view container,
// so its header protection is derived from this scrim instead.
@@ -1069,6 +1276,11 @@ protected void updateBackgroundVisibility(DeviceProfile deviceProfile) {
@VisibleForTesting
public void onAppsUpdated() {
Log.d(TAG, "onAppsUpdated; number of apps: " + mAllAppsStore.getApps().length);
+ mAH.forEach(holder -> {
+ if (holder.mAppsList != null) {
+ holder.mAppsList.onAppsUpdated();
+ }
+ });
mHasWorkApps = Stream.of(mAllAppsStore.getApps())
.anyMatch(mWorkManager.getItemInfoMatcher());
mHasPrivateApps = Stream.of(mAllAppsStore.getApps())
@@ -1077,11 +1289,18 @@ public void onAppsUpdated() {
rebindAdapters();
}
if (mHasWorkApps) {
- mWorkManager.reset();
+ try {
+ mWorkManager.reset();
+ } catch (NullPointerException e) {
+ }
}
if (mHasPrivateApps) {
- mPrivateProfileManager.reset();
+ try {
+ mPrivateProfileManager.reset();
+ } catch (NullPointerException e) {
+ }
}
+ updateOneUiPageIndicator();
mActivityContext.getStatsLogManager().logger()
.withCardinality(mAllAppsStore.getApps().length)
@@ -1166,7 +1385,11 @@ public OnFocusChangeListener getSearchFocusChangeListener() {
/** The current apps recycler view in the container. */
private AllAppsRecyclerView getActiveAppsRecyclerView() {
if (!mUsingTabs || isPersonalTab()) {
- return mAH.get(AdapterHolder.MAIN).mRecyclerView;
+ AllAppsRecyclerView recyclerView = mAH.get(AdapterHolder.MAIN).mRecyclerView;
+ if (recyclerView == null && mOneUiPagedView != null) {
+ recyclerView = mOneUiPagedView.getCurrentRecyclerView();
+ }
+ return recyclerView;
} else {
return mAH.get(AdapterHolder.WORK).mRecyclerView;
}
@@ -1177,7 +1400,13 @@ private AllAppsRecyclerView getActiveAppsRecyclerView() {
* hidden while searching.
*/
public ViewGroup getAppsRecyclerViewContainer() {
- return mViewPager != null ? mViewPager : findViewById(R.id.apps_list_view);
+ if (mViewPager != null) {
+ return mViewPager;
+ }
+ if (mOneUiPagedView != null) {
+ return mOneUiPagedView;
+ }
+ return findViewById(R.id.apps_list_view);
}
/** The RV for search results, which is hidden while A-Z apps are visible. */
@@ -1225,12 +1454,27 @@ public void setInsets(Rect insets) {
if (!grid.isVerticalBarLayout() || FeatureFlags.enableResponsiveWorkspace()) {
int topPadding = grid.allAppsPadding.top;
- if (isSearchBarFloating() && !grid.shouldShowAllAppsOnSheet()) {
+ if (isSearchBarFloating() && !grid.shouldShowAllAppsOnSheet(getContext())) {
topPadding += getResources().getDimensionPixelSize(
R.dimen.all_apps_additional_top_padding_floating_search);
}
setPadding(grid.allAppsLeftRightMargin, topPadding, grid.allAppsLeftRightMargin, 0);
}
+ if (mSearchContainer != null && mSearchContainer.getLayoutParams() instanceof MarginLayoutParams) {
+ MarginLayoutParams searchLp = (MarginLayoutParams) mSearchContainer.getLayoutParams();
+ String searchPlacement = LauncherPrefs.ALL_APPS_SEARCH_PLACEMENT.get(getContext());
+ if ("bottom".equals(searchPlacement) && !isSearchBarFloating()) {
+ int extraMargin = getResources().getDimensionPixelSize(R.dimen.all_apps_search_bar_bottom_padding_extra);
+ int bottomPaddingArea = Math.max(insets.bottom, mNavBarScrimHeight);
+ if (bottomPaddingArea == 0) {
+ bottomPaddingArea = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 32, getResources().getDisplayMetrics());
+ }
+ searchLp.bottomMargin = bottomPaddingArea + extraMargin + (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24, getResources().getDisplayMetrics());
+ } else {
+ searchLp.bottomMargin = 0;
+ }
+ mSearchContainer.setLayoutParams(searchLp);
+ }
InsettableFrameLayout.dispatchInsets(this, insets);
}
@@ -1267,6 +1511,35 @@ protected void dispatchDraw(Canvas canvas) {
}
}
+ @Override
+ protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+ if (AppDrawerStyle.isFullscreen(mAppDrawerStyle)
+ && child == getAppsRecyclerViewContainer()
+ && mSearchContainer != null
+ && mSearchContainer.getVisibility() == VISIBLE) {
+
+ String searchPlacement = LauncherPrefs.ALL_APPS_SEARCH_PLACEMENT.get(getContext());
+ if ("bottom".equals(searchPlacement) && !isSearchBarFloating()) {
+ float searchTop = mSearchContainer.getTop() + mSearchContainer.getTranslationY();
+ if (searchTop > child.getTop()) {
+ int saveCount = canvas.save();
+ canvas.clipRect(child.getLeft(), child.getTop(), child.getRight(), (int) searchTop);
+ boolean result = super.drawChild(canvas, child, drawingTime);
+ canvas.restoreToCount(saveCount);
+ return result;
+ }
+ }
+ }
+ return super.drawChild(canvas, child, drawingTime);
+ }
+
+ protected void setScrollbarVisibility(boolean visible) {
+ AllAppsRecyclerView rv = getActiveRecyclerView();
+ if (rv != null && rv.getScrollbar() != null) {
+ rv.getScrollbar().setVisibility(visible ? VISIBLE : GONE);
+ }
+ }
+
protected void updateSearchResultsVisibility() {
if (isSearching()) {
getSearchRecyclerView().setVisibility(VISIBLE);
@@ -1280,18 +1553,130 @@ protected void updateSearchResultsVisibility() {
if (mHeader.isSetUp()) {
mHeader.setActiveRV(getCurrentPage());
}
+ updateOneUiPageIndicatorState();
}
private void applyAdapterSideAndBottomPaddings(DeviceProfile grid) {
- int bottomPadding = Math.max(mInsets.bottom, mNavBarScrimHeight);
+ int bottomPadding = AppDrawerStyle.isVerticalPaged(mAppDrawerStyle) ? 0 : Math.max(mInsets.bottom, mNavBarScrimHeight);
mAH.forEach(adapterHolder -> {
adapterHolder.mPadding.bottom = bottomPadding;
adapterHolder.mPadding.left = grid.allAppsPadding.left;
adapterHolder.mPadding.right = grid.allAppsPadding.right;
adapterHolder.applyPadding();
});
+ applyOneUiPagePadding();
}
+
+ private void updateAppDrawerStyle() {
+ mAppDrawerStyle = AppDrawerStyle.get(getContext());
+ }
+
+ private boolean shouldShowFastScroller() {
+ if (!LauncherPrefs.DRAWER_SCROLLBAR.get(getContext())) {
+ return false;
+ }
+ return !AppDrawerStyle.isVerticalPaged(mAppDrawerStyle);
+ }
+
+ private void updateAppsPerRowState(@NonNull DeviceProfile dp, boolean refreshAdapterItems) {
+ int appsPerRow = dp.numShownAllAppsColumns;
+ for (AdapterHolder holder : mAH) {
+ int effectiveAppsPerRow = holder.mType != AdapterHolder.SEARCH && AppDrawerStyle.isHorizontalList(mAppDrawerStyle) ? 1 : appsPerRow;
+ holder.mAdapter.setAppsPerRow(appsPerRow);
+ holder.mAppsList.setNumAppsPerRowAllApps(effectiveAppsPerRow);
+ if (refreshAdapterItems) {
+ holder.mAppsList.updateAdapterItems();
+ }
+ }
+ }
+
+ private void clearOneUiPaging() {
+ mOneUiPagedView = null;
+ }
+
+ private void configureStyleForMainRecyclerView(@Nullable AllAppsRecyclerView mainRecyclerView) {
+ if (!AppDrawerStyle.isVerticalPaged(mAppDrawerStyle)) {
+ updateOneUiPageIndicatorState();
+ return;
+ }
+ bindOneUiPagedView();
+ updateOneUiPageIndicator();
+ }
+
+ private void bindOneUiPagedView() {
+ if (mOneUiPagedView == null) {
+ return;
+ }
+ mOneUiPagedView.setPageIndicator(mOneUiPageIndicator);
+ mOneUiPagedView.setOnActivePageChangedListener((recyclerView, page) -> {
+ mAH.get(AdapterHolder.MAIN).mRecyclerView = recyclerView;
+ if (mHeader != null && mHeader.isSetUp()) {
+ mHeader.updateMainRV(recyclerView);
+ }
+ updateOneUiPageIndicator();
+ });
+ mOneUiPagedView.setApps(getOneUiApps());
+ mAH.get(AdapterHolder.MAIN).mRecyclerView = mOneUiPagedView.getCurrentRecyclerView();
+ mOneUiPagedView.post(this::updateOneUiPageIndicator);
+ }
+
+ private void applyOneUiPagePadding() {
+ if (mOneUiPagedView == null) {
+ return;
+ }
+ mOneUiPagedView.setPagePadding(mAH.get(AdapterHolder.MAIN).mPadding);
+ }
+
+ private List getOneUiApps() {
+ List apps = new ArrayList<>();
+ for (AdapterItem item : mAH.get(AdapterHolder.MAIN).mAppsList.getAdapterItems()) {
+ if (BaseAllAppsAdapter.isIconViewType(item.viewType) && item.itemInfo != null) {
+ apps.add(item.itemInfo);
+ }
+ }
+ return apps;
+ }
+
+ private void registerMainIconContainers() {
+ if (mOneUiPagedView != null) {
+ for (AllAppsRecyclerView recyclerView : mOneUiPagedView.getRecyclerViews()) {
+ mAllAppsStore.registerIconContainer(recyclerView);
+ }
+ return;
+ }
+ mAllAppsStore.registerIconContainer(mAH.get(AdapterHolder.MAIN).mRecyclerView);
+ }
+
+ private void unregisterMainIconContainers() {
+ if (mOneUiPagedView != null) {
+ for (AllAppsRecyclerView recyclerView : mOneUiPagedView.getRecyclerViews()) {
+ mAllAppsStore.unregisterIconContainer(recyclerView);
+ }
+ }
+ mAllAppsStore.unregisterIconContainer(mAH.get(AdapterHolder.MAIN).mRecyclerView);
+ }
+
+ private void updateOneUiPageIndicator() {
+ if (mOneUiPageIndicator == null) {
+ return;
+ }
+ int pageCount = mOneUiPagedView == null ? 0 : mOneUiPagedView.getPageCount();
+ mOneUiPageIndicator.setMarkersCount(Math.max(1, pageCount));
+ mOneUiPageIndicator.setActiveMarker(mOneUiPagedView == null ? 0 : mOneUiPagedView.getNextPage());
+ updateOneUiPageIndicatorState();
+ }
+
+ private void updateOneUiPageIndicatorState() {
+ if (mOneUiPageIndicator == null) {
+ return;
+ }
+ boolean visible = AppDrawerStyle.isVerticalPaged(mAppDrawerStyle)
+ && !isSearching()
+ && mOneUiPagedView != null
+ && mOneUiPagedView.getPageCount() > 1;
+ mOneUiPageIndicator.setVisibility(visible ? VISIBLE : GONE);
+ }
private void setDeviceManagementResources() {
if (mActivityContext.getStringCache() != null) {
Button personalTab = findViewById(R.id.tab_personal);
@@ -1306,7 +1691,7 @@ private void setDeviceManagementResources() {
* Returns true if the container has work apps.
*/
public boolean shouldShowTabs() {
- return mHasWorkApps;
+ return mHasWorkApps && AppDrawerStyle.isNormal(mAppDrawerStyle);
}
// Used by tests only
@@ -1603,7 +1988,7 @@ public void invalidateHeader() {
public int getHeaderBottom() {
int bottom = (int) getTranslationY() + mHeader.getClipTop();
if (isSearchBarFloating()) {
- if (mActivityContext.getDeviceProfile().shouldShowAllAppsOnSheet()) {
+ if (mActivityContext.getDeviceProfile().shouldShowAllAppsOnSheet(getContext())) {
return bottom + mBottomSheetBackground.getTop();
}
return bottom;
@@ -1649,7 +2034,7 @@ public class AdapterHolder {
AdapterHolder(int type, AlphabeticalAppsList appsList) {
mType = type;
mAppsList = appsList;
- mAdapter = createAdapter(mAppsList);
+ mAdapter = createAdapter(mAppsList, mType);
mAppsList.setAdapter(mAdapter);
mLayoutManager = mAdapter.getLayoutManager();
}
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 10749483799..abbf605b76d 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -44,6 +44,8 @@ public class AllAppsGridAdapter extends BaseAllAppsAdapter {
private final AppsGridLayoutManager mGridLayoutMgr;
private final CopyOnWriteArrayList mOnLayoutCompletedListeners =
new CopyOnWriteArrayList<>();
+ private String mDrawerStyle;
+ private final boolean mAllowDrawerStyleModes;
/**
* Listener for {@link RecyclerView.LayoutManager#onLayoutCompleted(RecyclerView.State)}
@@ -70,13 +72,22 @@ public void removeOnLayoutCompletedListener(OnLayoutCompletedListener listener)
public AllAppsGridAdapter(ActivityContext activityContext, LayoutInflater inflater,
- AlphabeticalAppsList apps, SearchAdapterProvider> adapterProvider) {
+ AlphabeticalAppsList apps, SearchAdapterProvider> adapterProvider,
+ boolean allowDrawerStyleModes) {
super(activityContext, inflater, apps, adapterProvider);
mGridLayoutMgr = new AppsGridLayoutManager(mActivityContext.asContext());
+ mAllowDrawerStyleModes = allowDrawerStyleModes;
mGridLayoutMgr.setSpanSizeLookup(new GridSpanSizer());
+ mDrawerStyle = mAllowDrawerStyleModes
+ ? AppDrawerStyle.get(mActivityContext.asContext())
+ : AppDrawerStyle.NORMAL;
setAppsPerRow(activityContext.getDeviceProfile().numShownAllAppsColumns);
}
+ public String getDrawerStyle() {
+ return mDrawerStyle;
+ }
+
/**
* Returns the grid layout manager.
*/
@@ -183,7 +194,23 @@ protected int incrementTotalHeight(Adapter adapter, int position, int heightUnti
@Override
public void setAppsPerRow(int appsPerRow) {
- mAppsPerRow = appsPerRow;
+ mDrawerStyle = mAllowDrawerStyleModes
+ ? AppDrawerStyle.get(mActivityContext.asContext())
+ : AppDrawerStyle.NORMAL;
+
+ if (AppDrawerStyle.isHorizontalList(mDrawerStyle)) {
+ mAppsPerRow = 1;
+ } else {
+ mAppsPerRow = appsPerRow;
+ }
+
+ if (AppDrawerStyle.isVerticalPaged(mDrawerStyle)) {
+ mGridLayoutMgr.setOrientation(RecyclerView.HORIZONTAL);
+ mGridLayoutMgr.setSpanCount(getOneUiRows());
+ return;
+ }
+
+ mGridLayoutMgr.setOrientation(RecyclerView.VERTICAL);
int totalSpans = mAppsPerRow;
for (int itemPerRow : mAdapterProvider.getSupportedItemsPerRowArray()) {
if (totalSpans % itemPerRow != 0) {
@@ -193,6 +220,10 @@ public void setAppsPerRow(int appsPerRow) {
mGridLayoutMgr.setSpanCount(totalSpans);
}
+ private int getOneUiRows() {
+ return Math.max(1, mActivityContext.getDeviceProfile().getMaxAllAppsRowCount());
+ }
+
/**
* Helper class to size the grid items.
*/
@@ -212,6 +243,9 @@ public int getSpanSize(int position) {
}
int viewType = items.get(position).viewType;
if (isIconViewType(viewType)) {
+ if (AppDrawerStyle.isVerticalPaged(mDrawerStyle)) {
+ return 1;
+ }
return totalSpans / mAppsPerRow;
} else {
if (mAdapterProvider.isViewSupported(viewType)) {
diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java
index 1b6b7ebd515..bae713628cc 100644
--- a/src/com/android/launcher3/allapps/AllAppsStore.java
+++ b/src/com/android/launcher3/allapps/AllAppsStore.java
@@ -161,7 +161,7 @@ public int getDeferUpdatesFlags() {
return mDeferUpdatesFlags;
}
- private void notifyUpdate() {
+ public void notifyUpdate() {
if (mDeferUpdatesFlags != 0) {
Log.d(TAG, "notifyUpdate: deferring update");
mUpdatePending = true;
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index c518050241a..a68f0d7f8fd 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -110,7 +110,9 @@ public void setValue(AllAppsTransitionController controller, float progress) {
@Override
public Float get(AllAppsTransitionController controller) {
if (controller.mShouldShowAllAppsOnSheet) {
- return controller.mAppsView.getActiveRecyclerView().getTranslationY();
+ View activeRecyclerView = controller.mAppsView.getActiveRecyclerView();
+ return activeRecyclerView == null ? ALL_APPS_PULL_BACK_TRANSLATION_DEFAULT
+ : activeRecyclerView.getTranslationY();
} else {
return controller.getAppsViewPullbackTranslationY().getValue();
}
@@ -119,13 +121,19 @@ public Float get(AllAppsTransitionController controller) {
@Override
public void setValue(AllAppsTransitionController controller, float translation) {
if (controller.mShouldShowAllAppsOnSheet) {
- controller.mAppsView.getActiveRecyclerView().setTranslationY(translation);
+ View activeRecyclerView = controller.mAppsView.getActiveRecyclerView();
+ if (activeRecyclerView != null) {
+ activeRecyclerView.setTranslationY(translation);
+ }
controller.getAppsViewPullbackTranslationY().setValue(
ALL_APPS_PULL_BACK_TRANSLATION_DEFAULT);
} else {
controller.getAppsViewPullbackTranslationY().setValue(translation);
- controller.mAppsView.getActiveRecyclerView().setTranslationY(
- ALL_APPS_PULL_BACK_TRANSLATION_DEFAULT);
+ View activeRecyclerView = controller.mAppsView.getActiveRecyclerView();
+ if (activeRecyclerView != null) {
+ activeRecyclerView.setTranslationY(
+ ALL_APPS_PULL_BACK_TRANSLATION_DEFAULT);
+ }
}
}
};
@@ -138,7 +146,9 @@ public void setValue(AllAppsTransitionController controller, float translation)
@Override
public Float get(AllAppsTransitionController controller) {
if (controller.mShouldShowAllAppsOnSheet) {
- return controller.mAppsView.getActiveRecyclerView().getAlpha();
+ View activeRecyclerView = controller.mAppsView.getActiveRecyclerView();
+ return activeRecyclerView == null ? ALL_APPS_PULL_BACK_ALPHA_DEFAULT
+ : activeRecyclerView.getAlpha();
} else {
return controller.getAppsViewPullbackAlpha().getValue();
}
@@ -147,13 +157,19 @@ public Float get(AllAppsTransitionController controller) {
@Override
public void setValue(AllAppsTransitionController controller, float alpha) {
if (controller.mShouldShowAllAppsOnSheet) {
- controller.mAppsView.getActiveRecyclerView().setAlpha(alpha);
+ View activeRecyclerView = controller.mAppsView.getActiveRecyclerView();
+ if (activeRecyclerView != null) {
+ activeRecyclerView.setAlpha(alpha);
+ }
controller.getAppsViewPullbackAlpha().setValue(
ALL_APPS_PULL_BACK_ALPHA_DEFAULT);
} else {
controller.getAppsViewPullbackAlpha().setValue(alpha);
- controller.mAppsView.getActiveRecyclerView().setAlpha(
- ALL_APPS_PULL_BACK_ALPHA_DEFAULT);
+ View activeRecyclerView = controller.mAppsView.getActiveRecyclerView();
+ if (activeRecyclerView != null) {
+ activeRecyclerView.setAlpha(
+ ALL_APPS_PULL_BACK_ALPHA_DEFAULT);
+ }
}
}
};
@@ -197,7 +213,7 @@ public AllAppsTransitionController(Launcher l) {
DeviceProfile dp = mLauncher.getDeviceProfile();
mProgress = 1f;
mIsVerticalLayout = dp.isVerticalBarLayout();
- mShouldShowAllAppsOnSheet = dp.shouldShowAllAppsOnSheet();
+ mShouldShowAllAppsOnSheet = dp.shouldShowAllAppsOnSheet(mLauncher);
mNavScrimFlag = Themes.getAttrBoolean(l, R.attr.isMainColorDark)
? FLAG_DARK_NAV : FLAG_LIGHT_NAV;
@@ -221,7 +237,7 @@ public void onDeviceProfileChanged(DeviceProfile dp) {
mLauncher.getWorkspace().getPageIndicator().setTranslationY(0);
}
- mShouldShowAllAppsOnSheet = dp.shouldShowAllAppsOnSheet();
+ mShouldShowAllAppsOnSheet = dp.shouldShowAllAppsOnSheet(mLauncher);
}
/**
@@ -313,8 +329,9 @@ private void onScaleProgressChanged() {
if (hasScaleEffect != mHasScaleEffect) {
mHasScaleEffect = hasScaleEffect;
if (mHasScaleEffect) {
- modifyAttributesOnViewTree(rv, mLauncher.getAppsView(),
- CLIP_CHILDREN_FALSE_MODIFIER);
+ // Do not disable clipping if drawer opening, otherwise we tank frame rate
+ if (scaleProgress < 0.95f)
+ modifyAttributesOnViewTree(rv, mLauncher.getAppsView(), CLIP_CHILDREN_FALSE_MODIFIER);
} else {
restoreAttributesOnViewTree(rv, mLauncher.getAppsView(),
CLIP_CHILDREN_FALSE_MODIFIER);
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 58a24e4ca8a..46fdd45cca9 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -25,6 +25,8 @@
import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_NOTHING;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_PREINSTALLED_APPS_COUNT;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_USER_INSTALLED_APPS_COUNT;
+import static com.android.launcher3.util.Executors.DATA_HELPER_EXECUTOR;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.content.Context;
import android.text.Spannable;
@@ -363,8 +365,18 @@ public void updateAdapterItems() {
}
if (mAdapter != null) {
- DiffUtil.calculateDiff(new MyDiffCallback(oldItems, mAdapterItems), false)
- .dispatchUpdatesTo(mAdapter);
+ final List oldItemsSnapshot = oldItems;
+ final List newItemsSnapshot = new ArrayList<>(mAdapterItems);
+ DATA_HELPER_EXECUTOR.execute(() -> {
+ DiffUtil.DiffResult diffResult =
+ DiffUtil.calculateDiff(
+ new MyDiffCallback(oldItemsSnapshot, newItemsSnapshot), false);
+ MAIN_EXECUTOR.execute(() -> {
+ if (mAdapter != null) {
+ diffResult.dispatchUpdatesTo(mAdapter);
+ }
+ });
+ });
}
}
diff --git a/src/com/android/launcher3/allapps/AppDrawerStyle.java b/src/com/android/launcher3/allapps/AppDrawerStyle.java
new file mode 100644
index 00000000000..8b39ba5a9a2
--- /dev/null
+++ b/src/com/android/launcher3/allapps/AppDrawerStyle.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2026 VoltageOS
+ *
+ * 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.allapps;
+
+import android.content.Context;
+
+import com.android.launcher3.LauncherPrefs;
+
+public final class AppDrawerStyle {
+
+ public static final String NORMAL = "normal";
+ public static final String HORIZONTAL_LIST = "horizontal_list";
+ public static final String VERTICAL_PAGED = "vertical";
+ public static final String FULLSCREEN = "fullscreen";
+
+ private AppDrawerStyle() { }
+
+ public static String get(Context context) {
+ String style = LauncherPrefs.APP_DRAWER_STYLE.get(context);
+ return isSupported(style) ? style : NORMAL;
+ }
+
+ public static boolean isSupported(String style) {
+ return NORMAL.equals(style)
+ || HORIZONTAL_LIST.equals(style)
+ || VERTICAL_PAGED.equals(style)
+ || FULLSCREEN.equals(style);
+ }
+
+ public static boolean isNormal(String style) {
+ return NORMAL.equals(style);
+ }
+
+ public static boolean isHorizontalList(String style) {
+ return HORIZONTAL_LIST.equals(style);
+ }
+
+ public static boolean isVerticalPaged(String style) {
+ return VERTICAL_PAGED.equals(style);
+ }
+
+ public static boolean isFullscreen(String style) {
+ return FULLSCREEN.equals(style) || VERTICAL_PAGED.equals(style);
+ }
+
+ public static boolean isFullscreen(Context context) {
+ return isFullscreen(get(context));
+ }
+}
diff --git a/src/com/android/launcher3/allapps/AppInfoComparator.java b/src/com/android/launcher3/allapps/AppInfoComparator.java
index bbf8e5a5fb0..ef3d283a5cd 100644
--- a/src/com/android/launcher3/allapps/AppInfoComparator.java
+++ b/src/com/android/launcher3/allapps/AppInfoComparator.java
@@ -16,15 +16,22 @@
package com.android.launcher3.allapps;
import android.content.Context;
+import android.content.pm.PackageManager;
+import android.app.usage.UsageStats;
+import android.app.usage.UsageStatsManager;
import android.os.Process;
import android.os.UserHandle;
import android.text.TextUtils;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.LabelComparator;
import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
/**
* A comparator to arrange items based on user profiles.
@@ -35,15 +42,52 @@ public class AppInfoComparator implements Comparator {
private final UserHandle mMyUser;
private final LabelComparator mLabelComparator;
+ private final Context mContext;
+ private Map mUsageStats = null;
+ private long mLastUsageUpdateTime = 0;
+
public AppInfoComparator(Context context) {
+ mContext = context;
mUserManager = UserCache.INSTANCE.get(context);
mMyUser = Process.myUserHandle();
mLabelComparator = new LabelComparator();
}
+ private Map getUsageStats() {
+ long now = System.currentTimeMillis();
+ if (mUsageStats == null || now - mLastUsageUpdateTime > 60000) {
+ mUsageStats = new HashMap<>();
+ UsageStatsManager usm = (UsageStatsManager) mContext.getSystemService(Context.USAGE_STATS_SERVICE);
+ if (usm != null) {
+ long endtime = now;
+ long starttime = endtime - 1000L * 60 * 60 * 24 * 30; // 30 days
+ List stats = usm.queryUsageStats(UsageStatsManager.INTERVAL_BEST, starttime, endtime);
+ if (stats != null) {
+ for (UsageStats stat : stats) {
+ mUsageStats.put(stat.getPackageName(), stat.getTotalTimeInForeground());
+ }
+ }
+ }
+ mLastUsageUpdateTime = now;
+ }
+ return mUsageStats;
+ }
+
@Override
public int compare(AppInfo a, AppInfo b) {
- // Order by the title in the current locale
+ String sortMode = LauncherPrefs.get(mContext).get(LauncherPrefs.APP_DRAWER_SORT_MODE);
+
+ if ("install_date".equals(sortMode)) {
+ int result = Long.compare(b.firstInstallTime, a.firstInstallTime);
+ if (result != 0) return result;
+ } else if ("usage".equals(sortMode)) {
+ Map stats = getUsageStats();
+ long usageA = stats.containsKey(a.componentName.getPackageName()) ? stats.get(a.componentName.getPackageName()) : 0L;
+ long usageB = stats.containsKey(b.componentName.getPackageName()) ? stats.get(b.componentName.getPackageName()) : 0L;
+ int result = Long.compare(usageB, usageA);
+ if (result != 0) return result;
+ }
+
int result = mLabelComparator.compare(getSortingTitle(a), getSortingTitle(b));
if (result != 0) {
return result;
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
index 3cb3be1017b..4b5c6e8adc5 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
@@ -39,6 +39,7 @@
import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.allapps.search.SearchAdapterProvider;
import com.android.launcher3.model.data.AppInfo;
@@ -171,6 +172,7 @@ protected void setDecorationFillAlpha(int alpha) {
protected final LayoutInflater mLayoutInflater;
protected final OnClickListener mOnIconClickListener;
protected final OnLongClickListener mOnIconLongClickListener;
+ protected final int mTextColor;
protected OnFocusChangeListener mIconFocusListener;
public BaseAllAppsAdapter(ActivityContext activityContext, LayoutInflater inflater,
@@ -179,6 +181,8 @@ public BaseAllAppsAdapter(ActivityContext activityContext, LayoutInflater inflat
mApps = apps;
mLayoutInflater = inflater;
+ mTextColor = activityContext.asContext().getColor(R.color.launcher_foreground_primary);
+
mOnIconClickListener = mActivityContext.getItemOnClickListener();
mOnIconLongClickListener = mActivityContext.getAllAppsItemLongClickListener();
@@ -262,6 +266,7 @@ public void onBindViewHolder(ViewHolder holder, int position) {
AdapterItem adapterItem = mApps.getAdapterItems().get(position);
BubbleTextView icon = (BubbleTextView) holder.itemView;
icon.reset();
+ icon.setTextColor(mTextColor);
icon.applyFromApplicationInfo(adapterItem.itemInfo);
icon.setOnFocusChangeListener(mIconFocusListener);
icon.configureMinimalPopup(
@@ -280,6 +285,7 @@ public void onBindViewHolder(ViewHolder holder, int position) {
privateProfileManager.getReadyToAnimate())
&& privateProfileManager.getCurrentState() == STATE_ENABLED
? 0 : 1);
+ /*
Log.d(TAG, "onBindViewHolder: "
+ "isPrivateSpaceItem: " + isPrivateSpaceItem
+ " isStateTransitioning: " + privateProfileManager.isStateTransitioning()
@@ -287,6 +293,7 @@ public void onBindViewHolder(ViewHolder holder, int position) {
+ " readyToAnimate: " + privateProfileManager.getReadyToAnimate()
+ " currentState: " + privateProfileManager.getCurrentState()
+ " currentAlpha: " + icon.getAlpha());
+ */
}
// Views can still be bounded before the app list is updated hence showing icons
// after collapsing.
@@ -342,17 +349,45 @@ public void onBindViewHolder(ViewHolder holder, int position) {
}
private BubbleTextView getIconOnCreateSetup(ViewGroup parent) {
- int layout = mActivityContext.getDeviceProfile().inv.enableTwoLinesInAllApps
- ? R.layout.all_apps_icon_twoline : R.layout.all_apps_icon;
+ String drawerStyle = AppDrawerStyle.NORMAL;
+ if (this instanceof AllAppsGridAdapter) {
+ drawerStyle = ((AllAppsGridAdapter) this).getDrawerStyle();
+ } else {
+ drawerStyle = AppDrawerStyle.get(mActivityContext.asContext());
+ }
+ if (this instanceof AllAppsGridAdapter) {
+ drawerStyle = ((AllAppsGridAdapter) this).getDrawerStyle();
+ }
+ int layout;
+ if (AppDrawerStyle.isHorizontalList(drawerStyle)) {
+ layout = R.layout.all_apps_icon_horizontal_list;
+ } else {
+ layout = LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE.get(
+ mActivityContext.asContext())
+ ? R.layout.all_apps_icon_twoline : R.layout.all_apps_icon;
+ }
BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
layout, parent, false);
icon.setLongPressTimeoutFactor(1f);
icon.setOnFocusChangeListener(mIconFocusListener);
icon.setOnClickListener(mOnIconClickListener);
icon.setOnLongClickListener(mOnIconLongClickListener);
- // Ensure the all apps icon height matches the workspace icons in portrait mode.
- icon.getLayoutParams().height =
- mActivityContext.getDeviceProfile().getAllAppsProfile().getCellHeightPx();
+
+ ViewGroup.LayoutParams lp = icon.getLayoutParams();
+ if (AppDrawerStyle.isHorizontalList(drawerStyle)) {
+ lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
+ lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+ try {
+ java.lang.reflect.Field f = com.android.launcher3.BubbleTextView.class.getDeclaredField("mLayoutHorizontal");
+ f.setAccessible(true);
+ f.setBoolean(icon, true);
+ } catch (Exception e) {}
+ } else if (AppDrawerStyle.isVerticalPaged(drawerStyle)) {
+ lp.width = mActivityContext.getDeviceProfile().getAllAppsProfile().getCellWidthPx();
+ lp.height = mActivityContext.getDeviceProfile().getAllAppsProfile().getCellHeightPx();
+ } else {
+ lp.height = mActivityContext.getDeviceProfile().getAllAppsProfile().getCellHeightPx();
+ }
return icon;
}
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index 947945dc1d6..f2ae75a42a2 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -254,7 +254,7 @@ void setup(AllAppsRecyclerView mainRV, AllAppsRecyclerView workRV, SearchRecycle
/** Whether this header has been set up previously. */
boolean isSetUp() {
- return mMainRV != null;
+ return mMainRV != null || mSearchRV != null;
}
/** Set the active AllApps RV which will adjust the alpha of the header when scrolled. */
@@ -265,12 +265,26 @@ void setActiveRV(int rvType) {
mCurrentRV =
rvType == AdapterHolder.MAIN ? mMainRV
: rvType == AdapterHolder.WORK ? mWorkRV : mSearchRV;
- mCurrentRV.addOnScrollListener(mOnScrollListener);
+ if (mCurrentRV != null) {
+ mCurrentRV.addOnScrollListener(mOnScrollListener);
+ }
maybeSetTabVisibility(rvType == AdapterHolder.SEARCH ? GONE : VISIBLE);
updateExpectedHeight();
}
+ void updateMainRV(@Nullable AllAppsRecyclerView mainRV) {
+ boolean mainWasActive = mCurrentRV == mMainRV;
+ if (mainWasActive && mCurrentRV != null) {
+ mCurrentRV.removeOnScrollListener(mOnScrollListener);
+ }
+ mMainRV = mainRV;
+ if (mainWasActive && mMainRV != null) {
+ mCurrentRV = mMainRV;
+ mCurrentRV.addOnScrollListener(mOnScrollListener);
+ applyVerticalMove();
+ }
+ }
/** Update tab visibility to the given state, only if tabs are active (work profile exists). */
void maybeSetTabVisibility(int visibility) {
mTabLayout.setVisibility(mTabsHidden ? GONE : visibility);
@@ -396,7 +410,9 @@ public void reset(boolean animate) {
}
mHeaderCollapsed = false;
mSnappedScrolledY = -mMaxTranslation;
- mCurrentRV.scrollToTop();
+ if (mCurrentRV != null) {
+ mCurrentRV.scrollToTop();
+ }
}
public boolean isExpanded() {
@@ -436,6 +452,9 @@ public void onAnimationUpdate(ValueAnimator animation) {
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
calcOffset(mTempOffset);
+ if (mCurrentRV == null) {
+ return super.onInterceptTouchEvent(ev);
+ }
ev.offsetLocation(mTempOffset.x, mTempOffset.y);
mForwardToRecyclerView = mCurrentRV.onInterceptTouchEvent(ev);
ev.offsetLocation(-mTempOffset.x, -mTempOffset.y);
@@ -459,6 +478,11 @@ public boolean onTouchEvent(MotionEvent event) {
}
private void calcOffset(Point p) {
+ if (mCurrentRV == null || mCurrentRV.getParent() == null) {
+ p.x = 0;
+ p.y = 0;
+ return;
+ }
p.x = getLeft() - mCurrentRV.getLeft() - ((ViewGroup) mCurrentRV.getParent()).getLeft();
p.y = getTop() - mCurrentRV.getTop() - ((ViewGroup) mCurrentRV.getParent()).getTop();
}
diff --git a/src/com/android/launcher3/allapps/OneUiPagedAllAppsView.java b/src/com/android/launcher3/allapps/OneUiPagedAllAppsView.java
new file mode 100644
index 00000000000..5f0480b2e54
--- /dev/null
+++ b/src/com/android/launcher3/allapps/OneUiPagedAllAppsView.java
@@ -0,0 +1,462 @@
+/*
+ * Copyright (C) 2026 VoltageOS
+ * (C) 2026 crDroid Android 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.allapps;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.LauncherPrefs;
+import com.android.launcher3.PagedView;
+import com.android.launcher3.R;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.pageindicators.PageIndicatorDots;
+import com.android.launcher3.util.Themes;
+import com.android.launcher3.views.ActivityContext;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class OneUiPagedAllAppsView extends PagedView {
+
+ public interface OnActivePageChangedListener {
+ void onActivePageChanged(@Nullable AllAppsRecyclerView recyclerView, int page);
+ }
+
+ private static final int RECYCLED_VIEW_POOL_SIZE = 64;
+
+ private final ActivityContext mActivityContext;
+ private final LayoutInflater mLayoutInflater;
+ private final ArrayList mApps = new ArrayList<>();
+ private final ArrayList mPageRecyclerViews = new ArrayList<>();
+ private final Rect mPagePadding = new Rect();
+
+ private final RecyclerView.RecycledViewPool mSharedPool = new RecyclerView.RecycledViewPool();
+
+ @Nullable private OnActivePageChangedListener mOnActivePageChangedListener;
+ private int mLastItemsPerPage = -1;
+ private int mLastSpanCount = -1;
+ private int mLastCellHeight = -1;
+ private int mLastAppCount = -1;
+
+ private boolean mLayersEnabled = false;
+
+ private final Runnable mRebuildRunnable = () -> rebuildPages(false);
+ private final Runnable mRebuildPreserveRunnable = () -> rebuildPages(true);
+
+ public OneUiPagedAllAppsView(Context context) {
+ this(context, null);
+ }
+
+ public OneUiPagedAllAppsView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public OneUiPagedAllAppsView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mActivityContext = ActivityContext.lookupContext(context);
+ mLayoutInflater = LayoutInflater.from(context);
+ setClipToPadding(false);
+ setClipChildren(false);
+ setPageSpacing(0);
+ setMotionEventSplittingEnabled(false);
+
+ mSharedPool.setMaxRecycledViews(0 /* viewType */, RECYCLED_VIEW_POOL_SIZE);
+ }
+
+ public void setPageIndicator(@Nullable PageIndicatorDots pageIndicator) {
+ mPageIndicator = pageIndicator;
+ if (mPageIndicator != null) {
+ mPageIndicator.setMarkersCount(getChildCount());
+ mPageIndicator.setActiveMarker(getNextPage());
+ }
+ }
+
+ public void setOnActivePageChangedListener(@Nullable OnActivePageChangedListener listener) {
+ mOnActivePageChangedListener = listener;
+ }
+
+ public void setApps(@NonNull List apps) {
+ boolean appCountChanged = apps.size() != mLastAppCount;
+ mApps.clear();
+ mApps.addAll(apps);
+ mLastAppCount = apps.size();
+
+ removeCallbacks(mRebuildRunnable);
+ removeCallbacks(mRebuildPreserveRunnable);
+
+ if (appCountChanged) {
+ mLastItemsPerPage = -1;
+ }
+
+ if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0) {
+ rebuildPages(false);
+ } else {
+ post(mRebuildRunnable);
+ }
+ }
+
+ public void setPagePadding(@NonNull Rect padding) {
+ if (mPagePadding.equals(padding)) {
+ return;
+ }
+ mPagePadding.set(padding);
+ applyPaddingToPages();
+ }
+
+ @Nullable
+ public AllAppsRecyclerView getCurrentRecyclerView() {
+ int page = getNextPage();
+ return page >= 0 && page < mPageRecyclerViews.size() ? mPageRecyclerViews.get(page) : null;
+ }
+
+ @NonNull
+ public List getRecyclerViews() {
+ return Collections.unmodifiableList(mPageRecyclerViews);
+ }
+
+ @Override
+ protected void notifyPageSwitchListener(int prevPage) {
+ super.notifyPageSwitchListener(prevPage);
+ dispatchActivePageChanged();
+ }
+
+ @Override
+ protected void onPageBeginTransition() {
+ super.onPageBeginTransition();
+ enableHardwareLayers();
+ }
+
+ @Override
+ protected void onPageEndTransition() {
+ super.onPageEndTransition();
+ disableHardwareLayers();
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ boolean intercepted = super.onInterceptTouchEvent(ev);
+ if (intercepted) {
+ enableHardwareLayers();
+ }
+ return intercepted;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ boolean handled = super.onTouchEvent(ev);
+ if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) {
+ enableHardwareLayers();
+ }
+ return handled;
+ }
+
+ private void enableHardwareLayers() {
+ if (mLayersEnabled) {
+ return;
+ }
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ if (child.getLayerType() != LAYER_TYPE_HARDWARE) {
+ child.setLayerType(LAYER_TYPE_HARDWARE, null);
+ }
+ }
+ mLayersEnabled = true;
+ }
+
+ private void disableHardwareLayers() {
+ if (!mLayersEnabled) {
+ return;
+ }
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ if (child.getLayerType() != LAYER_TYPE_NONE) {
+ child.setLayerType(LAYER_TYPE_NONE, null);
+ }
+ }
+ mLayersEnabled = false;
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ boolean isTablet = mActivityContext.getDeviceProfile().getDeviceProperties().isTablet();
+ if (isTablet && (mPagePadding.left > 0 || mPagePadding.right > 0)) {
+ canvas.save();
+ canvas.clipRect(
+ getScrollX() + mPagePadding.left,
+ getScrollY(),
+ getScrollX() + getWidth() - mPagePadding.right,
+ getScrollY() + getHeight()
+ );
+ super.dispatchDraw(canvas);
+ canvas.restore();
+ } else {
+ super.dispatchDraw(canvas);
+ }
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ if (w != oldw || h != oldh) {
+ removeCallbacks(mRebuildPreserveRunnable);
+ post(mRebuildPreserveRunnable);
+ }
+ }
+
+ @Override
+ protected boolean canScroll(float absVScroll, float absHScroll) {
+ return absHScroll > absVScroll && super.canScroll(absVScroll, absHScroll);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ removeCallbacks(mRebuildRunnable);
+ removeCallbacks(mRebuildPreserveRunnable);
+ }
+
+ private void rebuildPages(boolean preservePage) {
+ int itemsPerPage = getItemsPerPage();
+ if (itemsPerPage <= 0) {
+ return;
+ }
+ int spanCount = Math.max(1, mActivityContext.getDeviceProfile().numShownAllAppsColumns);
+ int adjustedCellHeight = getAdjustedCellHeight();
+
+ if (preservePage
+ && itemsPerPage == mLastItemsPerPage
+ && spanCount == mLastSpanCount
+ && adjustedCellHeight == mLastCellHeight
+ && getChildCount() > 0
+ && !mPageRecyclerViews.isEmpty()) {
+ applyPaddingToPages();
+ return;
+ }
+ mLastItemsPerPage = itemsPerPage;
+ mLastSpanCount = spanCount;
+ mLastCellHeight = adjustedCellHeight;
+
+ int pageToRestore = preservePage
+ ? Math.min(getNextPage(), Math.max(0, getPageCount() - 1)) : 0;
+
+ disableHardwareLayers();
+
+ removeAllViews();
+ mPageRecyclerViews.clear();
+
+ int pageCount = Math.max(1, (int) Math.ceil(mApps.size() / (float) itemsPerPage));
+ for (int page = 0; page < pageCount; page++) {
+ int start = page * itemsPerPage;
+ int end = Math.min(start + itemsPerPage, mApps.size());
+ AllAppsRecyclerView recyclerView = createPageRecyclerView(spanCount, itemsPerPage,
+ adjustedCellHeight, mApps.subList(start, end));
+ mPageRecyclerViews.add(recyclerView);
+ addView(recyclerView);
+ }
+
+ applyPaddingToPages();
+ setCurrentPage(Math.min(pageToRestore, Math.max(0, getChildCount() - 1)));
+ if (mPageIndicator != null) {
+ mPageIndicator.setMarkersCount(getChildCount());
+ mPageIndicator.setActiveMarker(getNextPage());
+ }
+ dispatchActivePageChanged();
+ }
+
+ private AllAppsRecyclerView createPageRecyclerView(int spanCount, int itemsPerPage,
+ int cellHeight, List pageApps) {
+ AllAppsRecyclerView recyclerView = new AllAppsRecyclerView(getContext()) {
+ @Override
+ public void scrollToTop() {
+ if (getScrollbar() != null) {
+ getScrollbar().setThumbOffsetY(0);
+ }
+ RecyclerView.LayoutManager layoutManager = getLayoutManager();
+ if (layoutManager instanceof GridLayoutManager) {
+ ((GridLayoutManager) layoutManager).scrollToPositionWithOffset(0,
+ getPaddingTop());
+ }
+ }
+ };
+ recyclerView.setId(View.generateViewId());
+ recyclerView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT));
+ recyclerView.setClipToPadding(true);
+ recyclerView.setOverScrollMode(OVER_SCROLL_NEVER);
+ recyclerView.setVerticalScrollBarEnabled(false);
+ recyclerView.setHorizontalScrollBarEnabled(false);
+ recyclerView.setHasFixedSize(true);
+ recyclerView.setItemAnimator(null);
+ recyclerView.setNestedScrollingEnabled(false);
+ recyclerView.setItemViewCacheSize(itemsPerPage);
+ recyclerView.setRecycledViewPool(mSharedPool);
+
+ GridLayoutManager lm = new GridLayoutManager(getContext(), spanCount) {
+ @Override
+ public boolean canScrollVertically() {
+ return false;
+ }
+
+ @Override
+ public boolean canScrollHorizontally() {
+ return false;
+ }
+
+ @Override
+ public boolean supportsPredictiveItemAnimations() {
+ return false;
+ }
+ };
+ lm.setItemPrefetchEnabled(false);
+ recyclerView.setLayoutManager(lm);
+ recyclerView.setAdapter(new PageAdapter(pageApps, cellHeight));
+ return recyclerView;
+ }
+
+ private void applyPaddingToPages() {
+ for (AllAppsRecyclerView recyclerView : mPageRecyclerViews) {
+ if (recyclerView.getPaddingLeft() != mPagePadding.left
+ || recyclerView.getPaddingTop() != mPagePadding.top
+ || recyclerView.getPaddingRight() != mPagePadding.right
+ || recyclerView.getPaddingBottom() != mPagePadding.bottom) {
+ recyclerView.setPadding(mPagePadding.left, mPagePadding.top,
+ mPagePadding.right, mPagePadding.bottom);
+ }
+ }
+ }
+
+ private void dispatchActivePageChanged() {
+ if (mOnActivePageChangedListener != null) {
+ mOnActivePageChangedListener.onActivePageChanged(getCurrentRecyclerView(),
+ getNextPage());
+ }
+ }
+
+ private int getRowsPerPage() {
+ return Math.max(1, mActivityContext.getDeviceProfile().inv.numRows);
+ }
+
+ private int getAdjustedCellHeight() {
+ int height = getMeasuredHeight();
+ int baseCellHeight = Math.max(1,
+ mActivityContext.getDeviceProfile().getAllAppsProfile().getCellHeightPx());
+ if (height <= 0) {
+ return baseCellHeight;
+ }
+ int rows = getRowsPerPage();
+ if (rows <= 0) {
+ return baseCellHeight;
+ }
+ int availableHeight = Math.max(0, height - mPagePadding.top - mPagePadding.bottom);
+ return availableHeight / rows;
+ }
+
+ private int getItemsPerPage() {
+ int width = getMeasuredWidth();
+ if (width <= 0 || getMeasuredHeight() <= 0) {
+ return 0;
+ }
+ int cols = Math.max(1, mActivityContext.getDeviceProfile().numShownAllAppsColumns);
+ int rows = getRowsPerPage();
+ return rows * cols;
+ }
+
+ private class PageAdapter extends RecyclerView.Adapter {
+ private final List mPageApps;
+ private final int mCellHeight;
+ private final int mTextColor;
+ private final int mLayoutRes;
+ private final View.OnClickListener mClickListener;
+ private final View.OnLongClickListener mLongClickListener;
+
+ PageAdapter(List pageApps, int cellHeight) {
+ mPageApps = pageApps;
+ mCellHeight = cellHeight;
+ Context ctx = mActivityContext.asContext();
+ boolean forceDarkText = LauncherPrefs.ALL_APPS_DARK_TEXT.get(ctx);
+ mTextColor = forceDarkText
+ ? ctx.getResources().getColor(R.color.all_apps_label_color_dark_forced, null)
+ : Themes.getAttrColor(ctx, android.R.attr.textColorPrimary);
+ mLayoutRes = LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE.get(ctx)
+ ? R.layout.all_apps_icon_twoline : R.layout.all_apps_icon;
+ mClickListener = mActivityContext.getItemOnClickListener();
+ mLongClickListener = mActivityContext.getAllAppsItemLongClickListener();
+ }
+
+ @Override
+ public int getItemCount() {
+ return mPageApps.size();
+ }
+
+ @NonNull
+ @Override
+ public IconHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(mLayoutRes, parent,
+ false);
+ icon.setLongPressTimeoutFactor(1f);
+ icon.setOnClickListener(mClickListener);
+ icon.setOnLongClickListener(mLongClickListener);
+ ViewGroup.LayoutParams lp = icon.getLayoutParams();
+ lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
+ lp.height = mCellHeight;
+ return new IconHolder(icon);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull IconHolder holder, int position) {
+ BubbleTextView icon = holder.mIcon;
+ ViewGroup.LayoutParams lp = icon.getLayoutParams();
+ if (lp != null && lp.height != mCellHeight) {
+ lp.height = mCellHeight;
+ icon.setLayoutParams(lp);
+ }
+ icon.reset();
+ icon.setTextColor(mTextColor);
+ icon.applyFromApplicationInfo(mPageApps.get(position));
+ }
+
+ @Override
+ public void onViewRecycled(@NonNull IconHolder holder) {
+ super.onViewRecycled(holder);
+ holder.mIcon.reset();
+ }
+
+ class IconHolder extends RecyclerView.ViewHolder {
+ final BubbleTextView mIcon;
+
+ IconHolder(BubbleTextView icon) {
+ super(icon);
+ mIcon = icon;
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/allapps/RecyclerViewAnimationController.java b/src/com/android/launcher3/allapps/RecyclerViewAnimationController.java
index 8752e4a1ac2..7c6173c4d14 100644
--- a/src/com/android/launcher3/allapps/RecyclerViewAnimationController.java
+++ b/src/com/android/launcher3/allapps/RecyclerViewAnimationController.java
@@ -90,6 +90,8 @@ protected int onProgressUpdated(float expansionProgress) {
Integer top = null;
AllAppsRecyclerView allAppsRecyclerView = getRecyclerView();
+ List allAppsAdapters = allAppsRecyclerView.getApps().getAdapterItems();
+
for (int i = 0; i < allAppsRecyclerView.getChildCount(); i++) {
View currentView = allAppsRecyclerView.getChildAt(i);
if (currentView == null) {
@@ -99,8 +101,6 @@ protected int onProgressUpdated(float expansionProgress) {
top = currentView.getTop();
}
int adapterPosition = allAppsRecyclerView.getChildAdapterPosition(currentView);
- List allAppsAdapters = allAppsRecyclerView.getApps()
- .getAdapterItems();
if (adapterPosition < 0 || adapterPosition >= allAppsAdapters.size()) {
continue;
}
@@ -188,7 +188,28 @@ protected void animateToState(boolean expand, long duration, Runnable onEndRunna
duration = 0;
}
- mAnimator.addListener(forEndCallback(() -> mAnimator = null));
+ mAnimator.addListener(forEndCallback(() -> {
+ mAnimator = null;
+ AllAppsRecyclerView rv = getRecyclerView();
+ for (int i = 0; i < rv.getChildCount(); i++) {
+ rv.getChildAt(i).setLayerType(View.LAYER_TYPE_NONE, null);
+ }
+ }));
+ mAnimator.addListener(new android.animation.AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(android.animation.Animator animation) {
+ AllAppsRecyclerView rv = getRecyclerView();
+ List allAppsAdapters =
+ rv.getApps().getAdapterItems();
+ for (int i = 0; i < rv.getChildCount(); i++) {
+ View child = rv.getChildAt(i);
+ if (shouldAnimateView(child, rv.getChildAdapterPosition(child),
+ allAppsAdapters)) {
+ child.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ }
+ }
+ }
+ });
mAnimator.setDuration(duration).setInterpolator(timeInterpolator);
mAnimator.addListener(forSuccessCallback(onEndRunnable));
mAnimator.start();
@@ -199,6 +220,15 @@ protected void animateToState(boolean expand, long duration, Runnable onEndRunna
private void onChildAttached(View child) {
// Avoid allocating hardware layers for alpha changes.
child.forceHasOverlappingRendering(false);
+ // Use HW layer for newly attached children only if they will animate
+ if (isRunning()) {
+ int adapterPosition = getRecyclerView().getChildAdapterPosition(child);
+ List allAppsAdapters =
+ getRecyclerView().getApps().getAdapterItems();
+ if (shouldAnimateView(child, adapterPosition, allAppsAdapters)) {
+ child.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ }
+ }
child.setPivotY(0);
if (getAnimationProgress() > 0 && getAnimationProgress() < 1) {
// Before the child is rendered, apply the animation including it to avoid flicker.
@@ -258,6 +288,16 @@ protected boolean isRunning() {
return mAnimator != null;
}
+ /** Returns true if the view should animate and needs a hardware layer. */
+ private boolean shouldAnimateView(View view, int adapterPosition,
+ List allAppsAdapters) {
+ if (adapterPosition < 0 || adapterPosition >= allAppsAdapters.size()) {
+ return false;
+ }
+ BaseAllAppsAdapter.AdapterItem adapterItem = allAppsAdapters.get(adapterPosition);
+ return shouldAnimate(view, adapterItem.getDecorationInfo() != null, false);
+ }
+
/** Should only animate if the view is an app icon and if it has a decoration info. */
protected boolean shouldAnimate(View view, boolean hasDecorationInfo,
boolean firstAppRowComplete) {
diff --git a/src/com/android/launcher3/allapps/SearchTransitionController.java b/src/com/android/launcher3/allapps/SearchTransitionController.java
index d5c3b577883..cef132de63a 100644
--- a/src/com/android/launcher3/allapps/SearchTransitionController.java
+++ b/src/com/android/launcher3/allapps/SearchTransitionController.java
@@ -16,6 +16,7 @@
package com.android.launcher3.allapps;
+import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static com.android.app.animation.Interpolators.DECELERATE_1_7;
@@ -67,9 +68,9 @@ protected void animateToState(boolean goingToSearch, long duration, Runnable onE
}));
}
mAllAppsContainerView.getFloatingHeaderView().setFloatingRowsCollapsed(true);
- mAllAppsContainerView.getFloatingHeaderView().setVisibility(VISIBLE);
- mAllAppsContainerView.getFloatingHeaderView().maybeSetTabVisibility(VISIBLE);
- mAllAppsContainerView.getAppsRecyclerViewContainer().setVisibility(VISIBLE);
+ mAllAppsContainerView.getFloatingHeaderView().setVisibility(GONE);
+ mAllAppsContainerView.getFloatingHeaderView().maybeSetTabVisibility(GONE);
+ mAllAppsContainerView.getAppsRecyclerViewContainer().setVisibility(GONE);
getRecyclerView().setVisibility(VISIBLE);
}
@@ -114,7 +115,7 @@ protected int onProgressUpdated(float searchToAzProgress) {
*/
@Override
protected boolean shouldAnimate(View view, boolean hasDecorationInfo, boolean appRowComplete) {
- return !isAppIcon(view) || appRowComplete;
+ return appRowComplete;
}
@Override
diff --git a/src/com/android/launcher3/allapps/SearchUiManager.java b/src/com/android/launcher3/allapps/SearchUiManager.java
index bfd89678dfd..ce030542a75 100644
--- a/src/com/android/launcher3/allapps/SearchUiManager.java
+++ b/src/com/android/launcher3/allapps/SearchUiManager.java
@@ -36,6 +36,11 @@ public interface SearchUiManager {
*/
void resetSearch();
+ /**
+ * Notifies the search manager to focus search field and open keyboard.
+ */
+ void focusSearchField();
+
/**
* Called before dispatching a key event, in case the search manager wants to initialize
* some UI beforehand.
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index c99f97e66fd..903f24d919d 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -126,8 +126,14 @@ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
} else {
Log.i(TAG, "User tapped ime search button");
}
+ // Skip if the query is empty
+ String query = v.getText().toString();
+ if (query.isEmpty()) {
+ return false;
+ }
+
// selectFocusedView should return SearchTargetEvent that is passed onto onClick
- return mLauncher.getAppsView().getMainAdapterProvider().launchHighlightedItem();
+ return mLauncher.getAppsView().getMainAdapterProvider().performGoogleSearch(v, query);
}
return false;
}
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
index ee78a0cc997..6bcf197d21e 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@@ -23,8 +23,11 @@
import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
import android.content.Context;
+import android.content.res.Resources;
import android.content.Intent;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.PaintDrawable;
import android.text.Selection;
import android.text.SpannableStringBuilder;
import android.text.method.TextKeyListener;
@@ -36,7 +39,9 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.ExtendedEditText;
import com.android.launcher3.Insettable;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.ActivityAllAppsContainerView;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
@@ -44,6 +49,7 @@
import com.android.launcher3.allapps.SearchUiManager;
import com.android.launcher3.search.SearchCallback;
import com.android.launcher3.util.ApiWrapper;
+import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ActivityContext;
import java.util.ArrayList;
@@ -102,10 +108,22 @@ protected void onDetachedFromWindow() {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Update the width to match the grid padding
+ if (mAppsView == null) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ return;
+ }
DeviceProfile dp = mLauncher.getDeviceProfile();
int myRequestedWidth = getSize(widthMeasureSpec);
- int rowWidth = myRequestedWidth - mAppsView.getActiveRecyclerView().getPaddingLeft()
- - mAppsView.getActiveRecyclerView().getPaddingRight();
+ View widthSource = mAppsView.getActiveRecyclerView();
+ if (widthSource == null) {
+ widthSource = mAppsView.getAppsRecyclerViewContainer();
+ }
+ if (widthSource == null) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ return;
+ }
+ int rowWidth = myRequestedWidth - widthSource.getPaddingLeft()
+ - widthSource.getPaddingRight();
int cellWidth = DeviceProfile.calculateCellWidth(rowWidth,
dp.getWorkspaceIconProfile().getCellLayoutBorderSpacePx().x, dp.numShownHotseatIcons);
@@ -121,6 +139,10 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
+ Drawable gIcon = getContext().getDrawable(R.drawable.ic_super_g_color);
+ Drawable gIconThemed = getContext().getDrawable(R.drawable.ic_super_g_themed);
+ Drawable sIcon = getContext().getDrawable(R.drawable.ic_allapps_search);
+
// Shift the widget horizontally so that its centered in the parent (b/63428078)
View parent = (View) getParent();
int availableWidth = parent.getWidth() - parent.getPaddingLeft() - parent.getPaddingRight();
@@ -129,7 +151,37 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto
int shift = expectedLeft - left;
setTranslationX(shift);
+ if (Utilities.showQSB(getContext()) && !LauncherPrefs.DOCK_THEME.get(getContext())) {
+ setCompoundDrawablesRelativeWithIntrinsicBounds(gIcon, null, null, null);
+ } else if (Utilities.showQSB(getContext()) && LauncherPrefs.DOCK_THEME.get(getContext())) {
+ setCompoundDrawablesRelativeWithIntrinsicBounds(gIconThemed, null, null, null);
+ } else {
+ setCompoundDrawablesRelativeWithIntrinsicBounds(sIcon, null, null, null);
+ }
+
offsetTopAndBottom(mContentOverlap);
+
+ setUpBackground();
+ }
+
+ private void setUpBackground() {
+ Context context = getContext();
+ float cornerRadius = getCornerRadius(context);
+ int color = Themes.getAttrColor(context, R.attr.qsbFillColor);
+ if (LauncherPrefs.DOCK_THEME.get(context))
+ color = Themes.getAttrColor(context, R.attr.qsbFillColorThemed);
+ PaintDrawable pd = new PaintDrawable(color);
+ pd.setCornerRadius(cornerRadius);
+ setClipToOutline(cornerRadius > 0);
+ setBackground(pd);
+ }
+
+ private float getCornerRadius(Context context) {
+ Resources res = context.getResources();
+ float qsbWidgetHeight = res.getDimension(R.dimen.qsb_widget_height);
+ float qsbWidgetPadding = res.getDimension(R.dimen.qsb_widget_vertical_padding);
+ float innerHeight = qsbWidgetHeight - 2 * qsbWidgetPadding;
+ return (innerHeight / 2) * ((float)LauncherPrefs.SEARCH_RADIUS_SIZE.get(context) / 100f);
}
@Override
@@ -150,6 +202,11 @@ public void resetSearch() {
mSearchBarController.reset();
}
+ @Override
+ public void focusSearchField() {
+ mSearchBarController.focusSearchField();
+ }
+
@Override
public void preDispatchKeyEvent(KeyEvent event) {
// Determine if the key event was actual text, if so, focus the search bar and then dispatch
diff --git a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
index 2c092827323..1c3e9457002 100644
--- a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
+++ b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
@@ -39,8 +39,6 @@
*/
public class DefaultAppSearchAlgorithm implements SearchAlgorithm {
- private static final int MAX_RESULTS_COUNT = 5;
-
private final LauncherAppState mAppState;
private final Handler mResultHandler;
private final boolean mAddNoResultsMessage;
@@ -96,17 +94,15 @@ public static ArrayList getTitleMatchResult(Context context, List new IconPackPrefSetter(ctx, mComponent));
+ icons.setOnPreferenceChangeListener(this);
+
+ THREAD_POOL_EXECUTOR.execute(() -> {
+ MetadataExtractor extractor = new MetadataExtractor(mContext, mComponent);
+
+ CharSequence source = extractor.getSource();
+ CharSequence lastUpdate = extractor.getLastUpdate();
+ CharSequence version = mContext.getString(
+ R.string.app_info_version_value,
+ extractor.getVersionName(),
+ extractor.getVersionCode());
+ Intent marketIntent = extractor.getMarketIntent();
+
+ MAIN_EXECUTOR.execute(() -> {
+ Preference sourcePref = findPreference(KEY_SOURCE);
+ Preference lastUpdatePref = findPreference(KEY_LAST_UPDATE);
+ Preference versionPref = findPreference(KEY_VERSION);
+ Preference morePref = findPreference(KEY_MORE);
+
+ sourcePref.setSummary(source);
+ lastUpdatePref.setSummary(lastUpdate);
+ versionPref.setSummary(version);
+ morePref.setOnPreferenceClickListener(this);
+
+ if (marketIntent != null) {
+ sourcePref.setOnPreferenceClickListener(
+ pref -> tryStartActivity(marketIntent));
+ }
+ });
+ });
+ }
+
+ private boolean tryStartActivity(Intent intent) {
+ Bundle opts = getAppTransitionManager()
+ .getActivityLaunchOptions(getView(), mItemInfo)
+ .toBundle();
+ try {
+ mLauncher.startActivity(intent, opts);
+ } catch (Exception ignored) {
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ if (newValue.equals(IconDatabase.getGlobal(mContext))) {
+ IconDatabase.resetForComponent(mContext, mKey);
+ } else {
+ IconDatabase.setForComponent(mContext, mKey, (String) newValue);
+ }
+ AppReloader.get(mContext).reload(mKey);
+ return true;
+ }
+
+ private void onMoreClick() {
+ PackageManagerHelper.startDetailsActivityForInfo(InfoBottomSheet.mViewContext, mItemInfo,
+ InfoBottomSheet.mSourceBounds, ActivityOptions.makeBasic().toBundle());
+ }
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ onMoreClick();
+ return true;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/customization/MetadataExtractor.java b/src/com/android/launcher3/customization/MetadataExtractor.java
new file mode 100644
index 00000000000..3fa444d418e
--- /dev/null
+++ b/src/com/android/launcher3/customization/MetadataExtractor.java
@@ -0,0 +1,88 @@
+package com.android.launcher3.customization;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+
+import java.io.File;
+
+import static android.content.pm.PackageManager.GET_META_DATA;
+
+class MetadataExtractor {
+ private final Context mContext;
+ private final PackageManager mPm;
+
+ private String mSourcePkg = "";
+ private Intent mMarketIntent;
+
+ private long mLastUpdate = 0;
+ private String mVersionName = "Unknown";
+ private long mVersionCode = 0;
+
+ MetadataExtractor(Context context, ComponentName cn) {
+ mContext = context;
+ mPm = context.getPackageManager();
+ String pkg = cn.getPackageName();
+
+ try {
+ mSourcePkg = mPm.getInstallerPackageName(pkg);
+ if (!TextUtils.isEmpty(mSourcePkg)) {
+ mMarketIntent = new Intent(Intent.ACTION_VIEW)
+ .setData(Uri.parse("market://details?id=" + pkg))
+ .setPackage(mSourcePkg)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ }
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ }
+
+ try {
+ PackageInfo pi = mPm.getPackageInfo(pkg, GET_META_DATA);
+ mLastUpdate = new File(pi.applicationInfo.sourceDir).lastModified();
+ mVersionName = pi.versionName;
+ mVersionCode = pi.getLongVersionCode();
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ }
+ }
+
+ CharSequence getSource() {
+ if (TextUtils.isEmpty(mSourcePkg)) {
+ return mContext.getString(R.string.app_info_source_system);
+ }
+ try {
+ ApplicationInfo pi = mPm.getApplicationInfo(mSourcePkg, 0);
+ return pi.loadLabel(mPm);
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ }
+ return mSourcePkg;
+ }
+
+ String getLastUpdate() {
+ return DateUtils.formatDateTime(mContext, mLastUpdate,
+ DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_SHOW_DATE);
+ }
+
+ String getVersionName() {
+ return mVersionName;
+ }
+
+ long getVersionCode() {
+ return mVersionCode;
+ }
+
+ Intent getMarketIntent() {
+ return mMarketIntent;
+ }
+
+}
diff --git a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
index 4557197626b..a0f701b8df6 100644
--- a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
+++ b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
@@ -28,6 +28,7 @@
import com.android.launcher3.RemoveAnimationSettingsTracker;
import com.android.launcher3.backuprestore.LauncherRestoreEventLogger;
import com.android.launcher3.compose.core.widgetpicker.WidgetPickerComposeWrapper;
+import com.android.launcher3.data.wallpaper.service.WallpaperService;
import com.android.launcher3.dragndrop.SystemDragController;
import com.android.launcher3.folder.FolderNameSuggestionLoader;
import com.android.launcher3.graphics.GridCustomizationsProxy;
@@ -149,6 +150,8 @@ public interface LauncherBaseAppComponent {
/** Tracker for any app icon changes */
IconChangeTracker getIconChangeTracker();
+ WallpaperService getWallpaperService();
+
/** Builder for LauncherBaseAppComponent. */
interface Builder {
@BindsInstance Builder appContext(@ApplicationContext Context context);
diff --git a/src/com/android/launcher3/dagger/SettingsModule.kt b/src/com/android/launcher3/dagger/SettingsModule.kt
index c08fcfdcebe..66d3b8e157e 100644
--- a/src/com/android/launcher3/dagger/SettingsModule.kt
+++ b/src/com/android/launcher3/dagger/SettingsModule.kt
@@ -17,6 +17,9 @@
package com.android.launcher3.dagger
import android.net.Uri
+import com.android.launcher3.taskbar.TaskbarManagerImpl.ENABLE_TASKBAR_URI
+import com.android.launcher3.taskbar.TaskbarManagerImpl.NAVBAR_IME_SPACE_URI
+import com.android.launcher3.taskbar.TaskbarManagerImpl.NAVIGATION_BAR_HINT_URI
import com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI
import dagger.Module
import dagger.Provides
@@ -25,6 +28,20 @@ import javax.inject.Named
@Module
class SettingsModule {
+ @Provides
+ @IntoSet
+ @Named("SETTINGS_ENABLED_BY_DEFAULT")
+ fun provideEnableTaskbarDefaults(): Uri = ENABLE_TASKBAR_URI
+
+ @Provides
+ @IntoSet
+ @Named("SETTINGS_ENABLED_BY_DEFAULT")
+ fun provideNavigationBarIMEDefaults(): Uri = NAVBAR_IME_SPACE_URI
+
+ @Provides
+ @IntoSet
+ @Named("SETTINGS_ENABLED_BY_DEFAULT")
+ fun provideNavigationBarHintDefaults(): Uri = NAVIGATION_BAR_HINT_URI
@Provides
@IntoSet
diff --git a/src/com/android/launcher3/data/AppDatabase.kt b/src/com/android/launcher3/data/AppDatabase.kt
new file mode 100644
index 00000000000..6b1db1cab3f
--- /dev/null
+++ b/src/com/android/launcher3/data/AppDatabase.kt
@@ -0,0 +1,42 @@
+package com.android.launcher3.data
+
+import android.content.Context
+import androidx.room.Database
+import androidx.room.Room
+import androidx.room.RoomDatabase
+import androidx.sqlite.db.SimpleSQLiteQuery
+
+import com.android.launcher3.data.wallpaper.Wallpaper
+import com.android.launcher3.data.wallpaper.service.WallpaperDao
+import com.android.launcher3.util.override.MainThreadInitializedObject
+
+import kotlinx.coroutines.runBlocking
+
+@Database(entities = [Wallpaper::class], version = 1, exportSchema = false)
+abstract class AppDatabase : RoomDatabase() {
+
+ abstract fun wallpaperDao(): WallpaperDao
+
+ suspend fun checkpoint() {
+ wallpaperDao().checkpoint(SimpleSQLiteQuery("pragma wal_checkpoint(full)"))
+ }
+
+ fun checkpointSync() {
+ runBlocking {
+ checkpoint()
+ }
+ }
+
+ companion object {
+
+ @JvmField
+ val INSTANCE = MainThreadInitializedObject { context ->
+ Room.databaseBuilder(
+ context,
+ AppDatabase::class.java,
+ "preferences",
+ ).build()
+ }
+
+ }
+}
diff --git a/src/com/android/launcher3/data/wallpaper/Wallpaper.kt b/src/com/android/launcher3/data/wallpaper/Wallpaper.kt
new file mode 100644
index 00000000000..f93a3d79dd5
--- /dev/null
+++ b/src/com/android/launcher3/data/wallpaper/Wallpaper.kt
@@ -0,0 +1,13 @@
+package com.android.launcher3.data.wallpaper
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity(tableName = "Wallpapers")
+data class Wallpaper(
+ @PrimaryKey(autoGenerate = true) val id: Long = 0,
+ val imagePath: String,
+ val rank: Int,
+ val timestamp: Long,
+ val checksum: String? = null,
+)
diff --git a/src/com/android/launcher3/data/wallpaper/service/WallpaperDao.kt b/src/com/android/launcher3/data/wallpaper/service/WallpaperDao.kt
new file mode 100644
index 00000000000..98ae21b1382
--- /dev/null
+++ b/src/com/android/launcher3/data/wallpaper/service/WallpaperDao.kt
@@ -0,0 +1,41 @@
+package com.android.launcher3.data.wallpaper.service
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import androidx.room.RawQuery
+import androidx.sqlite.db.SupportSQLiteQuery
+
+import com.android.launcher3.data.wallpaper.Wallpaper
+
+@Dao
+interface WallpaperDao {
+
+ @Query("SELECT * FROM wallpapers ORDER BY timestamp DESC LIMIT :limit")
+ suspend fun getTopWallpapers(limit: Int = 4): List
+
+ @Query("SELECT * FROM wallpapers WHERE checksum = :checksum LIMIT 1")
+ suspend fun getByChecksum(checksum: String): Wallpaper?
+
+ @Insert(onConflict = OnConflictStrategy.ABORT)
+ suspend fun insert(wallpaper: Wallpaper): Long
+
+ @Query("DELETE FROM wallpapers WHERE id = :id")
+ suspend fun deleteWallpaper(id: Long): Int
+
+ @Query("UPDATE wallpapers SET rank = rank + 1 WHERE rank >= 0")
+ suspend fun bumpAllRanks()
+
+ @Query("SELECT * FROM wallpapers WHERE id != :excludeId ORDER BY timestamp DESC")
+ suspend fun getTopWallpapersExcluding(excludeId: Long): List
+
+ @Query("UPDATE wallpapers SET rank = :rank WHERE id = :id")
+ suspend fun setRankOnly(id: Long, rank: Int): Int
+
+ @Query("UPDATE wallpapers SET rank = :rank, timestamp = :timestamp WHERE id = :id")
+ suspend fun updateWallpaper(id: Long, rank: Int, timestamp: Long): Int
+
+ @RawQuery
+ suspend fun checkpoint(supportSQLiteQuery: SupportSQLiteQuery): Int
+}
diff --git a/src/com/android/launcher3/data/wallpaper/service/WallpaperService.kt b/src/com/android/launcher3/data/wallpaper/service/WallpaperService.kt
new file mode 100644
index 00000000000..921bdb0ccac
--- /dev/null
+++ b/src/com/android/launcher3/data/wallpaper/service/WallpaperService.kt
@@ -0,0 +1,164 @@
+package com.android.launcher3.data.wallpaper.service
+
+import android.app.WallpaperManager
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.drawable.BitmapDrawable
+import android.util.Log
+
+import androidx.room.withTransaction
+
+import com.android.launcher3.dagger.ApplicationContext
+import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.data.AppDatabase
+import com.android.launcher3.data.wallpaper.Wallpaper
+import com.android.launcher3.util.DaggerSingletonObject
+import com.android.launcher3.util.SafeCloseable
+
+import java.io.ByteArrayOutputStream
+import java.io.File
+import java.io.FileOutputStream
+import java.security.MessageDigest
+import javax.inject.Inject
+
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+import kotlinx.coroutines.withContext
+
+@LauncherAppSingleton
+class WallpaperService @Inject constructor(
+ @ApplicationContext private val context: Context,
+) : SafeCloseable {
+
+ private val rankMutex = Mutex()
+
+ private val roomDb by lazy { AppDatabase.INSTANCE.get(context) }
+ private val dao by lazy { roomDb.wallpaperDao() }
+
+ suspend fun saveWallpaper(wallpaperManager: WallpaperManager) {
+ runCatching {
+ val wallpaperDrawable = wallpaperManager.drawable as? BitmapDrawable
+ val currentBitmap = wallpaperDrawable?.bitmap ?: return
+ val byteArray = bitmapToByteArray(currentBitmap)
+ saveWallpaper(byteArray)
+ }.onFailure {
+ Log.e("WallpaperChange", "Error detecting wallpaper change: ${it.message}", it)
+ }
+ }
+
+ private fun calculateChecksum(imageData: ByteArray): String {
+ return MessageDigest.getInstance("MD5")
+ .digest(imageData)
+ .joinToString("") { "%02x".format(it) }
+ }
+
+ private suspend fun saveWallpaper(imageData: ByteArray) = withContext(Dispatchers.IO) {
+ rankMutex.withLock {
+ val timestamp = System.currentTimeMillis()
+ val checksum = calculateChecksum(imageData)
+
+ val existingWallpapers = dao.getTopWallpapers()
+
+ val matched = existingWallpapers.firstOrNull { it.checksum == checksum }
+ if (matched != null) {
+ Log.d("WallpaperService", "Wallpaper already exists with checksum: $checksum")
+ promoteToRank0(matched.id, timestamp)
+ return@withContext
+ }
+
+ if (existingWallpapers.size >= 4) {
+ val toRemove = existingWallpapers.minByOrNull { it.timestamp }
+ if (toRemove != null) {
+ dao.deleteWallpaper(toRemove.id)
+ deleteWallpaperFile(toRemove.imagePath)
+ }
+ }
+
+ val topWallpapers = dao.getTopWallpapers()
+
+ if (topWallpapers.any { it.rank == 0 }) {
+ dao.bumpAllRanks()
+ }
+
+ val imagePath = saveImageToAppStorage(imageData)
+ dao.insert(
+ Wallpaper(
+ imagePath = imagePath,
+ rank = 0,
+ timestamp = timestamp,
+ checksum = checksum
+ )
+ )
+ }
+ }
+
+ private suspend fun promoteToRank0(id: Long, timestamp: Long) {
+ roomDb.withTransaction {
+ dao.updateWallpaper(id, rank = 0, timestamp = timestamp)
+
+ val others = dao.getTopWallpapersExcluding(excludeId = id)
+ others.forEachIndexed { index, w ->
+ dao.setRankOnly(w.id, rank = index + 1)
+ }
+ }
+ }
+
+ suspend fun updateWallpaperRank(selectedWallpaper: Wallpaper) = withContext(Dispatchers.IO) {
+ rankMutex.withLock {
+ val now = System.currentTimeMillis()
+ promoteToRank0(selectedWallpaper.id, now)
+ }
+ }
+
+ suspend fun getTopWallpapers(): List = withContext(Dispatchers.IO) {
+ dao.getTopWallpapers()
+ }
+
+ fun getTopWallpapersBlocking(): List {
+ return runBlocking {
+ withContext(Dispatchers.IO) { dao.getTopWallpapers() }
+ }
+ }
+
+ private fun deleteWallpaperFile(imagePath: String) {
+ runCatching {
+ val file = File(imagePath)
+ if (file.exists()) file.delete()
+ }
+ }
+
+ private fun saveImageToAppStorage(imageData: ByteArray): String {
+ val storageDir = File(context.filesDir, "wallpapers")
+ if (!storageDir.exists()) storageDir.mkdirs()
+
+ val imageHash = imageData.hashCode().toString()
+ val imageFile = File(storageDir, "wallpaper_$imageHash.jpg")
+
+ if (!imageFile.exists()) {
+ runCatching {
+ FileOutputStream(imageFile).use { it.write(imageData) }
+ }.onFailure {
+ Log.e("WallpaperService", "Error saving image: ${it.message}", it)
+ }
+ }
+ return imageFile.absolutePath
+ }
+
+ override fun close() {
+ TODO("Not yet implemented")
+ }
+
+ private fun bitmapToByteArray(bitmap: Bitmap): ByteArray {
+ val stream = ByteArrayOutputStream()
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream)
+ return stream.toByteArray()
+ }
+
+ companion object {
+ @JvmField
+ val INSTANCE = DaggerSingletonObject(LauncherAppComponent::getWallpaperService)
+ }
+}
diff --git a/src/com/android/launcher3/deviceprofile/AllAppsProfile.kt b/src/com/android/launcher3/deviceprofile/AllAppsProfile.kt
index b69e35d9313..a04c42cd4f4 100644
--- a/src/com/android/launcher3/deviceprofile/AllAppsProfile.kt
+++ b/src/com/android/launcher3/deviceprofile/AllAppsProfile.kt
@@ -71,10 +71,12 @@ data class AllAppsProfile(
scale: Float,
iconSizePx: Int,
iconDrawablePaddingOriginalPx: Int,
+ allAppsCellHeightMultiplier: Float,
): AllAppsProfile {
val allAppsBorderSpacePx = calculateAllAppsBorderSpacePx(inv, metric, typeIndex, scale)
var allAppsCellHeightPx =
- (pxFromDp(inv.allAppsCellSize[typeIndex].y, metric) + allAppsBorderSpacePx.y)
+ ((pxFromDp(inv.allAppsCellSize[typeIndex].y, metric) + allAppsBorderSpacePx.y)
+ * allAppsCellHeightMultiplier).toInt()
var allAppsIconSizePx = pxFromDp(inv.allAppsIconSize[typeIndex], metric)
val allAppsIconTextSizePx =
pxFromSp(inv.allAppsIconTextSize[typeIndex], metric).toFloat()
@@ -133,6 +135,7 @@ data class AllAppsProfile(
metric: DisplayMetrics,
typeIndex: Int,
scale: Float,
+ allAppsCellHeightMultiplier: Float,
): AllAppsProfile {
val allAppsBorderSpacePx = calculateAllAppsBorderSpacePx(inv, metric, typeIndex, scale)
val allAppsIconSizePx = max(1, pxFromDp(inv.allAppsIconSize[typeIndex], metric, scale))
@@ -143,8 +146,8 @@ data class AllAppsProfile(
// AllApps cells don't have real space between cells,
// so we add the border space to the cell height
cellHeightPx =
- (pxFromDp(inv.allAppsCellSize.get(typeIndex).y, metric) +
- allAppsBorderSpacePx.y),
+ ((pxFromDp(inv.allAppsCellSize.get(typeIndex).y, metric) +
+ allAppsBorderSpacePx.y) * allAppsCellHeightMultiplier).toInt(),
iconSizePx = allAppsIconSizePx,
// We need the double conversion to keep the original behaviour
iconTextSizePx =
@@ -163,6 +166,7 @@ data class AllAppsProfile(
responsiveAllAppsHeightSpec: CalculatedResponsiveSpec,
iconSizeSteps: IconSizeSteps,
isVerticalBarLayout: Boolean,
+ allAppsCellHeightMultiplier: Float,
): AllAppsProfile {
var allAppsIconSizePx = responsiveAllAppsCellSpec.iconSize
var allAppsIconTextSizePx: Float = responsiveAllAppsCellSpec.iconTextSize.toFloat()
@@ -174,7 +178,8 @@ data class AllAppsProfile(
var maxAllAppsTextLineCount = responsiveAllAppsCellSpec.iconTextMaxLineCount
val allAppsBorderSpacePx =
Point(responsiveAllAppsWidthSpec.gutterPx, responsiveAllAppsHeightSpec.gutterPx)
- var allAppsCellHeightPx = responsiveAllAppsHeightSpec.cellSizePx
+ var allAppsCellHeightPx = (responsiveAllAppsHeightSpec.cellSizePx
+ * allAppsCellHeightMultiplier).toInt()
var allAppsCellWidthPx = responsiveAllAppsWidthSpec.cellSizePx
// Reduce the size of the app icon if it doesn't fit
@@ -247,6 +252,7 @@ data class AllAppsProfile(
scale: Float,
iconSizePx: Int,
iconDrawablePaddingOriginalPx: Int,
+ allAppsCellHeightMultiplier: Float,
) =
when {
isScalableGrid -> {
@@ -257,11 +263,12 @@ data class AllAppsProfile(
scale = scale,
iconSizePx = iconSizePx,
iconDrawablePaddingOriginalPx = iconDrawablePaddingOriginalPx,
+ allAppsCellHeightMultiplier = allAppsCellHeightMultiplier,
)
}
else -> {
- createAllAppsProfileNonScalable(res, inv, metric, typeIndex, scale)
+ createAllAppsProfileNonScalable(res, inv, metric, typeIndex, scale, allAppsCellHeightMultiplier)
}
}
}
diff --git a/src/com/android/launcher3/deviceprofile/HotseatProfile.kt b/src/com/android/launcher3/deviceprofile/HotseatProfile.kt
index 0aa574c0171..ce47c4db5ec 100644
--- a/src/com/android/launcher3/deviceprofile/HotseatProfile.kt
+++ b/src/com/android/launcher3/deviceprofile/HotseatProfile.kt
@@ -58,6 +58,7 @@ data class HotseatProfile(
isVerticalBarLayout: Boolean,
responsiveHotseatSpec: CalculatedHotseatSpec?,
workspacePageIndicatorHeight: Int,
+ isQsbVisible: Boolean,
): HotseatProfile {
val areNavButtonsInline = isTaskbarPresent && !deviceProperties.isGestureMode
var inlineNavButtonsEndSpacingPx = 0
@@ -88,8 +89,10 @@ data class HotseatProfile(
val hotseatBarWorkspaceSpacePx =
if (responsiveHotseatSpec != null) 0
else res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_side_padding)
- val hotseatQsbHeight = res.getDimensionPixelSize(R.dimen.qsb_widget_height)
- val hotseatQsbShadowHeight = res.getDimensionPixelSize(R.dimen.qsb_shadow_height)
+ val hotseatQsbHeight = if (isQsbVisible) res.getDimensionPixelSize(R.dimen.qsb_widget_height)
+ else 0
+ val hotseatQsbShadowHeight = if (isQsbVisible) res.getDimensionPixelSize(R.dimen.qsb_shadow_height)
+ else 0
return HotseatProfile(
areNavButtonsInline = areNavButtonsInline,
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index d32bbe3626e..0596a5d6715 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -287,8 +287,11 @@ public void animateViewIntoPosition(DragView dragView, final View child, int dur
// In most cases this additional scale factor should be a no-op (1). It mainly accounts
// for alternate grids where the source and destination icon sizes are different
- toScale *= ((1f * destRect.width())
- / (dragView.getMeasuredWidth() - dragView.getBlurSizeOutline()));
+ float scaleFactor = (1f * destRect.width())
+ / (dragView.getMeasuredWidth() - dragView.getBlurSizeOutline());
+ if (!Float.isNaN(scaleFactor) && !Float.isInfinite(scaleFactor)) {
+ toScale *= scaleFactor;
+ }
// This accounts for the offset of the DragView created by scaling it about its
// center as it animates into place.
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 8a76f8532ec..f2b1bffaaab 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -257,7 +257,7 @@ public void setItemInfo(final ItemInfo info) {
Pair fullDrawable = Utilities.getFullDrawable(
mActivity, info, w, h,
themeManager.isIconThemeEnabled());
- if (fullDrawable != null) {
+ if (fullDrawable != null && fullDrawable.first != null) {
AdaptiveIconDrawable adaptiveIcon = fullDrawable.first;
int blurMargin = (int) mActivity.getResources()
.getDimension(R.dimen.blur_size_medium_outline) / 2;
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 46db72d96c7..0d335dee09a 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -54,6 +54,7 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.OnAlarmListener;
import com.android.launcher3.R;
@@ -210,6 +211,13 @@ public static FolderIcon inflateIcon(int resId, ActivityContext activity,
icon.mFolderName = icon.findViewById(R.id.folder_icon_name);
if (icon.mFolderName.shouldShowLabel()) {
icon.mFolderName.applyLabel(folderInfo.title);
+ if (group != null && LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE.get(group.getContext())) {
+ icon.mFolderName.setSingleLine(false);
+ icon.mFolderName.setMaxLines(2);
+ } else {
+ icon.mFolderName.setSingleLine(true);
+ icon.mFolderName.setMaxLines(1);
+ }
}
icon.mFolderName.setCompoundDrawablePadding(0);
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) icon.mFolderName.getLayoutParams();
@@ -628,13 +636,14 @@ public void drawDot(Canvas canvas) {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ boolean shouldShowLabel = mFolderName.shouldShowLabel();
boolean shouldCenterIcon = mActivity.getDeviceProfile().getWorkspaceIconProfile()
.getIconCenterVertically();
- if (shouldCenterIcon) {
+ if (shouldCenterIcon || !shouldShowLabel) {
int iconSize = mActivity.getDeviceProfile().getWorkspaceIconProfile().getIconSizePx();
Paint.FontMetrics fm = mFolderName.getPaint().getFontMetrics();
- int cellHeightPx = iconSize + mFolderName.getCompoundDrawablePadding()
- + (int) Math.ceil(fm.bottom - fm.top);
+ int textHeight = shouldShowLabel ? (int) Math.ceil(fm.bottom - fm.top) : 0;
+ int cellHeightPx = iconSize + mFolderName.getCompoundDrawablePadding() + textHeight;
setPadding(getPaddingLeft(), (MeasureSpec.getSize(heightMeasureSpec)
- cellHeightPx) / 2, getPaddingRight(), getPaddingBottom());
}
diff --git a/src/com/android/launcher3/graphics/DrawableFactory.java b/src/com/android/launcher3/graphics/DrawableFactory.java
new file mode 100644
index 00000000000..5e515417a5c
--- /dev/null
+++ b/src/com/android/launcher3/graphics/DrawableFactory.java
@@ -0,0 +1,114 @@
+/*
+ * 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.graphics;
+
+//import static com.android.launcher3.graphics.IconShape.getShapePath;
+import static com.android.launcher3.util.override.MainThreadInitializedObject.forOverride;
+
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Process;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+
+import androidx.annotation.UiThread;
+
+import com.android.launcher3.icons.FastBitmapDrawable;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.R;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.util.SafeCloseable;
+import com.android.launcher3.util.override.ResourceBasedOverride;
+import com.android.launcher3.util.override.MainThreadInitializedObject;
+
+/**
+ * Factory for creating new drawables.
+ */
+public class DrawableFactory implements ResourceBasedOverride, SafeCloseable {
+
+ public static final MainThreadInitializedObject INSTANCE =
+ forOverride(DrawableFactory.class, R.string.drawable_factory_class);
+
+ protected final UserHandle mMyUser = Process.myUserHandle();
+ protected final ArrayMap mUserBadges = new ArrayMap<>();
+
+ /**
+ * Returns a FastBitmapDrawable with the icon.
+ */
+ public FastBitmapDrawable newIcon(Context context, ItemInfoWithIcon info) {
+ FastBitmapDrawable drawable = new FastBitmapDrawable(info.bitmap);
+ drawable.setDisabled(info.isDisabled());
+ return drawable;
+ }
+
+ public FastBitmapDrawable newIcon(Context context, BitmapInfo info, ActivityInfo target) {
+ return new FastBitmapDrawable(info);
+ }
+
+ /**
+ * Returns a drawable that can be used as a badge for the user or null.
+ */
+ @UiThread
+ public Drawable getBadgeForUser(UserHandle user, Context context, int badgeSize) {
+ if (mMyUser.equals(user)) {
+ return null;
+ }
+
+ Bitmap badgeBitmap = getUserBadge(user, context, badgeSize);
+ FastBitmapDrawable d = new FastBitmapDrawable(badgeBitmap);
+ d.setFilterBitmap(true);
+ d.setBounds(0, 0, badgeBitmap.getWidth(), badgeBitmap.getHeight());
+ return d;
+ }
+
+ @Override
+ public void close() {
+ }
+
+ protected synchronized Bitmap getUserBadge(UserHandle user, Context context, int badgeSize) {
+ Bitmap badgeBitmap = mUserBadges.get(user);
+ if (badgeBitmap != null) {
+ return badgeBitmap;
+ }
+
+ final Resources res = context.getApplicationContext().getResources();
+ badgeBitmap = Bitmap.createBitmap(badgeSize, badgeSize, Bitmap.Config.ARGB_8888);
+
+ Drawable drawable = context.getPackageManager().getUserBadgedDrawableForDensity(
+ new BitmapDrawable(res, badgeBitmap), user, new Rect(0, 0, badgeSize, badgeSize),
+ 0);
+ if (drawable instanceof BitmapDrawable) {
+ badgeBitmap = ((BitmapDrawable) drawable).getBitmap();
+ } else {
+ badgeBitmap.eraseColor(Color.TRANSPARENT);
+ Canvas c = new Canvas(badgeBitmap);
+ drawable.setBounds(0, 0, badgeSize, badgeSize);
+ drawable.draw(c);
+ c.setBitmap(null);
+ }
+
+ mUserBadges.put(user, badgeBitmap);
+ return badgeBitmap;
+ }
+}
diff --git a/src/com/android/launcher3/graphics/SysUiScrim.java b/src/com/android/launcher3/graphics/SysUiScrim.java
index a9d4010bb1a..cdc12311d2b 100644
--- a/src/com/android/launcher3/graphics/SysUiScrim.java
+++ b/src/com/android/launcher3/graphics/SysUiScrim.java
@@ -19,6 +19,7 @@
import static android.graphics.Paint.FILTER_BITMAP_FLAG;
import android.animation.ObjectAnimator;
+import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
@@ -33,6 +34,7 @@
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.statemanager.StatefulContainer;
@@ -45,7 +47,8 @@
/**
* View scrim which draws behind hotseat and workspace
*/
-public class SysUiScrim implements View.OnAttachStateChangeListener {
+public class SysUiScrim implements View.OnAttachStateChangeListener,
+ SharedPreferences.OnSharedPreferenceChangeListener {
/**
* Receiver used to get a signal that the user unlocked their device.
@@ -76,17 +79,21 @@ public void onUserPresent() {
private final RectF mTopMaskRect = new RectF();
private final Paint mTopMaskPaint = new Paint(FILTER_BITMAP_FLAG | DITHER_FLAG);
- private final Bitmap mTopMaskBitmap;
+ private Bitmap mTopMaskBitmap;
private final int mTopMaskHeight;
private final RectF mBottomMaskRect = new RectF();
private final Paint mBottomMaskPaint = new Paint(FILTER_BITMAP_FLAG | DITHER_FLAG);
- private final Bitmap mBottomMaskBitmap;
+ private Bitmap mBottomMaskBitmap;
private final int mBottomMaskHeight;
private final View mRoot;
private final StatefulContainer mContainer;
- private final boolean mHideSysUiScrim;
+ private final SharedPreferences mPrefs;
+ private boolean mAttachListenerAdded;
+ private ObjectAnimator mRunningAnimator;
+
+ private boolean mShowSysUiScrim;
private boolean mSkipScrimAnimationForTest = false;
private boolean mAnimateScrimOnNextDraw = false;
@@ -100,48 +107,40 @@ public SysUiScrim(View view) {
mTopMaskHeight = ResourceUtils.pxFromDp(TOP_MASK_HEIGHT_DP, dm);
mBottomMaskHeight = ResourceUtils.pxFromDp(BOTTOM_MASK_HEIGHT_DP, dm);
- mHideSysUiScrim = Themes.getAttrBoolean(view.getContext(), R.attr.isWorkspaceDarkText);
-
- mTopMaskBitmap = mHideSysUiScrim ? null : createDitheredAlphaMask(mTopMaskHeight,
- new int[]{0x3DFFFFFF, 0x0AFFFFFF, 0x00FFFFFF},
- new float[]{0f, 0.7f, 1f});
- mTopMaskPaint.setColor(0xFF222222);
- mBottomMaskBitmap = mHideSysUiScrim ? null : createDitheredAlphaMask(mBottomMaskHeight,
- new int[]{0x00FFFFFF, 0x2FFFFFFF},
- new float[]{0f, 1f});
+ mShowSysUiScrim = LauncherPrefs.SHOW_TOP_SHADOW.get(view.getContext());
+ createMaskBitmaps();
- if (!mHideSysUiScrim) {
+ if (mShowSysUiScrim) {
view.addOnAttachStateChangeListener(this);
+ mAttachListenerAdded = true;
}
+ mPrefs = LauncherPrefs.getPrefs(view.getContext());
+ mPrefs.registerOnSharedPreferenceChangeListener(this);
}
/**
* Draw the top and bottom scrims
*/
public void draw(Canvas canvas) {
- if (!mHideSysUiScrim) {
+ if (mShowSysUiScrim) {
if (mSysUiProgress.value <= 0) {
mAnimateScrimOnNextDraw = false;
return;
}
-
if (mAnimateScrimOnNextDraw) {
mSysUiAnimMultiplier.value = 0;
reapplySysUiAlphaNoInvalidate();
- ObjectAnimator oa = mSysUiAnimMultiplier.animateToValue(1);
- oa.setDuration(600);
- oa.setStartDelay(mContainer.getWindow().getTransitionBackgroundFadeDuration());
- oa.start();
+ if (mRunningAnimator != null) mRunningAnimator.cancel();
+ mRunningAnimator = mSysUiAnimMultiplier.animateToValue(1);
+ mRunningAnimator.setDuration(600);
+ mRunningAnimator.setStartDelay(
+ mContainer.getWindow().getTransitionBackgroundFadeDuration());
+ mRunningAnimator.start();
mAnimateScrimOnNextDraw = false;
}
-
- if (mDrawTopScrim) {
- canvas.drawBitmap(mTopMaskBitmap, null, mTopMaskRect, mTopMaskPaint);
- }
- if (mDrawBottomScrim) {
- canvas.drawBitmap(mBottomMaskBitmap, null, mBottomMaskRect, mBottomMaskPaint);
- }
+ if (mDrawTopScrim) canvas.drawBitmap(mTopMaskBitmap, null, mTopMaskRect, mTopMaskPaint);
+ if (mDrawBottomScrim) canvas.drawBitmap(mBottomMaskBitmap, null, mBottomMaskRect, mBottomMaskPaint);
}
}
@@ -162,14 +161,13 @@ public AnimatedFloat getSysUIProgress() {
/**
* Determines whether to draw the top and/or bottom scrim based on new insets.
*
- * In order for the bottom scrim to be drawn this 3 condition should be meet at the same time:
- * the device is in 3 button navigation, the taskbar is not present and the Hotseat is
- * horizontal
+ * In order for the bottom scrim to be drawn these 2 conditions should be met:
+ * the taskbar is not present and the Hotseat is horizontal
*/
public void onInsetsChanged(Rect insets) {
DeviceProfile dp = mContainer.getDeviceProfile();
mDrawTopScrim = insets.top > 0;
- mDrawBottomScrim = !dp.isVerticalBarLayout() && !dp.getDeviceProperties().isGestureMode() && !dp.isTaskbarPresent;
+ mDrawBottomScrim = !dp.isVerticalBarLayout() && !dp.isTaskbarPresent;
}
@Override
@@ -180,6 +178,51 @@ public void onViewAttachedToWindow(View view) {
@Override
public void onViewDetachedFromWindow(View view) {
ScreenOnTracker.INSTANCE.get(mContainer.asContext()).removeListener(mScreenOnListener);
+ if (mRunningAnimator != null) { mRunningAnimator.cancel(); mRunningAnimator = null; }
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+ if (LauncherPrefs.SHOW_TOP_SHADOW.getSharedPrefKey().equals(key)) {
+ boolean newVal = prefs.getBoolean(key, true);
+ if (newVal != mShowSysUiScrim) {
+ mShowSysUiScrim = newVal;
+
+ if (mShowSysUiScrim && !mAttachListenerAdded) {
+ mRoot.addOnAttachStateChangeListener(this);
+ mAttachListenerAdded = true;
+ } else if (!mShowSysUiScrim && mAttachListenerAdded) {
+ if (mRoot.isAttachedToWindow()) {
+ ScreenOnTracker.INSTANCE.get(mContainer.asContext())
+ .removeListener(mScreenOnListener);
+ }
+ mRoot.removeOnAttachStateChangeListener(this);
+ mAttachListenerAdded = false;
+ }
+ }
+ createMaskBitmaps();
+ mRoot.invalidate();
+ }
+ }
+
+ public void release() {
+ if (mAttachListenerAdded) {
+ mRoot.removeOnAttachStateChangeListener(this);
+ mAttachListenerAdded = false;
+ }
+ mPrefs.unregisterOnSharedPreferenceChangeListener(this);
+ mTopMaskBitmap = null;
+ mBottomMaskBitmap = null;
+ }
+
+ private void createMaskBitmaps() {
+ mTopMaskBitmap = mShowSysUiScrim ? createDitheredAlphaMask(mTopMaskHeight,
+ new int[]{0x3DFFFFFF, 0x0AFFFFFF, 0x00FFFFFF},
+ new float[]{0f, 0.7f, 1f}) : null;
+ mTopMaskPaint.setColor(0xFF222222);
+ mBottomMaskBitmap = mShowSysUiScrim ? createDitheredAlphaMask(mBottomMaskHeight,
+ new int[]{0x00FFFFFF, 0x2FFFFFFF},
+ new float[]{0f, 1f}) : null;
}
/**
@@ -201,7 +244,7 @@ public void skipScrimAnimation() {
private void reapplySysUiAlpha() {
reapplySysUiAlphaNoInvalidate();
- if (!mHideSysUiScrim) {
+ if (mShowSysUiScrim) {
mRoot.invalidate();
}
}
diff --git a/src/com/android/launcher3/graphics/ThemeManager.kt b/src/com/android/launcher3/graphics/ThemeManager.kt
index 6aff7b17a1f..c99304de435 100644
--- a/src/com/android/launcher3/graphics/ThemeManager.kt
+++ b/src/com/android/launcher3/graphics/ThemeManager.kt
@@ -18,6 +18,9 @@ package com.android.launcher3.graphics
import android.content.Context
import android.content.res.Resources
+import android.database.ContentObserver
+import android.net.Uri
+import android.provider.Settings
import com.android.launcher3.LauncherPrefChangeListener
import com.android.launcher3.LauncherPrefs
import com.android.launcher3.LauncherPrefs.Companion.backedUpItem
@@ -96,12 +99,21 @@ constructor(
lifecycle.addCloseable(receiver)
val prefListener = LauncherPrefChangeListener {
- if (it == PREF_ICON_SHAPE.sharedPrefKey) verifyIconState()
+ if (it == PREF_ICON_SHAPE.sharedPrefKey
+ || it == THEMED_ICON_STYLE.sharedPrefKey) {
+ verifyIconState()
+ }
}
- prefs.addListener(prefListener, PREF_ICON_SHAPE)
+ prefs.addListener(prefListener, PREF_ICON_SHAPE, THEMED_ICON_STYLE)
+
+ val settingsObserver = SettingsObserver()
+ settingsObserver.register()
+ syncSettingsToPrefs()
+
lifecycle.addCloseable(themePreference.forEach(mainExecutor) { verifyIconState() })
lifecycle.addCloseable {
- prefs.removeListener(prefListener, PREF_ICON_SHAPE)
+ prefs.removeListener(prefListener, PREF_ICON_SHAPE, THEMED_ICON_STYLE)
+ settingsObserver.close()
iconState.closeController()
}
}
@@ -171,6 +183,10 @@ constructor(
themeKey?.run { iconThemeFactories[factoryId]?.createController(themeId) }
}
+ val themedIconStyle = prefs.get(THEMED_ICON_STYLE)
+ val themedIconPack = Settings.Secure.getString(
+ context.contentResolver, KEY_THEMED_ICON_PACK) ?: ""
+
return IconState(
iconMask = iconMask,
folderRadius = folderRadius,
@@ -179,6 +195,8 @@ constructor(
folderShape = folderShape,
shapeRadius = shapeModel?.shapeRadius ?: DEFAULT_ICON_RADIUS,
themeCode = themeCode,
+ themedIconStyle = themedIconStyle,
+ themedIconPack = themedIconPack,
)
}
@@ -192,8 +210,10 @@ constructor(
val isCircle: Boolean = iconShape is ShapeDelegate.Circle,
val folderShape: ShapeDelegate,
val shapeRadius: Float,
+ val themedIconStyle: String = "axion",
+ val themedIconPack: String = "",
) {
- fun toUniqueId() = "$themeCode,$isCircle"
+ fun toUniqueId() = "$themeCode,$isCircle,$themedIconStyle,$themedIconPack"
val iconShapeInfo = IconShapeInfo.fromPath(iconShape.getPath(), DEFAULT_PATH_SIZE_INT)
val folderShapeInfo = IconShapeInfo.fromPath(folderShape.getPath(), DEFAULT_PATH_SIZE_INT)
@@ -205,6 +225,41 @@ constructor(
}
}
+ private fun syncSettingsToPrefs() {
+ val contentResolver = context.contentResolver
+ val themedIconStyle = Settings.Secure.getString(
+ contentResolver, KEY_THEMED_ICON_STYLE) ?: "axion"
+ if (prefs.get(THEMED_ICON_STYLE) != themedIconStyle) {
+ prefs.put(THEMED_ICON_STYLE, themedIconStyle)
+ }
+
+ val enabled = Settings.Secure.getInt(contentResolver, KEY_THEMED_ICONS, 0) == 1
+ if (enabled != isMonoThemeEnabled) {
+ isMonoThemeEnabled = enabled
+ }
+ }
+
+ private inner class SettingsObserver : ContentObserver(uiExecutor.handler), AutoCloseable {
+ fun register() {
+ context.contentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(KEY_THEMED_ICON_STYLE), false, this)
+ context.contentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(KEY_THEMED_ICONS), false, this)
+ context.contentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(KEY_THEMED_ICON_PACK), false, this)
+ }
+
+ override fun onChange(selfChange: Boolean, uri: Uri?) {
+ super.onChange(selfChange, uri)
+ syncSettingsToPrefs()
+ verifyIconState()
+ }
+
+ override fun close() {
+ context.contentResolver.unregisterContentObserver(this)
+ }
+ }
+
/** Interface for receiving theme change events */
fun interface ThemeChangeListener {
fun onThemeChanged()
@@ -214,10 +269,14 @@ constructor(
@JvmField val INSTANCE = DaggerSingletonObject(LauncherAppComponent::getThemeManager)
@JvmField val PREF_ICON_SHAPE = backedUpItem("icon_shape_model", "")
+ @JvmField val THEMED_ICON_STYLE = backedUpItem(KEY_THEMED_ICON_STYLE, "axion")
@JvmField val DEFAULT_SHAPE_DELEGATE = pickBestShape(shapeStr = "")
private const val ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED"
+ private const val KEY_THEMED_ICONS = "themed_icons"
+ private const val KEY_THEMED_ICON_STYLE = "themed_icon_style"
+ private const val KEY_THEMED_ICON_PACK = "themed_icon_pack"
private val CONFIG_ICON_MASK_RES_ID: Int =
Resources.getSystem().getIdentifier("config_icon_mask", "string", "android")
diff --git a/src/com/android/launcher3/icons/AxIconsHelper.java b/src/com/android/launcher3/icons/AxIconsHelper.java
new file mode 100644
index 00000000000..cab81b86ba4
--- /dev/null
+++ b/src/com/android/launcher3/icons/AxIconsHelper.java
@@ -0,0 +1,161 @@
+/*
+ * SPDX-FileCopyrightText: 2026 AlphaDroid
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package com.android.launcher3.icons;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+
+import org.json.JSONObject;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * Helper that bridges AxThemePicker's icon pack selection (stored in
+ * Settings.Secure "theme_engine_data") with Launcher3's icon loading.
+ */
+public class AxIconsHelper {
+
+ private static final String TAG = "AxIconsHelper";
+ private static final String SETTINGS_THEME_ENGINE_DATA = "theme_engine_data";
+ private static final String CATEGORY_ICON_PACK = "icon_pack";
+ private static final String THEMED_ICON_STYLE_SETTING = "themed_icon_style";
+ private static final String STYLE_AOSP = "aosp";
+
+ /**
+ * Returns true if the "axion" themed icon style is active (neutral palette).
+ * Returns false if "aosp" style is selected (accent palette).
+ */
+ public static boolean isAxIconsEnabled(Context context) {
+ if (context == null) return true;
+ String style = Settings.Secure.getString(
+ context.getContentResolver(), THEMED_ICON_STYLE_SETTING);
+ return !STYLE_AOSP.equals(style);
+ }
+
+ /**
+ * Returns the currently active icon pack package from theme_engine_data,
+ * or null if none is set.
+ */
+ public static String getActiveIconPackPackage(Context context) {
+ try {
+ String json = Settings.Secure.getString(
+ context.getContentResolver(), SETTINGS_THEME_ENGINE_DATA);
+ if (TextUtils.isEmpty(json)) return null;
+
+ JSONObject config = new JSONObject(json);
+ JSONObject themes = config.optJSONObject("themes");
+ if (themes == null) return null;
+
+ JSONObject iconPack = themes.optJSONObject(CATEGORY_ICON_PACK);
+ if (iconPack == null) return null;
+
+ if (!iconPack.optBoolean("enabled", false)) return null;
+
+ String pkg = iconPack.optString("packageName", "");
+ return TextUtils.isEmpty(pkg) || "null".equals(pkg) ? null : pkg;
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to read theme_engine_data", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns true if an icon pack is actively set via theme_engine_data.
+ */
+ public static boolean hasActiveIconPack(Context context) {
+ return getActiveIconPackPackage(context) != null;
+ }
+
+ /**
+ * Writes the icon pack selection to theme_engine_data so AxThemePicker stays in sync.
+ */
+ public static void setActiveIconPackPackage(Context context, String packageName) {
+ try {
+ String json = Settings.Secure.getString(
+ context.getContentResolver(), SETTINGS_THEME_ENGINE_DATA);
+ JSONObject config = TextUtils.isEmpty(json) ? new JSONObject() : new JSONObject(json);
+
+ JSONObject themes = config.optJSONObject("themes");
+ if (themes == null) themes = new JSONObject();
+
+ JSONObject iconPackConfig = new JSONObject();
+ if (TextUtils.isEmpty(packageName)) {
+ iconPackConfig.put("enabled", false);
+ iconPackConfig.put("packageName", JSONObject.NULL);
+ } else {
+ iconPackConfig.put("enabled", true);
+ iconPackConfig.put("packageName", packageName);
+ }
+ themes.put(CATEGORY_ICON_PACK, iconPackConfig);
+ config.put("themes", themes);
+
+ if (!config.has("version")) config.put("version", 1);
+
+ Settings.Secure.putString(
+ context.getContentResolver(), SETTINGS_THEME_ENGINE_DATA, config.toString());
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to write theme_engine_data", e);
+ }
+ }
+
+ /**
+ * Attempts to load an icon for the given component from the active icon pack.
+ * Returns null if no icon pack is active or the pack doesn't have an icon for this component.
+ */
+ public static Drawable loadIconPackDrawable(Context context, ComponentName cn, int iconDpi) {
+ String packPackage = getActiveIconPackPackage(context);
+ if (packPackage == null) return null;
+
+ try {
+ PackageManager pm = context.getPackageManager();
+ Resources packRes = pm.getResourcesForApplication(packPackage);
+
+ int filterResId = packRes.getIdentifier("appfilter", "xml", packPackage);
+ if (filterResId == 0) return null;
+
+ String targetComponent = "ComponentInfo{" + cn.flattenToString() + "}";
+ String drawableName = null;
+
+ XmlResourceParser parser = packRes.getXml(filterResId);
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (type != XmlPullParser.START_TAG) continue;
+ if (!"item".equals(parser.getName())) continue;
+
+ String component = parser.getAttributeValue(null, "component");
+ if (targetComponent.equals(component)) {
+ drawableName = parser.getAttributeValue(null, "drawable");
+ break;
+ }
+ }
+ parser.close();
+
+ if (drawableName == null) return null;
+
+ int drawableId = packRes.getIdentifier(drawableName, "drawable", packPackage);
+ if (drawableId == 0) return null;
+
+ return packRes.getDrawableForDensity(drawableId, iconDpi, null);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Icon pack not found: " + packPackage);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to load icon from pack: " + packPackage, e);
+ }
+ return null;
+ }
+
+ /**
+ * Returns true if the given drawable was loaded from an icon pack.
+ */
+ public static boolean isIconPackDrawable(Drawable drawable) {
+ return drawable != null && drawable.getClass().getName().contains("ResourceDrawable");
+ }
+}
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 1f8649be065..cc5c1e7b517 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -71,12 +71,17 @@
import com.android.launcher3.util.ApplicationInfoWrapper;
import com.android.launcher3.util.CancellableTask;
import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.CustomAppNameStore;
import com.android.launcher3.util.DaggerSingletonTracker;
+import com.android.launcher3.util.FlagOp;
import com.android.launcher3.util.InstantAppResolver;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.widget.WidgetSections;
import com.android.launcher3.widget.WidgetSections.WidgetSection;
+import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.TaskSchedule;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -101,6 +106,7 @@ public class IconCache extends BaseIconCache {
private static final String TAG = "Launcher.IconCache";
private final LauncherApps mLauncherApps;
+ private final Context mContext;
private final UserCache mUserManager;
private final InstallSessionHelper mInstallSessionHelper;
private final InstantAppResolver mInstantAppResolver;
@@ -117,13 +123,14 @@ public IconCache(
InvariantDeviceProfile idp,
@Nullable @Named("ICONS_DB") String dbFileName,
UserCache userCache,
- LauncherIconProvider iconProvider,
+ ThirdPartyIconProvider iconProvider,
InstallSessionHelper installSessionHelper,
LauncherIcons.IconPool iconPool,
InstantAppResolver instantAppResolver,
DaggerSingletonTracker lifecycle) {
super(context, dbFileName, MODEL_EXECUTOR.getLooper(),
idp.fillResIconDpi, idp.iconBitmapSize, true /* inMemoryCache */, iconProvider);
+ mContext = context;
mLauncherApps = context.getSystemService(LauncherApps.class);
mUserManager = userCache;
mInstallSessionHelper = installSessionHelper;
@@ -510,6 +517,7 @@ private void loadIconSubsection(
}
Trace.beginSection("loadIconSubsectionWithFallback");
+ ArrayList tasksList = new ArrayList<>();
// Fallback title and icon loading
for (ComponentName cn : duplicateIconRequestsMap.keySet()) {
IconRequestInfo iconRequestInfo = duplicateIconRequestsMap.get(cn).get(0);
@@ -534,30 +542,35 @@ private void loadIconSubsection(
entry.bitmap = icon;
}
entry.contentDescription = itemInfo.contentDescription;
-
- if (loadFallbackIcon) {
- loadFallbackIcon(
- lai,
- entry,
- LauncherActivityCachingLogic.INSTANCE,
- DEFAULT_LOOKUP_FLAG.withUsePackageIcon(false),
- /* usePackageTitle= */ loadFallbackTitle,
- cn,
- sectionKey.first);
- }
- if (loadFallbackTitle && TextUtils.isEmpty(entry.title) && lai != null) {
- loadFallbackTitle(
- lai,
- entry,
- LauncherActivityCachingLogic.INSTANCE,
- sectionKey.first);
- }
-
- for (IconRequestInfo iconRequest : duplicateIconRequestsMap.get(cn)) {
- applyCacheEntry(entry, iconRequest.itemInfo);
- }
+ TaskSchedule.SafelyRunnable runnable = new TaskSchedule.SafelyRunnable() {
+ @Override
+ public void onTaskRun() {
+ if(loadFallbackIcon){
+ loadFallbackIcon(
+ lai,
+ entry,
+ LauncherActivityCachingLogic.INSTANCE,
+ DEFAULT_LOOKUP_FLAG.withUsePackageIcon(false),
+ /* usePackageTitle= */ loadFallbackTitle,
+ cn,
+ sectionKey.first);
+ }
+ if(loadFallbackTitle &&TextUtils.isEmpty(entry.title)&&lai !=null){
+ loadFallbackTitle(
+ lai,
+ entry,
+ LauncherActivityCachingLogic.INSTANCE,
+ sectionKey.first);
+ }
+ for(IconRequestInfo iconRequest :duplicateIconRequestsMap.get(cn)){
+ applyCacheEntry(entry, iconRequest.itemInfo);
+ }
+ }
+ };
+ tasksList.add(runnable);
}
}
+ TaskSchedule.runTasks(tasksList, Executors.THREAD_POOL_EXECUTOR, Math.max(Runtime.getRuntime().availableProcessors() / 2, 2), 2000);
Trace.endSection();
}
@@ -601,12 +614,22 @@ private synchronized BitmapInfo getBadgedIcon(@Nullable final BitmapInfo bitmap,
if (bitmap == null) {
return getDefaultIcon(user);
}
- return bitmap.withFlags(getUserFlagOpLocked(user));
+ FlagOp userFlagOp = getUserFlagOpLocked(user);
+ int currentFlags = bitmap.getFlags();
+ // Skip creating a new BitMap since identical
+ if (currentFlags == userFlagOp.apply(currentFlags)) {
+ return bitmap;
+ }
+ return bitmap.withFlags(userFlagOp);
}
protected void applyCacheEntry(@NonNull final CacheEntry entry,
@NonNull final ItemInfoWithIcon info) {
info.title = Utilities.trim(entry.title);
+ String customTitle = CustomAppNameStore.getCustomName(mContext, info);
+ if (customTitle != null) {
+ info.title = customTitle;
+ }
info.contentDescription = entry.contentDescription;
info.bitmap = entry.bitmap;
// Clear any previously set appTitle, if the packageOverride is no longer valid
@@ -631,10 +654,19 @@ protected void applyCacheEntry(@NonNull final CacheEntry entry,
}
info.appTitle = Utilities.trim(info.title);
info.title = Utilities.trim(packageEntry.title);
+ customTitle = CustomAppNameStore.getCustomName(mContext, info);
+ if (customTitle != null) {
+ info.title = customTitle;
+ }
info.contentDescription = packageEntry.contentDescription;
info.bitmap = packageEntry.bitmap;
}
+ public synchronized void clearDb() {
+ clearMemoryCache();
+ iconDb.clear();
+ }
+
public void updateSessionCache(PackageUserKey key, PackageInstaller.SessionInfo info) {
cachePackageInstallInfo(key.mPackageName, key.mUser, info.getAppIcon(),
info.getAppLabel());
diff --git a/src/com/android/launcher3/icons/LauncherIconProvider.java b/src/com/android/launcher3/icons/LauncherIconProvider.java
index bd901ecfb15..cb263074a62 100644
--- a/src/com/android/launcher3/icons/LauncherIconProvider.java
+++ b/src/com/android/launcher3/icons/LauncherIconProvider.java
@@ -15,9 +15,14 @@
*/
package com.android.launcher3.icons;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ComponentInfo;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
@@ -66,10 +71,26 @@ protected ThemeData getThemeDataForPackage(String packageName) {
return getThemedIconMap().get(packageName);
}
+ @Override
+ public Drawable getIcon(ComponentInfo info, int iconDpi) {
+ if (info instanceof ActivityInfo) {
+ ActivityInfo activityInfo = (ActivityInfo) info;
+ ComponentName cn = new ComponentName(activityInfo.packageName, activityInfo.name);
+ Drawable iconPackIcon = AxIconsHelper.loadIconPackDrawable(mContext, cn, iconDpi);
+ if (iconPackIcon != null) {
+ return iconPackIcon;
+ }
+ }
+ return super.getIcon(info, iconDpi);
+ }
+
@Override
public void updateSystemState() {
super.updateSystemState();
- mSystemState += "," + mThemeManager.getIconState().toUniqueId();
+ String iconPack = AxIconsHelper.getActiveIconPackPackage(mContext);
+ mSystemState += "," + mThemeManager.getIconState().toUniqueId()
+ + Build.VERSION.INCREMENTAL
+ + "," + (iconPack != null ? iconPack : "") + ",icon-pack-shape-v2";
}
private Map getThemedIconMap() {
diff --git a/src/com/android/launcher3/icons/ThirdPartyDrawableFactory.java b/src/com/android/launcher3/icons/ThirdPartyDrawableFactory.java
new file mode 100644
index 00000000000..718df2e9ec0
--- /dev/null
+++ b/src/com/android/launcher3/icons/ThirdPartyDrawableFactory.java
@@ -0,0 +1,60 @@
+package com.android.launcher3.icons;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+
+import com.android.launcher3.graphics.DrawableFactory;
+import com.android.launcher3.icons.FastBitmapDrawable;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.util.ComponentKey;
+
+import com.android.launcher3.icons.calendar.DateChangeReceiver;
+import com.android.launcher3.icons.calendar.DynamicCalendar;
+import com.android.launcher3.icons.clock.CustomClock;
+import com.android.launcher3.icons.clock.DynamicClock;
+import com.android.launcher3.icons.pack.IconPackManager;
+import com.android.launcher3.icons.pack.IconResolver;
+
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+
+public class ThirdPartyDrawableFactory extends DrawableFactory {
+ private final IconPackManager mManager;
+ private final DynamicClock mDynamicClockDrawer;
+ private final CustomClock mCustomClockDrawer;
+ private final DateChangeReceiver mCalendars;
+
+ public ThirdPartyDrawableFactory(Context context) {
+ mManager = IconPackManager.get(context);
+ mDynamicClockDrawer = new DynamicClock(context);
+ mCustomClockDrawer = new CustomClock(context);
+ mCalendars = new DateChangeReceiver(context);
+ }
+
+ @Override
+ public FastBitmapDrawable newIcon(Context context, ItemInfoWithIcon info) {
+ if (info != null && info.getTargetComponent() != null
+ && info.itemType == ITEM_TYPE_APPLICATION) {
+ ComponentKey key = new ComponentKey(info.getTargetComponent(), info.user);
+
+ IconResolver resolver = mManager.resolve(key);
+ mCalendars.setIsDynamic(key, (resolver != null && resolver.isCalendar())
+ || info.getTargetComponent().getPackageName().equals(DynamicCalendar.CALENDAR));
+
+ if (resolver != null) {
+ if (resolver.isClock()) {
+ Drawable drawable = resolver.getIcon(0, () -> null);
+ if (drawable != null) {
+ FastBitmapDrawable fb = mCustomClockDrawer.drawIcon(
+ info, drawable, resolver.clockData());
+ fb.setDisabled(info.isDisabled());
+ return fb;
+ }
+ }
+ } else if (info.getTargetComponent().equals(DynamicClock.DESK_CLOCK)) {
+ return mDynamicClockDrawer.drawIcon(info);
+ }
+ }
+
+ return super.newIcon(context, info);
+ }
+}
diff --git a/src/com/android/launcher3/icons/ThirdPartyIconProvider.java b/src/com/android/launcher3/icons/ThirdPartyIconProvider.java
new file mode 100644
index 00000000000..a5105f2302e
--- /dev/null
+++ b/src/com/android/launcher3/icons/ThirdPartyIconProvider.java
@@ -0,0 +1,57 @@
+package com.android.launcher3.icons;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.pm.ComponentInfo;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppSingleton;
+import com.android.launcher3.graphics.ThemeManager;
+import com.android.launcher3.icons.pack.IconResolver;
+import com.android.launcher3.util.ComponentKey;
+
+import javax.inject.Inject;
+
+import static com.android.launcher3.icons.BaseIconFactory.CONFIG_HINT_NO_WRAP;
+
+@LauncherAppSingleton
+public class ThirdPartyIconProvider extends LauncherIconProvider {
+
+ private final Context mContext;
+ private final ThemeManager mThemeManager;
+
+ @Inject
+ public ThirdPartyIconProvider(
+ @ApplicationContext Context context,
+ ThemeManager themeManager) {
+ super(context, themeManager);
+ mContext = context;
+ mThemeManager = themeManager;
+ }
+
+ public ThirdPartyIconProvider(Context context) {
+ this(context, ThemeManager.INSTANCE.get(context));
+ }
+
+ @SuppressLint("WrongConstant")
+ @Override
+ public Drawable getIcon(ComponentInfo info, int iconDpi) {
+ ComponentKey key = new ComponentKey(
+ info.getComponentName(), UserHandle.getUserHandleForUid(info.applicationInfo.uid));
+
+ IconResolver.DefaultDrawableProvider fallback =
+ () -> super.getIcon(info, iconDpi);
+ Drawable icon = ThirdPartyIconUtils.getByKey(mContext, key, iconDpi, fallback);
+
+ if (icon == null) {
+ return fallback.get();
+ }
+ if (!(icon instanceof AdaptiveIconDrawable)) {
+ icon.setChangingConfigurations(icon.getChangingConfigurations() | CONFIG_HINT_NO_WRAP);
+ }
+ return icon;
+ }
+}
diff --git a/src/com/android/launcher3/icons/ThirdPartyIconUtils.java b/src/com/android/launcher3/icons/ThirdPartyIconUtils.java
new file mode 100644
index 00000000000..e591cafa677
--- /dev/null
+++ b/src/com/android/launcher3/icons/ThirdPartyIconUtils.java
@@ -0,0 +1,39 @@
+package com.android.launcher3.icons;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+
+import com.android.launcher3.icons.calendar.DynamicCalendar;
+import com.android.launcher3.icons.clock.CustomClock;
+import com.android.launcher3.icons.clock.DynamicClock;
+import com.android.launcher3.icons.pack.IconPackManager;
+import com.android.launcher3.icons.pack.IconResolver;
+import com.android.launcher3.util.ComponentKey;
+
+class ThirdPartyIconUtils {
+ static Drawable getByKey(Context context, ComponentKey key, int iconDpi,
+ IconResolver.DefaultDrawableProvider fallback) {
+ IconResolver resolver = IconPackManager.get(context).resolve(key);
+ Drawable icon = resolver == null
+ ? null
+ : resolver.getIcon(iconDpi, fallback);
+
+ // Icon pack clocks go first.
+ if (icon != null && resolver.isClock()) {
+ return CustomClock.getClock(context, icon, resolver.clockData());
+ }
+
+ // Google Clock goes second, but only if the icon pack does not override it.
+ if (icon == null && key.componentName.equals(DynamicClock.DESK_CLOCK)) {
+ String packageName = key.componentName.getPackageName();
+ return ClockDrawableWrapper.forPackage(context, packageName, iconDpi);
+ }
+
+ // Google Calendar is checked last. Only applied if the icon pack does not override it.
+ if (icon == null && key.componentName.getPackageName().equals(DynamicCalendar.CALENDAR)) {
+ return DynamicCalendar.load(context, key.componentName, iconDpi);
+ }
+
+ return icon;
+ }
+}
diff --git a/src/com/android/launcher3/icons/calendar/DateChangeReceiver.java b/src/com/android/launcher3/icons/calendar/DateChangeReceiver.java
new file mode 100644
index 00000000000..b3ed6aeb64a
--- /dev/null
+++ b/src/com/android/launcher3/icons/calendar/DateChangeReceiver.java
@@ -0,0 +1,47 @@
+package com.android.launcher3.icons.calendar;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.AppReloader;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Listens for date change events and uses the IconReloader to reload all loaded calendar icons
+ * when the date has changed.
+ */
+public class DateChangeReceiver extends BroadcastReceiver {
+ private final Set mDynamicCalendars = new HashSet<>();
+
+ public DateChangeReceiver(Context context) {
+ super();
+
+ IntentFilter filter = new IntentFilter(Intent.ACTION_DATE_CHANGED);
+ filter.addAction(Intent.ACTION_TIME_CHANGED);
+ filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+
+ Handler handler = MODEL_EXECUTOR.getHandler();
+ context.registerReceiver(this, filter, null, handler);
+ }
+
+ public void setIsDynamic(ComponentKey key, boolean calendar) {
+ if (calendar) {
+ mDynamicCalendars.add(key);
+ } else {
+ mDynamicCalendars.remove(key);
+ }
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ AppReloader.get(context).reload(mDynamicCalendars);
+ }
+}
diff --git a/src/com/android/launcher3/icons/calendar/DynamicCalendar.java b/src/com/android/launcher3/icons/calendar/DynamicCalendar.java
new file mode 100644
index 00000000000..a8cc84eafce
--- /dev/null
+++ b/src/com/android/launcher3/icons/calendar/DynamicCalendar.java
@@ -0,0 +1,51 @@
+package com.android.launcher3.icons.calendar;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+
+import java.util.Calendar;
+
+public class DynamicCalendar {
+ public static final String CALENDAR = "com.google.android.calendar";
+
+ public static Drawable load(Context context, ComponentName component, int iconDpi) {
+ try {
+ PackageManager pm = context.getPackageManager();
+ Bundle metaData = pm.getActivityInfo(component,
+ PackageManager.GET_META_DATA | PackageManager.GET_UNINSTALLED_PACKAGES).metaData;
+
+ Resources resourcesForApplication = pm.getResourcesForApplication(DynamicCalendar.CALENDAR);
+ int dayResId = DynamicCalendar.getDayResId(metaData, resourcesForApplication);
+ if (dayResId != 0) {
+ return resourcesForApplication.getDrawableForDensity(dayResId, iconDpi);
+ }
+ } catch (PackageManager.NameNotFoundException ignored) {
+ }
+ return null;
+ }
+
+ public static int getDayResId(Bundle bundle, Resources resources) {
+ if (bundle != null) {
+ int dateArrayId = bundle.getInt(CALENDAR + ".dynamic_icons_nexus_round", 0);
+ if (dateArrayId != 0) {
+ try {
+ TypedArray dateIds = resources.obtainTypedArray(dateArrayId);
+ int dateId = dateIds.getResourceId(getDayOfMonth(), 0);
+ dateIds.recycle();
+ return dateId;
+ } catch (Resources.NotFoundException ex) {
+ }
+ }
+ }
+ return 0;
+ }
+
+ public static int getDayOfMonth() {
+ return Calendar.getInstance().get(Calendar.DAY_OF_MONTH) - 1;
+ }
+}
diff --git a/src/com/android/launcher3/icons/clock/AutoUpdateClock.java b/src/com/android/launcher3/icons/clock/AutoUpdateClock.java
new file mode 100644
index 00000000000..d6c50d3c3af
--- /dev/null
+++ b/src/com/android/launcher3/icons/clock/AutoUpdateClock.java
@@ -0,0 +1,168 @@
+package com.android.launcher3.icons.clock;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.os.SystemClock;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.FastBitmapDrawable;
+import com.android.launcher3.icons.FastBitmapDrawableDelegate;
+import com.android.launcher3.icons.FastBitmapDrawableDelegate.DelegateFactory;
+import com.android.launcher3.icons.IconShape;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
+
+import java.util.TimeZone;
+
+// AutoUpdateClock wraps FastBitmapDrawable (since FastBitmapDrawable is final)
+// and implements Runnable for scheduling clock updates
+class AutoUpdateClock implements Runnable {
+ private final FastBitmapDrawable mDrawable;
+ private ClockLayers mLayers;
+ private ClockDelegate mDelegate;
+
+ AutoUpdateClock(ItemInfoWithIcon info, ClockLayers layers, Context context) {
+ mLayers = layers;
+ // Create FastBitmapDrawable with clock delegate factory
+ // Use IconShape.EMPTY as the base, delegate will handle custom drawing
+ ClockDelegateFactory factory = new ClockDelegateFactory(layers.clone(), this);
+ mDrawable = new FastBitmapDrawable(
+ info.bitmap,
+ IconShape.EMPTY,
+ factory
+ );
+ mDrawable.setDisabled(info.isDisabled());
+ }
+
+ // Return the wrapped FastBitmapDrawable (used when FastBitmapDrawable is expected)
+ public FastBitmapDrawable getDrawable() {
+ return mDrawable;
+ }
+
+ // Delegate common Drawable methods
+ public void setBounds(Rect bounds) {
+ mDrawable.setBounds(bounds);
+ }
+
+ public Rect getBounds() {
+ return mDrawable.getBounds();
+ }
+
+ public void invalidateSelf() {
+ mDrawable.invalidateSelf();
+ }
+
+ public void scheduleSelf(Runnable what, long when) {
+ mDrawable.scheduleSelf(what, when);
+ }
+
+ public void unscheduleSelf(Runnable what) {
+ mDrawable.unscheduleSelf(what);
+ }
+
+ public void setDisabled(boolean disabled) {
+ mDrawable.setDisabled(disabled);
+ }
+
+ private void rescheduleUpdate() {
+ unscheduleSelf(this);
+ long now = SystemClock.uptimeMillis();
+ boolean hasSeconds = (mLayers != null && mLayers.mSecondIndex != -1);
+ long interval = hasSeconds ? 1000L : 60_000L;
+ long next = now - (now % interval) + interval;
+ scheduleSelf(this, next);
+ }
+
+ // Used only by Google Clock
+ void updateLayers(ClockLayers layers) {
+ if (layers != null) {
+ ClockLayers newLayers = layers.clone();
+ if (newLayers != null) {
+ mLayers = newLayers;
+ if (mDelegate != null) {
+ mDelegate.updateLayers(newLayers);
+ }
+ if (newLayers.mDrawable != null) {
+ newLayers.mDrawable.setBounds(getBounds());
+ }
+ }
+ }
+ invalidateSelf();
+ }
+
+ void setTimeZone(TimeZone timeZone) {
+ if (mLayers != null) {
+ mLayers.setTimeZone(timeZone);
+ invalidateSelf();
+ }
+ }
+
+ @Override
+ public void run() {
+ if (mLayers != null && mLayers.updateAngles()) {
+ invalidateSelf();
+ }
+ rescheduleUpdate();
+ }
+
+ private static class ClockDelegate implements FastBitmapDrawableDelegate {
+ private ClockLayers mLayers;
+ private final AutoUpdateClock mHost;
+
+ ClockDelegate(ClockLayers layers, AutoUpdateClock host) {
+ mLayers = layers;
+ mHost = host;
+ }
+
+ void updateLayers(ClockLayers layers) {
+ mLayers = layers;
+ }
+
+ @Override
+ public void drawContent(@NonNull BitmapInfo info, @NonNull IconShape shape,
+ @NonNull Canvas canvas, @NonNull Rect bounds, @NonNull Paint paint) {
+ if (mLayers != null && mLayers.mDrawable != null) {
+ canvas.drawBitmap(mLayers.bitmap, null, bounds, paint);
+ mLayers.updateAngles();
+ canvas.scale(mLayers.scale, mLayers.scale,
+ bounds.exactCenterX() + mLayers.offset,
+ bounds.exactCenterY() + mLayers.offset);
+ canvas.clipPath(mLayers.mDrawable.getIconMask());
+ mLayers.mDrawable.getForeground().draw(canvas);
+ } else {
+ // Draw default icon - delegate to parent or draw bitmap directly
+ canvas.drawBitmap(info.icon, null, bounds, paint);
+ }
+ }
+
+ @Override
+ public void onBoundsChange(@NonNull Rect bounds) {
+ if (mLayers != null && mLayers.mDrawable != null) {
+ mLayers.mDrawable.setBounds(bounds);
+ }
+ }
+ }
+
+ private static class ClockDelegateFactory implements DelegateFactory {
+ private final ClockLayers mLayers;
+ private final AutoUpdateClock mClock;
+
+ ClockDelegateFactory(ClockLayers layers, AutoUpdateClock clock) {
+ mLayers = layers;
+ mClock = clock;
+ }
+
+ @NonNull
+ @Override
+ public FastBitmapDrawableDelegate newDelegate(@NonNull BitmapInfo bitmapInfo,
+ @NonNull IconShape iconShape, @NonNull Paint paint,
+ @NonNull FastBitmapDrawable host) {
+ ClockDelegate delegate = new ClockDelegate(mLayers, mClock);
+ mClock.mDelegate = delegate;
+ return delegate;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/icons/clock/ClockLayers.java b/src/com/android/launcher3/icons/clock/ClockLayers.java
new file mode 100644
index 00000000000..bef174a56c9
--- /dev/null
+++ b/src/com/android/launcher3/icons/clock/ClockLayers.java
@@ -0,0 +1,99 @@
+package com.android.launcher3.icons.clock;
+
+import android.annotation.TargetApi;
+import android.graphics.Bitmap;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+
+import java.util.Calendar;
+import java.util.TimeZone;
+
+@TargetApi(26)
+class ClockLayers {
+ AdaptiveIconDrawable mDrawable;
+ LayerDrawable mLayerDrawable;
+ private final Calendar mCurrentTime;
+ int mHourIndex;
+ int mMinuteIndex;
+ int mSecondIndex;
+ int mDefaultHour;
+ int mDefaultMinute;
+ int mDefaultSecond;
+ float scale;
+ float offset;
+ Bitmap bitmap;
+
+ ClockLayers() {
+ mCurrentTime = Calendar.getInstance();
+ }
+
+ public final void setDrawable(Drawable drawable) {
+ if (drawable instanceof AdaptiveIconDrawable) {
+ mDrawable = (AdaptiveIconDrawable) drawable;
+ Drawable foreground = mDrawable.getForeground();
+ if (foreground instanceof LayerDrawable) {
+ mLayerDrawable = (LayerDrawable) foreground;
+ } else {
+ mLayerDrawable = null;
+ }
+ } else if (drawable instanceof LayerDrawable) {
+ mDrawable = null;
+ mLayerDrawable = (LayerDrawable) drawable;
+ } else {
+ mDrawable = null;
+ mLayerDrawable = null;
+ }
+ }
+
+ @Override
+ public ClockLayers clone() {
+ ClockLayers ret = null;
+ if (mDrawable == null && mLayerDrawable == null) {
+ return null;
+ }
+ ClockLayers clone = new ClockLayers();
+ clone.scale = scale;
+ clone.offset = offset;
+ clone.mHourIndex = mHourIndex;
+ clone.mMinuteIndex = mMinuteIndex;
+ clone.mSecondIndex = mSecondIndex;
+ clone.mDefaultHour = mDefaultHour;
+ clone.mDefaultMinute = mDefaultMinute;
+ clone.mDefaultSecond = mDefaultSecond;
+ if (mDrawable != null) {
+ clone.setDrawable(mDrawable.getConstantState().newDrawable());
+ } else {
+ clone.setDrawable(mLayerDrawable.getConstantState().newDrawable());
+ }
+ clone.bitmap = bitmap;
+ if (clone.mLayerDrawable != null) {
+ ret = clone;
+ }
+ return ret;
+ }
+
+ boolean updateAngles() {
+ mCurrentTime.setTimeInMillis(System.currentTimeMillis());
+
+ int hour = (mCurrentTime.get(Calendar.HOUR) + (12 - mDefaultHour)) % 12;
+ int minute = (mCurrentTime.get(Calendar.MINUTE) + (60 - mDefaultMinute)) % 60;
+ int second = (mCurrentTime.get(Calendar.SECOND) + (60 - mDefaultSecond)) % 60;
+
+ boolean hasChanged = false;
+ if (mHourIndex != -1 && mLayerDrawable.getDrawable(mHourIndex).setLevel(hour * 60 + mCurrentTime.get(Calendar.MINUTE))) {
+ hasChanged = true;
+ }
+ if (mMinuteIndex != -1 && mLayerDrawable.getDrawable(mMinuteIndex).setLevel(minute + mCurrentTime.get(Calendar.HOUR) * 60)) {
+ hasChanged = true;
+ }
+ if (mSecondIndex != -1 && mLayerDrawable.getDrawable(mSecondIndex).setLevel(second * 10)) {
+ hasChanged = true;
+ }
+ return hasChanged;
+ }
+
+ void setTimeZone(TimeZone timeZone) {
+ mCurrentTime.setTimeZone(timeZone);
+ }
+}
diff --git a/src/com/android/launcher3/icons/clock/CustomClock.java b/src/com/android/launcher3/icons/clock/CustomClock.java
new file mode 100644
index 00000000000..94173e98ed2
--- /dev/null
+++ b/src/com/android/launcher3/icons/clock/CustomClock.java
@@ -0,0 +1,132 @@
+package com.android.launcher3.icons.clock;
+
+import android.annotation.TargetApi;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Process;
+
+import com.android.launcher3.icons.FastBitmapDrawable;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.util.Preconditions;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.TimeZone;
+import java.util.WeakHashMap;
+
+@TargetApi(Build.VERSION_CODES.TIRAMISU)
+public class CustomClock {
+ private final Context mContext;
+ private final Set mUpdaters = Collections.newSetFromMap(new WeakHashMap<>());
+
+ public CustomClock(Context context) {
+ mContext = context;
+
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ loadTimeZone(intent.getStringExtra("time-zone"));
+ }
+ }, new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED), null,
+ new Handler(Looper.getMainLooper()));
+ }
+
+ public static Drawable getClock(Context context, Drawable drawable, Metadata metadata) {
+ ClockLayers clone = getClockLayers(context, drawable, metadata, false).clone();
+ if (clone != null) {
+ clone.updateAngles();
+ return clone.mDrawable;
+ }
+ return null;
+ }
+
+ private static ClockLayers getClockLayers(Context context, Drawable drawableForDensity,
+ Metadata metadata, boolean normalizeIcon) {
+ Preconditions.assertWorkerThread();
+ ClockLayers layers = new ClockLayers();
+ layers.setDrawable(drawableForDensity.mutate());
+ layers.mHourIndex = metadata.HOUR_LAYER_INDEX;
+ layers.mMinuteIndex = metadata.MINUTE_LAYER_INDEX;
+ layers.mSecondIndex = metadata.SECOND_LAYER_INDEX;
+ layers.mDefaultHour = metadata.DEFAULT_HOUR;
+ layers.mDefaultMinute = metadata.DEFAULT_MINUTE;
+ layers.mDefaultSecond = metadata.DEFAULT_SECOND;
+ if (normalizeIcon) {
+ LauncherIcons obtain = LauncherIcons.obtain(context);
+ layers.bitmap = obtain.createBadgedIconBitmap(
+ new AdaptiveIconDrawable(
+ layers.mDrawable.getBackground().getConstantState().newDrawable(),
+ null)).icon;
+
+ int iconBitmapSize = LauncherAppState.getInstance(context)
+ .getInvariantDeviceProfile().iconBitmapSize;
+ layers.offset = (int) Math.ceil((double) (0.010416667f * ((float) iconBitmapSize)));
+ obtain.recycle();
+ }
+
+ LayerDrawable layerDrawable = layers.mLayerDrawable;
+ int numberOfLayers = layerDrawable.getNumberOfLayers();
+
+ if (layers.mHourIndex < 0 || layers.mHourIndex >= numberOfLayers) {
+ layers.mHourIndex = -1;
+ }
+ if (layers.mMinuteIndex < 0 || layers.mMinuteIndex >= numberOfLayers) {
+ layers.mMinuteIndex = -1;
+ }
+ if (layers.mSecondIndex < 0 || layers.mSecondIndex >= numberOfLayers) {
+ layers.mSecondIndex = -1;
+ }
+
+ return layers;
+ }
+
+ public FastBitmapDrawable drawIcon(ItemInfoWithIcon info, Drawable drawableForDensity,
+ Metadata metadata) {
+ final AutoUpdateClock updater = new AutoUpdateClock(info,
+ getClockLayers(mContext, drawableForDensity, metadata,true).clone(),
+ mContext
+ );
+ mUpdaters.add(updater);
+ return updater.getDrawable();
+ }
+
+ private void loadTimeZone(String timeZoneId) {
+ TimeZone timeZone = timeZoneId == null ?
+ TimeZone.getDefault() :
+ TimeZone.getTimeZone(timeZoneId);
+
+ for (AutoUpdateClock a : mUpdaters) {
+ a.setTimeZone(timeZone);
+ }
+ }
+
+ public static class Metadata {
+ final int HOUR_LAYER_INDEX;
+ final int MINUTE_LAYER_INDEX;
+ final int SECOND_LAYER_INDEX;
+
+ final int DEFAULT_HOUR;
+ final int DEFAULT_MINUTE;
+ final int DEFAULT_SECOND;
+
+ public Metadata(int hourIndex, int minuteIndex, int secondIndex,
+ int defaultHour, int defaultMinute, int defaultSecond) {
+ HOUR_LAYER_INDEX = hourIndex;
+ MINUTE_LAYER_INDEX = minuteIndex;
+ SECOND_LAYER_INDEX = secondIndex;
+ DEFAULT_HOUR = defaultHour;
+ DEFAULT_MINUTE = defaultMinute;
+ DEFAULT_SECOND = defaultSecond;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/icons/clock/DynamicClock.java b/src/com/android/launcher3/icons/clock/DynamicClock.java
new file mode 100644
index 00000000000..e05551f3777
--- /dev/null
+++ b/src/com/android/launcher3/icons/clock/DynamicClock.java
@@ -0,0 +1,162 @@
+package com.android.launcher3.icons.clock;
+
+import android.annotation.TargetApi;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Process;
+import android.os.UserHandle;
+
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
+import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.icons.BaseIconFactory.IconOptions;
+import com.android.launcher3.icons.FastBitmapDrawable;
+import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.util.Preconditions;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.TimeZone;
+import java.util.WeakHashMap;
+
+@TargetApi(26)
+public class DynamicClock extends BroadcastReceiver {
+ public static final ComponentName DESK_CLOCK = new ComponentName(
+ "com.google.android.deskclock",
+ "com.android.deskclock.DeskClock");
+
+ private final Set mUpdaters;
+ private ClockLayers mLayers;
+ private final Context mContext;
+
+ public DynamicClock(Context context) {
+ mUpdaters = Collections.newSetFromMap(new WeakHashMap<>());
+ mLayers = new ClockLayers();
+ mContext = context;
+ final Handler handler = new Handler(MODEL_EXECUTOR.getLooper());
+
+ IntentFilter filter = new IntentFilter();
+ filter.addDataScheme("package");
+ filter.addDataSchemeSpecificPart(DESK_CLOCK.getPackageName(), 0);
+ filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+
+ mContext.registerReceiver(this, filter, null, handler);
+ handler.post(this::updateMainThread);
+
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ loadTimeZone(intent.getStringExtra("time-zone"));
+ }
+ }, new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED), null, new Handler(Looper.getMainLooper()));
+ }
+
+ public static Drawable getClock(Context context, int iconDpi) {
+ ClockLayers clone = getClockLayers(context, iconDpi, false).clone();
+ if (clone != null) {
+ clone.updateAngles();
+ return clone.mDrawable;
+ }
+ return null;
+ }
+
+ private static ClockLayers getClockLayers(Context context, int iconDpi, boolean normalizeIcon) {
+ Preconditions.assertWorkerThread();
+ ClockLayers layers = new ClockLayers();
+ try {
+ PackageManager packageManager = context.getPackageManager();
+ ApplicationInfo applicationInfo = packageManager.getApplicationInfo("com.google.android.deskclock", PackageManager.GET_META_DATA | PackageManager.GET_UNINSTALLED_PACKAGES);
+ Bundle metaData = applicationInfo.metaData;
+ if (metaData != null) {
+ int levelPerTickIcon = metaData.getInt("com.google.android.apps.nexuslauncher.LEVEL_PER_TICK_ICON_ROUND", 0);
+ if (levelPerTickIcon != 0) {
+ Drawable drawableForDensity = packageManager.getResourcesForApplication(applicationInfo).getDrawableForDensity(levelPerTickIcon, iconDpi);
+ layers.setDrawable(drawableForDensity.mutate());
+ layers.mHourIndex = metaData.getInt("com.google.android.apps.nexuslauncher.HOUR_LAYER_INDEX", -1);
+ layers.mMinuteIndex = metaData.getInt("com.google.android.apps.nexuslauncher.MINUTE_LAYER_INDEX", -1);
+ layers.mSecondIndex = metaData.getInt("com.google.android.apps.nexuslauncher.SECOND_LAYER_INDEX", -1);
+ layers.mDefaultHour = metaData.getInt("com.google.android.apps.nexuslauncher.DEFAULT_HOUR", 0);
+ layers.mDefaultMinute = metaData.getInt("com.google.android.apps.nexuslauncher.DEFAULT_MINUTE", 0);
+ layers.mDefaultSecond = metaData.getInt("com.google.android.apps.nexuslauncher.DEFAULT_SECOND", 0);
+ if (normalizeIcon) {
+ LauncherIcons obtain = LauncherIcons.obtain(context);
+ layers.bitmap = obtain.createBadgedIconBitmap(
+ new AdaptiveIconDrawable(layers.mDrawable.getBackground().getConstantState().newDrawable(), null)).icon;
+ int iconBitmapSize = LauncherAppState.getInstance(context).getInvariantDeviceProfile().iconBitmapSize;
+ layers.offset = (int) Math.ceil((double) (0.010416667f * ((float) iconBitmapSize)));
+ obtain.recycle();
+ }
+
+ LayerDrawable layerDrawable = layers.mLayerDrawable;
+ int numberOfLayers = layerDrawable.getNumberOfLayers();
+
+ if (layers.mHourIndex < 0 || layers.mHourIndex >= numberOfLayers) {
+ layers.mHourIndex = -1;
+ }
+ if (layers.mMinuteIndex < 0 || layers.mMinuteIndex >= numberOfLayers) {
+ layers.mMinuteIndex = -1;
+ }
+ if (layers.mSecondIndex < 0 || layers.mSecondIndex >= numberOfLayers) {
+ layers.mSecondIndex = -1;
+ } else {
+ layerDrawable.setDrawable(layers.mSecondIndex, null);
+ layers.mSecondIndex = -1;
+ }
+ }
+ }
+ } catch (Exception e) {
+ layers.mDrawable = null;
+ }
+ return layers;
+ }
+
+ private void loadTimeZone(String timeZoneId) {
+ TimeZone timeZone = timeZoneId == null ?
+ TimeZone.getDefault() :
+ TimeZone.getTimeZone(timeZoneId);
+
+ for (AutoUpdateClock a : mUpdaters) {
+ a.setTimeZone(timeZone);
+ }
+ }
+
+ private void updateMainThread() {
+ MAIN_EXECUTOR.execute(() -> updateWrapper(getClockLayers(mContext,
+ LauncherAppState.getIDP(mContext).fillResIconDpi,
+ true)));
+ }
+
+ private void updateWrapper(ClockLayers wrapper) {
+ this.mLayers = wrapper;
+ for (AutoUpdateClock updater : mUpdaters) {
+ updater.updateLayers(wrapper.clone());
+ }
+ }
+
+ public FastBitmapDrawable drawIcon(ItemInfoWithIcon info) {
+ final AutoUpdateClock updater = new AutoUpdateClock(info, mLayers.clone(), mContext);
+ mUpdaters.add(updater);
+ return updater.getDrawable();
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ updateMainThread();
+ }
+}
diff --git a/src/com/android/launcher3/icons/pack/GetLaunchableInfoTask.java b/src/com/android/launcher3/icons/pack/GetLaunchableInfoTask.java
new file mode 100644
index 00000000000..6bbfc5224f0
--- /dev/null
+++ b/src/com/android/launcher3/icons/pack/GetLaunchableInfoTask.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2019 The LineageOS 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.pack;
+
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.AsyncTask;
+import android.os.Build;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+final class GetLaunchableInfoTask extends AsyncTask> {
+
+ private final PackageManager pm;
+ private final int limit;
+ private final Callback callback;
+
+ GetLaunchableInfoTask(PackageManager pm, int limit, Callback callback) {
+ this.pm = pm;
+ this.limit = limit;
+ this.callback = callback;
+ }
+
+ @Override
+ protected List doInBackground(Void... voids) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+ return new ArrayList<>();
+ }
+
+ final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null)
+ .addCategory(Intent.CATEGORY_LAUNCHER);
+
+ return pm.queryIntentActivities(mainIntent, 0)
+ .parallelStream()
+ .sorted(new ResolveInfo.DisplayNameComparator(pm))
+ .limit(limit)
+ .map(ri -> ri.activityInfo)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ protected void onPostExecute(List list) {
+ callback.onLoadCompleted(list);
+ }
+
+ interface Callback {
+ void onLoadCompleted(List result);
+ }
+}
diff --git a/src/com/android/launcher3/icons/pack/IconPack.java b/src/com/android/launcher3/icons/pack/IconPack.java
new file mode 100644
index 00000000000..3e7236b3c00
--- /dev/null
+++ b/src/com/android/launcher3/icons/pack/IconPack.java
@@ -0,0 +1,98 @@
+package com.android.launcher3.icons.pack;
+
+import android.content.ComponentName;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.util.SparseArray;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Class that handles the metadata and data of any icon pack.
+ */
+class IconPack {
+ private final ApplicationInfo mAi;
+ private final CharSequence mPackageLabel;
+ private Data mData;
+ private Resources mRes;
+
+ IconPack(ApplicationInfo ai, CharSequence label) {
+ mAi = ai;
+ mPackageLabel = label;
+ }
+
+ ApplicationInfo getAi() {
+ return mAi;
+ }
+
+ String getPackage() {
+ return mAi.packageName;
+ }
+
+ CharSequence getTitle() {
+ return mPackageLabel;
+ }
+
+ Data getData(PackageManager pm)
+ throws PackageManager.NameNotFoundException, XmlPullParserException, IOException {
+ if (mData == null) {
+ mData = IconPackParser.parsePackage(pm, getResources(pm), getPackage());
+ }
+ return mData;
+ }
+
+ int getDrawableId(PackageManager pm, ComponentName name)
+ throws PackageManager.NameNotFoundException, IOException, XmlPullParserException {
+ Data data = getData(pm);
+ Resources res = getResources(pm);
+ String pkg = getPackage();
+ return res.getIdentifier(data.drawables.get(name), "drawable", pkg);
+ }
+
+ private Resources getResources(PackageManager pm) throws PackageManager.NameNotFoundException {
+ if (mRes == null) {
+ mRes = pm.getResourcesForApplication(getPackage());
+ }
+ return mRes;
+ }
+
+ static class Data {
+ final Map drawables = new HashMap<>();
+ final Map calendarPrefix = new HashMap<>();
+ final SparseArray clockMetadata = new SparseArray<>();
+ final List iconBacks = new ArrayList<>();
+ final List iconMasks = new ArrayList<>();
+ final List iconUpons = new ArrayList<>();
+ float scale = 1f;
+
+ boolean hasMasking() {
+ return !iconBacks.isEmpty() || !iconMasks.isEmpty() || !iconUpons.isEmpty();
+ }
+ }
+
+ static class Clock {
+ final int hourLayerIndex;
+ final int minuteLayerIndex;
+ final int secondLayerIndex;
+ final int defaultHour;
+ final int defaultMinute;
+ final int defaultSecond;
+
+ Clock(int hourLayerIndex, int minuteLayerIndex, int secondLayerIndex,
+ int defaultHour, int defaultMinute, int defaultSecond) {
+ this.hourLayerIndex = hourLayerIndex;
+ this.minuteLayerIndex = minuteLayerIndex;
+ this.secondLayerIndex = secondLayerIndex;
+ this.defaultHour = defaultHour;
+ this.defaultMinute = defaultMinute;
+ this.defaultSecond = defaultSecond;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/icons/pack/IconPackHeaderPreference.java b/src/com/android/launcher3/icons/pack/IconPackHeaderPreference.java
new file mode 100644
index 00000000000..aeacb1f2c7d
--- /dev/null
+++ b/src/com/android/launcher3/icons/pack/IconPackHeaderPreference.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2020 Shift GmbH
+ *
+ * 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.pack;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.view.View;
+import android.widget.ImageView;
+import android.util.AttributeSet;
+import android.util.Log;
+
+import androidx.core.content.res.TypedArrayUtils;
+import androidx.preference.CheckBoxPreference;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.launcher3.R;
+import com.android.launcher3.icons.IconProvider;
+
+
+public class IconPackHeaderPreference extends RadioHeaderPreference {
+ private static final String TAG = "IconPackHeaderPreference";
+ private static final int PREVIEW_ICON_NUM = 8;
+ // This value has been selected as an average of usual "device profile-computed" values
+ private static final int PREVIEW_ICON_DPI = 500;
+
+ private final Context context;
+ private ImageView[] icons = null;
+
+ public IconPackHeaderPreference(Context context) {
+ this(context, null);
+ }
+
+ public IconPackHeaderPreference(Context context, AttributeSet attrs) {
+ this(context, attrs, TypedArrayUtils.getAttr(context,
+ androidx.preference.R.attr.preferenceStyle,
+ android.R.attr.preferenceStyle));
+ }
+
+ public IconPackHeaderPreference(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ this.context = context;
+
+ setLayoutResource(R.layout.preference_widget_icons_preview);
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ final ImageView[] imageViews = {
+ (ImageView) holder.findViewById(R.id.pref_icon_a),
+ (ImageView) holder.findViewById(R.id.pref_icon_b),
+ (ImageView) holder.findViewById(R.id.pref_icon_c),
+ (ImageView) holder.findViewById(R.id.pref_icon_d),
+ (ImageView) holder.findViewById(R.id.pref_icon_e),
+ (ImageView) holder.findViewById(R.id.pref_icon_f),
+ (ImageView) holder.findViewById(R.id.pref_icon_g),
+ (ImageView) holder.findViewById(R.id.pref_icon_h)
+ };
+ this.icons = imageViews;
+ onRadioElementSelected(null);
+ }
+
+ @Override
+ public void onDetached() {
+ this.icons = null;
+ super.onDetached();
+ }
+
+ @Override
+ public void onRadioElementSelected(String key) {
+ if (icons == null || icons.length == 0) {
+ return;
+ }
+
+ final IconProvider iconProvider = IconProvider.INSTANCE.get(context);
+ final PackageManager pm = context.getPackageManager();
+ new GetLaunchableInfoTask(pm, PREVIEW_ICON_NUM, (aiList) -> {
+ for (int i = 0; i < icons.length; i++) {
+ icons[i].setImageDrawable(iconProvider.getIcon(
+ aiList.get(i), PREVIEW_ICON_DPI));
+ }
+ }).execute();
+ }
+}
diff --git a/src/com/android/launcher3/icons/pack/IconPackManager.java b/src/com/android/launcher3/icons/pack/IconPackManager.java
new file mode 100644
index 00000000000..8c54880e520
--- /dev/null
+++ b/src/com/android/launcher3/icons/pack/IconPackManager.java
@@ -0,0 +1,197 @@
+package com.android.launcher3.icons.pack;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Handler;
+import android.util.Log;
+
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.util.ComponentKey;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import com.android.launcher3.customization.IconDatabase;
+import com.android.launcher3.util.AppReloader;
+
+public class IconPackManager extends BroadcastReceiver {
+ private static final String TAG = "IconPackManager";
+
+ private static final String[] ICON_INTENT_ACTIONS = new String[] {
+ "com.fede.launcher.THEME_ICONPACK",
+ "com.anddoes.launcher.THEME",
+ "com.novalauncher.THEME",
+ "com.teslacoilsw.launcher.THEME",
+ "com.gau.go.launcherex.theme",
+ "org.adw.launcher.THEMES",
+ "net.oneplus.launcher.icons.ACTION_PICK_ICON",
+ "org.adw.launcher.icons.ACTION_PICK_ICON"
+ };
+
+ private static final Intent[] ICON_INTENTS = new Intent[ICON_INTENT_ACTIONS.length];
+ private static IconPackManager sInstance;
+
+ static {
+ for (int i = 0; i < ICON_INTENT_ACTIONS.length; i++) {
+ ICON_INTENTS[i] = new Intent(ICON_INTENT_ACTIONS[i]);
+ }
+ }
+
+ public static synchronized IconPackManager get(Context context) {
+ if (sInstance == null) {
+ sInstance = new IconPackManager(context);
+ }
+ return sInstance;
+ }
+
+ private final Context mContext;
+ private final Map mProviders = new HashMap<>();
+ private final Handler mHandler = new Handler(MODEL_EXECUTOR.getLooper());
+
+ private IconPackManager(Context context) {
+ mContext = context;
+ reloadProviders();
+
+ IntentFilter filter = new IntentFilter();
+ filter.addDataScheme("package");
+ filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+ filter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
+
+ // Called when any app has been installed, enabled, disabled, updated or deleted.
+ context.getApplicationContext().registerReceiver(this, filter, null, mHandler);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getData() != null) {
+ String pkg = intent.getData().getEncodedSchemeSpecificPart();
+ if (pkg != null) {
+ Log.d(TAG, "Received intent action " + intent.getAction() + " for " + pkg);
+ AppReloader appReloader = AppReloader.get(mContext);
+
+ // Create a list of apps that are using the changed package icon pack,
+ // either through the global setting or with an override.
+ Set updateKeys = appReloader.withIconPack(pkg);
+
+ // Remove the changed package from the providers to reload the application info.
+ mProviders.remove(pkg);
+
+ // This can reset the global preference, so do this after creating the list.
+ reloadProviders();
+
+ // Ensure all icons are up-to-date after this icon pack change.
+ // Calendar and clock information will automatically be reloaded by this call.
+ appReloader.reload(updateKeys);
+ }
+ }
+ }
+
+ private void reloadProviders() {
+ PackageManager pm = mContext.getPackageManager();
+ Set info = new HashSet<>();
+ for (Intent intent : ICON_INTENTS) {
+ info.addAll(pm.queryIntentActivities(intent, PackageManager.GET_META_DATA));
+ }
+
+ // Remove unavailable packs
+ for (String packageName : mProviders.keySet().toArray(new String[0])) {
+ boolean foundPackageName = false;
+ for (ResolveInfo ri : info) {
+ if (ri.activityInfo.packageName.equals(packageName)) {
+ foundPackageName = true;
+ break;
+ }
+ }
+ if (!foundPackageName) {
+ mProviders.remove(packageName);
+ }
+ }
+
+ // Add new packs
+ for (ResolveInfo ri : info) {
+ String packageName = ri.activityInfo.packageName;
+ if (!mProviders.containsKey(packageName)) {
+ ApplicationInfo ai = ri.activityInfo.applicationInfo;
+ CharSequence label = ai.loadLabel(pm);
+ mProviders.put(packageName, new IconPack(ai, label));
+ }
+ }
+
+ String global = IconDatabase.getGlobal(mContext);
+ if (!global.isEmpty() && !mProviders.containsKey(global)) {
+ // The global icon pack has been removed, so reset it.
+ // This constraint ensures that the global icon pack is always available,
+ // even if the launcher did not receive the uninstall intent.
+ Log.e(TAG, "Resetting global icon pack because provider " + global + " was removed");
+ IconDatabase.resetGlobal(mContext);
+ }
+ }
+
+ public Map getProviderNames() {
+ Map providerTitles = new HashMap<>();
+ for (Map.Entry pack : mProviders.entrySet()) {
+ providerTitles.put(pack.getKey(), pack.getValue().getTitle());
+ }
+ return providerTitles;
+ }
+
+ public boolean packContainsActivity(String packPackage, ComponentName componentName) {
+ try {
+ IconPack pack = mProviders.get(packPackage);
+ IconPack.Data data = pack.getData(mContext.getPackageManager());
+ return data.drawables.containsKey(componentName);
+ } catch (PackageManager.NameNotFoundException | XmlPullParserException | IOException ignored) {
+ return false;
+ }
+ }
+
+ /**
+ * Creates a resolver that can load the icon once.
+ * This resolver should not be stored, as the resolution strategy could change when
+ * the selected icon pack in use is uninstalled or updated.
+ * @param key Component for which an icon should be extracted.
+ * @return Resolver that loads the icon, or null if there is no resolution strategy.
+ */
+ public IconResolver resolve(ComponentKey key) {
+ String packPackage = IconDatabase.getByComponent(mContext, key);
+ if (mProviders.containsKey(packPackage)) {
+ // The icon provider package is available.
+ try {
+ IconPack pack = mProviders.get(packPackage);
+ IconPack.Data data = pack.getData(mContext.getPackageManager());
+ if (data.drawables.containsKey(key.componentName)) {
+ int drawableId = pack.getDrawableId(mContext.getPackageManager(), key.componentName);
+ if (drawableId != 0) {
+ return new IconResolverExternal(mContext.getPackageManager(), pack.getAi(),
+ drawableId,
+ data.calendarPrefix.get(key.componentName),
+ data.clockMetadata.get(drawableId));
+ }
+ }
+ if (data.hasMasking()) {
+ return new IconResolverMasked(mContext, data, pack.getAi(), key.hashCode());
+ }
+ } catch (PackageManager.NameNotFoundException | XmlPullParserException | IOException ignored) {
+ }
+ } else if (!packPackage.isEmpty()) {
+ // The provider is not available, save for next time.
+ IconDatabase.resetForComponent(mContext, key);
+ }
+ return null;
+ }
+}
diff --git a/src/com/android/launcher3/icons/pack/IconPackParser.java b/src/com/android/launcher3/icons/pack/IconPackParser.java
new file mode 100644
index 00000000000..ef08f706978
--- /dev/null
+++ b/src/com/android/launcher3/icons/pack/IconPackParser.java
@@ -0,0 +1,128 @@
+package com.android.launcher3.icons.pack;
+
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.util.Log;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.List;
+
+class IconPackParser {
+ private static final String TAG = "IconPackParser";
+
+ static IconPack.Data parsePackage(PackageManager pm, Resources res, String pkg)
+ throws IOException, XmlPullParserException {
+ IconPack.Data iconPack = new IconPack.Data();
+
+ int resId = res.getIdentifier("appfilter", "xml", pkg);
+ if (resId != 0) {
+ XmlResourceParser parseXml = pm.getXml(pkg, resId, null);
+ while (parseXml.next() != XmlPullParser.END_DOCUMENT) {
+ if (parseXml.getEventType() == XmlPullParser.START_TAG) {
+ switch (parseXml.getName()) {
+ case "item":
+ addItem(parseXml, iconPack);
+ break;
+ case "calendar":
+ addCalendar(parseXml, iconPack);
+ break;
+ case "iconback":
+ addImgsTo(res, pkg, parseXml, iconPack.iconBacks);
+ break;
+ case "iconmask":
+ addImgsTo(res, pkg, parseXml, iconPack.iconMasks);
+ break;
+ case "iconupon":
+ addImgsTo(res, pkg, parseXml, iconPack.iconUpons);
+ break;
+ case "scale":
+ setScale(parseXml, iconPack);
+ break;
+ case "dynamic-clock":
+ addClock(res, pkg, parseXml, iconPack);
+ break;
+ }
+ }
+ }
+ }
+
+ return iconPack;
+ }
+
+ private static void addItem(XmlResourceParser parseXml,
+ IconPack.Data iconPack) {
+ String component = parseXml.getAttributeValue(null, "component");
+ String drawable = parseXml.getAttributeValue(null, "drawable");
+ if (component != null && drawable != null) {
+ ComponentName componentName = parseComponent(component);
+ if (componentName != null) {
+ iconPack.drawables.put(componentName, drawable);
+ }
+ }
+ }
+
+ private static void addCalendar(XmlResourceParser parseXml, IconPack.Data iconPack) {
+ String component = parseXml.getAttributeValue(null, "component");
+ String prefix = parseXml.getAttributeValue(null, "prefix");
+ if (component != null && prefix != null) {
+ ComponentName componentName = parseComponent(component);
+ if (componentName != null) {
+ iconPack.calendarPrefix.put(componentName, prefix);
+ }
+ }
+ }
+
+ private static ComponentName parseComponent(String component) {
+ if (component.startsWith("ComponentInfo{") && component.endsWith("}")) {
+ component = component.substring(14, component.length() - 1);
+ return ComponentName.unflattenFromString(component);
+ }
+ return null;
+ }
+
+ private static void addImgsTo(Resources res, String pkg, XmlResourceParser parseXml,
+ List list) {
+ for (int i = 0; i < parseXml.getAttributeCount(); i++) {
+ if (parseXml.getAttributeName(i).startsWith("img")) {
+ int id = res.getIdentifier(parseXml.getAttributeValue(i), "drawable", pkg);
+ if (id != 0) {
+ list.add(id);
+ }
+ }
+ }
+ }
+
+ private static void setScale(XmlResourceParser parseXml, IconPack.Data iconPack) {
+ String factor = parseXml.getAttributeValue(null, "factor");
+ if (factor != null) {
+ try {
+ // ToDo: Parse reference to dimens
+ iconPack.scale = Float.parseFloat(factor);
+ } catch (NumberFormatException e) {
+ Log.d(TAG, "Invalid scale: " + factor, e);
+ }
+ }
+ }
+
+ private static void addClock(Resources res, String pkg, XmlResourceParser parseXml,
+ IconPack.Data iconPack) {
+ String drawable = parseXml.getAttributeValue(null, "drawable");
+ if (drawable != null) {
+ int drawableId = res.getIdentifier(drawable, "drawable", pkg);
+ if (drawableId != 0) {
+ iconPack.clockMetadata.put(drawableId, new IconPack.Clock(
+ parseXml.getAttributeIntValue(null, "hourLayerIndex", -1),
+ parseXml.getAttributeIntValue(null, "minuteLayerIndex", -1),
+ parseXml.getAttributeIntValue(null, "secondLayerIndex", -1),
+ parseXml.getAttributeIntValue(null, "defaultHour", 0),
+ parseXml.getAttributeIntValue(null, "defaultMinute", 0),
+ parseXml.getAttributeIntValue(null, "defaultSecond", 0)));
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/icons/pack/IconPackSettingsActivity.java b/src/com/android/launcher3/icons/pack/IconPackSettingsActivity.java
new file mode 100644
index 00000000000..01886dcdfbc
--- /dev/null
+++ b/src/com/android/launcher3/icons/pack/IconPackSettingsActivity.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2020 Shift GmbH
+ * Copyright (C) 2026 crDroid Android 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.pack;
+
+import static androidx.preference.PreferenceFragmentCompat.ARG_PREFERENCE_ROOT;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.widget.Toast;
+
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback;
+import androidx.preference.PreferenceFragmentCompat.OnPreferenceStartScreenCallback;
+import androidx.preference.PreferenceScreen;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.PackageManagerHelper;
+
+import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
+
+public final class IconPackSettingsActivity extends CollapsingToolbarBaseActivity implements
+ OnPreferenceStartFragmentCallback, OnPreferenceStartScreenCallback {
+
+ public static final String EXTRA_FRAGMENT_ARGS = ":settings:fragment_args";
+
+ // Intent extra to indicate the pref-key to highlighted when opening the settings activity
+ public static final String EXTRA_FRAGMENT_HIGHLIGHT_KEY = ":settings:fragment_args_key";
+ // Intent extra to indicate the pref-key of the root screen when opening the settings activity
+ public static final String EXTRA_FRAGMENT_ROOT_KEY = ARG_PREFERENCE_ROOT;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.settings_activity);
+
+ Intent intent = getIntent();
+
+ if (savedInstanceState == null) {
+ Bundle args = intent.getBundleExtra(EXTRA_FRAGMENT_ARGS);
+ if (args == null) {
+ args = new Bundle();
+ }
+
+ String highlight = intent.getStringExtra(EXTRA_FRAGMENT_HIGHLIGHT_KEY);
+ if (!TextUtils.isEmpty(highlight)) {
+ args.putString(EXTRA_FRAGMENT_HIGHLIGHT_KEY, highlight);
+ }
+ String root = intent.getStringExtra(EXTRA_FRAGMENT_ROOT_KEY);
+ if (!TextUtils.isEmpty(root)) {
+ args.putString(EXTRA_FRAGMENT_ROOT_KEY, root);
+ }
+
+ final FragmentManager fm = getSupportFragmentManager();
+ final Fragment f = fm.getFragmentFactory().instantiate(getClassLoader(),
+ getString(R.string.icon_pack_settings_class));
+ f.setArguments(args);
+ // Display the fragment as the main content.
+ fm.beginTransaction().replace(
+ com.android.settingslib.collapsingtoolbar.R.id.content_frame, f).commit();
+ }
+ }
+
+ private boolean startPreference(String fragment, Bundle args, String key) {
+ if (getSupportFragmentManager().isStateSaved()) {
+ // Sometimes onClick can come after onPause because of being posted on the handler.
+ // Skip starting new preferences in that case.
+ return false;
+ }
+ final FragmentManager fm = getSupportFragmentManager();
+ final Fragment f = fm.getFragmentFactory().instantiate(getClassLoader(), fragment);
+ f.setArguments(args);
+ // Display the fragment as the main content.
+ fm.beginTransaction().replace(
+ com.android.settingslib.collapsingtoolbar.R.id.content_frame, f).commit();
+
+ return true;
+ }
+
+ @Override
+ public boolean onPreferenceStartFragment(
+ PreferenceFragmentCompat preferenceFragment, Preference pref) {
+ return startPreference(pref.getFragment(), pref.getExtras(), pref.getKey());
+ }
+
+ @Override
+ public boolean onPreferenceStartScreen(PreferenceFragmentCompat caller, PreferenceScreen pref) {
+ Bundle args = new Bundle();
+ args.putString(ARG_PREFERENCE_ROOT, pref.getKey());
+ return startPreference(getString(R.string.icon_pack_title), args, pref.getKey());
+ }
+
+ @Override
+ public boolean onNavigateUp() {
+ onBackPressed();
+ return true;
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ final MenuInflater menuInflater = getMenuInflater();
+ menuInflater.inflate(R.menu.menu_icon_pack, menu);
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ int id = item.getItemId();
+ if (id == android.R.id.home) {
+ finish();
+ return true;
+ } else if (id == R.id.menu_icon_pack) {
+ return openMarket();
+ } else {
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ private boolean openMarket() {
+ final String query = getString(R.string.icon_pack_title);
+ final Intent intent = PackageManagerHelper.getMarketSearchIntent(this, query);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ if (intent.resolveActivity(getPackageManager()) == null) {
+ Toast.makeText(this, R.string.icon_pack_no_market, Toast.LENGTH_LONG)
+ .show();
+ return false;
+ }
+
+ startActivity(intent);
+ return true;
+ }
+}
diff --git a/src/com/android/launcher3/icons/pack/IconPackSettingsFragment.java b/src/com/android/launcher3/icons/pack/IconPackSettingsFragment.java
new file mode 100644
index 00000000000..c39b7098c72
--- /dev/null
+++ b/src/com/android/launcher3/icons/pack/IconPackSettingsFragment.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2020 Shift GmbH
+ * Copyright (C) 2026 crDroid Android 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.pack;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+
+import androidx.preference.Preference;
+
+import com.android.launcher3.R;
+
+import com.android.launcher3.customization.IconDatabase;
+
+import com.android.settingslib.widget.SelectorWithWidgetPreference;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+public final class IconPackSettingsFragment extends RadioSettingsFragment {
+ private static final IntentFilter PKG_UPDATE_INTENT = new IntentFilter();
+
+ private static final String[] ICON_INTENT_ACTIONS = new String[] {
+ "com.fede.launcher.THEME_ICONPACK",
+ "com.anddoes.launcher.THEME",
+ "com.novalauncher.THEME",
+ "com.teslacoilsw.launcher.THEME",
+ "com.gau.go.launcherex.theme",
+ "org.adw.launcher.THEMES",
+ "org.adw.launcher.icons.ACTION_PICK_ICON",
+ "net.oneplus.launcher.icons.ACTION_PICK_ICON",
+ };
+
+ private static final Intent[] ICON_INTENTS = new Intent[ICON_INTENT_ACTIONS.length];
+
+ static {
+ for (int i = 0; i < ICON_INTENT_ACTIONS.length; i++) {
+ ICON_INTENTS[i] = new Intent(ICON_INTENT_ACTIONS[i]);
+ }
+ }
+
+ static {
+ PKG_UPDATE_INTENT.addAction(Intent.ACTION_PACKAGE_INSTALL);
+ PKG_UPDATE_INTENT.addAction(Intent.ACTION_PACKAGE_ADDED);
+ PKG_UPDATE_INTENT.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ PKG_UPDATE_INTENT.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ PKG_UPDATE_INTENT.addDataScheme("package");
+ }
+
+ private BroadcastReceiver broadCastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ reloadPreferences();
+ }
+ };
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ reloadPreferences();
+ getActivity().registerReceiver(broadCastReceiver, PKG_UPDATE_INTENT);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ getActivity().unregisterReceiver(broadCastReceiver);
+ }
+
+ @Override
+ protected List getPreferences(Context context) {
+ final String currentIconPack = IconDatabase.getGlobal(context);
+ final List prefsList = new ArrayList<>();
+ final Set iconPacks = getAvailableIconPacks(context);
+
+ for (final IconPackInfo entry : iconPacks) {
+ final boolean isCurrent = currentIconPack.equals(entry.pkgName);
+ final SelectorWithWidgetPreference pref = buildPreference(context,
+ entry.pkgName, entry.label, isCurrent);
+ prefsList.add(pref);
+
+ if (isCurrent) {
+ setSelectedPreference(pref);
+ }
+ }
+
+ return prefsList;
+ }
+
+ @Override
+ public void onSelected(String key) {
+ IconDatabase.setGlobal(getActivity(), key);
+ super.onSelected(key);
+ }
+
+ @Override
+ protected IconPackHeaderPreference getHeader(Context context) {
+ return new IconPackHeaderPreference(context);
+ }
+
+ private Set getAvailableIconPacks(Context context) {
+ final PackageManager pm = context.getPackageManager();
+ final Set availablePacks = new LinkedHashSet<>();
+ final List eligiblePacks = new ArrayList<>();
+ for (Intent intent : ICON_INTENTS) {
+ eligiblePacks.addAll(pm.queryIntentActivities(intent, PackageManager.GET_META_DATA));
+ }
+
+ // Add default
+ final String defaultLabel = context.getString(R.string.icon_pack_default_label);
+ availablePacks.add(new IconPackInfo(IconDatabase.VALUE_DEFAULT, defaultLabel));
+ // Add user-installed packs
+ for (final ResolveInfo r : eligiblePacks) {
+ availablePacks.add(new IconPackInfo(
+ r.activityInfo.packageName, (String) r.loadLabel(pm)));
+ }
+ return availablePacks;
+ }
+
+ private SelectorWithWidgetPreference buildPreference(Context context, String pkgName,
+ String label, boolean isChecked) {
+ final SelectorWithWidgetPreference pref = new SelectorWithWidgetPreference(context);
+ pref.setKey(pkgName);
+ pref.setTitle(label);
+ pref.setPersistent(false);
+ pref.setChecked(isChecked);
+ if (!pkgName.equals(IconDatabase.VALUE_DEFAULT)) {
+ Intent intent = context.getPackageManager().getLaunchIntentForPackage(pkgName);
+ if (intent != null) {
+ pref.setExtraWidgetOnClickListener((v) -> {
+ context.startActivity(intent);
+ });
+ }
+ }
+ return pref;
+ }
+
+ private static class IconPackInfo {
+ final String pkgName;
+ final String label;
+
+ IconPackInfo(String pkgName, String label) {
+ this.pkgName = pkgName;
+ this.label = label;
+ }
+
+ @Override
+ public int hashCode() {
+ return pkgName.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == null) return false;
+ if (!(other instanceof IconPackInfo)) return false;
+ return pkgName.equals(((IconPackInfo) other).pkgName);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/icons/pack/IconResolver.java b/src/com/android/launcher3/icons/pack/IconResolver.java
new file mode 100644
index 00000000000..c6efdc10cce
--- /dev/null
+++ b/src/com/android/launcher3/icons/pack/IconResolver.java
@@ -0,0 +1,25 @@
+package com.android.launcher3.icons.pack;
+
+import android.graphics.drawable.Drawable;
+
+import com.android.launcher3.icons.clock.CustomClock;
+
+public interface IconResolver {
+ boolean isCalendar();
+
+ boolean isClock();
+
+ CustomClock.Metadata clockData();
+
+ /**
+ * Resolves an external icon for a given density.
+ * @param iconDpi Positive integer. If it is non-positive the full scale drawable is returned.
+ * @param fallback Method to load the drawable when resolving using the override fails.
+ * @return Loaded drawable, or fallback drawable when resolving fails.
+ */
+ Drawable getIcon(int iconDpi, DefaultDrawableProvider fallback);
+
+ interface DefaultDrawableProvider {
+ Drawable get();
+ }
+}
diff --git a/src/com/android/launcher3/icons/pack/IconResolverExternal.java b/src/com/android/launcher3/icons/pack/IconResolverExternal.java
new file mode 100644
index 00000000000..df6a5c118fc
--- /dev/null
+++ b/src/com/android/launcher3/icons/pack/IconResolverExternal.java
@@ -0,0 +1,85 @@
+package com.android.launcher3.icons.pack;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+
+import java.util.Calendar;
+
+import com.android.launcher3.icons.clock.CustomClock;
+
+public class IconResolverExternal implements IconResolver {
+ private final PackageManager mPm;
+ private final ApplicationInfo mPackInfo;
+ private final int mDrawableId;
+ private final String mCalendarPrefix;
+ private final IconPack.Clock mClockData;
+
+ IconResolverExternal(PackageManager pm, ApplicationInfo packInfo, int drawableId,
+ String calendarPrefix, IconPack.Clock clockData) {
+ mPm = pm;
+ mPackInfo = packInfo;
+ mDrawableId = drawableId;
+ mCalendarPrefix = calendarPrefix;
+ mClockData = clockData;
+ }
+
+ public boolean isCalendar() {
+ return mCalendarPrefix != null;
+ }
+
+ public boolean isClock() {
+ return mClockData != null;
+ }
+
+ public CustomClock.Metadata clockData() {
+ return new CustomClock.Metadata(
+ mClockData.hourLayerIndex,
+ mClockData.minuteLayerIndex,
+ mClockData.secondLayerIndex,
+ mClockData.defaultHour,
+ mClockData.defaultMinute,
+ mClockData.defaultSecond
+ );
+ }
+
+ public Drawable getIcon(int iconDpi, DefaultDrawableProvider fallback) {
+ try {
+ Resources res = mPm.getResourcesForApplication(mPackInfo);
+
+ // First try loading the calendar.
+ if (isCalendar()) {
+ String calendarId = mCalendarPrefix + Calendar.getInstance().get(Calendar.DAY_OF_MONTH);
+ int drawableId = res.getIdentifier(calendarId, "drawable", mPackInfo.packageName);
+ if (drawableId != 0) {
+ Drawable drawable;
+ if (iconDpi > 0) {
+ // Try loading with the right density
+ drawable = res.getDrawableForDensity(drawableId, iconDpi, null);
+ if (drawable != null) {
+ return drawable;
+ }
+ }
+
+ drawable = mPm.getDrawable(mPackInfo.packageName, drawableId, null);
+ if (drawable != null) {
+ return drawable;
+ }
+ }
+ }
+
+ if (iconDpi > 0) {
+ // Fall back to mipmap loading with correct density.
+ Drawable drawable = res.getDrawableForDensity(mDrawableId, iconDpi, null);
+ if (drawable != null) {
+ return drawable;
+ }
+ }
+ } catch (PackageManager.NameNotFoundException | Resources.NotFoundException ignored) {
+ }
+
+ // Finally, try directly returning the drawable.
+ return mPm.getDrawable(mPackInfo.packageName, mDrawableId, mPackInfo);
+ }
+}
diff --git a/src/com/android/launcher3/icons/pack/IconResolverMasked.java b/src/com/android/launcher3/icons/pack/IconResolverMasked.java
new file mode 100644
index 00000000000..bb672a5f4ab
--- /dev/null
+++ b/src/com/android/launcher3/icons/pack/IconResolverMasked.java
@@ -0,0 +1,174 @@
+package com.android.launcher3.icons.pack;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+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.Matrix;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+
+import com.android.launcher3.icons.BaseIconFactory;
+import com.android.launcher3.icons.LauncherIcons;
+import static com.android.launcher3.icons.LauncherIcons.CONFIG_HINT_NO_WRAP;
+
+import com.android.launcher3.icons.clock.CustomClock;
+
+public class IconResolverMasked implements IconResolver {
+ private final Context mContext;
+ private final IconPack.Data mData;
+ private final ApplicationInfo mPackInfo;
+ private final int mHashCode;
+
+ private final Canvas mCanvas = new Canvas();
+ private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+
+ IconResolverMasked(Context context, IconPack.Data data, ApplicationInfo packInfo,
+ int hashCode) {
+ mContext = context;
+ mData = data;
+ mPackInfo = packInfo;
+ mHashCode = hashCode & 0xFFFF;
+ }
+
+ @Override
+ public boolean isCalendar() {
+ return false;
+ }
+
+ @Override
+ public boolean isClock() {
+ return false;
+ }
+
+ @Override
+ public CustomClock.Metadata clockData() {
+ return null;
+ }
+
+ @Override
+ public Drawable getIcon(int iconDpi, DefaultDrawableProvider fallback) {
+ Drawable icon = fallback.get();
+
+ LauncherIcons li = LauncherIcons.obtain(mContext);
+ PackageManager pm = mContext.getPackageManager();
+ try {
+ Resources res = pm.getResourcesForApplication(mPackInfo);
+
+ // Re-render without scaling after creating the bitmap in the right dimensions.
+ Bitmap iconBm = li.createScaledBitmap(icon, BaseIconFactory.MODE_WITH_SHADOW);
+ mCanvas.setBitmap(iconBm);
+ icon.setBounds(0, 0, iconBm.getWidth(), iconBm.getHeight());
+ icon.draw(mCanvas);
+
+ // Scale the bitmap using the icon pack scale.
+ scaleBitmap(iconBm, mData.scale);
+
+ // Cut parts off using the mask image.
+ if (!mData.iconMasks.isEmpty()) {
+ int iconMask = mData.iconMasks.get(mHashCode % mData.iconMasks.size());
+ maskBitmap(iconBm, res.getDrawableForDensity(iconMask, iconDpi, null));
+ }
+
+ // Add icon back after scaling.
+ if (!mData.iconBacks.isEmpty()) {
+ int iconBack = mData.iconBacks.get(mHashCode % mData.iconBacks.size());
+ backBitmap(iconBm, res.getDrawableForDensity(iconBack, iconDpi, null), li);
+ }
+
+ // Render upon image onto icon. We use SRC_ATOP to make sure it stays within bounds.
+ if (!mData.iconUpons.isEmpty()) {
+ int iconUpon = mData.iconUpons.get(mHashCode % mData.iconUpons.size());
+ uponBitmap(iconBm, res.getDrawableForDensity(iconUpon, iconDpi, null));
+ }
+
+ return new BitmapDrawable(mContext.getResources(), iconBm);
+ } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
+ e.printStackTrace();
+ }
+
+ li.recycle();
+ return icon;
+ }
+
+ private void scaleBitmap(Bitmap bitmap, float scale) {
+ if (scale != 1f) {
+ Bitmap iconBmScaled = Bitmap.createScaledBitmap(bitmap,
+ (int)(bitmap.getWidth() * mData.scale),
+ (int)(bitmap.getHeight() * mData.scale),
+ true);
+
+ float move = 0.5f * (1f - mData.scale);
+
+ Matrix matrix = new Matrix();
+ matrix.postTranslate(move * bitmap.getWidth(), move * bitmap.getHeight());
+
+ bitmap.eraseColor(Color.TRANSPARENT);
+ mCanvas.setBitmap(bitmap);
+ mCanvas.drawBitmap(iconBmScaled, matrix, null);
+
+ iconBmScaled.recycle();
+ }
+ }
+
+ private void maskBitmap(Bitmap bitmap, Drawable mask) {
+ if (mask != null) {
+ Bitmap maskBm = Bitmap.createBitmap(
+ bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
+
+ // Render the mask image to a bitmap.
+ mCanvas.setBitmap(maskBm);
+ mask.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());
+ mask.draw(mCanvas);
+
+ // Then use PorterDuff to remove black mask parts.
+ mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
+ mCanvas.setBitmap(bitmap);
+ mCanvas.drawBitmap(maskBm, 0f, 0f, mPaint);
+
+ maskBm.recycle();
+ }
+ }
+
+ @SuppressLint("WrongConstant")
+ private void backBitmap(Bitmap bitmap, Drawable back, LauncherIcons li) {
+ if (back != null) {
+ Bitmap backBm = Bitmap.createBitmap(
+ bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
+
+ mCanvas.setBitmap(backBm);
+ back.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());
+ back.draw(mCanvas);
+
+ mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OVER));
+ mCanvas.setBitmap(bitmap);
+ mCanvas.drawBitmap(backBm, 0f, 0f, mPaint);
+
+ backBm.recycle();
+ }
+ }
+
+ private void uponBitmap(Bitmap bitmap, Drawable upon) {
+ if (upon != null) {
+ Bitmap uponBm = Bitmap.createBitmap(
+ bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
+
+ mCanvas.setBitmap(uponBm);
+ upon.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());
+ upon.draw(mCanvas);
+
+ mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP));
+ mCanvas.setBitmap(bitmap);
+ mCanvas.drawBitmap(uponBm, 0f, 0f, mPaint);
+
+ uponBm.recycle();
+ }
+ }
+}
diff --git a/src/com/android/launcher3/icons/pack/RadioHeaderPreference.java b/src/com/android/launcher3/icons/pack/RadioHeaderPreference.java
new file mode 100644
index 00000000000..9e7e32eee42
--- /dev/null
+++ b/src/com/android/launcher3/icons/pack/RadioHeaderPreference.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 Shift GmbH
+ *
+ * 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.pack;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import androidx.core.content.res.TypedArrayUtils;
+import androidx.preference.Preference;
+
+import com.android.launcher3.R;
+
+public abstract class RadioHeaderPreference extends Preference {
+
+ public RadioHeaderPreference(Context context) {
+ this(context, null);
+ }
+
+ public RadioHeaderPreference(Context context, AttributeSet attrs) {
+ this(context, attrs, TypedArrayUtils.getAttr(context,
+ androidx.preference.R.attr.preferenceStyle,
+ android.R.attr.preferenceStyle));
+ }
+
+ public RadioHeaderPreference(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ setIconSpaceReserved(false);
+ }
+
+ public abstract void onRadioElementSelected(String key);
+}
diff --git a/src/com/android/launcher3/icons/pack/RadioSettingsFragment.java b/src/com/android/launcher3/icons/pack/RadioSettingsFragment.java
new file mode 100644
index 00000000000..27632616703
--- /dev/null
+++ b/src/com/android/launcher3/icons/pack/RadioSettingsFragment.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2020 Shift GmbH
+ * Copyright (C) 2026 crDroid Android 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.pack;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settingslib.widget.SelectorWithWidgetPreference;
+import com.android.settingslib.widget.SettingsBasePreferenceFragment;
+
+import java.util.List;
+
+public abstract class RadioSettingsFragment extends SettingsBasePreferenceFragment implements
+ Preference.OnPreferenceClickListener {
+ private SelectorWithWidgetPreference selectedPreference = null;
+ private RadioHeaderPreference headerPref = null;
+
+ protected abstract List getPreferences(Context context);
+
+ @Override
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ final PreferenceManager prefManager = getPreferenceManager();
+ final Context context = prefManager.getContext();
+ final PreferenceScreen screen = prefManager.createPreferenceScreen(context);
+
+ headerPref = getHeader(context);
+ if (headerPref != null) {
+ screen.addPreference(headerPref);
+ }
+
+ loadPreferences(context, screen, null);
+ setPreferenceScreen(screen);
+ }
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ if (preference instanceof SelectorWithWidgetPreference) {
+ onSelected(preference.getKey());
+
+ if (selectedPreference != null) {
+ selectedPreference.setChecked(false);
+ }
+ selectedPreference = (SelectorWithWidgetPreference) preference;
+ selectedPreference.setChecked(true);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onDestroyView() {
+ selectedPreference = null;
+ headerPref = null;
+ super.onDestroyView();
+ }
+
+ protected RadioHeaderPreference getHeader(Context context) {
+ return null;
+ }
+
+ protected final void setSelectedPreference(SelectorWithWidgetPreference preference) {
+ selectedPreference = preference;
+ }
+
+ protected void onSelected(String key) {
+ if (headerPref != null) {
+ headerPref.onRadioElementSelected(key);
+ }
+ }
+
+ protected void reloadPreferences() {
+ final PreferenceScreen screen = getPreferenceScreen();
+ if (screen == null) {
+ return;
+ }
+
+ // Save current key for later
+ final String currentKey = selectedPreference == null ?
+ null : selectedPreference.getKey();
+ selectedPreference = null;
+
+ // Reload header contents
+ if (headerPref != null) {
+ headerPref.onRadioElementSelected(currentKey);
+ }
+
+ // Remove radio preferences (backwards so we don't mess up indices)
+ final int numPreferences = screen.getPreferenceCount();
+ for (int i = numPreferences - 1; i >= 0; i--) {
+ final Preference p = screen.getPreference(i);
+ if (p instanceof SelectorWithWidgetPreference) {
+ screen.removePreference(p);
+ }
+ }
+
+ // Add radio preferences
+ final PreferenceManager prefManager = getPreferenceManager();
+ final Context context = prefManager.getContext();
+ loadPreferences(context, screen, currentKey);
+ }
+
+ private void loadPreferences(Context context, PreferenceScreen screen,
+ String currentKey) {
+ boolean hasSetNewCurrent = false;
+
+ final List prefs = getPreferences(context);
+ for (final SelectorWithWidgetPreference p : prefs) {
+ if (currentKey != null && currentKey.equals(p.getKey())) {
+ p.setChecked(true);
+ selectedPreference = p;
+ hasSetNewCurrent = true;
+ }
+ p.setOnPreferenceClickListener(this);
+ screen.addPreference(p);
+ }
+
+ if (!hasSetNewCurrent && currentKey != null && !prefs.isEmpty()) {
+ // Old "current" preference was removed, fallback to
+ // the first candidate
+ selectedPreference = prefs.get(0);
+ selectedPreference.setChecked(true);
+ onPreferenceClick(selectedPreference);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/lineage/trust/HiddenAppsFilter.java b/src/com/android/launcher3/lineage/trust/HiddenAppsFilter.java
new file mode 100644
index 00000000000..88a54ffabd9
--- /dev/null
+++ b/src/com/android/launcher3/lineage/trust/HiddenAppsFilter.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2019 The LineageOS 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.lineage.trust;
+
+import android.content.ComponentName;
+import android.content.Context;
+
+import com.android.launcher3.dagger.ApplicationContext;
+
+import com.android.launcher3.AppFilter;
+import com.android.launcher3.lineage.trust.db.TrustDatabaseHelper;
+
+import javax.inject.Inject;
+
+@SuppressWarnings("unused")
+public class HiddenAppsFilter extends AppFilter {
+ private TrustDatabaseHelper mDbHelper;
+
+ @Inject
+ public HiddenAppsFilter(@ApplicationContext Context context) {
+ super(context);
+
+ mDbHelper = TrustDatabaseHelper.getInstance(context);
+ }
+
+ @Override
+ public boolean shouldShowApp(ComponentName app) {
+ return !mDbHelper.isPackageHidden(app.getPackageName()) && super.shouldShowApp(app);
+ }
+}
diff --git a/src/com/android/launcher3/lineage/trust/LoadTrustComponentsTask.java b/src/com/android/launcher3/lineage/trust/LoadTrustComponentsTask.java
new file mode 100644
index 00000000000..62ffd09fc0d
--- /dev/null
+++ b/src/com/android/launcher3/lineage/trust/LoadTrustComponentsTask.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2019-2024 The LineageOS 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.lineage.trust;
+
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.AppFilter;
+import com.android.launcher3.lineage.trust.db.TrustComponent;
+import com.android.launcher3.lineage.trust.db.TrustDatabaseHelper;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class LoadTrustComponentsTask extends AsyncTask> {
+ @NonNull
+ private TrustDatabaseHelper mDbHelper;
+
+ @NonNull
+ private PackageManager mPackageManager;
+
+ @NonNull
+ private AppFilter mAppFilter;
+
+ @NonNull
+ private Callback mCallback;
+
+ LoadTrustComponentsTask(@NonNull TrustDatabaseHelper dbHelper,
+ @NonNull PackageManager packageManager,
+ @NonNull AppFilter appFilter,
+ @NonNull Callback callback) {
+ mDbHelper = dbHelper;
+ mPackageManager = packageManager;
+ mAppFilter = appFilter;
+ mCallback = callback;
+ }
+
+ @Override
+ protected List doInBackground(Void... voids) {
+ List list = new ArrayList<>();
+
+ Intent filter = new Intent(Intent.ACTION_MAIN, null);
+ filter.addCategory(Intent.CATEGORY_LAUNCHER);
+
+ List apps = mPackageManager.queryIntentActivities(filter,
+ PackageManager.GET_META_DATA);
+
+ int numPackages = apps.size();
+ for (int i = 0; i < numPackages; i++) {
+ ResolveInfo app = apps.get(i);
+
+ if (!mAppFilter.shouldShowApp(app.activityInfo.getComponentName())) {
+ continue;
+ }
+
+ try {
+ String pkgName = app.activityInfo.packageName;
+ String label = mPackageManager.getApplicationLabel(
+ mPackageManager.getApplicationInfo(pkgName,
+ PackageManager.GET_META_DATA)).toString();
+ Drawable icon = app.loadIcon(mPackageManager);
+ boolean isHidden = mDbHelper.isPackageHidden(pkgName);
+ boolean isProtected = mDbHelper.isPackageProtected(pkgName);
+
+ list.add(new TrustComponent(pkgName, icon, label, isHidden, isProtected));
+
+ publishProgress(Math.round(i * 100f / numPackages));
+ } catch (PackageManager.NameNotFoundException ignored) {
+ }
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ Collections.sort(list, (a, b) -> a.getLabel().compareTo(b.getLabel()));
+ }
+
+ return list;
+ }
+
+ @Override
+ protected void onProgressUpdate(Integer... values) {
+ if (values.length > 0) {
+ mCallback.onLoadListProgress(values[0]);
+ }
+ }
+
+ @Override
+ protected void onPostExecute(List trustComponents) {
+ mCallback.onLoadCompleted(trustComponents);
+ }
+
+ interface Callback {
+ void onLoadListProgress(int progress);
+ void onLoadCompleted(List result);
+ }
+}
diff --git a/src/com/android/launcher3/lineage/trust/TrustAppsActivity.java b/src/com/android/launcher3/lineage/trust/TrustAppsActivity.java
new file mode 100644
index 00000000000..3427f96d300
--- /dev/null
+++ b/src/com/android/launcher3/lineage/trust/TrustAppsActivity.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2019-2024 The LineageOS 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.lineage.trust;
+
+import static com.android.launcher3.lineage.trust.db.TrustComponent.Kind.HIDDEN;
+import static com.android.launcher3.lineage.trust.db.TrustComponent.Kind.PROTECTED;
+
+import android.app.ActionBar;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.graphics.Insets;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.WindowInsetsCompat;
+import androidx.recyclerview.widget.DefaultItemAnimator;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.launcher3.AppFilter;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherPrefs;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.lineage.trust.db.TrustComponent;
+import com.android.launcher3.lineage.trust.db.TrustDatabaseHelper;
+
+import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
+
+import java.util.List;
+
+public class TrustAppsActivity extends CollapsingToolbarBaseActivity implements
+ TrustAppsAdapter.Listener,
+ LoadTrustComponentsTask.Callback,
+ UpdateItemTask.UpdateCallback {
+
+ private static final String KEY_TRUST_ONBOARDING = "pref_trust_onboarding";
+
+ private RecyclerView mRecyclerView;
+ private LinearLayout mLoadingView;
+ private ProgressBar mProgressBar;
+
+ private TrustDatabaseHelper mDbHelper;
+ private TrustAppsAdapter mAdapter;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstance) {
+ super.onCreate(savedInstance);
+
+ setContentView(R.layout.activity_hidden_apps);
+
+ mRecyclerView = findViewById(R.id.hidden_apps_list);
+ mLoadingView = findViewById(R.id.hidden_apps_loading);
+ mLoadingView.setVisibility(View.VISIBLE);
+ mProgressBar = findViewById(R.id.hidden_apps_progress_bar);
+
+ ViewCompat.setOnApplyWindowInsetsListener(mRecyclerView, (view, insets) -> {
+ Insets systemInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars());
+ view.setPadding(
+ view.getPaddingLeft(),
+ systemInsets.top,
+ view.getPaddingRight(),
+ systemInsets.bottom
+ );
+ return WindowInsetsCompat.CONSUMED;
+ });
+
+ final boolean hasSecureKeyguard = Utilities.hasSecureKeyguard(this);
+ mAdapter = new TrustAppsAdapter(this, hasSecureKeyguard);
+ mDbHelper = TrustDatabaseHelper.getInstance(this);
+
+ mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
+ mRecyclerView.setItemAnimator(new DefaultItemAnimator());
+ mRecyclerView.setAdapter(mAdapter);
+
+ showOnBoarding(false);
+
+ final AppFilter appFilter = new AppFilter(this);
+ new LoadTrustComponentsTask(mDbHelper, getPackageManager(), appFilter, this).execute();
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater menuInflater = getMenuInflater();
+ menuInflater.inflate(R.menu.menu_trust_apps, menu);
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ int id = item.getItemId();
+ if (id == android.R.id.home) {
+ finish();
+ return true;
+ } else if (id == R.id.menu_trust_help) {
+ showOnBoarding(true);
+ return true;
+ } else {
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ @Override
+ public void onHiddenItemChanged(@NonNull TrustComponent component) {
+ new UpdateItemTask(mDbHelper, this, HIDDEN).execute(component);
+ }
+
+ @Override
+ public void onProtectedItemChanged(@NonNull TrustComponent component) {
+ new UpdateItemTask(mDbHelper, this, PROTECTED).execute(component);
+ }
+
+ @Override
+ public void onUpdated(boolean result) {
+ LauncherAppState.INSTANCE.get(this).getModel().reloadIfActive();
+ }
+
+ @Override
+ public void onLoadListProgress(int progress) {
+ mProgressBar.setProgress(progress);
+ }
+
+ @Override
+ public void onLoadCompleted(List result) {
+ mLoadingView.setVisibility(View.GONE);
+ mRecyclerView.setVisibility(View.VISIBLE);
+ mAdapter.update(result);
+ }
+
+ private void showOnBoarding(boolean forceShow) {
+ SharedPreferences preferenceManager = LauncherPrefs.getPrefs(this);
+ if (!forceShow && preferenceManager.getBoolean(KEY_TRUST_ONBOARDING, false)) {
+ return;
+ }
+
+ preferenceManager.edit()
+ .putBoolean(KEY_TRUST_ONBOARDING, true)
+ .apply();
+
+ new AlertDialog.Builder(this)
+ .setView(R.layout.dialog_trust_welcome)
+ .setPositiveButton(android.R.string.ok, null)
+ .show();
+ }
+}
diff --git a/src/com/android/launcher3/lineage/trust/TrustAppsAdapter.java b/src/com/android/launcher3/lineage/trust/TrustAppsAdapter.java
new file mode 100644
index 00000000000..6b827d6e1e8
--- /dev/null
+++ b/src/com/android/launcher3/lineage/trust/TrustAppsAdapter.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2019 The LineageOS 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.lineage.trust;
+
+import android.graphics.drawable.Animatable2;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.DiffUtil;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.launcher3.R;
+import com.android.launcher3.lineage.trust.db.TrustComponent;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class TrustAppsAdapter extends RecyclerView.Adapter {
+ private List mList = new ArrayList<>();
+ private Listener mListener;
+ private boolean mHasSecureKeyguard;
+
+ TrustAppsAdapter(Listener listener, boolean hasSecureKeyguard) {
+ mListener = listener;
+ mHasSecureKeyguard = hasSecureKeyguard;
+ }
+
+ public void update(List list) {
+ DiffUtil.DiffResult result = DiffUtil.calculateDiff(new Callback(mList, list));
+ mList = list;
+ result.dispatchUpdatesTo(this);
+ }
+
+ @NonNull
+ @Override
+ public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int type) {
+ return new ViewHolder(LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.item_hidden_app, parent, false));
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ViewHolder viewHolder, int i) {
+ viewHolder.bind(mList.get(i), mHasSecureKeyguard);
+ }
+
+ @Override
+ public int getItemCount() {
+ return mList.size();
+ }
+
+ public interface Listener {
+ void onHiddenItemChanged(@NonNull TrustComponent component);
+
+ void onProtectedItemChanged(@NonNull TrustComponent component);
+ }
+
+ class ViewHolder extends RecyclerView.ViewHolder {
+ private ImageView mIconView;
+ private TextView mLabelView;
+ private ImageView mHiddenView;
+ private ImageView mProtectedView;
+
+ ViewHolder(@NonNull View itemView) {
+ super(itemView);
+
+ mIconView = itemView.findViewById(R.id.item_hidden_app_icon);
+ mLabelView = itemView.findViewById(R.id.item_hidden_app_title);
+ mHiddenView = itemView.findViewById(R.id.item_hidden_app_switch);
+ mProtectedView = itemView.findViewById(R.id.item_protected_app_switch);
+ }
+
+ void bind(TrustComponent component, boolean hasSecureKeyguard) {
+ mIconView.setImageDrawable(component.getIcon());
+ mLabelView.setText(component.getLabel());
+
+ mHiddenView.setImageResource(component.isHidden() ?
+ R.drawable.ic_hidden_locked : R.drawable.ic_hidden_unlocked);
+ mProtectedView.setImageResource(component.isProtected() ?
+ R.drawable.ic_protected_locked : R.drawable.ic_protected_unlocked);
+
+ mProtectedView.setVisibility(hasSecureKeyguard ? View.VISIBLE : View.GONE);
+
+ mHiddenView.setOnClickListener(v -> {
+ component.invertVisibility();
+
+ mHiddenView.setImageResource(component.isHidden() ?
+ R.drawable.avd_hidden_lock : R.drawable.avd_hidden_unlock);
+ AnimatedVectorDrawable avd = (AnimatedVectorDrawable) mHiddenView.getDrawable();
+
+ int position = getAdapterPosition();
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
+ avd.registerAnimationCallback(new Animatable2.AnimationCallback() {
+ @Override
+ public void onAnimationEnd(Drawable drawable) {
+ updateHiddenList(position, component);
+ }
+ });
+ avd.start();
+ } else {
+ avd.start();
+ updateHiddenList(position, component);
+ }
+ });
+
+ mProtectedView.setOnClickListener(v -> {
+ component.invertProtection();
+
+ mProtectedView.setImageResource(component.isProtected() ?
+ R.drawable.avd_protected_lock : R.drawable.avd_protected_unlock);
+ AnimatedVectorDrawable avd = (AnimatedVectorDrawable) mProtectedView.getDrawable();
+
+ int position = getAdapterPosition();
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
+ avd.registerAnimationCallback(new Animatable2.AnimationCallback() {
+ @Override
+ public void onAnimationEnd(Drawable drawable) {
+ updateProtectedList(position, component);
+ }
+ });
+ avd.start();
+ } else {
+ avd.start();
+ updateProtectedList(position, component);
+ }
+ });
+ }
+
+ private void updateHiddenList(int position, TrustComponent component) {
+ mListener.onHiddenItemChanged(component);
+ updateList(position, component);
+ }
+
+ private void updateProtectedList(int position, TrustComponent component) {
+ mListener.onProtectedItemChanged(component);
+ updateList(position, component);
+ }
+
+ private void updateList(int position, TrustComponent component) {
+ mList.set(position, component);
+ notifyItemChanged(position);
+ }
+ }
+
+ private static class Callback extends DiffUtil.Callback {
+ List mOldList;
+ List mNewList;
+
+ public Callback(List oldList,
+ List newList) {
+ mOldList = oldList;
+ mNewList = newList;
+ }
+
+
+ @Override
+ public int getOldListSize() {
+ return mOldList.size();
+ }
+
+ @Override
+ public int getNewListSize() {
+ return mNewList.size();
+ }
+
+ @Override
+ public boolean areItemsTheSame(int iOld, int iNew) {
+ String oldPkg = mOldList.get(iOld).getPackageName();
+ String newPkg = mNewList.get(iNew).getPackageName();
+ return oldPkg.equals(newPkg);
+ }
+
+ @Override
+ public boolean areContentsTheSame(int iOld, int iNew) {
+ return mOldList.get(iOld).equals(mNewList.get(iNew));
+ }
+ }
+}
diff --git a/src/com/android/launcher3/lineage/trust/UpdateItemTask.java b/src/com/android/launcher3/lineage/trust/UpdateItemTask.java
new file mode 100644
index 00000000000..aad0f1a99e6
--- /dev/null
+++ b/src/com/android/launcher3/lineage/trust/UpdateItemTask.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2019 The LineageOS 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.lineage.trust;
+
+import android.os.AsyncTask;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.lineage.trust.db.TrustComponent;
+import com.android.launcher3.lineage.trust.db.TrustDatabaseHelper;
+
+public class UpdateItemTask extends AsyncTask {
+ @NonNull
+ private TrustDatabaseHelper mDbHelper;
+ @NonNull
+ private UpdateCallback mCallback;
+ @NonNull
+ private TrustComponent.Kind mKind;
+
+ UpdateItemTask(@NonNull TrustDatabaseHelper dbHelper,
+ @NonNull UpdateCallback callback,
+ @NonNull TrustComponent.Kind kind) {
+ mDbHelper = dbHelper;
+ mCallback = callback;
+ mKind = kind;
+ }
+
+ @Override
+ protected Boolean doInBackground(TrustComponent... trustComponents) {
+ if (trustComponents.length < 1) {
+ return false;
+ }
+
+ TrustComponent component = trustComponents[0];
+ String pkgName = component.getPackageName();
+
+ switch (mKind) {
+ case HIDDEN:
+ if (component.isHidden()) {
+ mDbHelper.addHiddenApp(pkgName);
+ } else {
+ mDbHelper.removeHiddenApp(pkgName);
+ }
+ break;
+ case PROTECTED:
+ if (component.isProtected()) {
+ mDbHelper.addProtectedApp(pkgName);
+ } else {
+ mDbHelper.removeProtectedApp(pkgName);
+ }
+ break;
+ }
+ return true;
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ mCallback.onUpdated(result);
+ }
+
+ interface UpdateCallback {
+ void onUpdated(boolean result);
+ }
+}
diff --git a/src/com/android/launcher3/lineage/trust/db/TrustComponent.java b/src/com/android/launcher3/lineage/trust/db/TrustComponent.java
new file mode 100644
index 00000000000..5342bde3df4
--- /dev/null
+++ b/src/com/android/launcher3/lineage/trust/db/TrustComponent.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2019 The LineageOS 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.lineage.trust.db;
+
+import android.graphics.drawable.Drawable;
+import androidx.annotation.NonNull;
+
+public class TrustComponent {
+ @NonNull
+ private final String mPackageName;
+ @NonNull
+ private final Drawable mIcon;
+ @NonNull
+ private final String mLabel;
+
+ private boolean mIsHidden;
+ private boolean mIsProtected;
+
+ public TrustComponent(@NonNull String packageName, @NonNull Drawable icon,
+ @NonNull String label, boolean isHidden, boolean isProtected) {
+ mPackageName = packageName;
+ mIcon = icon;
+ mLabel = label;
+ mIsHidden = isHidden;
+ mIsProtected = isProtected;
+ }
+
+ @NonNull
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ @NonNull
+ public Drawable getIcon() {
+ return mIcon;
+ }
+
+ @NonNull
+ public String getLabel() {
+ return mLabel;
+ }
+
+ public boolean isHidden() {
+ return mIsHidden;
+ }
+
+ public boolean isProtected() {
+ return mIsProtected;
+ }
+
+ public void invertVisibility() {
+ mIsHidden = !mIsHidden;
+ }
+
+ public void invertProtection() {
+ mIsProtected = !mIsProtected;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof TrustComponent)) {
+ return false;
+ }
+
+ TrustComponent otherComponent = (TrustComponent) other;
+ return otherComponent.getPackageName().equals(mPackageName) &&
+ otherComponent.isHidden() == mIsHidden;
+ }
+
+ @Override
+ public int hashCode() {
+ return mPackageName.hashCode() + (mIsHidden ? 1 : 0);
+ }
+
+ public enum Kind {
+ HIDDEN,
+ PROTECTED,
+ }
+}
diff --git a/src/com/android/launcher3/lineage/trust/db/TrustDatabaseHelper.java b/src/com/android/launcher3/lineage/trust/db/TrustDatabaseHelper.java
new file mode 100644
index 00000000000..fcbdbe97ff9
--- /dev/null
+++ b/src/com/android/launcher3/lineage/trust/db/TrustDatabaseHelper.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2019 The LineageOS 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.lineage.trust.db;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+public class TrustDatabaseHelper extends SQLiteOpenHelper {
+ private static final int DATABASE_VERSION = 1;
+ private static final String DATABASE_NAME = "trust_apps_db";
+
+ private static final String TABLE_NAME = "trust_apps";
+ private static final String KEY_UID = "uid";
+ private static final String KEY_PKGNAME = "pkgname";
+ private static final String KEY_HIDDEN = "hidden";
+ private static final String KEY_PROTECTED = "protected";
+
+ @Nullable
+ private static TrustDatabaseHelper sSingleton;
+
+ private TrustDatabaseHelper(@NonNull Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ public static synchronized TrustDatabaseHelper getInstance(@NonNull Context context) {
+ if (sSingleton == null) {
+ sSingleton = new TrustDatabaseHelper(context);
+ }
+
+ return sSingleton;
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ String CMD_CREATE_TABLE = "CREATE TABLE " + TABLE_NAME +
+ "(" +
+ KEY_UID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+ KEY_PKGNAME + " TEXT," +
+ KEY_HIDDEN + " INTEGER DEFAULT 0," +
+ KEY_PROTECTED + " INTEGER DEFAULT 0" +
+ ")";
+ db.execSQL(CMD_CREATE_TABLE);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ }
+
+ public void addHiddenApp(@NonNull String packageName) {
+ if (isPackageHidden(packageName)) {
+ return;
+ }
+
+ SQLiteDatabase db = getWritableDatabase();
+ db.beginTransaction();
+
+ try {
+ ContentValues values = new ContentValues();
+ values.put(KEY_PKGNAME, packageName);
+ values.put(KEY_HIDDEN, 1);
+
+ int rows = db.update(TABLE_NAME, values, KEY_PKGNAME + " = ?",
+ new String[]{KEY_PKGNAME});
+ if (rows != 1) {
+ // Entry doesn't exist, create a new one
+ db.insertOrThrow(TABLE_NAME, null, values);
+ }
+ db.setTransactionSuccessful();
+ } catch (Exception e) {
+ // Ignored
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ public void addProtectedApp(@NonNull String packageName) {
+ if (isPackageProtected(packageName)) {
+ return;
+ }
+
+ SQLiteDatabase db = getWritableDatabase();
+ db.beginTransaction();
+
+ try {
+ ContentValues values = new ContentValues();
+ values.put(KEY_PKGNAME, packageName);
+ values.put(KEY_PROTECTED, 1);
+
+ int rows = db.update(TABLE_NAME, values, KEY_PKGNAME + " = ?",
+ new String[]{KEY_PKGNAME});
+ if (rows != 1) {
+ // Entry doesn't exist, create a new one
+ db.insertOrThrow(TABLE_NAME, null, values);
+ }
+ db.setTransactionSuccessful();
+ } catch (Exception e) {
+ // Ignored
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+
+ public void removeHiddenApp(@NonNull String packageName) {
+ if (!isPackageHidden(packageName)) {
+ return;
+ }
+
+ SQLiteDatabase db = getWritableDatabase();
+ db.beginTransaction();
+
+ try {
+ ContentValues values = new ContentValues();
+ values.put(KEY_HIDDEN, 0);
+
+ db.update(TABLE_NAME, values, KEY_PKGNAME + " = ?", new String[]{packageName});
+ db.setTransactionSuccessful();
+ } catch (Exception e) {
+ // Ignored
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ public void removeProtectedApp(@NonNull String packageName) {
+ if (!isPackageProtected(packageName)) {
+ return;
+ }
+
+ SQLiteDatabase db = getWritableDatabase();
+ db.beginTransaction();
+
+ try {
+ ContentValues values = new ContentValues();
+ values.put(KEY_PROTECTED, 0);
+
+ db.update(TABLE_NAME, values, KEY_PKGNAME + " = ?", new String[]{packageName});
+ db.setTransactionSuccessful();
+ } catch (Exception e) {
+ // Ignored
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ public boolean isPackageHidden(@NonNull String packageName) {
+ String query = String.format("SELECT * FROM %s WHERE %s = ? AND %s = ?", TABLE_NAME,
+ KEY_PKGNAME, KEY_HIDDEN);
+ SQLiteDatabase db = getReadableDatabase();
+ Cursor cursor = db.rawQuery(query, new String[]{packageName, String.valueOf(1)});
+ boolean result = false;
+ try {
+ result = cursor.getCount() != 0;
+ } catch (Exception e) {
+ // Ignored
+ } finally {
+ if (cursor != null && !cursor.isClosed()) {
+ cursor.close();
+ }
+ }
+
+ return result;
+ }
+
+ public int getTotalPackageHidden() {
+ String query = String.format("SELECT * FROM %s WHERE %s = ?", TABLE_NAME, KEY_HIDDEN);
+ SQLiteDatabase db = getReadableDatabase();
+ Cursor cursor = db.rawQuery(query, new String[]{ String.valueOf(1) });
+ int result = 0;
+ try {
+ result = cursor.getCount();
+ } catch (Exception e) {
+ // Ignored
+ } finally {
+ if (cursor != null && !cursor.isClosed()) {
+ cursor.close();
+ }
+ }
+
+ return result;
+ }
+
+ public boolean isPackageProtected(@NonNull String packageName) {
+ String query = String.format("SELECT * FROM %s WHERE %s = ? AND %s = ?", TABLE_NAME,
+ KEY_PKGNAME, KEY_PROTECTED);
+ SQLiteDatabase db = getReadableDatabase();
+ Cursor cursor = db.rawQuery(query, new String[]{packageName, String.valueOf(1)});
+ boolean result = false;
+ try {
+ result = cursor.getCount() != 0;
+ } catch (Exception e) {
+ // Ignored
+ } finally {
+ if (cursor != null && !cursor.isClosed()) {
+ cursor.close();
+ }
+ }
+
+ return result;
+ }
+}
diff --git a/src/com/android/launcher3/logging/FileLog.kt b/src/com/android/launcher3/logging/FileLog.kt
index 2d3f0458418..c1e8559333a 100644
--- a/src/com/android/launcher3/logging/FileLog.kt
+++ b/src/com/android/launcher3/logging/FileLog.kt
@@ -43,7 +43,7 @@ import java.util.concurrent.TimeUnit
* but whose effect may not be visible immediately.
*/
object FileLog {
- const val ENABLED: Boolean = true
+ const val ENABLED: Boolean = false
private const val FILE_NAME_PREFIX = "log-"
private const val MAX_LOG_FILE_SIZE = (8 shl 20 /** 8 mb **/ ).toLong()
public const val LOG_DAYS: Int = 4
diff --git a/src/com/android/launcher3/model/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java
index b712b1ecdd6..864c10be1d9 100644
--- a/src/com/android/launcher3/model/AllAppsList.java
+++ b/src/com/android/launcher3/model/AllAppsList.java
@@ -33,8 +33,11 @@
import com.android.launcher3.AppFilter;
import com.android.launcher3.Flags;
import com.android.launcher3.compat.AlphabeticIndexCompat;
+import com.android.launcher3.dagger.ApplicationContext;
import com.android.launcher3.dagger.LauncherAppSingleton;
import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.lineage.trust.db.TrustDatabaseHelper;
+import com.android.launcher3.lineage.trust.HiddenAppsFilter;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.AppsListData;
import com.android.launcher3.model.data.ItemInfo;
@@ -42,6 +45,7 @@
import com.android.launcher3.pm.PackageInstallInfo;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.ApiWrapper;
+import com.android.launcher3.util.CustomAppNameStore;
import com.android.launcher3.util.FlagOp;
import com.android.launcher3.util.PackageManagerHelper;
@@ -83,6 +87,8 @@ public class AllAppsList {
private boolean mDataChanged = false;
+ private TrustDatabaseHelper mTrustData;
+
private AlphabeticIndexCompat mIndex;
/**
@@ -99,11 +105,13 @@ public class AllAppsList {
*/
@Inject
public AllAppsList(@NonNull IconCache iconCache,
- @NonNull AppFilter appFilter,
- @NonNull Provider repositoryProvider) {
+ @NonNull HiddenAppsFilter appFilter,
+ @NonNull Provider repositoryProvider,
+ @ApplicationContext Context context) {
mIconCache = iconCache;
mAppFilter = appFilter;
mRepo = repositoryProvider;
+ mTrustData = TrustDatabaseHelper.getInstance(context);
mIndex = new AlphabeticIndexCompat(LocaleList.getDefault());
}
@@ -155,6 +163,9 @@ public void add(AppInfo info, LauncherActivityInfo activityInfo) {
}
public void add(AppInfo info, LauncherActivityInfo activityInfo, boolean loadIcon) {
+ if (mTrustData.isPackageHidden(info.getTargetPackage())) {
+ return;
+ }
if (!mAppFilter.shouldShowApp(info.componentName)) {
return;
}
@@ -252,6 +263,24 @@ public void updateIconsAndLabels(HashSet packages, UserHandle user) {
}
}
+ /** Updates the display title for a single app after a custom rename. */
+ public void updateCustomAppTitle(Context context, IconCache iconCache,
+ ComponentName component, UserHandle user) {
+ for (AppInfo info : data) {
+ if (!info.user.equals(user) || !component.equals(info.componentName)) {
+ continue;
+ }
+ String customTitle = CustomAppNameStore.getCustomName(context, info);
+ if (customTitle != null) {
+ info.title = customTitle;
+ } else {
+ iconCache.updateTitleAndIcon(info);
+ }
+ info.sectionName = mIndex.computeSectionName(info.title);
+ mDataChanged = true;
+ }
+ }
+
/**
* Add and remove icons for this package which has been updated.
* @param outRemovedComponents any component removed as a result of this update will
diff --git a/src/com/android/launcher3/model/DatabaseHelper.java b/src/com/android/launcher3/model/DatabaseHelper.java
index ae84c6486bf..cda9122cbb0 100644
--- a/src/com/android/launcher3/model/DatabaseHelper.java
+++ b/src/com/android/launcher3/model/DatabaseHelper.java
@@ -254,7 +254,7 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Favorites.SCREEN, IntArray.wrap(-777, -778)), null);
}
case 30: {
- if (BuildConfig.QSB_ON_FIRST_SCREEN) {
+ if (BuildConfig.USE_QUICKSPACE_VIEW) {
// Clean up first row in screen 0 as it might contain junk data.
Log.d(TAG, "Cleaning up first row");
db.delete(Favorites.TABLE_NAME,
diff --git a/src/com/android/launcher3/model/GridSizeMigrationLogic.kt b/src/com/android/launcher3/model/GridSizeMigrationLogic.kt
index 61797e24036..406c6d79c80 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationLogic.kt
+++ b/src/com/android/launcher3/model/GridSizeMigrationLogic.kt
@@ -26,7 +26,6 @@ import com.android.launcher3.LauncherSettings
import com.android.launcher3.LauncherSettings.Favorites
import com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME
import com.android.launcher3.LauncherSettings.Favorites.TMP_TABLE
-import com.android.launcher3.Utilities.qsbOnFirstScreen
import com.android.launcher3.dagger.ApplicationContext
import com.android.launcher3.logging.FileLog
import com.android.launcher3.logging.StatsLogManager
@@ -509,7 +508,7 @@ constructor(
val occupied = GridOccupancy(trgX, trgY)
val trg = Point(trgX, trgY)
val next: Point =
- if (screenId == 0 && qsbOnFirstScreen()) {
+ if (screenId == 0 && LauncherPrefs.SHOW_QUICKSPACE.get(context)) {
Point(0, 1 /* smartspace */)
} else {
Point(0, 0)
diff --git a/src/com/android/launcher3/model/ItemInstallQueue.java b/src/com/android/launcher3/model/ItemInstallQueue.java
index f2b1a7d6219..098e1a40772 100644
--- a/src/com/android/launcher3/model/ItemInstallQueue.java
+++ b/src/com/android/launcher3/model/ItemInstallQueue.java
@@ -54,6 +54,7 @@
import com.android.launcher3.dagger.LauncherBaseAppComponent;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.model.tasks.AddWorkspaceItemsTask;
@@ -205,6 +206,13 @@ public void queueItem(String packageName, UserHandle userHandle) {
queuePendingShortcutInfo(new PendingInstallShortcutInfo(packageName, userHandle));
}
+ /**
+ * Adds an app activity item to the install queue.
+ */
+ public void queueItem(AppInfo appInfo) {
+ queuePendingShortcutInfo(new PendingInstallShortcutInfo(appInfo.getIntent(), appInfo.user));
+ }
+
/**
* Returns a stream of all pending shortcuts in the queue
*/
@@ -273,6 +281,15 @@ public PendingInstallShortcutInfo(String packageName, UserHandle userHandle) {
user = userHandle;
}
+ /**
+ * Initializes a PendingInstallShortcutInfo to represent a specific launcher
+ * activity intent.
+ */
+ public PendingInstallShortcutInfo(Intent launchIntent, UserHandle userHandle) {
+ itemType = Favorites.ITEM_TYPE_APPLICATION;
+ intent = new Intent(launchIntent);
+ user = userHandle;
+ }
/**
* Initializes a PendingInstallShortcutInfo to represent a deep shortcut.
*/
@@ -308,30 +325,48 @@ public Pair getItemInfo(Context context) {
switch (itemType) {
case ITEM_TYPE_APPLICATION: {
String packageName = intent.getPackage();
- List laiList =
- context.getSystemService(LauncherApps.class)
- .getActivityList(packageName, user);
+ ComponentName requestedComponent = intent.getComponent();
+ if (packageName == null && requestedComponent != null) {
+ packageName = requestedComponent.getPackageName();
+ }
+ if (packageName == null) {
+ return null;
+ }
+ LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
+ List laiList = launcherApps.getActivityList(packageName, user);
final WorkspaceItemInfo si = new WorkspaceItemInfo();
si.user = user;
- LauncherActivityInfo lai;
- boolean usePackageIcon = laiList.isEmpty();
+ LauncherActivityInfo lai = null;
+ if (requestedComponent != null) {
+ for (LauncherActivityInfo info : laiList) {
+ if (requestedComponent.equals(info.getComponentName())) {
+ lai = info;
+ break;
+ }
+ }
+ }
+ if (lai == null && !laiList.isEmpty()) {
+ lai = laiList.get(0);
+ }
+
+ boolean usePackageIcon = lai == null;
if (usePackageIcon) {
- lai = null;
- si.intent = makeLaunchIntent(new ComponentName(packageName, ""))
- .setPackage(packageName);
+ ComponentName fallbackComponent = requestedComponent != null
+ ? requestedComponent : new ComponentName(packageName, "");
+ si.intent = makeLaunchIntent(fallbackComponent).setPackage(packageName);
si.status |= WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON;
} else {
- lai = laiList.get(0);
si.intent = makeLaunchIntent(lai);
if (Flags.enableSupportForArchiving()
&& lai.getActivityInfo().isArchived) {
si.runtimeStatusFlags |= FLAG_ARCHIVED;
}
}
+ final LauncherActivityInfo activityInfoForIcon = lai;
LauncherAppState.getInstance(context).getIconCache()
- .getTitleAndIcon(si, () -> lai,
+ .getTitleAndIcon(si, () -> activityInfoForIcon,
DESKTOP_ICON_FLAG.withUsePackageIcon(usePackageIcon));
return Pair.create(si, null);
}
@@ -395,7 +430,7 @@ private static String getIntentPackage(Intent intent) {
private PendingInstallShortcutInfo decode(int itemType, UserHandle user, Intent intent) {
switch (itemType) {
case Favorites.ITEM_TYPE_APPLICATION:
- return new PendingInstallShortcutInfo(intent.getPackage(), user);
+ return new PendingInstallShortcutInfo(intent, user);
case Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
List si = ShortcutKey.fromIntent(intent, user)
.buildRequest(mContext)
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index ab4b7408369..963e119236e 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -22,7 +22,6 @@
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
-import static com.android.launcher3.Utilities.qsbOnFirstScreen;
import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED;
import static com.android.launcher3.model.data.WorkspaceItemInfo.FLAG_RESTORED_FULL_BLEED;
@@ -638,7 +637,7 @@ protected boolean checkItemPlacement(ItemInfo item) {
if (!mOccupied.containsKey(item.screenId)) {
GridOccupancy screen = new GridOccupancy(countX + 1, countY + 1);
- if (qsbOnFirstScreen() && item.screenId == Workspace.FIRST_SCREEN_ID) {
+ if (LauncherPrefs.SHOW_QUICKSPACE.get(mContext) && item.screenId == Workspace.FIRST_SCREEN_ID) {
// Mark the first X columns (X is width of the search container) in the first row as
// occupied (if the feature is enabled) in order to account for the search
// container.
diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java
index f2144c0e3ab..5758d4c6cf0 100644
--- a/src/com/android/launcher3/model/WidgetsModel.java
+++ b/src/com/android/launcher3/model/WidgetsModel.java
@@ -30,6 +30,8 @@
import com.android.launcher3.dagger.ApplicationContext;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.cache.CachedObject;
+import com.android.launcher3.lineage.trust.db.TrustDatabaseHelper;
+import com.android.launcher3.lineage.trust.HiddenAppsFilter;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.pm.ShortcutConfigActivityInfo;
import com.android.launcher3.util.ComponentKey;
@@ -75,6 +77,7 @@ public class WidgetsModel {
private final InvariantDeviceProfile mIdp;
private final IconCache mIconCache;
private final AppFilter mAppFilter;
+ private final TrustDatabaseHelper mTrustData;
@Inject
public WidgetsModel(
@@ -86,12 +89,14 @@ public WidgetsModel(
mIdp = idp;
mIconCache = iconCache;
mAppFilter = appFilter;
+ mTrustData = TrustDatabaseHelper.getInstance(context);
}
public WidgetsModel(Context context) {
this(context,
LauncherAppState.getIDP(context),
- LauncherAppState.getInstance(context).getIconCache(), new AppFilter(context));
+ LauncherAppState.getInstance(context).getIconCache(),
+ new HiddenAppsFilter(context));
}
/**
@@ -202,7 +207,8 @@ private synchronized void setWidgetsAndShortcuts(
}
// Refresh the validity checker with latest app state.
- mWidgetValidityCheckForPicker = new WidgetValidityCheckForPicker(mIdp, mAppFilter);
+ mWidgetValidityCheckForPicker = new WidgetValidityCheckForPicker(mIdp, mAppFilter,
+ mTrustData);
// Temporary cache for {@link PackageItemInfos} to avoid having to go through
// {@link mPackageItemInfos} to locate the key to be used for {@link #mWidgetsList}
@@ -296,14 +302,21 @@ private static class WidgetValidityCheckForPicker implements Predicate
+ if (
+ item.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
+ item.targetComponent != component
+ ) {
+ return@updateAndCollectWorkspaceItemInfos false
+ }
+ applyDisplayTitle(context, iconCache, activityInfo, item)
+ true
+ },
+ null,
+ )
+
+ apps.updateCustomAppTitle(context, iconCache, component, user)
+
+ taskController.bindUpdatedWorkspaceItems(updatedWorkspaceItems)
+ taskController.bindApplicationsIfNeeded()
+ }
+ }
+
+ companion object {
+ private fun applyDisplayTitle(
+ context: android.content.Context,
+ iconCache: IconCache,
+ activityInfo: android.content.pm.LauncherActivityInfo?,
+ info: ItemInfoWithIcon,
+ ) {
+ val customTitle = CustomAppNameStore.getCustomName(context, info)
+ if (customTitle != null) {
+ info.title = customTitle
+ return
+ }
+ if (activityInfo != null) {
+ iconCache.getTitleAndIcon(info, activityInfo, info.matchingLookupFlag)
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/model/tasks/PackageUpdatedTask.java b/src/com/android/launcher3/model/tasks/PackageUpdatedTask.java
index c4400995fe3..e431016ea9e 100644
--- a/src/com/android/launcher3/model/tasks/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/tasks/PackageUpdatedTask.java
@@ -29,6 +29,7 @@
import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
import android.os.UserHandle;
+import android.widget.Toast;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -36,6 +37,8 @@
import com.android.launcher3.Flags;
import com.android.launcher3.LauncherModel.ModelUpdateTask;
import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.AllAppsList;
@@ -97,11 +100,15 @@ public void execute(@NonNull ModelTaskController taskController, @NonNull BgData
final FlagOp flagOp = FlagOp.NO_OP.removeFlag(FLAG_DISABLED_NOT_AVAILABLE);
final HashSet removedComponents = new HashSet<>();
final HashMap> activitiesLists = new HashMap<>();
+ boolean needsRestart = false;
for (String packageName : mPackages) {
iconCache.updateIconsForPkg(packageName, mUser);
activitiesLists.put(
packageName,
appsList.updatePackage(context, packageName, mUser, removedComponents));
+ if (isTargetPackage(packageName)) {
+ needsRestart = true;
+ }
}
taskController.bindApplicationsIfNeeded();
@@ -306,6 +313,11 @@ public void execute(@NonNull ModelTaskController taskController, @NonNull BgData
}
taskController.bindUpdatedWidgets(dataModel);
}
+
+ if (needsRestart) {
+ Toast.makeText(context, R.string.updating_launcher_components, Toast.LENGTH_SHORT).show();
+ Utilities.restart();
+ }
}
/**
@@ -338,4 +350,9 @@ private boolean shouldRemoveRestoredShortcut(WorkspaceItemInfo itemInfo) {
&& !itemInfo.isArchived()
&& itemInfo.itemType == ITEM_TYPE_DEEP_SHORTCUT;
}
+
+ private boolean isTargetPackage(String packageName) {
+ return packageName.equals(Utilities.GSA_PACKAGE) ||
+ packageName.equals(Utilities.GEMINI_PACKAGE);
+ }
}
diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java
index 468599a44a5..5566262f8b6 100644
--- a/src/com/android/launcher3/notification/NotificationListener.java
+++ b/src/com/android/launcher3/notification/NotificationListener.java
@@ -185,6 +185,9 @@ private void dispatchUpdate(Predicate updatedDots) {
result = getActiveNotifications(keys);
} catch (SecurityException e) {
Log.e(TAG, "SecurityException: failed to fetch notifications");
+ } catch (RuntimeException e) {
+ // This is an upstream bug in the Android framework
+ Log.e(TAG, "RuntimeException: failed to fetch notifications");
}
return result == null ? new StatusBarNotification[0] : result;
}
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
index 2a1c799f473..fd8308ee446 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
@@ -266,6 +266,7 @@ public void setShouldAutoHide(boolean shouldAutoHide) {
hideAfterDelay();
} else if (!shouldAutoHide) {
mDelayedPaginationFadeHandler.removeCallbacksAndMessages(null);
+ animatePaginationToAlpha(VISIBLE_ALPHA);
}
}
diff --git a/src/com/android/launcher3/popup/Launcher3PopupDialog.kt b/src/com/android/launcher3/popup/Launcher3PopupDialog.kt
new file mode 100644
index 00000000000..6da4d4624df
--- /dev/null
+++ b/src/com/android/launcher3/popup/Launcher3PopupDialog.kt
@@ -0,0 +1,17 @@
+package com.android.launcher3.popup
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+
+import com.android.launcher3.R
+import com.android.launcher3.views.ActivityContext
+import com.android.launcher3.views.OptionsPopupView
+
+class Launcher3PopupDialog {
+ class Launcher3OptionsPopUp(context: T, attributeSet: AttributeSet) : OptionsPopupView(context, attributeSet)
+ where T : Context, T : ActivityContext {
+
+ override fun isShortcutOrWrapper(view: View): Boolean = view.id == R.id.wallpaper_container || super.isShortcutOrWrapper(view)
+ }
+}
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 3845d446e0b..650a88db502 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -1,6 +1,8 @@
package com.android.launcher3.popup;
import static com.android.launcher3.AbstractFloatingView.TYPE_FOLDER;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_TASK;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APPS;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APPS_PREDICTION;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DISMISS_PREDICTION_UNDO;
@@ -11,19 +13,32 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_WIDGETS_TAP;
import static com.android.launcher3.widget.picker.model.data.WidgetPickerDataUtils.findAllWidgetsForPackageUser;
+import android.app.ActivityManagerNative;
+import android.app.AlertDialog;
+import android.app.IActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
import android.graphics.Rect;
+import android.net.Uri;
import android.os.Process;
+import android.os.RemoteException;
import android.os.UserHandle;
+import android.text.InputFilter;
import android.util.Log;
+import android.view.InflateException;
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
+import android.widget.Toast;
+import android.os.UserHandle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -32,6 +47,8 @@
import com.android.launcher3.AbstractFloatingViewHelper;
import com.android.launcher3.DropTargetHandler;
import com.android.launcher3.Flags;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
@@ -39,6 +56,7 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.allapps.PrivateProfileManager;
+import com.android.launcher3.customization.InfoBottomSheet;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
@@ -48,6 +66,7 @@
import com.android.launcher3.util.ApiWrapper;
import com.android.launcher3.util.ApplicationInfoWrapper;
import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.CustomAppNameStore;
import com.android.launcher3.util.InstantAppResolver;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.PackageUserKey;
@@ -57,6 +76,7 @@
import com.android.launcher3.widget.picker.model.data.WidgetPickerData;
import com.android.wm.shell.shared.bubbles.logging.EntryPoint;
+import java.net.URISyntaxException;
import java.util.Arrays;
/**
@@ -236,12 +256,38 @@ public AccessibilityNodeInfo.AccessibilityAction createAccessibilityAction(
@Override
public void onClick(View view) {
+ dismissTaskMenuView();
Rect sourceBounds = Utilities.getViewBounds(view);
ActivityOptionsWrapper options = mTarget.getActivityLaunchOptions(view, mItemInfo);
- // Dismiss the taskMenu when the app launch animation is complete
- options.onEndCallback.add(this::dismissTaskMenuView);
- PackageManagerHelper.startDetailsActivityForInfo(view.getContext(), mItemInfo,
- sourceBounds, options.toBundle());
+
+ boolean fromRecents = (mItemInfo.itemType == ITEM_TYPE_TASK);
+ if (!fromRecents) {
+ if (mTarget instanceof Launcher) {
+ fromRecents = ((Launcher) mTarget)
+ .getStateManager()
+ .getState()
+ .isRecentsViewVisible;
+ } else {
+ fromRecents = true;
+ }
+ }
+
+ if (fromRecents) {
+ PackageManagerHelper.startDetailsActivityForInfo(view.getContext(), mItemInfo,
+ sourceBounds, options.toBundle());
+ } else {
+ try {
+ InfoBottomSheet cbs = (InfoBottomSheet) mTarget.getLayoutInflater().inflate(
+ R.layout.app_info_bottom_sheet,
+ mTarget.getDragLayer(),
+ false);
+ cbs.configureBottomSheet(sourceBounds, view.getContext());
+ cbs.populateAndShow(mItemInfo);
+ } catch (InflateException e) {
+ PackageManagerHelper.startDetailsActivityForInfo(view.getContext(), mItemInfo,
+ sourceBounds, options.toBundle());
+ }
+ }
mTarget.getStatsLogManager().logger().withItemInfo(mItemInfo)
.log(LAUNCHER_SYSTEM_SHORTCUT_APP_INFO_TAP);
}
@@ -494,6 +540,118 @@ public void onClick(View view) {
}
}
+ public static final Factory UNINSTALL = (activity, itemInfo, originalView) ->
+ itemInfo.getTargetComponent() == null ||
+ PackageManagerHelper.isSystemApp((Context) activity,
+ itemInfo.getTargetComponent().getPackageName())
+ ? null : new UnInstall(activity, itemInfo, originalView);
+
+ public static class UnInstall extends SystemShortcut {
+
+ public UnInstall(T target, ItemInfo itemInfo, View originalView) {
+ super(R.drawable.ic_uninstall_no_shadow, R.string.uninstall_drop_target_label,
+ target, itemInfo, originalView);
+ }
+
+ /**
+ * @return the component name that should be uninstalled or null.
+ */
+ private ComponentName getUninstallTarget(ItemInfo item, Context context) {
+ Intent intent = null;
+ UserHandle user = null;
+ if (item != null &&
+ (item.itemType == ITEM_TYPE_APPLICATION || item.itemType == ITEM_TYPE_TASK)) {
+ intent = item.getIntent();
+ user = item.user;
+ }
+ if (intent != null) {
+ LauncherActivityInfo info = context.getSystemService(LauncherApps.class)
+ .resolveActivity(intent, user);
+ if (info != null
+ && (info.getApplicationInfo().flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+ return info.getComponentName();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void onClick(View view) {
+ ComponentName cn = getUninstallTarget(mItemInfo, view.getContext());
+ if (cn == null) {
+ // System applications cannot be installed. For now, show a toast explaining that.
+ // We may give them the option of disabling apps this way.
+ Toast.makeText(view.getContext(), R.string.uninstall_system_app_text, Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ try {
+ Intent intent = Intent.parseUri(view.getContext().getString(R.string.delete_package_intent), 0)
+ .setData(Uri.fromParts("package", cn.getPackageName(), cn.getClassName()))
+ .putExtra(Intent.EXTRA_USER, mItemInfo.user);
+ ((Context) mTarget).startActivity(intent);
+ AbstractFloatingView.closeAllOpenViews(mTarget);
+ } catch (URISyntaxException e) {
+ // Do nothing.
+ }
+ }
+ }
+
+ public static final Factory KILL_APP = (activity, itemInfo, originalView) -> {
+ String packageName = itemInfo.getTargetComponent().getPackageName();
+ return packageName == null ? null : new KillApp(activity, itemInfo, originalView);
+ };
+
+ public static class KillApp extends SystemShortcut {
+ private final String mPackageName;
+
+ public KillApp(ActivityContext target, ItemInfo itemInfo, View originalView) {
+ super(R.drawable.ic_kill_app, R.string.recent_task_option_kill_app,
+ target, itemInfo, originalView);
+ mPackageName = itemInfo.getTargetComponent().getPackageName();
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (mPackageName != null) {
+ IActivityManager iam = ActivityManagerNative.getDefault();
+ try {
+ iam.forceStopPackage(mPackageName, UserHandle.USER_CURRENT);
+ Toast appKilled = Toast.makeText(view.getContext(), R.string.recents_app_killed, Toast.LENGTH_SHORT);
+ appKilled.show();
+ AbstractFloatingView.closeAllOpenViews(mTarget);
+ } catch (RemoteException e) { }
+ }
+ }
+ }
+
+ public static final Factory FLOATING = (activity, itemInfo, originalView) -> {
+ if (!Utilities.isResizeableActivity(originalView.getContext(),
+ itemInfo.getTargetComponent())) {
+ return null;
+ }
+ return new FloatingSystemShortcut(activity, itemInfo, originalView);
+ };
+
+ public static class FloatingSystemShortcut extends SystemShortcut {
+ private final ComponentName mComponentName;
+
+ public FloatingSystemShortcut(T target, ItemInfo itemInfo, View originalView) {
+ super(R.drawable.picture_in_picture_mobile_24px, R.string.recent_task_option_freeform,
+ target, itemInfo, originalView);
+ mComponentName = itemInfo.getTargetComponent();
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (mComponentName != null) {
+ Utilities.startLmoFreeform(view.getContext(), mComponentName,
+ mItemInfo.user.getIdentifier());
+ AbstractFloatingView.closeAllOpenViews(((ActivityContext) mTarget));
+ }
+ }
+ }
+
protected void dismissTaskMenuView() {
mAbstractFloatingViewHelper.closeOpenViews(mTarget, true,
AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
@@ -582,4 +740,147 @@ public void onClick(View view) {
}
}
}
+
+ public static final Factory RENAME_APP =
+ (activity, itemInfo, originalView) -> {
+ if (itemInfo.itemType == ITEM_TYPE_APPLICATION
+ && itemInfo.getTargetComponent() != null) {
+ return new RenameApp<>(activity, itemInfo, originalView);
+ }
+ return null;
+ };
+
+ public static class RenameApp extends SystemShortcut {
+ private static final int MAX_APP_NAME_LENGTH = 32;
+
+ public RenameApp(T target, ItemInfo itemInfo, @NonNull View originalView) {
+ super(getDrawableId(), R.string.rename_app_label, target,
+ itemInfo, originalView);
+ }
+
+ public static int getDrawableId() {
+ return R.drawable.ic_edit;
+ }
+
+ @Override
+ public void onClick(View view) {
+ dismissTaskMenuView();
+
+ Context context = view.getContext();
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(R.string.rename_app_title);
+
+ final EditText input = new EditText(context);
+ input.setText(mItemInfo.title);
+ input.setSelection(0, mItemInfo.title != null ? mItemInfo.title.length() : 0);
+ input.setFilters(new InputFilter[]{new InputFilter.LengthFilter(MAX_APP_NAME_LENGTH)});
+ input.setInputType(android.text.InputType.TYPE_CLASS_TEXT);
+
+ builder.setView(input);
+
+ builder.setPositiveButton(android.R.string.ok, (dialog, which) -> {
+ String newName = input.getText().toString().trim();
+ if (validateAndUpdateName(newName, context)) {
+ Toast.makeText(context,
+ R.string.app_renamed_successfully,
+ Toast.LENGTH_SHORT).show();
+ } else if (newName.isEmpty()) {
+ Toast.makeText(context,
+ R.string.rename_app_empty_error,
+ Toast.LENGTH_SHORT).show();
+ } else {
+ Toast.makeText(context,
+ context.getString(R.string.rename_app_length_error,
+ MAX_APP_NAME_LENGTH),
+ Toast.LENGTH_SHORT).show();
+ }
+ });
+
+ builder.setNegativeButton(android.R.string.cancel, (dialog, which) -> dialog.cancel());
+
+ if (CustomAppNameStore.getCustomName((Context) mTarget, mItemInfo) != null) {
+ builder.setNeutralButton(R.string.rename_app_reset, (dialog2, which2) -> {
+ resetToOriginalName(context);
+ Toast.makeText(context,
+ R.string.rename_app_reset_message,
+ Toast.LENGTH_SHORT).show();
+ });
+ }
+
+ AlertDialog dialog = builder.create();
+ dialog.show();
+
+ input.requestFocus();
+ InputMethodManager imm = (InputMethodManager)
+ context.getSystemService(Context.INPUT_METHOD_SERVICE);
+ if (imm != null) {
+ imm.showSoftInput(input, InputMethodManager.SHOW_IMPLICIT);
+ }
+ }
+
+ private boolean validateAndUpdateName(String newName, Context context) {
+ if (newName.isEmpty()) {
+ return false;
+ }
+
+ if (newName.length() > MAX_APP_NAME_LENGTH) {
+ return false;
+ }
+
+ mItemInfo.title = newName;
+
+ CustomAppNameStore.saveCustomName(context, mItemInfo, newName);
+ if (mItemInfo instanceof WorkspaceItemInfo) {
+ WorkspaceItemInfo wsInfo = (WorkspaceItemInfo) mItemInfo;
+ LauncherAppState.getInstance(context).getModel().getWriter(false, null, null)
+ .updateItemInDatabase(wsInfo);
+ }
+
+ forceUiUpdate(context);
+
+ return true;
+ }
+
+ private void resetToOriginalName(Context context) {
+ CustomAppNameStore.saveCustomName(context, mItemInfo, null);
+ CharSequence systemTitle = getSystemTitle(context, mItemInfo);
+ if (systemTitle != null) {
+ mItemInfo.title = systemTitle;
+ }
+
+ if (mItemInfo instanceof WorkspaceItemInfo && systemTitle != null) {
+ WorkspaceItemInfo wsInfo = (WorkspaceItemInfo) mItemInfo;
+ LauncherAppState.getInstance(context).getModel().getWriter(false, null, null)
+ .updateItemInDatabase(wsInfo);
+ }
+
+ forceUiUpdate(context);
+ }
+
+ @Nullable
+ private static CharSequence getSystemTitle(Context context, ItemInfo info) {
+ ComponentName cn = info.getTargetComponent();
+ if (cn == null) {
+ return null;
+ }
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ intent.setComponent(cn);
+ LauncherActivityInfo activityInfo = context.getSystemService(LauncherApps.class)
+ .resolveActivity(intent, info.user);
+ if (activityInfo != null) {
+ return Utilities.trim(activityInfo.getLabel());
+ }
+ return null;
+ }
+
+ private void forceUiUpdate(Context context) {
+ ComponentName cn = mItemInfo.getTargetComponent();
+ if (cn != null) {
+ LauncherAppState.getInstance(context).getModel()
+ .onCustomAppNameChanged(cn, mItemInfo.user);
+ }
+ }
+ }
}
diff --git a/src/com/android/launcher3/popup/WallpaperCarouselView.kt b/src/com/android/launcher3/popup/WallpaperCarouselView.kt
new file mode 100644
index 00000000000..40b97b1fa3f
--- /dev/null
+++ b/src/com/android/launcher3/popup/WallpaperCarouselView.kt
@@ -0,0 +1,252 @@
+package com.android.launcher3.popup
+
+import android.animation.ValueAnimator
+import android.annotation.SuppressLint
+import android.app.WallpaperManager
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.util.AttributeSet
+import android.util.Log
+import android.view.Gravity
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.ProgressBar
+import androidx.cardview.widget.CardView
+import androidx.core.content.ContextCompat
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.LauncherPrefs
+import com.android.launcher3.R
+import com.android.launcher3.data.wallpaper.Wallpaper
+import com.android.launcher3.data.wallpaper.service.WallpaperService
+import com.android.launcher3.util.Themes
+import com.android.launcher3.views.ActivityContext
+import com.android.launcher3.views.IconFrame
+import java.io.File
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+class WallpaperCarouselView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+) : LinearLayout(context, attrs, defStyleAttr) {
+
+ private val deviceProfile: DeviceProfile by lazy {
+ (ActivityContext.lookupContext(context) as ActivityContext).deviceProfile
+ }
+ private var currentItemIndex = 0
+ private val iconFrame = IconFrame(context).apply {
+ setIcon(R.drawable.ic_tick)
+ setBackgroundWithRadius(Themes.getColorAccent(context), 100F)
+ }
+ private val loadingView = ProgressBar(context).apply { isIndeterminate = true }
+
+ private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
+
+ private var applyJob: Job? = null
+
+ init {
+ orientation = HORIZONTAL
+ addView(loadingView)
+ observeWallpapers()
+ }
+
+ private fun observeWallpapers() {
+ loadingView.visibility = VISIBLE
+ scope.launch {
+ val wallpapers = withContext(Dispatchers.IO) {
+ runCatching { WallpaperService.INSTANCE.get(context).getTopWallpapers() }
+ .getOrDefault(emptyList())
+ }
+
+ if (!isAttachedToWindow) return@launch
+
+ visibility = if (wallpapers.isEmpty()) GONE else VISIBLE
+ if (wallpapers.isNotEmpty()) displayWallpapers(wallpapers) else loadingView.visibility = GONE
+ }
+ }
+
+ private fun displayWallpapers(wallpapers: List) {
+ removeAllViews()
+
+ val appliedIndex = wallpapers.indexOfFirst { it.rank == 0 }.let { if (it >= 0) it else 0 }
+ currentItemIndex = appliedIndex
+
+ val totalWidth = calculateTotalWidth()
+ val firstItemWidth = totalWidth * 0.4
+ val itemWidth = calculateItemWidth(totalWidth, wallpapers.size, firstItemWidth)
+ val margin = (totalWidth * 0.03).toInt()
+
+ wallpapers.forEachIndexed { index, wallpaper ->
+ val cardView = createCardView(index, firstItemWidth, itemWidth, margin, wallpaper)
+ addView(cardView)
+ loadWallpaperImage(wallpaper, cardView, index == currentItemIndex)
+ }
+ loadingView.visibility = GONE
+ }
+
+ private fun calculateTotalWidth(): Int {
+ return width.takeIf { it > 0 }
+ ?: (deviceProfile.deviceProperties.widthPx * if (deviceProfile.deviceProperties.isLandscape || deviceProfile.deviceProperties.isPhone) 0.5 else 0.8).toInt()
+ }
+
+ private fun calculateItemWidth(totalWidth: Int, itemCount: Int, firstItemWidth: Double): Double {
+ if (itemCount <= 1) return totalWidth.toDouble()
+ val remainingWidth = totalWidth - firstItemWidth
+ val marginBetweenItems = totalWidth * 0.03
+ return (remainingWidth - (marginBetweenItems * (itemCount - 1))) / (itemCount - 1)
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ private fun createCardView(
+ index: Int,
+ firstItemWidth: Double,
+ itemWidth: Double,
+ margin: Int,
+ wallpaper: Wallpaper,
+ ): CardView {
+ return CardView(context).apply {
+ radius = Themes.getDialogCornerRadius(context) / 2
+ layoutParams = LayoutParams(
+ if (index == currentItemIndex) firstItemWidth.toInt() else itemWidth.toInt(),
+ LayoutParams.MATCH_PARENT,
+ ).apply { setMargins(if (index > 0) margin else 0, 0, 0, 0) }
+
+ setOnTouchListener { _, ev ->
+ if (ev.actionMasked == MotionEvent.ACTION_DOWN) {
+ animateWidthTransition(index, firstItemWidth, itemWidth)
+ }
+ false
+ }
+ setOnClickListener {
+ currentItemIndex = index
+ setWallpaper(wallpaper, this)
+ }
+ }
+ }
+
+ private fun loadWallpaperImage(wallpaper: Wallpaper, cardView: CardView, isCurrent: Boolean) {
+ val path = wallpaper.imagePath
+ scope.launch {
+ val bitmap = withContext(Dispatchers.IO) {
+ runCatching {
+ val file = File(path)
+ if (!file.exists() || !file.canRead()) return@runCatching null
+ val opts = BitmapFactory.Options().apply { inSampleSize = 2 }
+ BitmapFactory.decodeFile(file.path, opts)
+ }.getOrNull()
+ }
+ if (!isAttachedToWindow) return@launch
+ if (bitmap != null) addImageView(cardView, bitmap, isCurrent)
+ }
+ }
+
+ private fun addImageView(cardView: CardView, bitmap: Bitmap, isCurrent: Boolean) {
+ val imageView = ImageView(context).apply {
+ setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_deepshortcut_placeholder))
+ scaleType = ImageView.ScaleType.CENTER_CROP
+ alpha = 1f
+ }
+ cardView.addView(imageView)
+ imageView.alpha = 0f
+ imageView.setImageBitmap(bitmap)
+ imageView.animate().alpha(1f).setDuration(200L).start()
+ if (isCurrent) {
+ addIconFrameToCenter(cardView)
+ }
+ }
+
+ private fun setWallpaper(wallpaper: Wallpaper, currentCardView: CardView) {
+ val spinner = createLoadingSpinner()
+
+ currentCardView.removeView(iconFrame)
+ currentCardView.addView(spinner)
+
+ applyJob?.cancel()
+ applyJob = scope.launch {
+ val success = withContext(Dispatchers.IO) {
+ runCatching {
+ val bmp = BitmapFactory.decodeFile(wallpaper.imagePath) ?: return@runCatching false
+ WallpaperManager.getInstance(context).setBitmap(
+ bmp, null, true, WallpaperManager.FLAG_SYSTEM
+ )
+ if (LauncherPrefs.WALLPAPER_CAROUSEL_LOCKSCREEN.get(context)) {
+ WallpaperManager.getInstance(context).setBitmap(
+ bmp, null, true, WallpaperManager.FLAG_LOCK
+ )
+ }
+ WallpaperService.INSTANCE.get(context).updateWallpaperRank(wallpaper)
+ true
+ }.getOrDefault(false)
+ }
+
+ if (!isAttachedToWindow) return@launch
+ currentCardView.removeView(spinner)
+
+ if (success) {
+ addIconFrameToCenter(currentCardView)
+ observeWallpapers()
+ }
+ }
+ }
+
+ private fun createLoadingSpinner() = ProgressBar(context).apply {
+ isIndeterminate = true
+ layoutParams = FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply {
+ gravity = Gravity.CENTER
+ }
+ }
+
+ private fun addIconFrameToCenter(cardView: CardView? = getChildAt(currentItemIndex) as CardView) {
+ if (cardView == null) return
+ (iconFrame.parent as? ViewGroup)?.removeView(iconFrame)
+ cardView.addView(
+ iconFrame,
+ FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply {
+ gravity = Gravity.CENTER
+ },
+ )
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ super.onMeasure(
+ MeasureSpec.makeMeasureSpec(calculateTotalWidth(), MeasureSpec.EXACTLY),
+ heightMeasureSpec,
+ )
+ }
+
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+ scope.cancel()
+ removeAllViews()
+ }
+
+ private fun animateWidthTransition(newIndex: Int, firstItemWidth: Double, itemWidth: Double) {
+ for (i in 0 until childCount) {
+ (getChildAt(i) as? CardView)?.let { cardView ->
+ val targetWidth = if (i == newIndex) firstItemWidth.toInt() else itemWidth.toInt()
+ if (cardView.layoutParams.width != targetWidth) {
+ ValueAnimator.ofInt(cardView.layoutParams.width, targetWidth).apply {
+ duration = 300L
+ addUpdateListener {
+ cardView.layoutParams.width = it.animatedValue as Int
+ cardView.requestLayout()
+ }
+ start()
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/preview/LauncherPreviewRenderer.java b/src/com/android/launcher3/preview/LauncherPreviewRenderer.java
index a2efce8345c..4bb99a1836a 100644
--- a/src/com/android/launcher3/preview/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/preview/LauncherPreviewRenderer.java
@@ -350,7 +350,7 @@ public void bindCompleteModel(@NonNull WorkspaceData itemIdMap, boolean isBindin
CellLayoutLayoutParams lp = new CellLayoutLayoutParams(
0, 0, firstScreen.getCountX(), 1);
lp.canReorder = false;
- firstScreen.addViewToCellLayout(qsb, 0, R.id.search_container_workspace, lp, true);
+ firstScreen.addViewToCellLayout(qsb, 0, R.id.reserved_container_workspace, lp, true);
}
}
measureAndLayoutRootView();
diff --git a/src/com/android/launcher3/qsb/QsbContainerView.java b/src/com/android/launcher3/qsb/QsbContainerView.java
index d79a2a91070..e42e2e7f445 100644
--- a/src/com/android/launcher3/qsb/QsbContainerView.java
+++ b/src/com/android/launcher3/qsb/QsbContainerView.java
@@ -33,6 +33,7 @@
import android.os.Bundle;
import android.provider.Settings;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -47,6 +48,7 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.dagger.LauncherComponentProvider;
import com.android.launcher3.graphics.FragmentWithPreview;
@@ -59,6 +61,8 @@
*/
public class QsbContainerView extends FrameLayout {
+ private static final String TAG = "QsbContainerView";
+
public static final String SEARCH_ENGINE_SETTINGS_KEY = "selected_search_engine";
/**
@@ -72,10 +76,17 @@ public static String getSearchWidgetPackageName(@NonNull Context context) {
String providerPkg = Settings.Secure.getString(context.getContentResolver(),
SEARCH_ENGINE_SETTINGS_KEY);
if (providerPkg == null) {
- SearchManager searchManager = context.getSystemService(SearchManager.class);
- ComponentName componentName = searchManager.getGlobalSearchActivity();
- if (componentName != null) {
- providerPkg = searchManager.getGlobalSearchActivity().getPackageName();
+ try {
+ SearchManager searchManager = context.getSystemService(SearchManager.class);
+ ComponentName componentName = searchManager.getGlobalSearchActivity();
+ if (componentName != null) {
+ providerPkg = searchManager.getGlobalSearchActivity().getPackageName();
+ }
+ if (providerPkg == null && Utilities.isGSAEnabled(context)) {
+ providerPkg = Utilities.GSA_PACKAGE;
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Search package name exception", e);
}
}
return providerPkg;
@@ -98,7 +109,7 @@ public static AppWidgetProviderInfo getSearchWidgetProviderInfo(@NonNull Context
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
for (AppWidgetProviderInfo info :
appWidgetManager.getInstalledProvidersForPackage(providerPkg, null)) {
- if (info.provider.getPackageName().equals(providerPkg) && info.configure == null) {
+ if (info.provider.getPackageName().equals(providerPkg)) {
if ((info.widgetCategory
& AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX) != 0) {
return info;
@@ -290,7 +301,7 @@ private void rebindFragment() {
}
public boolean isQsbEnabled() {
- return BuildConfig.QSB_ON_FIRST_SCREEN;
+ return false;
}
protected Bundle createBindOptions() {
diff --git a/src/com/android/launcher3/qsb/QsbLayout.java b/src/com/android/launcher3/qsb/QsbLayout.java
new file mode 100644
index 00000000000..4853bfa8526
--- /dev/null
+++ b/src/com/android/launcher3/qsb/QsbLayout.java
@@ -0,0 +1,333 @@
+package com.android.launcher3.qsb;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.LayerDrawable;
+import android.graphics.drawable.PaintDrawable;
+import android.net.Uri;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import com.android.launcher3.LauncherPrefs;
+import com.android.launcher3.R;
+import com.android.launcher3.Reorderable;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.qsb.QsbContainerView;
+import com.android.launcher3.util.MultiTranslateDelegate;
+import com.android.launcher3.util.Themes;
+import android.view.View;
+
+public class QsbLayout extends FrameLayout implements Reorderable {
+
+ private static final String TAG = "QsbLayout";
+
+ private ImageView micIcon;
+ private ImageView gIcon;
+ private ImageView lensIcon;
+ private ImageView geminiIcon;
+ private FrameLayout inner;
+ private FrameLayout outer;
+
+ private final MultiTranslateDelegate mTranslateDelegate = new MultiTranslateDelegate(this);
+ private float mScaleForReorderBounce = 1f;
+
+ private boolean mIsThemed;
+ private boolean mIsPixelStyle;
+
+ public QsbLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public QsbLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ micIcon = findViewById(R.id.mic_icon);
+ gIcon = findViewById(R.id.g_icon);
+ lensIcon = findViewById(R.id.lens_icon);
+ geminiIcon = findViewById(R.id.gemini_icon);
+ inner = findViewById(R.id.inner);
+ outer = findViewById(R.id.outer);
+
+ mIsPixelStyle = outer != null;
+
+ setUpMainSearch();
+ setUpBackground();
+ clipIconRipples();
+
+ mIsThemed = LauncherPrefs.DOCK_THEME.get(getContext());
+
+ setupGIcon();
+ setupLensIcon();
+ setupMicIcon();
+ setupGeminiIcon();
+ }
+
+ private void clipIconRipples() {
+ float cornerRadius = getCornerRadius();
+ PaintDrawable pd = new PaintDrawable(Color.TRANSPARENT);
+ pd.setCornerRadius(cornerRadius);
+ micIcon.setClipToOutline(cornerRadius > 0);
+ micIcon.setBackground(pd);
+ lensIcon.setClipToOutline(cornerRadius > 0);
+ lensIcon.setBackground(pd);
+ gIcon.setClipToOutline(cornerRadius > 0);
+ gIcon.setBackground(pd);
+ if (!mIsPixelStyle) {
+ geminiIcon.setClipToOutline(cornerRadius > 0);
+ geminiIcon.setBackground(pd);
+ }
+ }
+
+ private void setUpBackground() {
+ float cornerRadius = getCornerRadius();
+ int alphaValue = (LauncherPrefs.HOTSEAT_QSB_OPACITY.get(getContext()) * 255) / 100;
+ int baseColor = Themes.getAttrColor(getContext(), R.attr.qsbFillColor);
+ if (LauncherPrefs.DOCK_THEME.get(getContext()))
+ baseColor = Themes.getAttrColor(getContext(), R.attr.qsbFillColorThemed);
+ int color = (baseColor & 0x00FFFFFF) | (alphaValue << 24);
+ float strokeWidth = LauncherPrefs.HOTSEAT_QSB_STROKE_WIDTH.get(getContext());
+
+ PaintDrawable backgroundDrawable = new PaintDrawable(color);
+ backgroundDrawable.setCornerRadius(cornerRadius);
+
+ if (mIsPixelStyle) {
+ setUpOuterBackground(strokeWidth);
+ setUpGeminiCircleBackground(cornerRadius, color);
+ }
+
+ if (strokeWidth != 0f && !mIsPixelStyle) {
+ PaintDrawable strokeDrawable = new PaintDrawable(Themes.getColorAccent(getContext()));
+ strokeDrawable.getPaint().setStyle(Paint.Style.STROKE);
+ strokeDrawable.getPaint().setStrokeWidth(strokeWidth);
+ strokeDrawable.setCornerRadius(cornerRadius);
+ LayerDrawable combinedDrawable = new LayerDrawable(new Drawable[]{backgroundDrawable, strokeDrawable});
+
+ inner.setClipToOutline(cornerRadius > 0);
+ inner.setBackground(combinedDrawable);
+ } else {
+ inner.setClipToOutline(cornerRadius > 0);
+ inner.setBackground(backgroundDrawable);
+ }
+ }
+
+ private void setUpOuterBackground(float strokeWidth) {
+ if (outer == null) return;
+
+ int alphaValue = (LauncherPrefs.HOTSEAT_QSB_OUTER_OPACITY.get(getContext()) * 255) / 100;
+ float cornerRadius = getOuterCornerRadius();
+ int baseColor = Themes.getAttrColor(getContext(), R.attr.qsbOuterColorThemed);
+ int outerColor = (baseColor & 0x00FFFFFF) | (alphaValue << 24);
+
+ PaintDrawable outerFill = new PaintDrawable(outerColor);
+ outerFill.setCornerRadius(cornerRadius);
+
+ if (strokeWidth != 0f) {
+ PaintDrawable outerStroke = new PaintDrawable(Themes.getColorAccent(getContext()));
+ outerStroke.getPaint().setStyle(Paint.Style.STROKE);
+ outerStroke.getPaint().setStrokeWidth(strokeWidth);
+ outerStroke.setCornerRadius(cornerRadius);
+
+ LayerDrawable outerCombined = new LayerDrawable(new Drawable[]{outerFill, outerStroke});
+ outer.setClipToOutline(cornerRadius > 0);
+ outer.setBackground(outerCombined);
+ } else {
+ outer.setClipToOutline(cornerRadius > 0);
+ outer.setBackground(outerFill);
+ }
+ }
+
+ private void setUpGeminiCircleBackground(float cornerRadius, int innerColor) {
+ if (geminiIcon == null) return;
+
+ GradientDrawable background = new GradientDrawable();
+ background.setShape(GradientDrawable.RECTANGLE);
+ background.setCornerRadius(cornerRadius);
+ background.setColor(innerColor);
+ geminiIcon.setBackground(background);
+ geminiIcon.setClipToOutline(cornerRadius > 0);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+
+ setMeasuredDimension(width, height);
+
+ for (int i = 0; i < getChildCount(); i++) {
+ final View child = getChildAt(i);
+ if (child != null) {
+ measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
+ }
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ setOnClickListener(null);
+ if (gIcon != null) gIcon.setOnClickListener(null);
+ if (lensIcon != null) lensIcon.setOnClickListener(null);
+ if (micIcon != null) micIcon.setOnClickListener(null);
+ if (geminiIcon != null) geminiIcon.setOnClickListener(null);
+ if (inner != null) inner.setBackground(null);
+ if (outer != null) outer.setBackground(null);
+ }
+
+ private void setUpMainSearch() {
+ setOnClickListener(view -> {
+ try {
+ Intent intent = new Intent();
+ intent.setAction("android.search.action.GLOBAL_SEARCH");
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ intent.setPackage(QsbContainerView.getSearchWidgetPackageName(view.getContext()));
+ view.getContext().startActivity(intent);
+ } catch (Exception e) {
+ Log.e(TAG, "Main search launch failed", e);
+ }
+ });
+ }
+
+ private void setupGIcon() {
+ if (gIcon == null) return;
+
+ gIcon.setImageResource(mIsThemed
+ ? R.drawable.ic_super_g_themed
+ : R.drawable.ic_super_g_color);
+
+ gIcon.setOnClickListener(view -> {
+ try {
+ Intent intent = view.getContext().getPackageManager().getLaunchIntentForPackage(Utilities.GSA_PACKAGE);
+ if (intent != null) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ view.getContext().startActivity(intent);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Google icon launch failed", e);
+ }
+ });
+ }
+
+ private void setupLensIcon() {
+ if (lensIcon == null) return;
+
+ lensIcon.setImageResource(mIsThemed
+ ? R.drawable.ic_lens_themed
+ : R.drawable.ic_lens_color);
+
+ lensIcon.setOnClickListener(view -> {
+ try {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setComponent(new ComponentName(Utilities.GSA_PACKAGE, Utilities.LENS_ACTIVITY));
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ intent.setData(Uri.parse(Utilities.LENS_URI));
+ intent.putExtra("LensHomescreenShortcut", true);
+ view.getContext().startActivity(intent);
+ } catch (Exception e) {
+ lensIcon.setVisibility(View.GONE);
+ Log.e(TAG, "Lens icon launch failed", e);
+ }
+ });
+ }
+
+ private void setupMicIcon() {
+ if (micIcon == null) return;
+
+ if (Utilities.isMusicSearchEnabled(getContext())) {
+ micIcon.setImageResource(mIsThemed
+ ? R.drawable.ic_music_themed
+ : R.drawable.ic_music_color);
+ } else {
+ micIcon.setImageResource(mIsThemed
+ ? R.drawable.ic_mic_themed
+ : R.drawable.ic_mic_color);
+ }
+
+ micIcon.setOnClickListener(view -> {
+ try {
+ Intent intent = new Intent();
+ if (Utilities.isMusicSearchEnabled(view.getContext())) {
+ intent.setAction("com.google.android.googlequicksearchbox.MUSIC_SEARCH");
+ intent.setPackage(QsbContainerView.getSearchWidgetPackageName(view.getContext()));
+ } else {
+ intent.setAction("android.intent.action.VOICE_COMMAND");
+ }
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ view.getContext().startActivity(intent);
+ } catch (Exception e) {
+ micIcon.setVisibility(View.GONE);
+ Log.e(TAG, "Mic icon launch failed", e);
+ }
+ });
+ }
+
+ private void setupGeminiIcon() {
+ if (geminiIcon == null) return;
+
+ if (!Utilities.isPackageInstalled(getContext(), Utilities.GEMINI_PACKAGE)) {
+ geminiIcon.setVisibility(View.GONE);
+ return;
+ }
+
+ geminiIcon.setVisibility(View.VISIBLE);
+ geminiIcon.setImageResource(mIsThemed
+ ? R.drawable.ic_gemini_themed
+ : R.drawable.ic_gemini_color);
+
+ geminiIcon.setOnClickListener(view -> {
+ Context ctx = view.getContext();
+ try {
+ Intent intent = view.getContext().getPackageManager().getLaunchIntentForPackage(Utilities.GEMINI_PACKAGE);
+ if (intent != null) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ view.getContext().startActivity(intent);
+ }
+ } catch (Exception e) {
+ geminiIcon.setVisibility(View.GONE);
+ Log.e(TAG, "Gemini launch failed", e);
+ }
+ });
+ }
+
+ private float getCornerRadius() {
+ Resources res = getContext().getResources();
+ float qsbWidgetHeight = res.getDimension(R.dimen.qsb_widget_height);
+ float qsbWidgetPadding = res.getDimension(R.dimen.qsb_widget_vertical_padding);
+ float innerHeight = qsbWidgetHeight - 2 * qsbWidgetPadding;
+ return (innerHeight / 2) * ((float)LauncherPrefs.SEARCH_RADIUS_SIZE.get(getContext()) / 100f);
+ }
+
+ private float getOuterCornerRadius() {
+ Resources res = getContext().getResources();
+ float qsbWidgetHeight = res.getDimension(R.dimen.qsb_widget_height);
+ return (qsbWidgetHeight / 2) * ((float)LauncherPrefs.SEARCH_RADIUS_SIZE.get(getContext()) / 100f);
+ }
+
+ @Override
+ public MultiTranslateDelegate getTranslateDelegate() {
+ return mTranslateDelegate;
+ }
+
+ @Override
+ public void setReorderBounceScale(float scale) {
+ mScaleForReorderBounce = scale;
+ super.setScaleX(scale);
+ super.setScaleY(scale);
+ }
+
+ @Override
+ public float getReorderBounceScale() {
+ return mScaleForReorderBounce;
+ }
+}
diff --git a/src/com/android/launcher3/quickspace/LauncherQuickLookBridge.kt b/src/com/android/launcher3/quickspace/LauncherQuickLookBridge.kt
new file mode 100644
index 00000000000..02708503414
--- /dev/null
+++ b/src/com/android/launcher3/quickspace/LauncherQuickLookBridge.kt
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2026 AxionOS 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.quickspace
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.BitmapFactory
+import android.os.Handler
+import android.os.IBinder
+import android.os.RemoteException
+import android.util.Log
+import com.android.axion.quicklook.IAxQuickLookService
+import com.android.axion.quicklook.IQuickLookCallback
+import com.android.axion.quicklook.QuickLookTarget
+import com.android.axion.quicklook.weatherData
+import com.android.internal.util.alpha.OmniJawsClient
+
+/**
+ * Binds to [AxQuickLook] and forwards lockscreen-grade weather (OmniJaws vs Google as resolved by
+ * AxQuickLook) into Launcher Quickspace.
+ */
+class LauncherQuickLookBridge(
+ private val context: Context,
+ private val mainHandler: Handler,
+) {
+ interface Listener {
+ fun onQuickLookWeatherUpdated(displayText: String, icon: Bitmap?)
+
+ fun onQuickLookWeatherCleared()
+ }
+
+ private var listener: Listener? = null
+ private var service: IAxQuickLookService? = null
+ private var bound = false
+
+ private val callback =
+ object : IQuickLookCallback.Stub() {
+ override fun onTargetsUpdated(targets: MutableList) {
+ mainHandler.post { processTargets(targets) }
+ }
+ }
+
+ private val connection =
+ object : ServiceConnection {
+ override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
+ service = IAxQuickLookService.Stub.asInterface(binder)
+ try {
+ service?.registerCallback(callback)
+ service?.getCurrentTargets()?.let { processTargets(it) }
+ } catch (e: RemoteException) {
+ Log.e(TAG, "registerCallback / getCurrentTargets", e)
+ }
+ }
+
+ override fun onServiceDisconnected(name: ComponentName?) {
+ service = null
+ bound = false
+ listener?.onQuickLookWeatherCleared()
+ }
+ }
+
+ fun start(l: Listener) {
+ listener = l
+ val intent =
+ Intent(SERVICE_ACTION).apply {
+ setPackage(SERVICE_PACKAGE)
+ }
+ try {
+ bound = context.bindService(intent, connection, Context.BIND_AUTO_CREATE)
+ } catch (e: SecurityException) {
+ Log.e(TAG, "bindService", e)
+ bound = false
+ }
+ if (!bound) {
+ listener?.onQuickLookWeatherCleared()
+ }
+ }
+
+ fun stop() {
+ listener = null
+ try {
+ service?.unregisterCallback(callback)
+ } catch (_: RemoteException) {
+ }
+ service = null
+ if (bound) {
+ try {
+ context.unbindService(connection)
+ } catch (_: IllegalArgumentException) {
+ }
+ bound = false
+ }
+ }
+
+ private fun processTargets(targets: List) {
+ val l = listener ?: return
+ val weatherTarget =
+ targets.firstOrNull { it.targetType == QuickLookTarget.TYPE_WEATHER } ?: run {
+ l.onQuickLookWeatherCleared()
+ return
+ }
+ val wd =
+ weatherTarget.weatherData ?: run {
+ l.onQuickLookWeatherCleared()
+ return
+ }
+ val text = buildWeatherLabel(wd)
+ val icon =
+ wd.iconBytes?.let { bytes ->
+ try {
+ BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
+ } catch (_: Exception) {
+ null
+ }
+ }
+ ?: wd.conditionCode.takeIf { it != 0 }?.let { code ->
+ drawableToBitmap(OmniJawsClient.get().getWeatherConditionImage(context, code))
+ }
+ l.onQuickLookWeatherUpdated(text, icon)
+ }
+
+ private fun drawableToBitmap(drawable: android.graphics.drawable.Drawable?): Bitmap? {
+ if (drawable == null) return null
+ return try {
+ val w = drawable.intrinsicWidth.coerceAtLeast(1)
+ val h = drawable.intrinsicHeight.coerceAtLeast(1)
+ val bmp = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
+ val canvas = Canvas(bmp)
+ drawable.setBounds(0, 0, canvas.width, canvas.height)
+ drawable.draw(canvas)
+ bmp
+ } catch (_: Exception) {
+ null
+ }
+ }
+
+ private fun buildWeatherLabel(wd: com.android.axion.quicklook.WeatherData): String {
+ val sb = StringBuilder()
+ wd.city?.takeIf { it.isNotBlank() }?.let { sb.append(it).append(' ') }
+ sb.append(wd.temp).append(wd.tempUnit ?: "°")
+ if (wd.condition.isNotBlank()) {
+ sb.append(" • ").append(wd.condition)
+ }
+ return sb.toString()
+ }
+
+ companion object {
+ private const val TAG = "LauncherQuickLookBridge"
+ private const val SERVICE_ACTION = "com.android.axion.quicklook.SERVICE"
+ private const val SERVICE_PACKAGE = "com.android.axion.quicklook"
+
+ @JvmStatic
+ fun isInstalled(context: Context): Boolean =
+ try {
+ context.packageManager.getPackageInfo(SERVICE_PACKAGE, 0)
+ true
+ } catch (_: Exception) {
+ false
+ }
+ }
+}
diff --git a/src/com/android/launcher3/quickspace/QuickEventsController.java b/src/com/android/launcher3/quickspace/QuickEventsController.java
new file mode 100644
index 00000000000..3e2dd4f8cdd
--- /dev/null
+++ b/src/com/android/launcher3/quickspace/QuickEventsController.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2020-2026 crDroid Android 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.quickspace;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.icu.text.DateFormat;
+import android.icu.text.DisplayContext;
+import android.view.View.OnClickListener;
+
+import androidx.core.content.ContextCompat;
+
+import com.android.launcher3.LauncherPrefs;
+import com.android.launcher3.R;
+
+import java.util.Calendar;
+import java.util.concurrent.ThreadLocalRandom;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import com.android.launcher3.quickspace.receivers.QuickSpaceActionReceiver;
+
+public class QuickEventsController {
+
+ private final Context mContext;
+ private final Resources mResources;
+
+ private String mEventTitle;
+ private String mEventTitleSub;
+ private String mGreetings;
+ private String mClockExt;
+ private OnClickListener mEventTitleSubAction = null;
+ private Drawable mEventSubIcon = null;
+
+ private boolean mIsQuickEvent = false;
+
+ private final Map mCachedPSAMap = new HashMap<>();
+
+ // PSA + Personality
+ private String[] mPSAStr;
+
+ // NowPlaying
+ private boolean mEventNowPlaying = false;
+ private String mNowPlayingTitle;
+ private String mNowPlayingArtist;
+ private Drawable mMediaIcon;
+ private OnClickListener mLaunchMediaApp = null;
+ private boolean mPlayingActive = false;
+
+ public QuickEventsController(Context context) {
+ mContext = context;
+ mResources = context.getResources();
+ }
+
+ public void initQuickEvents() {
+ updateQuickEvents();
+ }
+
+ public void updateQuickEvents() {
+ nowPlayingEvent();
+ initNowPlayingEvent();
+ psonalityEvent();
+ }
+
+ public void updatePsonality() {
+ psonalityEvent();
+ }
+
+ private void nowPlayingEvent() {
+ if (mEventNowPlaying && !mPlayingActive) {
+ mIsQuickEvent = false;
+ mEventNowPlaying = false;
+ }
+ }
+
+ private void initNowPlayingEvent() {
+ if (!LauncherPrefs.SHOW_QUICKSPACE_NOWPLAYING.get(mContext)) return;
+
+ if (!mPlayingActive) return;
+
+ if (mNowPlayingTitle == null) return;
+
+ mEventTitle = mNowPlayingTitle;
+ mGreetings = mResources.getString(R.string.qe_now_playing_ext_one);
+ mClockExt = "";
+ if (mNowPlayingArtist == null ) {
+ mEventTitleSub = mResources.getString(R.string.qe_now_playing_unknown_artist);
+ } else {
+ mEventTitleSub = mNowPlayingArtist;
+ }
+ mEventSubIcon = mMediaIcon;
+ mIsQuickEvent = true;
+ mEventNowPlaying = true;
+ mEventTitleSubAction = mLaunchMediaApp;
+ }
+
+ private static String formatDateTime(Context context) {
+ String styleText;
+ DateFormat dateFormat;
+ if (LauncherPrefs.SHOW_QUICKSPACE_ALT.get(context)) {
+ styleText = context.getString(R.string.quickspace_date_format_minimalistic);
+ } else {
+ styleText = context.getString(R.string.quickspace_date_format);
+ }
+ dateFormat = DateFormat.getInstanceForSkeleton(styleText, Locale.getDefault());
+ dateFormat.setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE);
+ return dateFormat.format(System.currentTimeMillis());
+ }
+
+ private void psonalityEvent() {
+ if (mEventNowPlaying) return;
+
+ mEventTitle = formatDateTime(mContext);
+ mEventTitleSubAction = QuickSpaceActionReceiver.getCalendarAction();
+
+ int hourOfDay = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
+
+ if (hourOfDay >= 5 && hourOfDay <= 9) {
+ mGreetings = mResources.getString(R.string.quickspace_grt_morning);
+ mClockExt = mResources.getString(R.string.quickspace_ext_one);
+ } else if (hourOfDay >= 12 && hourOfDay <= 15) {
+ mGreetings = mResources.getString(R.string.quickspace_grt_afternoon);
+ mClockExt = mResources.getString(R.string.quickspace_ext_two);
+ } else if (hourOfDay >= 16 && hourOfDay <= 20) {
+ mGreetings = mResources.getString(R.string.quickspace_grt_evening);
+ mClockExt = mResources.getString(R.string.quickspace_ext_two);
+ } else if (hourOfDay >= 21 && hourOfDay <= 23) {
+ mGreetings = mResources.getString(R.string.quickspace_grt_night);
+ mClockExt = mResources.getString(R.string.quickspace_ext_two);
+ } else if (hourOfDay >= 0 && hourOfDay <= 3) {
+ mGreetings = mResources.getString(R.string.quickspace_grt_night);
+ mClockExt = mResources.getString(R.string.quickspace_ext_two);
+ } else {
+ mGreetings = mResources.getString(R.string.quickspace_grt_general);
+ mClockExt = mResources.getString(R.string.quickspace_ext_two);
+ }
+
+ mIsQuickEvent = false;
+ if (LauncherPrefs.SHOW_QUICKSPACE_PSONALITY.get(mContext)) {
+ int luckNumber = getLuckyNumber(13);
+ if (luckNumber == 7) {
+ mPSAStr = mResources.getStringArray(R.array.quickspace_psa_homer);
+ if (mPSAStr != null && mPSAStr.length > 0) {
+ mEventTitleSub = mPSAStr[getLuckyNumber(0, mPSAStr.length - 1)];
+ mEventSubIcon = ContextCompat.getDrawable(mContext, R.drawable.ic_homer);
+ mIsQuickEvent = true;
+ }
+ } else if (luckNumber == 13) {
+ mPSAStr = mResources.getStringArray(R.array.quickspace_psa_random);
+ if (mPSAStr != null && mPSAStr.length > 0) {
+ mEventTitleSub = mPSAStr[getLuckyNumber(0, mPSAStr.length - 1)];
+ mEventSubIcon = ContextCompat.getDrawable(
+ mContext, R.drawable.ic_quickspace_pacman);
+ mIsQuickEvent = true;
+ }
+ }
+ }
+ }
+
+ public boolean isQuickEvent() {
+ return mIsQuickEvent;
+ }
+
+ public String getTitle() {
+ return mEventTitle;
+ }
+
+ public String getActionTitle() {
+ return mEventTitleSub;
+ }
+
+ public String getClockExt() {
+ return mClockExt;
+ }
+
+ public String getGreetings() {
+ return mGreetings;
+ }
+
+ public OnClickListener getAction() {
+ return mEventTitleSubAction;
+ }
+
+ public Drawable getActionIcon() {
+ return mEventSubIcon;
+ }
+
+ public int getLuckyNumber(int max) {
+ return getLuckyNumber(0, max);
+ }
+
+ public int getLuckyNumber(int min, int max) {
+ return ThreadLocalRandom.current().nextInt(min, max + 1);
+ }
+
+ public void setMediaInfo(String title, String artist, boolean activePlayback,
+ Drawable mediaIcon, OnClickListener launchMediaApp) {
+ mNowPlayingTitle = title;
+ mNowPlayingArtist = artist;
+ mPlayingActive = activePlayback;
+ mMediaIcon = mediaIcon;
+ mLaunchMediaApp = launchMediaApp;
+ }
+
+ public boolean isNowPlaying() {
+ return mPlayingActive;
+ }
+
+ private String[] getCachedArray(int resId) {
+ if (!mCachedPSAMap.containsKey(resId)) {
+ mCachedPSAMap.put(resId, mResources.getStringArray(resId));
+ }
+ return mCachedPSAMap.get(resId);
+ }
+}
diff --git a/src/com/android/launcher3/quickspace/QuickSpaceView.java b/src/com/android/launcher3/quickspace/QuickSpaceView.java
new file mode 100644
index 00000000000..066d5ba8098
--- /dev/null
+++ b/src/com/android/launcher3/quickspace/QuickSpaceView.java
@@ -0,0 +1,366 @@
+/*
+ * Copyright (C) 2018-2025 crDroid Android 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.quickspace;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils.TruncateAt;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.launcher3.LauncherPrefs;
+import com.android.launcher3.R;
+import com.android.launcher3.util.Themes;
+
+import com.android.launcher3.quickspace.QuickspaceController.OnDataListener;
+import com.android.launcher3.quickspace.receivers.QuickSpaceActionReceiver;
+
+public class QuickSpaceView extends FrameLayout implements OnDataListener {
+
+ private static final String TAG = "Launcher3:QuickSpaceView";
+ private static final boolean DEBUG = false;
+
+ public ColorStateList mColorStateList;
+ public int mQuickspaceBackgroundRes;
+
+ public ViewGroup mQuickspaceContent;
+ public ImageView mEventSubIcon;
+ public ImageView mNowPlayingIcon;
+ public TextView mEventTitleSub;
+ public TextView mEventTitleSubColored;
+ public TextView mGreetingsExt;
+ public TextView mGreetingsExtClock;
+ public ViewGroup mWeatherContentSub;
+ public ImageView mWeatherIconSub;
+ public TextView mWeatherTempSub;
+ public TextView mEventTitle;
+
+ public boolean mIsQuickEvent;
+ public boolean mWeatherAvailable;
+ private boolean mFinishedInflate;
+ private boolean mListenerRegistered;
+
+ private boolean mIsAlternateStyle = false;
+
+ public QuickspaceController mController;
+
+ public QuickSpaceView(Context context, AttributeSet set) {
+ super(context, set);
+ if (!LauncherPrefs.SHOW_QUICKSPACE.get(context)) return;
+ mController = new QuickspaceController(context);
+ mColorStateList = ColorStateList.valueOf(Themes.getAttrColor(getContext(), R.attr.workspaceTextColor));
+ mQuickspaceBackgroundRes = R.drawable.bg_quickspace;
+ setClipChildren(false);
+ }
+
+ @Override
+ public void onDataUpdated() {
+ if (mController == null) return;
+ boolean altUI = LauncherPrefs.SHOW_QUICKSPACE_ALT.get(getContext());
+ if (mEventTitle == null || mIsAlternateStyle != altUI) {
+ prepareLayout(altUI);
+ }
+ mIsQuickEvent = mController.isQuickEvent();
+ mWeatherAvailable = mController.isWeatherAvailable();
+ loadDoubleLine(altUI);
+ }
+
+ private final void loadDoubleLine(boolean useAlternativeQuickspaceUI) {
+ mEventTitle.setText(mController.getEventController().getTitle());
+ if (useAlternativeQuickspaceUI) {
+ String greetingsExt = mController.getEventController().getGreetings();
+ if (greetingsExt != null && !greetingsExt.isEmpty()) {
+ mGreetingsExt.setVisibility(View.VISIBLE);
+ mGreetingsExt.setText(greetingsExt);
+ mGreetingsExt.setEllipsize(TruncateAt.END);
+ mGreetingsExt.setOnClickListener(mController.getEventController().getAction());
+ } else {
+ mGreetingsExt.setVisibility(View.GONE);
+ }
+ String greetingsExtClock = mController.getEventController().getClockExt();
+ if (greetingsExtClock != null && !greetingsExtClock.isEmpty()) {
+ mGreetingsExtClock.setVisibility(View.VISIBLE);
+ mGreetingsExtClock.setText(greetingsExtClock);
+ mGreetingsExtClock.setOnClickListener(mController.getEventController().getAction());
+ } else {
+ mGreetingsExtClock.setVisibility(View.GONE);
+ }
+ }
+ boolean shouldShowPsa = mIsQuickEvent && (LauncherPrefs.SHOW_QUICKSPACE_PSONALITY.get(getContext()) ||
+ mController.getEventController().isNowPlaying());
+ if (shouldShowPsa) {
+ maybeSetMarquee(mEventTitle);
+ mEventTitle.setOnClickListener(mController.getEventController().getAction());
+ mEventTitleSub.setText(mController.getEventController().getActionTitle());
+ maybeSetMarquee(mEventTitleSub);
+ mEventTitleSub.setOnClickListener(mController.getEventController().getAction());
+
+ if (mEventTitleSub.getVisibility() != View.VISIBLE) {
+ animateIn(mEventTitleSub);
+ }
+
+ if (useAlternativeQuickspaceUI) {
+ if (mController.getEventController().isNowPlaying()) {
+ animateOut(mEventSubIcon);
+ animateIn(mEventTitleSubColored);
+ animateIn(mNowPlayingIcon);
+ mNowPlayingIcon.setOnClickListener(mController.getEventController().getAction());
+ mEventTitleSubColored.setText(getContext().getString(R.string.qe_now_playing_by));
+ mEventTitleSubColored.setOnClickListener(mController.getEventController().getAction());
+ } else {
+ setEventSubIcon();
+ animateOut(mEventTitleSubColored);
+ animateOut(mNowPlayingIcon);
+ }
+ } else {
+ setEventSubIcon();
+ }
+ } else {
+ animateOut(mEventTitleSub);
+ animateOut(mEventSubIcon);
+ if (useAlternativeQuickspaceUI) {
+ animateOut(mEventTitleSubColored);
+ animateOut(mNowPlayingIcon);
+ }
+ }
+ bindWeather(mWeatherContentSub, mWeatherTempSub, mWeatherIconSub);
+ }
+
+ private void maybeSetMarquee(TextView tv) {
+ tv.setSelected(false);
+ tv.setEllipsize(TruncateAt.END);
+ final float textWidth = tv.getPaint().measureText(tv.getText().toString());
+ tv.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ if (!tv.isAttachedToWindow()) { remove(); return; }
+ android.text.Layout layout = tv.getLayout();
+ if (layout != null && layout.getEllipsizedWidth() < textWidth) {
+ tv.setEllipsize(TruncateAt.MARQUEE);
+ tv.setMarqueeRepeatLimit(1);
+ tv.setSelected(true);
+ }
+ remove();
+ }
+
+ private void remove() {
+ tv.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ }
+ });
+ }
+
+ private void setEventSubIcon() {
+ Drawable icon = mController.getEventController().getActionIcon();
+ if (icon != null) {
+ if (mEventSubIcon.getVisibility() != View.VISIBLE) {
+ animateIn(mEventSubIcon);
+ }
+ mEventSubIcon.setImageTintList(mController.getEventController().isNowPlaying() ? null : mColorStateList);
+ mEventSubIcon.setImageDrawable(icon);
+ mEventSubIcon.setOnClickListener(mController.getEventController().getAction());
+ } else {
+ animateOut(mEventSubIcon);
+ }
+ }
+
+ private final void bindWeather(View container, TextView title, ImageView icon) {
+ if (!mWeatherAvailable || mController.getEventController().isNowPlaying()) {
+ container.setVisibility(View.GONE);
+ return;
+ }
+ String weatherTemp = mController.getWeatherTemp();
+ if (weatherTemp == null || weatherTemp.isEmpty()) {
+ container.setVisibility(View.GONE);
+ return;
+ }
+ if (container.getVisibility() != View.VISIBLE) {
+ animateIn(container);
+ }
+ container.setOnClickListener(QuickSpaceActionReceiver.getWeatherAction());
+ title.setText(weatherTemp);
+ title.setOnClickListener(QuickSpaceActionReceiver.getWeatherAction());
+ Drawable d = mController.getWeatherIcon();
+ icon.setImageDrawable(d);
+ icon.setOnClickListener(QuickSpaceActionReceiver.getWeatherAction());
+ icon.setVisibility(d != null ? View.VISIBLE : View.GONE);
+ }
+
+ private final void loadViews() {
+ mEventTitle = (TextView) findViewById(R.id.quick_event_title);
+ mEventTitleSub = (TextView) findViewById(R.id.quick_event_title_sub);
+ mEventTitleSubColored = (TextView) findViewById(R.id.quick_event_title_sub_colored);
+ mNowPlayingIcon = (ImageView) findViewById(R.id.now_playing_icon_sub);
+ mEventSubIcon = (ImageView) findViewById(R.id.quick_event_icon_sub);
+ mWeatherIconSub = (ImageView) findViewById(R.id.quick_event_weather_icon);
+ mQuickspaceContent = (ViewGroup) findViewById(R.id.quickspace_content);
+ mWeatherContentSub = (ViewGroup) findViewById(R.id.quick_event_weather_content);
+ mWeatherTempSub = (TextView) findViewById(R.id.quick_event_weather_temp);
+ if (LauncherPrefs.SHOW_QUICKSPACE_ALT.get(getContext())) {
+ mGreetingsExtClock = (TextView) findViewById(R.id.extended_greetings_clock);
+ mGreetingsExt = (TextView) findViewById(R.id.extended_greetings);
+ }
+ }
+
+ private void clearOldViewState() {
+ View[] vs = new View[]{ mEventTitle, mEventTitleSub, mEventTitleSubColored,
+ mNowPlayingIcon, mEventSubIcon, mWeatherContentSub, mWeatherIconSub, mWeatherTempSub };
+ for (View v : vs) if (v != null) {
+ v.animate().cancel();
+ v.setOnClickListener(null);
+ if (v instanceof ImageView) {
+ ImageView iv = (ImageView) v;
+ iv.setImageDrawable(null);
+ iv.setImageBitmap(null);
+ iv.setBackground(null);
+ } else if (v instanceof TextView) {
+ TextView tv = (TextView) v;
+ tv.setSelected(false);
+ tv.setEllipsize(null);
+ tv.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
+ tv.setBackground(null);
+ } else {
+ v.setBackground(null);
+ }
+ }
+ }
+
+ private void prepareLayout(boolean alt) {
+ mIsAlternateStyle = alt;
+ int insertIndex = (mQuickspaceContent != null) ? indexOfChild(mQuickspaceContent) : -1;
+ if (mQuickspaceContent != null) {
+ clearOldViewState();
+ removeView(mQuickspaceContent);
+ }
+ addView(LayoutInflater.from(getContext()).inflate(
+ alt ? R.layout.quickspace_alternate_double : R.layout.quickspace_doubleline,
+ this, false),
+ insertIndex < 0 ? -1 : insertIndex);
+
+ loadViews();
+ getQuickSpaceView();
+ setBackgroundResource(mQuickspaceBackgroundRes);
+ }
+
+ private void getQuickSpaceView() {
+ if (mQuickspaceContent.getVisibility() != View.VISIBLE) {
+ mQuickspaceContent.setVisibility(View.VISIBLE);
+ mQuickspaceContent.setAlpha(0.0f);
+ mQuickspaceContent.animate().setDuration(200).alpha(1.0f);
+ }
+ }
+
+ private static final Interpolator ANIMATE_IN = new DecelerateInterpolator();
+ private static final Interpolator ANIMATE_OUT = new AccelerateInterpolator();
+
+ private void animateIn(View view) {
+ if (view.getVisibility() == View.VISIBLE && view.getAlpha() == 1f) {
+ return; // Already visible
+ }
+ view.setVisibility(View.VISIBLE);
+ view.setAlpha(0f);
+ view.setTranslationY(view.getHeight() / 2f);
+ view.animate()
+ .alpha(1f)
+ .translationY(0f)
+ .setDuration(300)
+ .setInterpolator(ANIMATE_IN)
+ .start();
+ }
+
+ private void animateOut(View view) {
+ if (view.getVisibility() != View.VISIBLE) {
+ return; // Already hidden
+ }
+ view.animate()
+ .alpha(0f)
+ .translationY(view.getHeight() / 2f)
+ .setDuration(400)
+ .setInterpolator(ANIMATE_OUT)
+ .withEndAction(() -> view.setVisibility(View.GONE))
+ .start();
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ if (mController != null && mFinishedInflate && !mListenerRegistered) {
+ mListenerRegistered = true;
+ mController.addListener(this);
+ }
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ if (mController == null) return;
+ clearOldViewState();
+ setBackground(null);
+ super.onDetachedFromWindow();
+ mController.onPause();
+ mController.removeListener(this);
+ mListenerRegistered = false;
+ }
+
+ @Override
+ public void onFinishInflate() {
+ super.onFinishInflate();
+ if (mController == null) return;
+ loadViews();
+ mFinishedInflate = true;
+ if (isAttachedToWindow() && !mListenerRegistered) {
+ mController.addListener(this);
+ mListenerRegistered = true;
+ }
+ }
+
+ public void onPause() {
+ if (mController != null) mController.onPause();
+ }
+
+ public void onResume() {
+ if (mController != null && mListenerRegistered) mController.onResume();
+ }
+
+ public void onDestroy() {
+ if (mController == null) return;
+ mController.onDestroy();
+ mController = null;
+ mQuickspaceContent = null;
+ mEventSubIcon = null;
+ mNowPlayingIcon = null;
+ mEventTitleSub = null;
+ mEventTitleSubColored = null;
+ mGreetingsExt = null;
+ mGreetingsExtClock = null;
+ mWeatherContentSub = null;
+ mWeatherIconSub = null;
+ mWeatherTempSub = null;
+ mEventTitle = null;
+ }
+
+ public void setPadding(int n, int n2, int n3, int n4) {
+ super.setPadding(0, 0, 0, 0);
+ }
+}
diff --git a/src/com/android/launcher3/quickspace/QuickspaceController.java b/src/com/android/launcher3/quickspace/QuickspaceController.java
new file mode 100644
index 00000000000..a3e563746f3
--- /dev/null
+++ b/src/com/android/launcher3/quickspace/QuickspaceController.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2021-2026 crDroid Android Project
+ * Copyright (C) 2026 AlphaDroid
+ *
+ * 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.quickspace;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.Handler;
+import android.media.MediaMetadata;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View.OnClickListener;
+
+import com.android.launcher3.LauncherPrefs;
+import com.android.launcher3.R;
+import com.android.launcher3.util.MediaSessionManagerHelper;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Controls the Quickspace (at-a-glance) area on the launcher home screen.
+ *
+ * Weather data comes exclusively from AxQuickLook via {@link LauncherQuickLookBridge}.
+ * AxQuickLook internally resolves OmniJaws vs Google weather based on the system-wide
+ * {@code lockscreen_weather_source} setting (configured in AlphaSettings).
+ */
+public class QuickspaceController implements MediaSessionManagerHelper.MediaMetadataListener {
+
+ private static final String TAG = "Launcher3:QuickspaceController";
+
+ private final List mListeners =
+ Collections.synchronizedList(new ArrayList<>());
+ private final Context mContext;
+ private QuickEventsController mEventsController;
+ private boolean mMediaRegistered = false;
+
+ private static final long PSA_UPDATE_DELAY_MS = 3 * 60 * 1000;
+
+ private final Handler mHandler = MAIN_EXECUTOR.getHandler();
+
+ private final LauncherQuickLookBridge mQuickLookBridge;
+ private boolean mQuickLookActive = false;
+
+ private String mWeatherText;
+ private Icon mWeatherIcon;
+ private int mLastBmpHash;
+
+ private final MediaSessionManagerHelper mMediaSessionHelper;
+
+ private final LauncherQuickLookBridge.Listener mQuickLookListener =
+ new LauncherQuickLookBridge.Listener() {
+ @Override
+ public void onQuickLookWeatherUpdated(String displayText, Bitmap icon) {
+ int hash = (icon == null) ? 0 : icon.getGenerationId();
+ if (TextUtils.equals(displayText, mWeatherText) && hash == mLastBmpHash) {
+ return;
+ }
+ mLastBmpHash = hash;
+ mWeatherText = displayText;
+ mWeatherIcon = icon == null ? null : Icon.createWithBitmap(icon);
+ notifyListeners();
+ }
+
+ @Override
+ public void onQuickLookWeatherCleared() {
+ mWeatherText = null;
+ mWeatherIcon = null;
+ mLastBmpHash = 0;
+ notifyListeners();
+ }
+ };
+
+ private final Runnable mOnDataUpdatedRunnable = () -> {
+ for (OnDataListener list : new ArrayList<>(mListeners)) {
+ list.onDataUpdated();
+ }
+ };
+
+ private final Runnable mPsaRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mHandler.removeCallbacks(this);
+ if (mEventsController == null) return;
+ mEventsController.updatePsonality();
+ mHandler.postDelayed(this, PSA_UPDATE_DELAY_MS);
+ notifyListeners();
+ }
+ };
+
+ public interface OnDataListener {
+ void onDataUpdated();
+ }
+
+ public QuickspaceController(Context context) {
+ mContext = context;
+ mEventsController = new QuickEventsController(context);
+ mMediaSessionHelper = MediaSessionManagerHelper.Companion.getInstance(context);
+ mQuickLookBridge = new LauncherQuickLookBridge(context, mHandler);
+ }
+
+ private void startWeather() {
+ if (!LauncherPrefs.SHOW_QUICKSPACE_WEATHER.get(mContext)) {
+ stopWeather();
+ return;
+ }
+ if (!mQuickLookActive && !mListeners.isEmpty()) {
+ mQuickLookBridge.start(mQuickLookListener);
+ mQuickLookActive = true;
+ Log.i(TAG, "AxQuickLook weather bridge started");
+ }
+ }
+
+ private void stopWeather() {
+ if (mQuickLookActive) {
+ mQuickLookBridge.stop();
+ mQuickLookActive = false;
+ mWeatherText = null;
+ mWeatherIcon = null;
+ mLastBmpHash = 0;
+ Log.i(TAG, "AxQuickLook weather bridge stopped");
+ }
+ }
+
+ public void addListener(OnDataListener listener) {
+ if (listener == null) return;
+ boolean wasEmpty = mListeners.isEmpty();
+ if (!mListeners.contains(listener)) {
+ mListeners.add(listener);
+ }
+ if (wasEmpty) {
+ startWeather();
+ registerMediaController();
+ mEventsController.initQuickEvents();
+ updatePSAevent();
+ }
+ listener.onDataUpdated();
+ }
+
+ public void removeListener(OnDataListener listener) {
+ if (listener == null) return;
+ mListeners.remove(listener);
+ if (mListeners.isEmpty()) {
+ stopWeather();
+ unregisterMediaController();
+ mHandler.removeCallbacks(mPsaRunnable);
+ mHandler.removeCallbacks(mOnDataUpdatedRunnable);
+ }
+ }
+
+ public boolean isQuickEvent() {
+ return mEventsController.isQuickEvent();
+ }
+
+ public QuickEventsController getEventController() {
+ return mEventsController;
+ }
+
+ public boolean isWeatherAvailable() {
+ if (!LauncherPrefs.SHOW_QUICKSPACE_WEATHER.get(mContext)) return false;
+ return !TextUtils.isEmpty(mWeatherText) || mWeatherIcon != null;
+ }
+
+ public Drawable getWeatherIcon() {
+ return mWeatherIcon != null ? mWeatherIcon.loadDrawable(mContext) : null;
+ }
+
+ public String getWeatherTemp() {
+ return mWeatherText;
+ }
+
+ public void onPause() {
+ unregisterMediaController();
+ mHandler.removeCallbacks(mPsaRunnable);
+ mHandler.removeCallbacks(mOnDataUpdatedRunnable);
+ stopWeather();
+ }
+
+ public void onResume() {
+ registerMediaController();
+ updateMediaController();
+ startWeather();
+ updatePSAevent();
+ notifyListeners();
+ }
+
+ public void onDestroy() {
+ unregisterMediaController();
+ stopWeather();
+ mHandler.removeCallbacks(mPsaRunnable);
+ mHandler.removeCallbacks(mOnDataUpdatedRunnable);
+ for (OnDataListener listener : new ArrayList<>(mListeners)) {
+ removeListener(listener);
+ }
+ }
+
+ private void updatePSAevent() {
+ mHandler.removeCallbacks(mPsaRunnable);
+ mHandler.post(mPsaRunnable);
+ }
+
+ public void notifyListeners() {
+ mHandler.removeCallbacks(mOnDataUpdatedRunnable);
+ mHandler.post(mOnDataUpdatedRunnable);
+ }
+
+ private void registerMediaController() {
+ if (mMediaRegistered) return;
+ mMediaSessionHelper.addMediaMetadataListener(this);
+ mMediaRegistered = true;
+ }
+
+ private void unregisterMediaController() {
+ if (!mMediaRegistered) return;
+ mMediaSessionHelper.removeMediaMetadataListener(this);
+ mMediaRegistered = false;
+ }
+
+ private boolean updateMediaController() {
+ if (!LauncherPrefs.SHOW_QUICKSPACE_NOWPLAYING.get(mContext)) {
+ return false;
+ }
+ MediaMetadata mediaMetadata = mMediaSessionHelper.getCurrentMediaMetadata();
+ boolean isPlaying = mMediaSessionHelper.isMediaPlaying();
+ String trackArtist = isPlaying && mediaMetadata != null ?
+ mediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST) : "";
+ String trackTitle = isPlaying && mediaMetadata != null ?
+ mediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE) : "";
+ Drawable mediaIcon = mMediaSessionHelper.getMediaAppIcon();
+ OnClickListener launchMediaApp =
+ view -> mMediaSessionHelper.launchMediaApp();
+ mEventsController.setMediaInfo(trackTitle, trackArtist, isPlaying,
+ mediaIcon, launchMediaApp);
+ mEventsController.updateQuickEvents();
+ return true;
+ }
+
+ @Override
+ public void onMediaMetadataChanged() {
+ if (updateMediaController()) notifyListeners();
+ }
+
+ @Override
+ public void onPlaybackStateChanged() {
+ if (updateMediaController()) notifyListeners();
+ }
+}
diff --git a/src/com/android/launcher3/quickspace/receivers/QuickSpaceActionReceiver.java b/src/com/android/launcher3/quickspace/receivers/QuickSpaceActionReceiver.java
new file mode 100644
index 00000000000..f4c8bf04a3c
--- /dev/null
+++ b/src/com/android/launcher3/quickspace/receivers/QuickSpaceActionReceiver.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2018-2025 crDroid Android 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.quickspace.receivers;
+
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.ContentUris;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.net.Uri;
+import android.os.Process;
+import android.os.UserHandle;
+import android.provider.CalendarContract;
+import android.view.View;
+import android.view.View.OnClickListener;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+
+public class QuickSpaceActionReceiver {
+
+ private static final OnClickListener calendarClick = v -> openGoogleCalendar(v);
+ private static final OnClickListener weatherClick = v -> openGoogleWeather(v);
+
+ private static Launcher launcherFrom(View v) {
+ return Launcher.getLauncher(v.getContext());
+ }
+
+ private static void openGoogleCalendar(View view) {
+ Uri.Builder b = CalendarContract.CONTENT_URI.buildUpon().appendPath("time");
+ ContentUris.appendId(b, System.currentTimeMillis());
+ Uri uri = b.build();
+ Intent i = new Intent(Intent.ACTION_VIEW)
+ .setData(uri)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ try {
+ launcherFrom(view).startActivitySafely(view, i, null);
+ } catch (ActivityNotFoundException ex) {
+ if (!Utilities.isGSAEnabled(view.getContext())) return;
+ LauncherApps la = view.getContext()
+ .getSystemService(LauncherApps.class);
+ if (la != null) {
+ la.startAppDetailsActivity(
+ new ComponentName("com.google.android.googlequicksearchbox", ""),
+ Process.myUserHandle(), null, null);
+ }
+ } catch (SecurityException ignored) { }
+ }
+
+ private static void openGoogleWeather(View view) {
+ if (!Utilities.isGSAEnabled(view.getContext())) return;
+ Intent i = new Intent(Intent.ACTION_VIEW)
+ .setData(Uri.parse("dynact://velour/weather/ProxyActivity"))
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
+ .setComponent(new ComponentName(
+ "com.google.android.googlequicksearchbox",
+ "com.google.android.apps.gsa.velour.DynamicActivityTrampoline"));
+ try {
+ launcherFrom(view).startActivitySafely(view, i, null);
+ } catch (ActivityNotFoundException ex) {
+ LauncherApps la = view.getContext()
+ .getSystemService(LauncherApps.class);
+ if (la != null) {
+ la.startAppDetailsActivity(
+ new ComponentName("com.google.android.googlequicksearchbox",
+ "com.google.android.apps.gsa.velour.DynamicActivityTrampoline"),
+ Process.myUserHandle(), null, null);
+ }
+ } catch (SecurityException ignored) { }
+ }
+
+ public static OnClickListener getCalendarAction() { return calendarClick; }
+ public static OnClickListener getWeatherAction() { return weatherClick; }
+}
diff --git a/src/com/android/launcher3/quickspace/views/DoubleShadowTextView.java b/src/com/android/launcher3/quickspace/views/DoubleShadowTextView.java
new file mode 100644
index 00000000000..966d16172a2
--- /dev/null
+++ b/src/com/android/launcher3/quickspace/views/DoubleShadowTextView.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2018-2025 crDroid Android 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.quickspace.views;
+
+import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+import com.android.launcher3.views.ShadowInfo;
+
+public class DoubleShadowTextView extends TextView {
+
+ private final ShadowInfo mShadowInfo;
+
+ public DoubleShadowTextView(Context context) {
+ this(context, null);
+ }
+
+ public DoubleShadowTextView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public DoubleShadowTextView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mShadowInfo = ShadowInfo.Companion.fromContext(context, attrs, defStyle);
+ setShadowLayer(
+ Math.max(mShadowInfo.getKeyShadowBlur() +
+ mShadowInfo.getKeyShadowOffsetX(),
+ mShadowInfo.getAmbientShadowBlur()), 0f, 0f,
+ mShadowInfo.getKeyShadowColor());
+ }
+
+ private boolean skipDoubleShadow() {
+ int textAlpha = Color.alpha(getCurrentTextColor());
+ int keyShadowAlpha = Color.alpha(mShadowInfo.getKeyShadowColor());
+ int ambientShadowAlpha = Color.alpha(mShadowInfo.getAmbientShadowColor());
+ if (textAlpha == 0 || (keyShadowAlpha == 0 && ambientShadowAlpha == 0)) {
+ getPaint().clearShadowLayer();
+ return true;
+ } else if (ambientShadowAlpha > 0 && keyShadowAlpha == 0) {
+ getPaint().setShadowLayer(mShadowInfo.getAmbientShadowBlur(), 0, 0,
+ getTextShadowColor(mShadowInfo.getAmbientShadowColor(), textAlpha));
+ return true;
+ } else if (keyShadowAlpha > 0 && ambientShadowAlpha == 0) {
+ getPaint().setShadowLayer(
+ mShadowInfo.getKeyShadowBlur(),
+ mShadowInfo.getKeyShadowOffsetX(),
+ mShadowInfo.getKeyShadowOffsetY(),
+ getTextShadowColor(mShadowInfo.getKeyShadowColor(), textAlpha));
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ // If text is transparent or shadow alpha is 0, don't draw any shadow
+ if (skipDoubleShadow()) {
+ super.onDraw(canvas);
+ return;
+ }
+ getPaint().setShadowLayer(mShadowInfo.getKeyShadowBlur(), 0, mShadowInfo.getKeyShadowOffsetX(), mShadowInfo.getKeyShadowColor());
+ super.onDraw(canvas);
+ }
+
+ // Multiplies the alpha of shadowColor by textAlpha.
+ private static int getTextShadowColor(int shadowColor, int textAlpha) {
+ return setColorAlphaBound(shadowColor,
+ Math.round(Color.alpha(shadowColor) * textAlpha / 255f));
+ }
+}
diff --git a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
index 8b355bfaf6a..cd86b02a6aa 100644
--- a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
+++ b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
@@ -190,8 +190,8 @@ constructor(
private const val NULL_LAYOUT_MANAGER_ERROR_STRING =
"activeRv's layoutManager should not be null"
- private const val PREINFLATE_ICONS_ROW_COUNT = 4
- private const val EXTRA_ICONS_COUNT = 2
+ private const val PREINFLATE_ICONS_ROW_COUNT = 8
+ private const val EXTRA_ICONS_COUNT = 4
const val PRELOAD_ALL_APPS_DAGGER_KEY = "PRELOAD_ALL_APPS"
}
diff --git a/src/com/android/launcher3/settings/SettingsAppDrawer.java b/src/com/android/launcher3/settings/SettingsAppDrawer.java
index 594e47b881f..f5ab5a0a30f 100644
--- a/src/com/android/launcher3/settings/SettingsAppDrawer.java
+++ b/src/com/android/launcher3/settings/SettingsAppDrawer.java
@@ -20,6 +20,7 @@
import android.app.Activity;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
@@ -40,12 +41,16 @@
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceGroup.PreferencePositionCallback;
import androidx.preference.PreferenceScreen;
+import androidx.preference.ListPreference;
import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.BuildConfig;
import com.android.launcher3.Flags;
+import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherFiles;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
+import com.android.launcher3.allapps.AppDrawerStyle;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.SettingsCache;
@@ -57,7 +62,8 @@
* Settings activity for Launcher.
*/
public class SettingsAppDrawer extends CollapsingToolbarBaseActivity
- implements OnPreferenceStartFragmentCallback, OnPreferenceStartScreenCallback {
+ implements OnPreferenceStartFragmentCallback, OnPreferenceStartScreenCallback,
+ SharedPreferences.OnSharedPreferenceChangeListener {
public static final String EXTRA_FRAGMENT_ARGS = ":settings:fragment_args";
@@ -98,6 +104,27 @@ protected void onCreate(Bundle savedInstanceState) {
// Display the fragment as the main content.
fm.beginTransaction().replace(com.android.settingslib.collapsingtoolbar.R.id.content_frame, f).commit();
}
+ LauncherPrefs.getPrefs(this).registerOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ LauncherPrefs.getPrefs(this).unregisterOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ if (LauncherPrefs.ALL_APPS_SEARCH_PLACEMENT.getSharedPrefKey().equals(key) ||
+ LauncherPrefs.DRAWER_SCROLLBAR.getSharedPrefKey().equals(key) ||
+ LauncherPrefs.ALL_APPS_DARK_TEXT.getSharedPrefKey().equals(key) ||
+ LauncherPrefs.APP_DRAWER_STYLE.getSharedPrefKey().equals(key) ||
+ LauncherPrefs.APP_DRAWER_CUSTOM_COLOR_ENABLED.getSharedPrefKey().equals(key) ||
+ LauncherPrefs.APP_DRAWER_CUSTOM_COLOR_LIGHT.getSharedPrefKey().equals(key) ||
+ LauncherPrefs.APP_DRAWER_CUSTOM_COLOR_DARK.getSharedPrefKey().equals(key) ||
+ LauncherPrefs.APP_DRAWER_SORT_MODE.getSharedPrefKey().equals(key)) {
+ LauncherAppState.INSTANCE.executeIfCreated(app -> app.setNeedsRestart());
+ }
}
private boolean startPreference(String fragment, Bundle args, String key) {
@@ -144,7 +171,8 @@ public boolean onOptionsItemSelected(MenuItem item) {
* This fragment shows the launcher preferences.
*/
public static class AppDrawerSettingsFragment extends SettingsBasePreferenceFragment implements
- SettingsCache.OnChangeListener {
+ SettingsCache.OnChangeListener,
+ SharedPreferences.OnSharedPreferenceChangeListener {
private boolean mRestartOnResume = false;
@@ -152,6 +180,14 @@ public static class AppDrawerSettingsFragment extends SettingsBasePreferenceFrag
private boolean mPreferenceHighlighted = false;
+ private static final String KEY_SEARCH_PLACEMENT = "pref_allapps_search_placement";
+ private static final String KEY_OPEN_KEYBOARD = "pref_drawer_open_keyboard";
+ private static final String KEY_APP_DRAWER_STYLE = "pref_app_drawer_style";
+
+ private ListPreference mSearchPlacementPref;
+ private ListPreference mDrawerStylePref;
+ private Preference mOpenKeyboardPref;
+
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -171,6 +207,12 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
PreferenceScreen screen = getPreferenceScreen();
+ mSearchPlacementPref = screen.findPreference(KEY_SEARCH_PLACEMENT);
+ mDrawerStylePref = screen.findPreference(KEY_APP_DRAWER_STYLE);
+ mOpenKeyboardPref = screen.findPreference(KEY_OPEN_KEYBOARD);
+ updateOpenKeyboardEnabled();
+ updateDrawerStyleSummary();
+
// If the target preference is not in the current preference screen, find the parent
// preference screen that contains the target preference and set it as the preference
// screen.
@@ -190,6 +232,9 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
if (getActivity() != null && !TextUtils.isEmpty(getPreferenceScreen().getTitle())) {
getActivity().setTitle(getPreferenceScreen().getTitle());
}
+
+ getPreferenceManager().getSharedPreferences()
+ .registerOnSharedPreferenceChangeListener(this);
}
private boolean isKeyInPreferenceGroup(String targetKey, PreferenceGroup parent) {
@@ -267,6 +312,35 @@ public void onResume() {
}
}
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ getPreferenceManager().getSharedPreferences()
+ .unregisterOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+ if (KEY_SEARCH_PLACEMENT.equals(key) || KEY_APP_DRAWER_STYLE.equals(key)) {
+ updateOpenKeyboardEnabled();
+ updateDrawerStyleSummary();
+ }
+ }
+
+ private void updateOpenKeyboardEnabled() {
+ if (mOpenKeyboardPref == null || mSearchPlacementPref == null) return;
+ boolean searchVisible = !"hidden".equals(mSearchPlacementPref.getValue());
+ mOpenKeyboardPref.setEnabled(searchVisible);
+ }
+
+ private void updateDrawerStyleSummary() {
+ if (mDrawerStylePref == null) {
+ return;
+ }
+ String style = mDrawerStylePref.getValue();
+ mDrawerStylePref.setSummary(mDrawerStylePref.getEntry());
+ }
+
@Override
public void onSettingsChanged(boolean isEnabled) {
// Developer options changed, try recreate
diff --git a/src/com/android/launcher3/settings/SettingsHomescreen.java b/src/com/android/launcher3/settings/SettingsHomescreen.java
index e0bd952565d..6fc31600a42 100644
--- a/src/com/android/launcher3/settings/SettingsHomescreen.java
+++ b/src/com/android/launcher3/settings/SettingsHomescreen.java
@@ -20,6 +20,7 @@
import android.app.Activity;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
@@ -33,6 +34,7 @@
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback;
import androidx.preference.PreferenceFragmentCompat.OnPreferenceStartScreenCallback;
@@ -43,10 +45,13 @@
import com.android.launcher3.BuildConfig;
import com.android.launcher3.Flags;
+import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherFiles;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.SettingsCache;
+import com.android.launcher3.util.VibratorWrapper;
import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
import com.android.settingslib.widget.SettingsBasePreferenceFragment;
@@ -55,7 +60,8 @@
* Settings activity for Launcher.
*/
public class SettingsHomescreen extends CollapsingToolbarBaseActivity
- implements OnPreferenceStartFragmentCallback, OnPreferenceStartScreenCallback {
+ implements OnPreferenceStartFragmentCallback, OnPreferenceStartScreenCallback,
+ SharedPreferences.OnSharedPreferenceChangeListener {
public static final String EXTRA_FRAGMENT_ARGS = ":settings:fragment_args";
@@ -96,6 +102,39 @@ protected void onCreate(Bundle savedInstanceState) {
// Display the fragment as the main content.
fm.beginTransaction().replace(com.android.settingslib.collapsingtoolbar.R.id.content_frame, f).commit();
}
+ LauncherPrefs.getPrefs(this).registerOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ LauncherPrefs.getPrefs(this).unregisterOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ if (LauncherPrefs.SHOW_HOTSEAT_BG.getSharedPrefKey().equals(key) ||
+ LauncherPrefs.HOTSEAT_OPACITY.getSharedPrefKey().equals(key) ||
+ LauncherPrefs.DOCK_SEARCH.getSharedPrefKey().equals(key) ||
+ LauncherPrefs.DOCK_SEARCH_PIXEL_STYLE.getSharedPrefKey().equals(key) ||
+ LauncherPrefs.DOCK_THEME.getSharedPrefKey().equals(key) ||
+ LauncherPrefs.SEARCH_RADIUS_SIZE.getSharedPrefKey().equals(key) ||
+ LauncherPrefs.DOCK_MUSIC_SEARCH.getSharedPrefKey().equals(key) ||
+ LauncherPrefs.HOTSEAT_QSB_OPACITY.getSharedPrefKey().equals(key) ||
+ LauncherPrefs.HOTSEAT_QSB_OUTER_OPACITY.getSharedPrefKey().equals(key) ||
+ LauncherPrefs.HOTSEAT_QSB_STROKE_WIDTH.getSharedPrefKey().equals(key) ||
+ LauncherPrefs.SHOW_STATUS_BAR.getSharedPrefKey().equals(key) ||
+ LauncherPrefs.SHORT_PARALLAX.getSharedPrefKey().equals(key) ||
+ LauncherPrefs.SINGLE_PAGE_CENTER.getSharedPrefKey().equals(key) ||
+ LauncherPrefs.DARK_STATUS_BAR.getSharedPrefKey().equals(key) ||
+ LauncherPrefs.SHOW_QUICKSPACE.getSharedPrefKey().equals(key) ||
+ LauncherPrefs.SHOW_QUICKSPACE_ALT.getSharedPrefKey().equals(key) ||
+ LauncherPrefs.SHOW_QUICKSPACE_PSONALITY.getSharedPrefKey().equals(key) ||
+ LauncherPrefs.SHOW_QUICKSPACE_NOWPLAYING.getSharedPrefKey().equals(key) ||
+ LauncherPrefs.SHOW_QUICKSPACE_WEATHER.getSharedPrefKey().equals(key) ||
+ LauncherPrefs.AUTO_HIDE_DOTS.getSharedPrefKey().equals(key)) {
+ LauncherAppState.INSTANCE.executeIfCreated(app -> app.setNeedsRestart());
+ }
}
private boolean startPreference(String fragment, Bundle args, String key) {
@@ -151,8 +190,10 @@ public static class HomescreenSettingsFragment extends SettingsBasePreferenceFra
private boolean mPreferenceHighlighted = false;
private static final String KEY_MINUS_ONE = "pref_enable_minus_one";
+ private static final String KEY_GENERAL_CATEGORY = "general_category";
private Preference mShowGoogleAppPref;
+ private Preference mShowGoogleBarPref;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
@@ -174,8 +215,16 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
PreferenceScreen screen = getPreferenceScreen();
mShowGoogleAppPref = screen.findPreference(KEY_MINUS_ONE);
+ mShowGoogleBarPref = screen.findPreference(LauncherPrefs.DOCK_SEARCH.getSharedPrefKey());
+
updateIsGoogleAppEnabled();
+ if (!VibratorWrapper.INSTANCE.get(getContext()).hasVibrator()) {
+ PreferenceCategory generalCategory = (PreferenceCategory) findPreference(KEY_GENERAL_CATEGORY);
+ Preference d2SHaptic = screen.findPreference(LauncherPrefs.SLEEP_GESTURE_HAPTIC.getSharedPrefKey());
+ generalCategory.removePreference(d2SHaptic);
+ }
+
// If the target preference is not in the current preference screen, find the parent
// preference screen that contains the target preference and set it as the preference
// screen.
@@ -259,6 +308,9 @@ private void updateIsGoogleAppEnabled() {
if (mShowGoogleAppPref != null) {
mShowGoogleAppPref.setEnabled(Utilities.isGSAEnabled(getContext()));
}
+ if (mShowGoogleBarPref != null) {
+ mShowGoogleBarPref.setEnabled(Utilities.isGSAEnabled(getContext()));
+ }
}
@Override
diff --git a/src/com/android/launcher3/settings/SettingsIcons.java b/src/com/android/launcher3/settings/SettingsIcons.java
index 8053e19c5ef..a13213f73b0 100644
--- a/src/com/android/launcher3/settings/SettingsIcons.java
+++ b/src/com/android/launcher3/settings/SettingsIcons.java
@@ -20,6 +20,10 @@
import android.app.Activity;
import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+
+import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
@@ -44,8 +48,13 @@
import com.android.launcher3.BuildConfig;
import com.android.launcher3.Flags;
import com.android.launcher3.LauncherFiles;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.customization.IconDatabase;
+import com.android.launcher3.icons.pack.IconPackSettingsActivity;
+import com.android.launcher3.settings.preference.ReloadingListPreference;
+import com.android.launcher3.util.AppReloader;
import com.android.launcher3.util.SettingsCache;
import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
@@ -144,7 +153,7 @@ public boolean onOptionsItemSelected(MenuItem item) {
* This fragment shows the launcher preferences.
*/
public static class IconsSettingsFragment extends SettingsBasePreferenceFragment implements
- SettingsCache.OnChangeListener {
+ SettingsCache.OnChangeListener, SharedPreferences.OnSharedPreferenceChangeListener {
private boolean mRestartOnResume = false;
@@ -152,6 +161,8 @@ public static class IconsSettingsFragment extends SettingsBasePreferenceFragment
private boolean mPreferenceHighlighted = false;
+ private ReloadingListPreference mIconPackPref;
+
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -169,6 +180,19 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
getPreferenceManager().setSharedPreferencesName(LauncherFiles.SHARED_PREFERENCES_KEY);
setPreferencesFromResource(R.xml.launcher_icons_preferences, rootKey);
+ updatePreferences();
+
+ LauncherPrefs.getPrefs(getContext())
+ .registerOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ LauncherPrefs.getPrefs(getContext()).unregisterOnSharedPreferenceChangeListener(this);
+ }
+
+ private void updatePreferences() {
PreferenceScreen screen = getPreferenceScreen();
for (int i = screen.getPreferenceCount() - 1; i >= 0; i--) {
Preference preference = screen.getPreference(i);
@@ -256,6 +280,15 @@ public void onSaveInstanceState(Bundle outState) {
outState.putBoolean(SAVE_HIGHLIGHTED_KEY, mPreferenceHighlighted);
}
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+ switch (key) {
+ case IconDatabase.KEY_ICON_PACK:
+ updatePreferences();
+ break;
+ }
+ }
+
/**
* Initializes a preference. This is called for every preference. Returning false here
* will remove that preference from the list.
@@ -264,6 +297,9 @@ protected boolean initPreference(Preference preference) {
switch (preference.getKey()) {
case NOTIFICATION_DOTS_PREFERENCE_KEY:
return BuildConfig.NOTIFICATION_DOTS_ENABLED;
+ case IconDatabase.KEY_ICON_PACK:
+ setupIconPackPreference(preference);
+ return true;
}
return true;
@@ -327,5 +363,18 @@ private PreferenceHighlighter createHighlighter() {
list, position, screen.findPreference(mHighLightKey))
: null;
}
+
+ private void setupIconPackPreference(Preference preference) {
+ final String pkgLabel = IconDatabase.getGlobalLabel(getContext());
+ preference.setSummary(pkgLabel);
+ preference.setOnPreferenceClickListener(p -> {
+ startActivity(new Intent(getContext(), IconPackSettingsActivity.class));
+ return true;
+ });
+ }
+ }
+
+ public interface OnResumePreferenceCallback {
+ void onResume();
}
}
diff --git a/src/com/android/launcher3/settings/SettingsMisc.java b/src/com/android/launcher3/settings/SettingsMisc.java
index 615ff4a2553..c37a37216e4 100644
--- a/src/com/android/launcher3/settings/SettingsMisc.java
+++ b/src/com/android/launcher3/settings/SettingsMisc.java
@@ -27,8 +27,11 @@
import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
import android.app.Activity;
+import android.content.Context;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
@@ -53,9 +56,12 @@
import com.android.launcher3.BuildConfig;
import com.android.launcher3.Flags;
import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherFiles;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.lineage.trust.TrustAppsActivity;
import com.android.launcher3.states.RotationHelper;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.SettingsCache;
@@ -63,17 +69,24 @@
import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
import com.android.settingslib.widget.SettingsBasePreferenceFragment;
+import com.android.systemui.shared.system.BlurUtils;
+
/**
* Settings activity for Launcher.
*/
public class SettingsMisc extends CollapsingToolbarBaseActivity
- implements OnPreferenceStartFragmentCallback, OnPreferenceStartScreenCallback {
+ implements OnPreferenceStartFragmentCallback, OnPreferenceStartScreenCallback,
+ SharedPreferences.OnSharedPreferenceChangeListener {
+
@VisibleForTesting
static final String DEVELOPER_OPTIONS_KEY = "pref_developer_options";
public static final String FIXED_LANDSCAPE_MODE = "pref_fixed_landscape_mode";
+ private static final String SUGGESTIONS_KEY = "pref_suggestions";
+ protected static final String DPS_PACKAGE = "com.google.android.as";
+
public static final String EXTRA_FRAGMENT_ARGS = ":settings:fragment_args";
// Intent extra to indicate the pref-key to highlighted when opening the settings activity
@@ -84,6 +97,8 @@ public class SettingsMisc extends CollapsingToolbarBaseActivity
private static final int DELAY_HIGHLIGHT_DURATION_MILLIS = 600;
public static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted";
+ public static final String KEY_TRUST_APPS = "pref_trust_apps";
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -113,6 +128,20 @@ protected void onCreate(Bundle savedInstanceState) {
// Display the fragment as the main content.
fm.beginTransaction().replace(com.android.settingslib.collapsingtoolbar.R.id.content_frame, f).commit();
}
+ LauncherPrefs.getPrefs(this).registerOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ LauncherPrefs.getPrefs(this).unregisterOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ if (LauncherPrefs.BLUR_DEPTH.getSharedPrefKey().equals(key)) {
+ LauncherAppState.INSTANCE.executeIfCreated(app -> app.setNeedsRestart());
+ }
}
private boolean startPreference(String fragment, Bundle args, String key) {
@@ -284,18 +313,30 @@ public void onSaveInstanceState(Bundle outState) {
* will remove that preference from the list.
*/
protected boolean initPreference(Preference preference) {
+ String key = preference.getKey();
+ if (key == null) {
+ return true;
+ }
+
+ if (key.equals(LauncherPrefs.BLUR_DEPTH.getSharedPrefKey())) {
+ return BlurUtils.supportsBlursOnWindows();
+ }
+
DisplayController.Info info = DisplayController.INSTANCE.get(getContext()).getInfo();
switch (preference.getKey()) {
case ALLOW_ROTATION_PREFERENCE_KEY:
- if (Flags.oneGridSpecs()) {
- return false;
- }
if (info.isTablet(info.realBounds)) {
// Launcher supports rotation by default. No need to show this setting.
return false;
}
+ if (!getContext().getResources().getBoolean(
+ com.android.internal.R.bool.config_supportAutoRotation)) {
+ // Not supported by the device, hide setting.
+ return false;
+ }
// Initialize the UI once
- preference.setDefaultValue(RotationHelper.getAllowRotationDefaultValue(info));
+ preference.setDefaultValue(
+ RotationHelper.getAllowRotationDefaultValue(getContext(), info));
return true;
case DEVELOPER_OPTIONS_KEY:
if (IS_STUDIO_BUILD) {
@@ -324,10 +365,32 @@ protected boolean initPreference(Preference preference) {
}
);
return !info.isTablet(info.realBounds);
+ case SUGGESTIONS_KEY:
+ // Show if Device Personalization Services is present.
+ return isDPSEnabled(getContext());
+
+ case KEY_TRUST_APPS:
+ preference.setOnPreferenceClickListener(p -> {
+ Utilities.showLockScreen(getActivity(),
+ getString(R.string.trust_apps_manager_name), () -> {
+ Intent intent = new Intent(getActivity(), TrustAppsActivity.class);
+ startActivity(intent);
+ });
+ return true;
+ });
+ return true;
}
return true;
}
+ public static boolean isDPSEnabled(Context context) {
+ try {
+ return context.getPackageManager().getApplicationInfo(DPS_PACKAGE, 0).enabled;
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+
@Override
public void onResume() {
super.onResume();
diff --git a/src/com/android/launcher3/settings/SettingsRecents.java b/src/com/android/launcher3/settings/SettingsRecents.java
index 500a6235aff..47e1c92097f 100644
--- a/src/com/android/launcher3/settings/SettingsRecents.java
+++ b/src/com/android/launcher3/settings/SettingsRecents.java
@@ -20,6 +20,7 @@
import android.app.Activity;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
@@ -43,10 +44,14 @@
import com.android.launcher3.BuildConfig;
import com.android.launcher3.Flags;
+import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherFiles;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.SettingsCache;
+import com.android.launcher3.util.VibratorWrapper;
import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
import com.android.settingslib.widget.SettingsBasePreferenceFragment;
@@ -55,7 +60,8 @@
* Settings activity for Launcher.
*/
public class SettingsRecents extends CollapsingToolbarBaseActivity
- implements OnPreferenceStartFragmentCallback, OnPreferenceStartScreenCallback {
+ implements OnPreferenceStartFragmentCallback, OnPreferenceStartScreenCallback,
+ SharedPreferences.OnSharedPreferenceChangeListener {
public static final String EXTRA_FRAGMENT_ARGS = ":settings:fragment_args";
@@ -67,6 +73,10 @@ public class SettingsRecents extends CollapsingToolbarBaseActivity
private static final int DELAY_HIGHLIGHT_DURATION_MILLIS = 600;
public static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted";
+ private static final String RECENTS_CATEGORY_ACTION = "recents_category_actions";
+ private static final String RECENTS_STYLE_PREF = "pref_recents_style";
+ private static final String RECENTS_SCROLL_VIBRATE_PREF = "pref_recents_scroll_vibrate";
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -96,6 +106,20 @@ protected void onCreate(Bundle savedInstanceState) {
// Display the fragment as the main content.
fm.beginTransaction().replace(com.android.settingslib.collapsingtoolbar.R.id.content_frame, f).commit();
}
+ LauncherPrefs.getPrefs(this).registerOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ LauncherPrefs.getPrefs(this).unregisterOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ if (LauncherPrefs.RECENTS_MEMINFO.getSharedPrefKey().equals(key)) {
+ LauncherAppState.INSTANCE.executeIfCreated(app -> app.setNeedsRestart());
+ }
}
private boolean startPreference(String fragment, Bundle args, String key) {
@@ -168,6 +192,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.launcher_recents_preferences, rootKey);
PreferenceScreen screen = getPreferenceScreen();
+ removeUnsupportedPreferences(screen);
// If the target preference is not in the current preference screen, find the parent
// preference screen that contains the target preference and set it as the preference
@@ -190,6 +215,17 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
}
}
+ private void removeUnsupportedPreferences(PreferenceGroup group) {
+ for (int i = group.getPreferenceCount() - 1; i >= 0; i--) {
+ Preference preference = group.getPreference(i);
+ if (!initPreference(preference)) {
+ group.removePreference(preference);
+ } else if (preference instanceof PreferenceGroup) {
+ removeUnsupportedPreferences((PreferenceGroup) preference);
+ }
+ }
+ }
+
private boolean isKeyInPreferenceGroup(String targetKey, PreferenceGroup parent) {
for (int i = 0; i < parent.getPreferenceCount(); i++) {
Preference pref = parent.getPreference(i);
@@ -248,6 +284,29 @@ public void onSaveInstanceState(Bundle outState) {
outState.putBoolean(SAVE_HIGHLIGHTED_KEY, mPreferenceHighlighted);
}
+ /**
+ * Initializes a preference. This is called for every preference. Returning false here
+ * will remove that preference from the list.
+ */
+ protected boolean initPreference(Preference preference) {
+ String key = preference.getKey();
+ if (key == null) {
+ return true;
+ }
+
+ DisplayController.Info info = DisplayController.INSTANCE.get(getContext()).getInfo();
+ if (key.equals(RECENTS_CATEGORY_ACTION) || key.equals(RECENTS_STYLE_PREF)
+ || key.equals(RECENTS_SCROLL_VIBRATE_PREF)) {
+ return !info.isTablet(info.realBounds);
+ }
+
+ if (key.equals(RECENTS_SCROLL_VIBRATE_PREF)) {
+ return VibratorWrapper.INSTANCE.get(getContext()).hasVibrator();
+ }
+
+ return true;
+ }
+
@Override
public void onResume() {
super.onResume();
diff --git a/src/com/android/launcher3/settings/preference/IconPackPrefSetter.java b/src/com/android/launcher3/settings/preference/IconPackPrefSetter.java
new file mode 100644
index 00000000000..04eb869c0a9
--- /dev/null
+++ b/src/com/android/launcher3/settings/preference/IconPackPrefSetter.java
@@ -0,0 +1,70 @@
+package com.android.launcher3.settings.preference;
+
+import android.content.ComponentName;
+import android.content.Context;
+
+import androidx.preference.ListPreference;
+
+import com.android.launcher3.R;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+import com.android.launcher3.icons.pack.IconPackManager;
+import com.android.launcher3.customization.IconDatabase;
+
+public class IconPackPrefSetter implements ReloadingListPreference.OnReloadListener {
+ private final Context mContext;
+ private final ComponentName mFilter;
+
+ public IconPackPrefSetter(Context context) {
+ this(context, null);
+ }
+
+ public IconPackPrefSetter(Context context, ComponentName filter) {
+ mContext = context;
+ mFilter = filter;
+ }
+
+ @Override
+ public Runnable listUpdater(ReloadingListPreference pref) {
+ IconPackManager ipm = IconPackManager.get(mContext);
+ Map packList = ipm.getProviderNames();
+ String globalPack = IconDatabase.getGlobal(mContext);
+
+ if (mFilter != null) {
+ // Filter for packs with icon for this app, or the global pack.
+ for (String pkg : new HashSet<>(packList.keySet())) {
+ if (!ipm.packContainsActivity(pkg, mFilter)) {
+ packList.remove(pkg);
+ }
+ }
+ }
+
+ CharSequence[] keys = new String[packList.size() + 1];
+ CharSequence[] values = new String[keys.length];
+ int i = 0;
+
+ // First value, system default, or the current icon pack if that has no icon yet.
+ keys[i] = mContext.getResources().getString(R.string.icon_pack_default_label);
+ values[i++] = packList.containsKey(globalPack) ? "" : globalPack;
+
+ // List of available icon packs
+ List> packs = new ArrayList<>(packList.entrySet());
+ Collections.sort(packs, (o1, o2) ->
+ normalizeTitle(o1.getValue()).compareTo(normalizeTitle(o2.getValue())));
+ for (Map.Entry entry : packs) {
+ keys[i] = entry.getValue();
+ values[i++] = entry.getKey();
+ }
+
+ return () -> pref.setEntriesWithValues(keys, values);
+ }
+
+ private String normalizeTitle(CharSequence title) {
+ return title.toString().toLowerCase();
+ }
+}
diff --git a/src/com/android/launcher3/settings/preference/ReloadingListPreference.java b/src/com/android/launcher3/settings/preference/ReloadingListPreference.java
new file mode 100644
index 00000000000..0d20df46cac
--- /dev/null
+++ b/src/com/android/launcher3/settings/preference/ReloadingListPreference.java
@@ -0,0 +1,86 @@
+package com.android.launcher3.settings.preference;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import androidx.preference.ListPreference;
+import androidx.preference.PreferenceManager;
+
+import java.util.function.Function;
+
+import com.android.launcher3.settings.SettingsIcons;
+import com.android.launcher3.R;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR;
+
+@SuppressWarnings("unused")
+public class ReloadingListPreference extends ListPreference
+ implements SettingsIcons.OnResumePreferenceCallback {
+ public interface OnReloadListener {
+ Runnable listUpdater(ReloadingListPreference pref);
+ }
+
+ private OnReloadListener mOnReloadListener;
+
+ public ReloadingListPreference(Context context) {
+ super(context);
+ }
+
+ public ReloadingListPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public ReloadingListPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public ReloadingListPreference(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onAttachedToHierarchy(PreferenceManager preferenceManager) {
+ super.onAttachedToHierarchy(preferenceManager);
+ if ("pref_icon_pack".equals(getKey())) {
+ setLayoutResource(R.layout.preference_layout_icon);
+ }
+ }
+
+ @Override
+ protected void onClick() {
+ // Run the entries updater on the main thread immediately.
+ // Should be fast as the data was cached from the async load before.
+ // If it wasn't, we need to block to ensure the data has been loaded.
+ loadEntries(false);
+ super.onClick();
+ }
+
+ public void setOnReloadListener(Function supplier) {
+ mOnReloadListener = supplier.apply(getContext());
+ loadEntries(true);
+ }
+
+ @Override
+ public void onResume() {
+ loadEntries(true);
+ }
+
+ private void loadEntries(boolean async) {
+ if (mOnReloadListener != null) {
+ if (async) {
+ THREAD_POOL_EXECUTOR.execute(
+ () -> MAIN_EXECUTOR.execute(mOnReloadListener.listUpdater(this)));
+ } else {
+ mOnReloadListener.listUpdater(this).run();
+ }
+ }
+ }
+
+ void setEntriesWithValues(CharSequence[] entries, CharSequence[] entryValues) {
+ setEntries(entries);
+ setEntryValues(entryValues);
+ setSummary("%s");
+ }
+}
diff --git a/src/com/android/launcher3/settings/preference/RestartPreference.java b/src/com/android/launcher3/settings/preference/RestartPreference.java
new file mode 100644
index 00000000000..6c14f3a05b8
--- /dev/null
+++ b/src/com/android/launcher3/settings/preference/RestartPreference.java
@@ -0,0 +1,38 @@
+package com.android.launcher3.settings.preference;
+
+import android.content.Context;
+import android.content.Intent;
+import android.util.AttributeSet;
+import android.widget.Toast;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+
+import androidx.preference.Preference;
+
+public class RestartPreference extends Preference {
+
+
+ public RestartPreference(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ public RestartPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public RestartPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public RestartPreference(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected void onClick() {
+ super.onClick();
+ Toast.makeText(getContext(), R.string.restarting_launcher, Toast.LENGTH_SHORT).show();
+ Utilities.restart();
+ }
+}
diff --git a/src/com/android/launcher3/settings/preferences/ColorPreference.java b/src/com/android/launcher3/settings/preferences/ColorPreference.java
new file mode 100644
index 00000000000..85b5aa4ca78
--- /dev/null
+++ b/src/com/android/launcher3/settings/preferences/ColorPreference.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2023-2026 crDroid Android 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.settings.preferences;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.drawable.GradientDrawable;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.launcher3.R;
+import com.google.android.material.slider.Slider;
+import com.google.android.material.tabs.TabLayout;
+
+public class ColorPreference extends Preference {
+
+ private int mColor = Color.WHITE;
+ private View mPreviewView;
+
+ public ColorPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setWidgetLayoutResource(R.layout.color_preference_widget);
+ }
+
+ public ColorPreference(Context context) {
+ this(context, null);
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+ mPreviewView = holder.findViewById(R.id.color_preview);
+ updatePreview(mPreviewView, mColor);
+ }
+
+ private void updatePreview(View view, int color) {
+ if (view != null) {
+ GradientDrawable shape = new GradientDrawable();
+ shape.setShape(GradientDrawable.OVAL);
+ shape.setColor(color);
+ shape.setStroke(2, Color.GRAY);
+ view.setBackground(shape);
+ }
+ }
+
+ @Override
+ protected void onClick() {
+ showColorPickerDialog();
+ }
+
+ private void showColorPickerDialog() {
+ Context context = getContext();
+ Context dialogContext = context;
+
+ int themeId = context.getResources().getIdentifier(
+ "Theme.Material3.DynamicColors.DayNight", "style", context.getPackageName());
+ if (themeId == 0) {
+ themeId = context.getResources().getIdentifier(
+ "Theme.MaterialComponents.DayNight", "style", context.getPackageName());
+ }
+ if (themeId == 0) {
+ themeId = context.getResources().getIdentifier(
+ "Theme.AppCompat.DayNight", "style", context.getPackageName());
+ }
+
+ if (themeId != 0) {
+ dialogContext = new android.view.ContextThemeWrapper(context, themeId);
+ }
+
+ View root = LayoutInflater.from(dialogContext).inflate(R.layout.color_picker_dialog, null);
+
+ final View preview = root.findViewById(R.id.color_picker_preview);
+ final TabLayout tabs = root.findViewById(R.id.color_picker_tabs);
+ final LinearLayout hsbContainer = root.findViewById(R.id.hsb_container);
+ final LinearLayout rgbContainer = root.findViewById(R.id.rgb_container);
+
+ final Slider sliderHue = root.findViewById(R.id.slider_hue);
+ final Slider sliderSaturation = root.findViewById(R.id.slider_saturation);
+ final Slider sliderBrightness = root.findViewById(R.id.slider_brightness);
+
+ final Slider sliderRed = root.findViewById(R.id.slider_red);
+ final Slider sliderGreen = root.findViewById(R.id.slider_green);
+ final Slider sliderBlue = root.findViewById(R.id.slider_blue);
+
+ final EditText editHex = root.findViewById(R.id.edit_hex);
+
+ final int[] currentColor = {mColor};
+ final float[] hsv = new float[3];
+ Color.colorToHSV(mColor, hsv);
+
+ Runnable updateUI = () -> {
+ updatePreview(preview, currentColor[0]);
+ editHex.setText(String.format("#%08X", currentColor[0]));
+
+ sliderHue.setValue(hsv[0]);
+ sliderSaturation.setValue(hsv[1] * 100);
+ sliderBrightness.setValue(hsv[2] * 100);
+
+ sliderRed.setValue(Color.red(currentColor[0]));
+ sliderGreen.setValue(Color.green(currentColor[0]));
+ sliderBlue.setValue(Color.blue(currentColor[0]));
+ };
+
+ tabs.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
+ @Override
+ public void onTabSelected(TabLayout.Tab tab) {
+ if (tab.getPosition() == 0) {
+ hsbContainer.setVisibility(View.VISIBLE);
+ rgbContainer.setVisibility(View.GONE);
+ } else {
+ hsbContainer.setVisibility(View.GONE);
+ rgbContainer.setVisibility(View.VISIBLE);
+ }
+ }
+ @Override
+ public void onTabUnselected(TabLayout.Tab tab) {}
+ @Override
+ public void onTabReselected(TabLayout.Tab tab) {}
+ });
+
+ Slider.OnChangeListener hsbListener = (slider, value, fromUser) -> {
+ if (fromUser) {
+ hsv[0] = sliderHue.getValue();
+ hsv[1] = sliderSaturation.getValue() / 100f;
+ hsv[2] = sliderBrightness.getValue() / 100f;
+ int alpha = Color.alpha(currentColor[0]);
+ currentColor[0] = Color.HSVToColor(alpha, hsv);
+
+ updatePreview(preview, currentColor[0]);
+ editHex.setText(String.format("#%08X", currentColor[0]));
+ sliderRed.setValue(Color.red(currentColor[0]));
+ sliderGreen.setValue(Color.green(currentColor[0]));
+ sliderBlue.setValue(Color.blue(currentColor[0]));
+ }
+ };
+ sliderHue.addOnChangeListener(hsbListener);
+ sliderSaturation.addOnChangeListener(hsbListener);
+ sliderBrightness.addOnChangeListener(hsbListener);
+
+ Slider.OnChangeListener rgbListener = (slider, value, fromUser) -> {
+ if (fromUser) {
+ int r = (int) sliderRed.getValue();
+ int g = (int) sliderGreen.getValue();
+ int b = (int) sliderBlue.getValue();
+ int alpha = Color.alpha(currentColor[0]);
+ currentColor[0] = Color.argb(alpha, r, g, b);
+
+ updatePreview(preview, currentColor[0]);
+ editHex.setText(String.format("#%08X", currentColor[0]));
+ Color.colorToHSV(currentColor[0], hsv);
+ sliderHue.setValue(hsv[0]);
+ sliderSaturation.setValue(hsv[1] * 100);
+ sliderBrightness.setValue(hsv[2] * 100);
+ }
+ };
+ sliderRed.addOnChangeListener(rgbListener);
+ sliderGreen.addOnChangeListener(rgbListener);
+ sliderBlue.addOnChangeListener(rgbListener);
+
+ editHex.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ try {
+ int color = Color.parseColor(s.toString());
+ if (color != currentColor[0]) {
+ currentColor[0] = color;
+ updatePreview(preview, currentColor[0]);
+ // update sliders without triggering infinite loop
+ Color.colorToHSV(currentColor[0], hsv);
+ sliderHue.setValue(hsv[0]);
+ sliderSaturation.setValue(hsv[1] * 100);
+ sliderBrightness.setValue(hsv[2] * 100);
+ sliderRed.setValue(Color.red(currentColor[0]));
+ sliderGreen.setValue(Color.green(currentColor[0]));
+ sliderBlue.setValue(Color.blue(currentColor[0]));
+ }
+ } catch (Exception e) {}
+ }
+ @Override
+ public void afterTextChanged(Editable s) {}
+ });
+
+ updateUI.run();
+
+ new AlertDialog.Builder(dialogContext)
+ .setTitle(getTitle())
+ .setView(root)
+ .setPositiveButton(android.R.string.ok, (dialog, which) -> {
+ mColor = currentColor[0];
+ persistInt(mColor);
+ updatePreview(mPreviewView, mColor);
+ })
+ .setNegativeButton(android.R.string.cancel, null)
+ .show();
+ }
+
+ @Override
+ protected Object onGetDefaultValue(TypedArray a, int index) {
+ return a.getInt(index, Color.WHITE);
+ }
+
+ @Override
+ protected void onSetInitialValue(Object defaultValue) {
+ mColor = getPersistedInt(defaultValue instanceof Integer ? (Integer) defaultValue : Color.WHITE);
+ }
+}
diff --git a/src/com/android/launcher3/settings/preferences/CustomSeekBarPreference.java b/src/com/android/launcher3/settings/preferences/CustomSeekBarPreference.java
new file mode 100644
index 00000000000..7da125253ef
--- /dev/null
+++ b/src/com/android/launcher3/settings/preferences/CustomSeekBarPreference.java
@@ -0,0 +1,375 @@
+/*
+ * Copyright (C) 2016-2026 crDroid Android 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.settings.preferences;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.MotionEvent;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.content.res.ResourcesCompat;
+import androidx.core.view.ViewCompat;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.launcher3.R;
+import com.android.settingslib.widget.SliderPreference;
+
+import com.google.android.material.slider.LabelFormatter;
+import com.google.android.material.slider.Slider;
+
+public class CustomSeekBarPreference extends SliderPreference {
+
+ private static final String SETTINGS_NS = "http://schemas.android.com/apk/res/com.android.settings";
+ private static final String ANDROIDNS = "http://schemas.android.com/apk/res/android";
+
+ private boolean mShowSign;
+ @Nullable
+ private String mUnits = "";
+ @Nullable
+ private String mDefaultValueText;
+ private boolean mDefaultValueTextExists;
+ private boolean mDefaultValueExists;
+ private int mDefaultValue;
+
+ private CharSequence mUserSummary;
+
+ private boolean mInUserDrag = false;
+
+ public CustomSeekBarPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ readLegacyAttrs(context, attrs);
+ initDefaults();
+ mUserSummary = super.getSummary();
+ updateSummaryNow();
+ }
+
+ public CustomSeekBarPreference(Context context) {
+ super(context, null);
+ initDefaults();
+ mUserSummary = super.getSummary();
+ updateSummaryNow();
+ }
+
+ private void initDefaults() {
+ setShowSliderValue(true);
+ setHapticFeedbackMode(HAPTIC_FEEDBACK_MODE_ON_TICKS);
+ setLabelFormater(new LabelFormatter() {
+ @Override public String getFormattedValue(float value) {
+ return formatValueForSummary((int) value);
+ }
+ });
+ }
+
+ private void readLegacyAttrs(Context c, AttributeSet attrs) {
+ if (attrs == null) return;
+ final TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.CustomSeekBarPreference);
+ try {
+ mShowSign = a.getBoolean(R.styleable.CustomSeekBarPreference_showSign, false);
+ final String units = a.getString(R.styleable.CustomSeekBarPreference_units);
+ if (units != null) mUnits = units;
+
+ final boolean continuous = a.getBoolean(
+ R.styleable.CustomSeekBarPreference_continuousUpdates, false);
+ setUpdatesContinuously(continuous);
+
+ mDefaultValueText = a.getString(
+ R.styleable.CustomSeekBarPreference_defaultValueText);
+ mDefaultValueTextExists = mDefaultValueText != null && !mDefaultValueText.isEmpty();
+
+ String defaultValue = attrs.getAttributeValue(ANDROIDNS, "defaultValue");
+ if (defaultValue == null) {
+ defaultValue = attrs.getAttributeValue(SETTINGS_NS, "defaultValue");
+ }
+ if (defaultValue != null && !defaultValue.isEmpty()) {
+ try {
+ mDefaultValue = Integer.parseInt(defaultValue);
+ mDefaultValueExists = true;
+ } catch (NumberFormatException ignored) {
+ mDefaultValueExists = false;
+ }
+ }
+
+ int interval = attrs.getAttributeIntValue(SETTINGS_NS, "interval", 0);
+ if (interval == 0) {
+ interval = attrs.getAttributeIntValue(ANDROIDNS, "interval", 0);
+ }
+ if (interval > 0) setSliderIncrement(interval);
+
+ // Guard against improper slider increment
+ int min = getMin();
+ int max = getMax();
+ int span = Math.max(0, max - min);
+
+ int step = getSliderIncrement();
+ if (step <= 0 || span == 0) {
+ setSliderIncrement(1); // Always use discrete steps for CustomSeekBarPreference
+ } else if ((span % step) != 0) {
+ int gcd = gcd(span, step);
+ if (gcd <= 0) gcd = 1;
+ setSliderIncrement(gcd);
+ }
+ } catch (Throwable ignored) {
+ // keep safe defaults
+ } finally {
+ a.recycle();
+ }
+ }
+
+ @Override
+ public void setSummary(CharSequence summary) {
+ mUserSummary = summary;
+ updateSummaryNow();
+ }
+
+ @Override
+ public void setValue(int sliderValue) {
+ super.setValue(sliderValue);
+ if (!mInUserDrag) updateSummaryNow();
+ }
+
+ private void updateSummaryNow() {
+ CharSequence composed = composeSummary(mUserSummary, getValue());
+ super.setSummary(composed);
+ }
+
+ private String formatValueForSummary(int v) {
+ if (mDefaultValueExists && mDefaultValueTextExists && v == mDefaultValue) {
+ return mDefaultValueText;
+ }
+ String s = String.valueOf(v);
+ if (mShowSign && v > 0) s = "+" + s;
+ if (mUnits != null && !mUnits.isEmpty()) s = s + " " + mUnits;
+ return s;
+ }
+
+ private CharSequence composeSummary(CharSequence userSummary, int v) {
+ final String valueText = formatValueForSummary(v);
+ if (userSummary == null || userSummary.length() == 0) return valueText;
+ return valueText + " \u2022 " + userSummary;
+ }
+
+ @Override
+ public void setDefaultValue(Object defaultValue) {
+ if (defaultValue instanceof Integer) {
+ mDefaultValueExists = true;
+ mDefaultValue = (Integer) defaultValue;
+ }
+ super.setDefaultValue(defaultValue);
+ updateSummaryNow();
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+
+ final TextView summaryView = (TextView) holder.findViewById(android.R.id.summary);
+ if (summaryView != null) {
+ summaryView.setText(composeSummary(mUserSummary, getValue()));
+ }
+
+ final View labelFrame = holder.findViewById(
+ com.android.settingslib.widget.preference.slider.R.id.label_frame);
+ final TextView startText = (TextView) holder.findViewById(android.R.id.text1);
+ final TextView endText = (TextView) holder.findViewById(android.R.id.text2);
+
+ if (labelFrame != null) {
+ boolean hasStart = startText != null && startText.getText() != null
+ && startText.getText().length() > 0;
+ boolean hasEnd = endText != null && endText.getText() != null
+ && endText.getText().length() > 0;
+ boolean parentWantsLabels = hasStart || hasEnd;
+
+ labelFrame.setVisibility((parentWantsLabels || mDefaultValueExists) ? View.VISIBLE : View.GONE);
+ }
+
+ if (endText != null) {
+ attachResetIcon(endText);
+ }
+
+ ViewGroup minusFrame = (ViewGroup) holder.findViewById(
+ com.android.settingslib.widget.preference.slider.R.id.icon_start_frame);
+ ImageView minusIcon = (ImageView) holder.findViewById(
+ com.android.settingslib.widget.preference.slider.R.id.icon_start);
+
+ ViewGroup plusFrame = (ViewGroup) holder.findViewById(
+ com.android.settingslib.widget.preference.slider.R.id.icon_end_frame);
+ ImageView plusIcon = (ImageView) holder.findViewById(
+ com.android.settingslib.widget.preference.slider.R.id.icon_end);
+
+ final Slider slider = (Slider) holder.findViewById(
+ com.android.settingslib.widget.preference.slider.R.id.slider);
+
+ int stepForClicks = Math.max(1, getSliderIncrement());
+
+ if (minusFrame != null && minusIcon != null) {
+ minusFrame.setVisibility(View.VISIBLE);
+ minusIcon.setImageResource(R.drawable.ic_custom_seekbar_minus);
+ minusFrame.setOnClickListener(v -> {
+ if (!isEnabled()) return;
+ int base = slider != null ? Math.round(slider.getValue()) : getValue();
+ int newVal = Math.max(getMin(), base - stepForClicks);
+ applyUserValue(newVal, slider);
+ updatePlusMinusEnabledStates(holder);
+ });
+ }
+
+ if (plusFrame != null && plusIcon != null) {
+ plusFrame.setVisibility(View.VISIBLE);
+ plusIcon.setImageResource(R.drawable.ic_custom_seekbar_plus);
+ plusFrame.setOnClickListener(v -> {
+ if (!isEnabled()) return;
+ int base = slider != null ? Math.round(slider.getValue()) : getValue();
+ int newVal = Math.min(getMax(), base + stepForClicks);
+ applyUserValue(newVal, slider);
+ updatePlusMinusEnabledStates(holder);
+ });
+ }
+
+ updatePlusMinusEnabledStates(holder);
+
+ if (slider != null && summaryView != null) {
+ slider.addOnChangeListener((s, value, fromUser) -> {
+ if (fromUser) {
+ summaryView.setText(composeSummary(mUserSummary, (int) value));
+ updatePlusMinusEnabledStates(holder);
+ }
+ });
+ slider.addOnSliderTouchListener(new Slider.OnSliderTouchListener() {
+ @Override
+ public void onStartTrackingTouch(@NonNull Slider s) {
+ mInUserDrag = true;
+ }
+
+ @Override
+ public void onStopTrackingTouch(@NonNull Slider s) {
+ mInUserDrag = false;
+ applyUserValue(Math.round(s.getValue()), s);
+ updatePlusMinusEnabledStates(holder);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onDependencyChanged(@NonNull Preference dependency, boolean disableDependent) {
+ super.onDependencyChanged(dependency, disableDependent);
+ notifyChanged();
+ }
+
+ private void applyUserValue(int newVal, @Nullable Slider slider) {
+ if (newVal == getValue()) return;
+ if (!callChangeListener(newVal)) {
+ if (slider != null) slider.setValue(getValue());
+ return;
+ }
+ setValue(newVal);
+ updateSummaryNow();
+ notifyChanged();
+ }
+
+ private static int gcd(int a, int b) {
+ a = Math.abs(a); b = Math.abs(b);
+ if (a == 0) return b;
+ if (b == 0) return a;
+ while (b != 0) {
+ int t = b; b = a % b; a = t;
+ }
+ return a;
+ }
+
+ private void updatePlusMinusEnabledStates(PreferenceViewHolder holder) {
+ View minusFrame = holder.findViewById(
+ com.android.settingslib.widget.preference.slider.R.id.icon_start_frame);
+ ImageView minusIcon = (ImageView) holder.findViewById(
+ com.android.settingslib.widget.preference.slider.R.id.icon_start);
+ View plusFrame = holder.findViewById(
+ com.android.settingslib.widget.preference.slider.R.id.icon_end_frame);
+ ImageView plusIcon = (ImageView) holder.findViewById(
+ com.android.settingslib.widget.preference.slider.R.id.icon_end);
+ boolean enabled = isEnabled();
+ int value = getValue();
+
+ if (minusFrame != null && minusIcon != null) {
+ int min = getMin();
+ minusFrame.setEnabled(enabled && (value > min));
+ minusIcon.setEnabled(enabled && (value > min));
+ }
+ if (plusFrame != null && plusIcon != null) {
+ int max = getMax();
+ plusFrame.setEnabled(enabled && (value < max));
+ plusIcon.setEnabled(enabled && (value < max));
+ }
+ }
+
+ private void attachResetIcon(TextView tv) {
+ if (!mDefaultValueExists) {
+ tv.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, null, null);
+ tv.setOnTouchListener(null);
+ tv.setClickable(false);
+ return;
+ }
+
+ final Drawable icon = ResourcesCompat.getDrawable(
+ tv.getResources(), R.drawable.ic_custom_seekbar_reset, tv.getContext().getTheme());
+ if (icon == null) return;
+
+ tv.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, icon, null);
+ tv.setCompoundDrawablePadding(dp(tv, 6));
+ tv.setClickable(isEnabled());
+ tv.setFocusable(isEnabled());
+
+ final int tapSlop = dp(tv, 8);
+
+ tv.setOnTouchListener((v, ev) -> {
+ if (!isEnabled() || ev.getAction() != MotionEvent.ACTION_UP) return false;
+
+ final boolean isRtl = ViewCompat.getLayoutDirection(tv) == ViewCompat.LAYOUT_DIRECTION_RTL;
+ final Drawable[] drs = tv.getCompoundDrawablesRelative();
+ final Drawable end = drs[2];
+ if (end == null) return false;
+
+ final int iconW = end.getIntrinsicWidth();
+ final int x = (int) ev.getX();
+
+ if (!isRtl) {
+ final int left = tv.getWidth() - ViewCompat.getPaddingEnd(tv) - iconW - tapSlop;
+ if (x >= left) { performReset(); return true; }
+ } else {
+ final int right = ViewCompat.getPaddingStart(tv) + iconW + tapSlop;
+ if (x <= right) { performReset(); return true; }
+ }
+ return false;
+ });
+ }
+
+ private void performReset() {
+ if (mDefaultValueExists) {
+ applyUserValue(mDefaultValue, null);
+ }
+ }
+
+ private static int dp(TextView v, int dp) {
+ return Math.round(dp * v.getResources().getDisplayMetrics().density);
+ }
+}
diff --git a/src/com/android/launcher3/shapes/ShapesProvider.kt b/src/com/android/launcher3/shapes/ShapesProvider.kt
index 8744502c816..94205e84f92 100644
--- a/src/com/android/launcher3/shapes/ShapesProvider.kt
+++ b/src/com/android/launcher3/shapes/ShapesProvider.kt
@@ -30,11 +30,58 @@ object ShapesProvider {
"M 35.209 6.878 C 36.326 5.895 36.884 5.404 37.397 5.006 C 44.82 -0.742 55.18 -0.742 62.603 5.006 C 63.116 5.404 63.674 5.895 64.791 6.878 C 65.164 7.207 65.351 7.371 65.539 7.529 C 68.167 9.734 71.303 11.248 74.663 11.932 C 74.902 11.981 75.147 12.025 75.637 12.113 C 77.1 12.375 77.831 12.506 78.461 12.66 C 87.573 14.893 94.032 23.011 94.176 32.412 C 94.186 33.062 94.151 33.805 94.08 35.293 C 94.057 35.791 94.045 36.04 94.039 36.285 C 93.958 39.72 94.732 43.121 96.293 46.18 C 96.404 46.399 96.522 46.618 96.759 47.056 C 97.467 48.366 97.821 49.021 98.093 49.611 C 102.032 58.143 99.727 68.266 92.484 74.24 C 91.983 74.653 91.381 75.089 90.177 75.961 C 89.774 76.254 89.572 76.4 89.377 76.548 C 86.647 78.626 84.477 81.353 83.063 84.483 C 82.962 84.707 82.865 84.936 82.671 85.395 C 82.091 86.766 81.8 87.451 81.51 88.033 C 77.31 96.44 67.977 100.945 58.801 98.994 C 58.166 98.859 57.451 98.659 56.019 98.259 C 55.54 98.125 55.3 98.058 55.063 97.998 C 51.74 97.154 48.26 97.154 44.937 97.998 C 44.699 98.058 44.46 98.125 43.981 98.259 C 42.549 98.659 41.834 98.859 41.199 98.994 C 32.023 100.945 22.69 96.44 18.49 88.033 C 18.2 87.451 17.909 86.766 17.329 85.395 C 17.135 84.936 17.038 84.707 16.937 84.483 C 15.523 81.353 13.353 78.626 10.623 76.548 C 10.428 76.4 10.226 76.254 9.823 75.961 C 8.619 75.089 8.017 74.653 7.516 74.24 C 0.273 68.266 -2.032 58.143 1.907 49.611 C 2.179 49.021 2.533 48.366 3.241 47.056 C 3.478 46.618 3.596 46.399 3.707 46.18 C 5.268 43.121 6.042 39.72 5.961 36.285 C 5.955 36.04 5.943 35.791 5.92 35.293 C 5.849 33.805 5.814 33.062 5.824 32.412 C 5.968 23.011 12.427 14.893 21.539 12.66 C 22.169 12.506 22.9 12.375 24.363 12.113 C 24.853 12.025 25.098 11.981 25.337 11.932 C 28.697 11.248 31.833 9.734 34.461 7.529 C 34.649 7.371 34.836 7.207 35.209 6.878 Z"
private const val ARCH_PATH =
"M50 0C77.614 0 100 22.386 100 50C100 85.471 100 86.476 99.9 87.321 99.116 93.916 93.916 99.116 87.321 99.9 86.476 100 85.471 100 83.46 100H16.54C14.529 100 13.524 100 12.679 99.9 6.084 99.116 .884 93.916 .1 87.321 0 86.476 0 85.471 0 83.46L0 50C0 22.386 22.386 0 50 0Z"
+ private const val PEBBLE_PATH = "M55,0 C25,0 0,25 0,50 0,78 28,100 55,100 85,100 100,85 100,58 100,30 86,0 55,0 Z"
+ private const val VESSEL_PATH = "M12.97,0 C8.41,0 4.14,2.55 2.21,6.68 -1.03,13.61 -0.71,21.78 3.16,28.46 4.89,31.46 4.89,35.2 3.16,38.2 -1.05,45.48 -1.05,54.52 3.16,61.8 4.89,64.8 4.89,68.54 3.16,71.54 -0.71,78.22 -1.03,86.39 2.21,93.32 4.14,97.45 8.41,100 12.97,100 21.38,100 78.62,100 87.03,100 91.59,100 95.85,97.45 97.79,93.32 101.02,86.39 100.71,78.22 96.84,71.54 95.1,68.54 95.1,64.8 96.84,61.8 101.05,54.52 101.05,45.48 96.84,38.2 95.1,35.2 95.1,31.46 96.84,28.46 100.71,21.78 101.02,13.61 97.79,6.68 95.85,2.55 91.59,0 87.03,0 78.62,0 21.38,0 12.97,0 Z"
+ private const val PURE_SQUARE_PATH = "M50,0L100,0 100,100 0,100 0,0z"
+ private const val SQUIRCLE_PATH = "M50,0 C10,0 0,10 0,50 0,90 10,100 50,100 90,100 100,90 100,50 100,10 90,0 50,0 Z"
+ private const val TAPERED_RECT_PATH = "M20,0 80,0 100,20 100,80 80,100 20,100 0,80 0,20 20,0 Z"
+ private const val TEARDROP_PATH = "M50,0 C77.6,0 100,22.4 100,50 L100,88 C100,94.6 94.6,100 88,100 L50,100 C22.4 100 0 77.6 0 50C0 22.4 22.4 0 50 0 Z"
+ private const val ROUNDED_RECT_PATH = "M50,0L88,0 C94.4,0 100,5.4 100 12 L100,88 C100,94.6 94.6 100 88 100 L12,100 C5.4,100 0,94.6 0,88 L0 12 C0 5.4 5.4 0 12 0 L50,0 Z"
+ private const val CLOUDY_PATH =
+ "M4,50 C2,45 0,39 0,33 C0,15 15,0 33,0 C39,0 45,2 50,4 C55,2 61,0 67,0 C85,0 100,15 100,33 C100,39 98,45 96,50 C98,55 100,61 100,66 C100,85 85,100 66,100 C61,100 55,98 50,96 C45,98 39,100 33,100 C15,100 0,85 0,66 C0,61 2,55 3,50 Z"
+ private const val CYLINDRICAL_PATH =
+ "M50,0A50,30 0,0,1 100,30V70A50,30 0,0,1 0,70V30A50,30 0,0,1 50,0z"
+ private const val FLOWER_PATH =
+ "M50,0 C60.6,0 69.9,5.3 75.6,13.5 78.5,17.8 82.3,21.5 86.6,24.5 94.7,30.1 100,39.4 100,50 100,60.6 94.7,69.9 86.5,75.6 82.2,78.5 78.5,82.3 75.5,86.6 69.9,94.7 60.6,100 50,100 39.4,100 30.1,94.7 24.4,86.5 21.5,82.2 17.7,78.5 13.4,75.5 5.3,69.9 0,60.6 0,50 0,39.4 5.3,30.1 13.5,24.4 17.8,21.5 21.5,17.7 24.5,13.4 30.1,5.3 39.4,0 50,0 Z"
+ private const val HEART_PATH =
+ "M50,20 C45,0 30,0 25,0 20,0 0,5 0,34 0,72 40,97 50,100 60,97 100,72 100,34 100,5 80,0 75,0 70,0 55,0 50,20 Z"
+ private const val HEXAGON_PATH =
+ "M12,0 88,0 100,50 88,100 12,100 0,50 12,0 Z"
+ private const val ROUNDED_HEXAGON_PATH =
+ "M4.8 33V67c0 5.8 3 11 8 13.7l29.4 17c4.9 2.7 11 2.7 15.9 0l29.4 -17c4.9 -2.7 8 -8 8 -13.7V33c0 -5.8 -3 -11 -8 -13.7l-29.4 -17c-4.9 -2.7 -11 -2.7 -15.9 0l-29.7 17C7.8 22.2 4.8 27.5 4.8 33z"
+ private const val IOS_STYLE_PATH =
+ "M24,0L76,0A24 24 0 0 1 100,24L100,76A24 24 0 0 1 76,100L24,100A24 24 0 0 1 0,76L0,24A24 24 0 0 1 24,0z"
+ private const val LEAF_PATH =
+ "M-0.06,0.07h67.37C85.36,0.07,100,14.71,100,32.76v67.37H32.63c-18.06,0-32.69-14.64-32.69-32.69L-0.06,0.07z"
+ private const val MEOW_PATH =
+ "M 4.432 31.65 C 4.656 31.126 4.736 30.551 4.663 29.985 C 4.026 26.269 1.314 11.591 0.116 4.701 C -0.235 2.992 0.443 0.912 1.895 0.145 C 2.336 -0.072 2.839 -0.014 3.293 0.112 C 4.378 0.473 5.381 1.08 6.357 1.718 C 10.502 4.416 14.621 7.167 18.732 9.933 C 19.823 10.666 21.252 10.658 22.334 9.911 C 30.185 4.6 39.651 1.503 49.833 1.503 C 60.18 1.503 69.786 4.701 77.716 10.161 C 78.797 10.922 80.235 10.937 81.331 10.199 C 86.109 6.997 90.875 3.776 95.646 0.564 C 96.99 -0.438 99.098 0.259 99.76 1.907 C 100.193 3.164 99.88 4.529 99.634 5.789 C 98.325 13.019 95.754 27.409 95.207 30.68 C 95.169 31.181 95.249 31.684 95.441 32.148 C 97.788 37.889 99.082 44.17 99.082 50.752 C 99.082 77.932 77.014 100 49.833 100 C 22.653 100 0.585 77.932 0.585 50.752 C 0.585 43.98 1.955 37.526 4.432 31.65 Z"
+ private const val SAMSUNG_STYLE_PATH =
+ "M46.2,0.4C45.2,0.5,41.9,0.8,40.9,0.8C40.4,0.9,39.6,1,39.1,1.1C38.5,1.1,37.8,1.3,37.5,1.3C36.9,1.4,36.2,1.5,35.4,1.7C34.8,1.8,33.3,2.1,32.9,2.2C32.8,2.3,32.6,2.3,32.5,2.3C32.4,2.3,32.2,2.4,32.2,2.4C32.1,2.5,31.7,2.6,31.3,2.6C30.9,2.7,30.5,2.8,30.5,2.9C30.5,2.9,30.4,3,30.2,3C30.1,3,29.9,3,29.8,3C29.8,3.1,29.4,3.2,29.1,3.3C28.8,3.4,28.2,3.6,27.7,3.8C27.2,3.9,26.8,4.1,26.8,4.1C26.7,4.1,26.5,4.1,26.4,4.2C25.5,4.6,24.8,4.8,24.7,4.8C24.6,4.8,24.5,4.9,24.5,4.9C24.5,5,24.4,5,24.3,5C24.2,5,24,5.1,24,5.1C24,5.2,23.9,5.3,23.7,5.3C23.6,5.3,23.5,5.3,23.5,5.4C23.5,5.4,23.4,5.5,23.3,5.5C23.2,5.5,23.1,5.5,23.1,5.6C23.1,5.7,23,5.7,22.9,5.8C22.8,5.8,22.6,5.8,22.5,5.9C22.3,6,22,6.2,21.9,6.3C21.7,6.4,21.5,6.5,21.5,6.5C21.5,6.5,21.3,6.6,21.1,6.7C20.9,6.8,20.5,7,20.1,7.2C19.8,7.4,19.4,7.6,19.2,7.7C19,7.8,18.9,7.9,18.9,8C18.9,8,18.8,8.1,18.8,8.1C18.6,8.1,17.5,8.8,17.5,8.9C17.5,9,17.5,9,17.4,9C17.3,9,17.2,9.1,17,9.2C16.8,9.4,16.4,9.7,16.1,10C15.8,10.2,15.4,10.5,15.3,10.6C15.2,10.7,14.9,11,14.6,11.2C14.3,11.4,14.1,11.6,14.1,11.7C14.1,11.7,14,11.8,13.8,11.9C13.7,11.9,13.6,12,13.6,12C13.6,12.1,13.1,12.6,12.6,13.2C12,13.7,11.4,14.4,11.3,14.5C11.1,14.7,10.9,15,10.8,15.1C10.6,15.3,10.4,15.6,10.3,15.8C10.2,16,10,16.1,10,16.1C9.9,16.1,9.9,16.2,9.9,16.3C9.9,16.3,9.9,16.4,9.8,16.4C9.8,16.4,9.7,16.5,9.6,16.6C9.5,16.8,9.4,17,9.2,17.1C9.1,17.3,8.9,17.6,8.8,17.8C8.6,18,8.5,18.2,8.4,18.2C8.4,18.2,8.3,18.3,8.3,18.5C8.3,18.6,8.2,18.7,8.2,18.7C8.1,18.7,8.1,18.7,8.1,18.8C8.1,18.8,7.9,19.1,7.8,19.3C7.6,19.5,7.4,20,7.2,20.3C7,20.7,6.7,21.1,6.6,21.3C6.5,21.5,6.3,21.8,6.3,22C6.2,22.2,6,22.4,6,22.4C6,22.4,5.9,22.5,5.9,22.6C5.9,22.6,5.8,22.8,5.7,23C5.6,23.2,5.5,23.4,5.4,23.5C5.3,23.6,5.2,24,5.1,24.3C4.9,24.5,4.8,25,4.7,25.2C4.6,25.5,4.5,25.7,4.4,25.8C4.4,25.8,4.4,26,4.4,26.1C4.4,26.2,4.3,26.3,4.3,26.4C4.2,26.4,4.1,26.7,4,27C3.9,27.3,3.8,27.6,3.8,27.6C3.8,27.7,3.7,28,3.6,28.2C3.5,28.5,3.3,28.9,3.3,29.1C3.2,29.3,3.2,29.5,3.2,29.6C3.2,29.7,3.1,29.8,3.1,29.8C3,29.8,3,29.9,3,30.1C3,30.2,2.9,30.4,2.9,30.4C2.8,30.4,2.8,30.7,2.7,30.9C2.7,31.1,2.6,31.3,2.6,31.4C2.5,31.4,2.5,31.5,2.5,31.7C2.5,31.9,2.5,32,2.4,32.1C2.4,32.1,2.3,32.4,2.2,32.7C2.2,33,2.1,33.4,2,33.6C1.9,34.3,1.7,35.1,1.6,35.7C1.4,36.9,1.3,37.2,1.2,38.2C1.1,38.8,1,39.5,0.9,39.8C0.9,40,0.8,40.5,0.8,40.9C0.8,41.3,0.7,42.5,0.6,43.5C0.4,46.1,0.4,53.6,0.6,56.3C0.7,57.3,0.8,58.6,0.8,59.1C0.9,59.6,1,60.4,1.1,60.9C1.1,61.5,1.3,62.2,1.3,62.5C1.4,63.5,1.7,64.7,1.9,65.7C2,66.1,2.1,66.5,2.1,66.8C2.2,67,2.3,67.3,2.3,67.5C2.4,67.7,2.5,68.2,2.6,68.6C2.7,69,2.8,69.4,2.9,69.5C2.9,69.5,3,69.7,3,69.8C3,70,3.3,71,3.8,72.3C3.9,72.8,4.1,73.2,4.1,73.3C4.1,73.3,4.1,73.5,4.2,73.6C4.3,73.8,4.4,74.1,4.5,74.4C4.6,74.7,4.7,74.9,4.7,75C4.8,75,4.8,75.1,4.8,75.3C4.8,75.4,4.9,75.5,4.9,75.5C5,75.5,5,75.7,5,75.8C5,75.9,5.1,76,5.1,76C5.2,76,5.3,76.1,5.3,76.3C5.3,76.4,5.3,76.5,5.4,76.5C5.4,76.5,5.5,76.6,5.5,76.7C5.5,76.8,5.5,76.9,5.6,76.9C5.7,76.9,5.7,77,5.8,77.1C5.8,77.2,5.8,77.4,5.9,77.6C6,77.7,6.2,78,6.3,78.2C6.7,78.9,7.1,79.7,7.4,80.1C7.5,80.3,7.6,80.5,7.6,80.5C7.6,80.7,8.8,82.5,8.9,82.5C8.9,82.5,9,82.6,9,82.6C9,82.7,9.1,82.9,9.2,83C9.4,83.2,9.7,83.6,10,83.9C10.2,84.2,10.4,84.5,10.5,84.5C10.5,84.6,10.7,84.8,10.9,85.1C11.7,86.1,13.7,88.1,14.7,88.8C14.9,89,15.1,89.2,15.2,89.3C15.3,89.4,15.6,89.6,15.8,89.7C16,89.9,16.1,90,16.1,90C16.1,90.1,16.2,90.1,16.3,90.1C16.3,90.1,16.4,90.1,16.4,90.2C16.4,90.2,16.5,90.3,16.6,90.4C16.8,90.5,17,90.6,17.1,90.8C17.3,90.9,17.6,91.1,17.8,91.3C18,91.4,18.3,91.6,18.3,91.6C18.4,91.7,18.5,91.8,18.6,91.8C18.6,91.8,18.7,91.8,18.7,91.8C18.8,92,21.3,93.4,22.7,94.2C22.9,94.3,23.2,94.4,23.4,94.5C23.5,94.6,23.7,94.7,23.8,94.7C23.9,94.7,24,94.8,24,94.8C24,94.9,24.1,94.9,24.2,95C24.3,95,24.5,95.1,24.6,95.1C24.7,95.1,25,95.2,25.2,95.3C25.5,95.4,25.7,95.5,25.8,95.6C25.8,95.6,25.9,95.7,26,95.7C26.1,95.7,26.3,95.7,26.5,95.8C26.7,95.9,27.2,96.1,28.2,96.4C28.5,96.5,28.9,96.7,29.1,96.7C29.2,96.8,29.4,96.9,29.5,96.9C29.6,96.9,29.7,96.9,29.8,97C29.8,97,29.9,97,30.1,97C30.2,97,30.4,97.1,30.4,97.1C30.7,97.3,31.6,97.5,31.8,97.5C31.9,97.5,32.1,97.5,32.1,97.6C32.1,97.6,32.2,97.7,32.4,97.7C32.5,97.7,33.2,97.9,33.9,98C34.7,98.2,35.5,98.4,35.7,98.4C36.7,98.6,37.2,98.7,37.6,98.8C37.8,98.8,38.3,98.9,38.8,99C40.1,99.1,40.5,99.2,44.5,99.5C46.9,99.7,53.8,99.6,56.5,99.4C59.7,99.1,60,99.1,61,98.9C61.6,98.9,62.3,98.8,62.6,98.7C63.2,98.6,63.9,98.5,64.7,98.4C65.1,98.3,67,97.8,67.6,97.7C67.8,97.6,68.1,97.6,68.2,97.5C69,97.3,69.5,97.2,69.5,97.1C69.5,97.1,69.6,97,69.8,97C69.9,97,70.3,97,70.9,96.7C71.4,96.5,72.4,96.2,72.9,96.1C73.2,96,73.5,95.9,73.7,95.8C73.8,95.7,74.1,95.6,74.4,95.5C74.7,95.4,74.9,95.3,75,95.3C75,95.2,75.2,95.2,75.3,95.2C75.4,95.2,75.6,95.1,75.6,95.1C75.6,95,75.6,95,75.7,95C75.8,95,76,94.9,76,94.9C76.1,94.8,76.2,94.7,76.3,94.7C76.4,94.7,76.5,94.7,76.5,94.6C76.5,94.6,76.6,94.5,76.7,94.5C76.8,94.5,76.9,94.5,76.9,94.4C76.9,94.3,77,94.3,77.1,94.3C77.2,94.3,77.8,94,77.9,93.9C77.9,93.9,78.1,93.8,78.2,93.7C78.4,93.6,78.9,93.4,79.2,93.2C79.6,93,80.1,92.7,80.3,92.5C81,92.2,83.2,90.7,83.2,90.6C83.2,90.6,83.2,90.6,83.3,90.6C83.4,90.6,83.4,90.5,83.4,90.5C83.5,90.4,83.7,90.3,83.9,90.1C84.1,89.9,84.4,89.7,84.4,89.7C84.5,89.6,84.7,89.5,84.9,89.3C85.9,88.5,88.7,85.7,89.1,85.1C89.1,84.9,89.2,84.8,89.3,84.8C89.3,84.8,89.5,84.6,89.6,84.4C89.8,84.1,90,83.9,90,83.9C90.1,83.9,90.1,83.9,90.1,83.8C90.1,83.7,90.2,83.6,90.6,83.1C90.7,83,90.8,82.8,90.8,82.8C90.8,82.7,90.9,82.7,90.9,82.7C91,82.7,91.1,82.5,91.2,82.4C91.3,82.2,91.4,82,91.4,82C91.5,82,91.5,82,91.5,81.9C91.5,81.8,91.5,81.8,91.6,81.7C91.6,81.7,92.5,80.3,93.2,79.1C93.8,78,94,77.6,94.2,77.3C94.3,77.1,94.4,76.8,94.5,76.6C94.6,76.5,94.7,76.3,94.7,76.2C94.7,76.1,94.8,76,94.8,76C94.9,76,94.9,75.9,95,75.8C95,75.7,95.1,75.5,95.1,75.4C95.1,75.3,95.2,75,95.3,74.8C95.4,74.6,95.5,74.3,95.6,74.2C95.6,74.1,95.7,73.9,95.8,73.7C95.9,73.5,95.9,73.3,95.9,73.2C95.9,73.1,96,73,96,72.9C96.1,72.9,96.2,72.4,96.5,71.7C96.5,71.5,96.7,71.1,96.7,71C96.8,70.8,96.9,70.6,96.9,70.5C96.9,70.4,96.9,70.3,97,70.2C97,70.2,97,70.1,97,69.9C97,69.8,97.1,69.6,97.1,69.6C97.2,69.5,97.4,68.8,97.5,68.3C97.5,68.1,97.6,68,97.6,68C97.7,68,97.7,67.9,97.7,67.8C97.7,67.7,97.7,67.4,97.8,67.3C97.9,66.9,98.2,65.4,98.4,64.8C98.6,63.6,98.6,63.1,98.9,61.3C99.1,60.1,99.2,59.7,99.5,55.5C99.7,53.2,99.6,46.1,99.4,43.5C99.3,42.6,99.2,41.4,99.2,40.9C99.1,40.4,99,39.5,98.9,39C98.9,38.5,98.8,37.8,98.7,37.5C98.6,36.9,98.5,36.4,98.4,35.5C98.4,35.3,98.2,34.7,98.1,34.2C98,33.6,97.8,33,97.8,32.8C97.7,32.6,97.6,32.3,97.6,32.2C97.6,32.1,97.5,31.7,97.4,31.3C97.3,30.9,97.2,30.5,97.1,30.5C97.1,30.5,97,30.4,97,30.2C97,30.1,97,29.9,97,29.8C96.9,29.8,96.8,29.4,96.7,29.1C96.6,28.8,96.5,28.3,96.4,28.1C96.3,27.8,96.1,27.3,96.1,27.1C96,26.8,95.9,26.5,95.8,26.3C95.7,26.2,95.6,25.9,95.5,25.6C95.4,25.4,95.3,25.1,95.3,25.1C95.2,25,95.2,24.9,95.2,24.8C95.2,24.6,95.1,24.5,95.1,24.5C95,24.5,95,24.4,95,24.3C95,24.2,94.9,24.1,94.9,24C94.8,23.9,94.7,23.8,94.7,23.7C94.7,23.6,94.7,23.5,94.6,23.5C94.6,23.5,94.5,23.4,94.5,23.3C94.5,23.2,94.5,23.1,94.4,23.1C94.3,23.1,94.3,23,94.3,22.9C94.3,22.8,94.2,22.6,94.1,22.5C94,22.3,93.8,22,93.7,21.8C93.6,21.5,93.3,21.1,93.2,20.9C93.1,20.6,92.8,20.1,92.6,19.8C92.1,18.9,91.2,17.5,91.1,17.5C91.1,17.5,91,17.5,91,17.4C91,17.3,90.9,17.1,90.8,17C90.6,16.8,90.3,16.4,90.1,16.1C89.8,15.8,89.6,15.5,89.5,15.5C89.5,15.4,89.3,15.2,89,14.9C88.2,13.9,86.2,11.9,85.5,11.3C85.3,11.1,85,10.9,84.9,10.8C84.7,10.6,84.4,10.4,84.2,10.3C84,10.2,83.9,10,83.9,10C83.9,9.9,83.8,9.9,83.8,9.9C83.7,9.9,83.6,9.9,83.6,9.8C83.6,9.8,83.5,9.7,83.4,9.6C83.2,9.5,83,9.3,82.8,9.2C82.6,9,82.3,8.8,82.1,8.7C81.9,8.6,81.8,8.5,81.8,8.4C81.8,8.4,81.7,8.3,81.5,8.3C81.4,8.3,81.3,8.2,81.3,8.2C81.3,8.1,81.3,8.1,81.2,8.1C81.2,8.1,80.9,7.9,80.7,7.8C80.5,7.6,80,7.4,79.7,7.2C79.3,7,78.9,6.7,78.7,6.6C78.5,6.5,78.2,6.4,78.1,6.3C77.9,6.2,77.5,6,77.3,5.8C77,5.7,76.7,5.5,76.6,5.5C76.5,5.4,76.1,5.2,75.8,5.1C75.5,5,75,4.8,74.8,4.7C74.5,4.6,74.3,4.5,74.2,4.4C74.2,4.4,74,4.4,73.9,4.4C73.8,4.4,73.7,4.3,73.7,4.3C73.6,4.2,73.5,4.2,73.4,4.1C73.2,4.1,72.7,3.9,72.3,3.8C71.8,3.6,71.2,3.4,70.9,3.3C70.6,3.2,70.3,3.1,70.2,3C70.2,3,70,3,69.8,3C69.7,3,69.5,2.9,69.5,2.9C69.5,2.8,68.9,2.6,68.4,2.5C68.2,2.5,68,2.5,67.9,2.4C67.7,2.3,67.5,2.3,66.3,2C65.9,1.9,65.4,1.8,65.2,1.8C64.5,1.6,63.9,1.5,62.7,1.3C62.4,1.3,61.7,1.1,61.2,1.1C60.2,0.9,59.3,0.8,56.6,0.6C54.9,0.5,47.6,0.3,46.2,0.4"
+ private const val STRETCHED_PATH =
+ "M100,50 C100,77 77,100 50,100 L10,100 C4,100 0,96 0,90 L0,50 C0,22 22,0 50,0 L90,0 C96,0 100,4 100,10 L100,50 Z"
const val CIRCLE_KEY = "circle"
const val SQUARE_KEY = "square"
const val FOUR_SIDED_COOKIE_KEY = "four_sided_cookie"
const val SEVEN_SIDED_COOKIE_KEY = "seven_sided_cookie"
const val ARCH_KEY = "arch"
+ const val PEBBLE_KEY = "pebble"
+ const val VESSEL_KEY = "vessel"
+ const val PURE_SQUARE_KEY = "pure_square"
+ const val SQUIRCLE_KEY = "squircle"
+ const val TAPERED_RECT_KEY = "tapered_rect"
+ const val TEARDROP_KEY = "teardrop"
+ const val ROUNDED_RECT_KEY = "rounded_rect"
+ const val CLOUDY_KEY = "cloudy"
+ const val CYLINDRICAL_KEY = "cylindrical"
+ const val FLOWER_KEY = "flower"
+ const val HEART_KEY = "heart"
+ const val HEXAGON_KEY = "hexagon"
+ const val ROUNDED_HEXAGON_KEY = "rounded_hexagon"
+ const val IOS_STYLE_KEY = "ios_style"
+ const val LEAF_KEY = "leaf"
+ const val MEOW_KEY = "meow"
+ const val SAMSUNG_STYLE_KEY = "samsung_style"
+ const val STRETCHED_KEY = "stretched"
val iconShapes: Array =
if (Flags.newCustomizationPickerUi() && LauncherFlags.enableLauncherIconShapes()) {
@@ -70,6 +117,110 @@ object ShapesProvider {
shapeRadius = 7.8f,
folderRadiusRatio = 1 / 4f,
),
+ IconShapeModel(
+ key = PEBBLE_KEY,
+ titleId = R.string.pebble_shape_title,
+ pathString = PEBBLE_PATH,
+ folderRadiusRatio = 1f,
+ shapeRadius = 26f,
+ ),
+ IconShapeModel(
+ key = VESSEL_KEY,
+ titleId = R.string.vessel_shape_title,
+ pathString = VESSEL_PATH,
+ folderRadiusRatio = 1 / 3f,
+ shapeRadius = 13f,
+ ),
+ IconShapeModel(
+ key = PURE_SQUARE_KEY,
+ titleId = R.string.pure_square_shape_title,
+ pathString = PURE_SQUARE_PATH,
+ folderRadiusRatio = 0f,
+ shapeRadius = 0f,
+ ),
+ IconShapeModel(
+ key = SQUIRCLE_KEY,
+ titleId = R.string.squircle_shape_title,
+ pathString = SQUIRCLE_PATH,
+ folderRadiusRatio = 1 / 3f,
+ shapeRadius = 12f,
+ ),
+ IconShapeModel(
+ key = TAPERED_RECT_KEY,
+ titleId = R.string.tapered_rect_shape_title,
+ pathString = TAPERED_RECT_PATH,
+ folderRadiusRatio = 1 / 3f,
+ shapeRadius = 10f,
+ ),
+ IconShapeModel(
+ key = TEARDROP_KEY,
+ titleId = R.string.teardrop_shape_title,
+ pathString = TEARDROP_PATH,
+ folderRadiusRatio = 1 / 2f,
+ shapeRadius = 16f,
+ ),
+ IconShapeModel(
+ key = ROUNDED_RECT_KEY,
+ titleId = R.string.rounded_rect_shape_title,
+ pathString = ROUNDED_RECT_PATH,
+ folderRadiusRatio = 1 / 4f,
+ shapeRadius = 8f,
+ ),
+ IconShapeModel(
+ key = CLOUDY_KEY,
+ titleId = R.string.cloudy_shape_title,
+ pathString = CLOUDY_PATH,
+ ),
+ IconShapeModel(
+ key = CYLINDRICAL_KEY,
+ titleId = R.string.cylindrical_shape_title,
+ pathString = CYLINDRICAL_PATH,
+ ),
+ IconShapeModel(
+ key = FLOWER_KEY,
+ titleId = R.string.flower_shape_title,
+ pathString = FLOWER_PATH,
+ ),
+ IconShapeModel(
+ key = HEART_KEY,
+ titleId = R.string.heart_shape_title,
+ pathString = HEART_PATH,
+ ),
+ IconShapeModel(
+ key = HEXAGON_KEY,
+ titleId = R.string.hexagon_shape_title,
+ pathString = HEXAGON_PATH,
+ ),
+ IconShapeModel(
+ key = ROUNDED_HEXAGON_KEY,
+ titleId = R.string.rounded_hexagon_shape_title,
+ pathString = ROUNDED_HEXAGON_PATH,
+ ),
+ IconShapeModel(
+ key = IOS_STYLE_KEY,
+ titleId = R.string.ios_style_shape_title,
+ pathString = IOS_STYLE_PATH,
+ ),
+ IconShapeModel(
+ key = LEAF_KEY,
+ titleId = R.string.leaf_shape_title,
+ pathString = LEAF_PATH,
+ ),
+ IconShapeModel(
+ key = MEOW_KEY,
+ titleId = R.string.meow_shape_title,
+ pathString = MEOW_PATH,
+ ),
+ IconShapeModel(
+ key = SAMSUNG_STYLE_KEY,
+ titleId = R.string.samsung_style_shape_title,
+ pathString = SAMSUNG_STYLE_PATH,
+ ),
+ IconShapeModel(
+ key = STRETCHED_KEY,
+ titleId = R.string.stretched_shape_title,
+ pathString = STRETCHED_PATH,
+ ),
)
} else {
arrayOf(
diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java
index 18ed8ade291..aa8d31455c1 100644
--- a/src/com/android/launcher3/statemanager/StateManager.java
+++ b/src/com/android/launcher3/statemanager/StateManager.java
@@ -257,8 +257,10 @@ public void onBackCancelled(S toState) {
private void goToState(
S state, boolean animated, long delay, AnimatorListener listener) {
- StateManagerProtoLogProxy.logGoToState(
- mState, state, getTrimmedStackTrace("StateManager.goToState"));
+ if (StateManagerProtoLogProxy.isEnabled()) {
+ StateManagerProtoLogProxy.logGoToState(
+ mState, state, getTrimmedStackTrace("StateManager.goToState"));
+ }
animated &= areAnimatorsEnabled();
if (getState() == state) {
@@ -345,8 +347,10 @@ public void prepareForAtomicAnimation(S fromState, S toState,
*/
public AnimatorSet createAtomicAnimation(
S fromState, S toState, StateAnimationConfig config) {
- StateManagerProtoLogProxy.logCreateAtomicAnimation(
- mState, toState, getTrimmedStackTrace("StateManager.createAtomicAnimation"));
+ if (StateManagerProtoLogProxy.isEnabled()) {
+ StateManagerProtoLogProxy.logCreateAtomicAnimation(
+ mState, toState, getTrimmedStackTrace("StateManager.createAtomicAnimation"));
+ }
PendingAnimation builder = new PendingAnimation(config.duration);
prepareForAtomicAnimation(fromState, toState, config);
@@ -484,9 +488,11 @@ public void setRestState(S restState) {
* Cancels the current animation.
*/
public void cancelAnimation() {
- StateManagerProtoLogProxy.logCancelAnimation(
- mConfig.currentAnimation != null,
- getTrimmedStackTrace("StateManager.cancelAnimation"));
+ if (StateManagerProtoLogProxy.isEnabled()) {
+ StateManagerProtoLogProxy.logCancelAnimation(
+ mConfig.currentAnimation != null,
+ getTrimmedStackTrace("StateManager.cancelAnimation"));
+ }
mConfig.reset();
// It could happen that a new animation is set as a result of an endListener on the
// existing animation.
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index c37f2594223..286dd149f45 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -38,6 +38,7 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherPrefChangeListener;
import com.android.launcher3.LauncherPrefs;
+import com.android.launcher3.R;
import com.android.launcher3.util.ContextTracker;
import com.android.launcher3.util.DisplayController;
@@ -53,7 +54,13 @@ public class RotationHelper implements LauncherPrefChangeListener,
/**
* Returns the default value of {@link #ALLOW_ROTATION_PREFERENCE_KEY} preference.
*/
- public static boolean getAllowRotationDefaultValue(DisplayController.Info info) {
+ public static boolean getAllowRotationDefaultValue(Context context,
+ DisplayController.Info info) {
+
+ if (context.getResources().getBoolean(R.bool.config_allowRotation)) {
+ return true;
+ }
+
// If the device's pixel density was scaled (usually via settings for A11y), use the
// original dimensions to determine if rotation is allowed of not.
float originalSmallestWidth = dpiFromPx(Math.min(info.currentSize.x, info.currentSize.y),
diff --git a/src/com/android/launcher3/touch/AllAppsSwipeController.java b/src/com/android/launcher3/touch/AllAppsSwipeController.java
index 24beb7c7c3c..463ec34f237 100644
--- a/src/com/android/launcher3/touch/AllAppsSwipeController.java
+++ b/src/com/android/launcher3/touch/AllAppsSwipeController.java
@@ -203,7 +203,7 @@ protected StateAnimationConfig getConfigForStates(LauncherState fromState,
* Applies Animation config values for transition from all apps to home.
*/
public static void applyAllAppsToNormalConfig(Launcher launcher, StateAnimationConfig config) {
- if (launcher.getDeviceProfile().shouldShowAllAppsOnSheet()) {
+ if (launcher.getDeviceProfile().shouldShowAllAppsOnSheet(launcher)) {
config.setInterpolator(ANIM_SCRIM_FADE,
Interpolators.reverse(ALL_APPS_SCRIM_RESPONDER));
config.setInterpolator(ANIM_ALL_APPS_FADE, FINAL_FRAME);
@@ -255,7 +255,7 @@ public static void applyAllAppsToNormalConfig(Launcher launcher, StateAnimationC
*/
public static void applyNormalToAllAppsAnimConfig(
Launcher launcher, StateAnimationConfig config) {
- if (launcher.getDeviceProfile().shouldShowAllAppsOnSheet()) {
+ if (launcher.getDeviceProfile().shouldShowAllAppsOnSheet(launcher)) {
config.setInterpolator(ANIM_ALL_APPS_FADE, INSTANT);
config.setInterpolator(ANIM_SCRIM_FADE, ALL_APPS_SCRIM_RESPONDER);
if (!config.isUserControlled()) {
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 970862d2f51..bcd7e8556e0 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -28,6 +28,7 @@
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import android.app.AlertDialog;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherApps;
@@ -49,6 +50,7 @@
import com.android.launcher3.apppairs.AppPairIcon;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.lineage.trust.db.TrustDatabaseHelper;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.InstanceIdSequence;
import com.android.launcher3.logging.StatsLogManager;
@@ -419,7 +421,16 @@ private static void startAppShortcutOrInfoActivity(View v, ItemInfo item, Launch
// Preload the icon to reduce latency b/w swapping the floating view with the original.
FloatingIconView.fetchIcon(launcher, v, item, true /* isOpening */);
}
- launcher.startActivitySafely(v, intent, item);
+
+ TrustDatabaseHelper db = TrustDatabaseHelper.getInstance(launcher);
+ ComponentName cn = item.getTargetComponent();
+ boolean isProtected = cn != null && db.isPackageProtected(cn.getPackageName());
+
+ if (isProtected) {
+ launcher.startActivitySafelyAuth(v, intent, item);
+ } else {
+ launcher.startActivitySafely(v, intent, item);
+ }
}
/**
diff --git a/src/com/android/launcher3/touch/WorkspaceTouchListener.java b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
index 5974fcfcc6f..423e9cc845d 100644
--- a/src/com/android/launcher3/touch/WorkspaceTouchListener.java
+++ b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
@@ -28,6 +28,7 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_INTERRUPTED;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORKSPACE_LONGPRESS;
+import android.content.Context;
import android.graphics.PointF;
import android.graphics.Rect;
import android.view.GestureDetector;
@@ -42,12 +43,14 @@
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.Workspace;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.util.TouchUtil;
+import com.android.launcher3.util.VibratorWrapper;
/**
* Helper class to handle touch on empty space in workspace and show options popup on long press
@@ -132,7 +135,7 @@ public boolean onTouch(View view, MotionEvent ev) {
}
boolean isInAllAppsBottomSheet = mLauncher.isInState(ALL_APPS)
- && mLauncher.getDeviceProfile().shouldShowAllAppsOnSheet();
+ && mLauncher.getDeviceProfile().shouldShowAllAppsOnSheet(mLauncher);
final boolean result;
if (mLongPressState == STATE_COMPLETED) {
@@ -222,4 +225,16 @@ private void maybeShowMenu() {
}
}
}
+
+ @Override
+ public boolean onDoubleTap(MotionEvent event) {
+ Context context = mWorkspace.getContext();
+ if (LauncherPrefs.SLEEP_GESTURE.get(context)) {
+ if (LauncherPrefs.SLEEP_GESTURE_HAPTIC.get(context))
+ VibratorWrapper.INSTANCE.get(context).vibrate(VibratorWrapper.EFFECT_CLICK);
+ mLauncher.onSleepEvent(event);
+ return true;
+ }
+ return false;
+ }
}
diff --git a/src/com/android/launcher3/util/AppReloader.java b/src/com/android/launcher3/util/AppReloader.java
new file mode 100644
index 00000000000..037adbbdc00
--- /dev/null
+++ b/src/com/android/launcher3/util/AppReloader.java
@@ -0,0 +1,81 @@
+package com.android.launcher3.util;
+
+import android.content.Context;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.ShortcutInfo;
+import android.os.UserHandle;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
+import android.content.pm.LauncherApps;
+import android.os.UserManager;
+import com.android.launcher3.util.ComponentKey;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import com.android.launcher3.customization.IconDatabase;
+
+public class AppReloader {
+ private static AppReloader sInstance;
+
+ public static synchronized AppReloader get(Context context) {
+ if (sInstance == null) {
+ sInstance = new AppReloader(context);
+ }
+ return sInstance;
+ }
+
+ private final Context mContext;
+ private final LauncherModel mModel;
+ private final UserManager mUsers;
+ private final LauncherApps mApps;
+
+ private AppReloader(Context context) {
+ mContext = context;
+ mModel = LauncherAppState.getInstance(context).getModel();
+ mUsers = context.getSystemService(UserManager.class);
+ mApps = context.getSystemService(LauncherApps.class);
+ }
+
+ public Set withIconPack(String iconPack) {
+ Set reloadKeys = new HashSet<>();
+ for (UserHandle user : mUsers.getUserProfiles()) {
+ for (LauncherActivityInfo info : mApps.getActivityList(null, user)) {
+ ComponentKey key = new ComponentKey(info.getComponentName(), info.getUser());
+ if (IconDatabase.getByComponent(mContext, key).equals(iconPack)) {
+ reloadKeys.add(key);
+ }
+ }
+ }
+ return reloadKeys;
+ }
+
+ public void reload() {
+ for (UserHandle user : mUsers.getUserProfiles()) {
+ Set pkgsSet = new HashSet<>();
+ for (LauncherActivityInfo info : mApps.getActivityList(null, user)) {
+ pkgsSet.add(info.getComponentName().getPackageName());
+ }
+ for (String pkg : pkgsSet) {
+ reload(user, pkg);
+ }
+ }
+ }
+
+ public void reload(ComponentKey key) {
+ reload(key.user, key.componentName.getPackageName());
+ }
+
+ public void reload(Collection keys) {
+ for (ComponentKey key : keys) {
+ reload(key);
+ }
+ }
+
+ private void reload(UserHandle user, String pkg) {
+ mModel.newModelCallbacks().onPackageChanged(pkg, user);
+ }
+}
diff --git a/src/com/android/launcher3/util/BerryBlackTheme.kt b/src/com/android/launcher3/util/BerryBlackTheme.kt
new file mode 100644
index 00000000000..714d8995d52
--- /dev/null
+++ b/src/com/android/launcher3/util/BerryBlackTheme.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import android.content.Context
+import android.content.res.Configuration
+import android.provider.Settings
+
+/** Matches [com.android.systemui.shade.ui.ShadeColors] berry / pure-black dark theme gating. */
+object BerryBlackTheme {
+ /** @return true when Secure `berry_black_theme` is on and UI is in night mode. */
+ @JvmStatic
+ fun isActive(context: Context): Boolean {
+ if (
+ Settings.Secure.getInt(
+ context.contentResolver,
+ Settings.Secure.BERRY_BLACK_THEME,
+ 0,
+ ) != 1
+ ) {
+ return false
+ }
+ val night = Configuration.UI_MODE_NIGHT_YES
+ val uiMode = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
+ return uiMode == night
+ }
+}
diff --git a/src/com/android/launcher3/util/CustomAppNameStore.java b/src/com/android/launcher3/util/CustomAppNameStore.java
new file mode 100644
index 00000000000..4635308c7f4
--- /dev/null
+++ b/src/com/android/launcher3/util/CustomAppNameStore.java
@@ -0,0 +1,55 @@
+/*
+ * SPDX-FileCopyrightText: crDroid Android Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.android.launcher3.util;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.model.data.ItemInfo;
+
+/** Persists user-defined app display names for launcher items. */
+public final class CustomAppNameStore {
+
+ private static final String CUSTOM_NAMES_PREFS = "custom_app_names";
+
+ private CustomAppNameStore() { }
+
+ @Nullable
+ public static String customNameKey(ItemInfo info) {
+ ComponentName cn = info.getTargetComponent();
+ if (cn == null) {
+ return null;
+ }
+ return cn.getPackageName() + "/" + cn.getClassName() + "/" + info.user.hashCode();
+ }
+
+ public static void saveCustomName(Context context, ItemInfo info, @Nullable String name) {
+ String key = customNameKey(info);
+ if (key == null) {
+ return;
+ }
+ SharedPreferences prefs = context.getSharedPreferences(CUSTOM_NAMES_PREFS,
+ Context.MODE_PRIVATE);
+ if (name == null) {
+ prefs.edit().remove(key).apply();
+ } else {
+ prefs.edit().putString(key, name).apply();
+ }
+ }
+
+ @Nullable
+ public static String getCustomName(Context context, ItemInfo info) {
+ String key = customNameKey(info);
+ if (key == null) {
+ return null;
+ }
+ return context.getSharedPreferences(CUSTOM_NAMES_PREFS, Context.MODE_PRIVATE)
+ .getString(key, null);
+ }
+}
diff --git a/src/com/android/launcher3/util/DaggerSingletonObject.java b/src/com/android/launcher3/util/DaggerSingletonObject.java
index a4bd30a1931..6d49340fb28 100644
--- a/src/com/android/launcher3/util/DaggerSingletonObject.java
+++ b/src/com/android/launcher3/util/DaggerSingletonObject.java
@@ -21,6 +21,7 @@
import com.android.launcher3.dagger.LauncherAppComponent;
import com.android.launcher3.dagger.LauncherComponentProvider;
+import java.util.function.Consumer;
import java.util.function.Function;
/**
@@ -30,12 +31,28 @@
public class DaggerSingletonObject {
private final Function mFunction;
+ private T mValue;
public DaggerSingletonObject(Function function) {
mFunction = function;
}
public T get(Context context) {
- return mFunction.apply(LauncherComponentProvider.get(context));
+ mValue = mFunction.apply(LauncherComponentProvider.get(context));
+ return mValue;
+ }
+
+ /**
+ * Executes the callback is the value is already created
+ * @return true if the callback was executed, false otherwise
+ */
+ public boolean executeIfCreated(Consumer callback) {
+ T v = mValue;
+ if (v != null) {
+ callback.accept(v);
+ return true;
+ } else {
+ return false;
+ }
}
}
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index cf88948925a..f2a30ea32e6 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -100,11 +100,13 @@ public class DisplayController {
public static final int CHANGE_DESKTOP_MODE = 1 << 6;
public static final int CHANGE_SHOW_LOCKED_TASKBAR = 1 << 7;
public static final int CHANGE_NIGHT_MODE = 1 << 8;
+ public static final int CHANGE_OVERLAYS = 1 << 9;
+ public static final int CHANGE_UI_MODE = 1 << 10;
public static final int CHANGE_ALL = CHANGE_ACTIVE_SCREEN | CHANGE_ROTATION
| CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS | CHANGE_NAVIGATION_MODE
| CHANGE_TASKBAR_PINNING | CHANGE_DESKTOP_MODE | CHANGE_SHOW_LOCKED_TASKBAR
- | CHANGE_NIGHT_MODE;
+ | CHANGE_NIGHT_MODE | CHANGE_OVERLAYS | CHANGE_UI_MODE;
private static final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
private static final String TARGET_OVERLAY_PACKAGE = "android";
@@ -125,6 +127,7 @@ public class DisplayController {
private final boolean mIsDesktopFormFactor;
private boolean mDestroyed = false;
+ private int mUiMode = -1;
@Inject
protected DisplayController(@ApplicationContext Context context,
@@ -249,9 +252,12 @@ private void onIntent(Intent intent) {
if (mDestroyed) {
return;
}
+ boolean overlaysChanged = false;
+ boolean uiModeChanged = false;
if (ACTION_OVERLAY_CHANGED.equals(intent.getAction())) {
Log.d(TAG, "Overlay changed, notifying listeners");
- notifyConfigChange(DEFAULT_DISPLAY);
+ overlaysChanged = true;
+ notifyConfigChange(DEFAULT_DISPLAY, overlaysChanged, uiModeChanged);
}
}
@@ -266,6 +272,7 @@ private void onConfigurationChanged(Configuration config, int displayId) {
PerDisplayInfo perDisplayInfo = mPerDisplayInfo.get(displayId);
Context windowContext = perDisplayInfo.mWindowContext;
Info info = perDisplayInfo.mInfo;
+ boolean uiModeChanged = mUiMode != config.uiMode;
if (config.densityDpi != info.densityDpi
|| config.fontScale != info.fontScale
|| !info.mScreenSizeDp.equals(
@@ -275,9 +282,11 @@ private void onConfigurationChanged(Configuration config, int displayId) {
!= info.showLockedTaskbarOnHome()
|| mWMProxy.showDesktopTaskbarForFreeformDisplay(windowContext)
!= info.showDesktopTaskbarForFreeformDisplay()
- || config.isNightModeActive() != info.mIsNightModeActive) {
- notifyConfigChange(displayId);
+ || config.isNightModeActive() != info.mIsNightModeActive
+ || uiModeChanged) {
+ notifyConfigChange(displayId, false, uiModeChanged);
}
+ mUiMode = config.uiMode;
}
public void setPriorityListener(DisplayInfoChangeListener listener) {
@@ -325,15 +334,20 @@ public Info getInfo() {
@AnyThread
public void notifyConfigChange() {
- notifyConfigChange(DEFAULT_DISPLAY);
+ notifyConfigChange(DEFAULT_DISPLAY, false, false);
}
@AnyThread
public void notifyConfigChange(int displayId) {
- notifyConfigChangeForDisplay(displayId);
+ notifyConfigChange(displayId, false, false);
}
- private int calculateChange(Info oldInfo, Info newInfo) {
+ @AnyThread
+ public void notifyConfigChange(int displayId, boolean overlaysChanged, boolean uiModeChanged) {
+ notifyConfigChangeForDisplay(displayId, overlaysChanged, uiModeChanged);
+ }
+
+ private int calculateChange(Info oldInfo, Info newInfo, boolean overlaysChanged, boolean uiModeChanged) {
int change = 0;
if (!newInfo.normalizedDisplayInfo.equals(oldInfo.normalizedDisplayInfo)) {
change |= CHANGE_ACTIVE_SCREEN;
@@ -359,6 +373,12 @@ private int calculateChange(Info oldInfo, Info newInfo) {
if (newInfo.mIsNightModeActive != oldInfo.mIsNightModeActive) {
change |= CHANGE_NIGHT_MODE;
}
+ if (overlaysChanged) {
+ change |= CHANGE_OVERLAYS;
+ }
+ if (uiModeChanged) {
+ change |= CHANGE_UI_MODE;
+ }
if (DEBUG) {
Log.d(TAG, "handleInfoChange - change: " + getChangeFlagsString(change));
@@ -382,11 +402,16 @@ private Info getNewInfo(Info oldInfo, Context displayInfoContext) {
@AnyThread
public void notifyConfigChangeForDisplay(int displayId) {
+ notifyConfigChangeForDisplay(displayId, false, false);
+ }
+
+ @AnyThread
+ public void notifyConfigChangeForDisplay(int displayId, boolean overlaysChanged, boolean uiModeChanged) {
PerDisplayInfo perDisplayInfo = mPerDisplayInfo.get(displayId);
if (perDisplayInfo == null) return;
Info oldInfo = perDisplayInfo.mInfo;
final Info newInfo = getNewInfo(oldInfo, perDisplayInfo.mWindowContext);
- final int flags = calculateChange(oldInfo, newInfo);
+ final int flags = calculateChange(oldInfo, newInfo, overlaysChanged, uiModeChanged);
if (flags != 0) {
MAIN_EXECUTOR.execute(() -> {
perDisplayInfo.mInfo = newInfo;
@@ -653,6 +678,8 @@ public String getChangeFlagsString(int change) {
appendFlag(result, change, CHANGE_DESKTOP_MODE, "CHANGE_DESKTOP_MODE");
appendFlag(result, change, CHANGE_SHOW_LOCKED_TASKBAR, "CHANGE_SHOW_LOCKED_TASKBAR");
appendFlag(result, change, CHANGE_NIGHT_MODE, "CHANGE_NIGHT_MODE");
+ appendFlag(result, change, CHANGE_OVERLAYS, "CHANGE_OVERLAYS");
+ appendFlag(result, change, CHANGE_UI_MODE, "CHANGE_UI_MODE");
return result.toString();
}
diff --git a/src/com/android/launcher3/util/ItemInfoMatcher.java b/src/com/android/launcher3/util/ItemInfoMatcher.java
index 063313a8c68..3ecc4d79baf 100644
--- a/src/com/android/launcher3/util/ItemInfoMatcher.java
+++ b/src/com/android/launcher3/util/ItemInfoMatcher.java
@@ -18,6 +18,7 @@
import android.content.ComponentName;
import android.os.UserHandle;
+import android.os.UserManager;
import androidx.annotation.NonNull;
@@ -45,6 +46,25 @@ public static Predicate ofUser(UserHandle user) {
return info -> info != null && info.user.equals(user);
}
+ public static Predicate ofCurrentOrDualUser(UserManager userManager, UserHandle user) {
+ return itemInfo -> itemInfo != null && (
+ isDualAppUser(userManager, itemInfo.user) || itemInfo.user.equals(user)
+ );
+ }
+
+ private static boolean isDualAppUser(UserManager userManager, UserHandle user) {
+ return user.getIdentifier() == getCloneUserId(userManager);
+ }
+
+ public static int getCloneUserId(UserManager userManager) {
+ for (UserHandle userHandle : userManager.getUserProfiles()) {
+ if (userManager.getUserInfo(userHandle.getIdentifier()).isCloneProfile()) {
+ return userHandle.getIdentifier();
+ }
+ }
+ return -1;
+ }
+
public static Predicate ofComponents(
HashSet components, UserHandle user) {
return info -> info != null && info.user.equals(user)
diff --git a/src/com/android/launcher3/util/MediaSessionManagerHelper.kt b/src/com/android/launcher3/util/MediaSessionManagerHelper.kt
new file mode 100644
index 00000000000..0ca11243086
--- /dev/null
+++ b/src/com/android/launcher3/util/MediaSessionManagerHelper.kt
@@ -0,0 +1,383 @@
+/*
+ * SPDX-FileCopyrightText: The risingOS Android Project
+ * SPDX-FileCopyrightText: The AxionAOSP Project
+ * SPDX-FileCopyrightText: crDroid Android Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.android.launcher3.util
+
+import android.content.Context
+import android.content.pm.PackageManager
+import android.graphics.drawable.Drawable
+import android.media.MediaMetadata
+import android.media.session.MediaController
+import android.media.session.MediaSessionLegacyHelper
+import android.media.session.MediaSessionManager
+import android.media.session.PlaybackState
+import android.os.Handler
+import android.os.Looper
+import android.os.SystemClock
+import android.util.Log
+import android.view.KeyEvent
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.isActive
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+class MediaSessionManagerHelper private constructor(ctx: Context) {
+
+ interface MediaMetadataListener {
+ fun onMediaMetadataChanged() {}
+ fun onPlaybackStateChanged() {}
+ }
+
+ private val context: Context = ctx.applicationContext
+
+ private val _mediaMetadata = MutableStateFlow(null)
+ val mediaMetadata: StateFlow = _mediaMetadata
+
+ private val _playbackState = MutableStateFlow(null)
+ val playbackState: StateFlow = _playbackState
+
+ private val job = SupervisorJob()
+ private val scope = CoroutineScope(job + Dispatchers.Main.immediate)
+
+ private var collectJob: Job? = null
+ private var sessionsUpdateJob: Job? = null
+ private var metadataRefreshJob: Job? = null
+ private var heartbeatJob: Job? = null
+
+ private var lastSavedPackageName: String? = null
+
+ private val mediaSessionManager: MediaSessionManager =
+ context.getSystemService(MediaSessionManager::class.java)!!
+
+ private var activeController: MediaController? = null
+ private val listeners = LinkedHashSet()
+
+ private val mainHandler = Handler(Looper.getMainLooper())
+ private var sessionsListening = false
+
+ private val mediaControllerCallback = object : MediaController.Callback() {
+ override fun onMetadataChanged(metadata: MediaMetadata?) {
+ _mediaMetadata.value = metadata
+ }
+
+ override fun onPlaybackStateChanged(state: PlaybackState?) {
+ _playbackState.value = state
+ }
+
+ override fun onSessionDestroyed() {
+ Log.d(TAG, "Active session destroyed, forcing refresh")
+ refreshActiveSessions()
+ }
+ }
+
+ private val sessionsChangedListener =
+ MediaSessionManager.OnActiveSessionsChangedListener { controllers ->
+ // Avoid doing any work if nobody is listening.
+ if (!sessionsListening || listeners.isEmpty()) return@OnActiveSessionsChangedListener
+
+ var safeControllers = controllers.orEmpty()
+
+ sessionsUpdateJob?.cancel()
+ sessionsUpdateJob = scope.launch {
+ if (safeControllers.isEmpty()) {
+ safeControllers = try {
+ mediaSessionManager.getActiveSessions(null)
+ } catch (e: Exception) {
+ Log.w(TAG, "Failed to get active sessions for re-evaluation", e)
+ return@launch
+ }
+ }
+ val picked = withContext(Dispatchers.IO) { pickBestController(safeControllers) }
+
+ if (sameSessions(activeController, picked)) return@launch
+
+ activeController?.unregisterCallback(mediaControllerCallback)
+ activeController = picked
+ picked?.registerCallback(mediaControllerCallback)
+
+ _mediaMetadata.value = picked?.metadata
+ _playbackState.value = picked?.playbackState
+ saveLastNonNullPackageName()
+ }
+ }
+
+ fun addMediaMetadataListener(listener: MediaMetadataListener) {
+ val wasEmpty = listeners.isEmpty()
+ listeners.add(listener)
+
+ if (wasEmpty) {
+ startTracking()
+ } else {
+ refreshActiveSessions()
+ }
+
+ // Push current state immediately.
+ listener.onMediaMetadataChanged()
+ listener.onPlaybackStateChanged()
+ }
+
+ fun removeMediaMetadataListener(listener: MediaMetadataListener) {
+ listeners.remove(listener)
+ if (listeners.isEmpty()) {
+ stopTracking()
+ }
+ }
+
+ private fun startTracking() {
+ if (!sessionsListening) {
+ mediaSessionManager.addOnActiveSessionsChangedListener(
+ sessionsChangedListener,
+ null,
+ mainHandler
+ )
+ sessionsListening = true
+
+ // Force an initial refresh.
+ sessionsChangedListener.onActiveSessionsChanged(
+ mediaSessionManager.getActiveSessions(null)
+ )
+ }
+
+ if (collectJob == null) {
+ collectJob = scope.launch {
+ launch {
+ mediaMetadata.collect { notifyListeners { onMediaMetadataChanged() } }
+ }
+ launch {
+ playbackState.collect { notifyListeners { onPlaybackStateChanged() } }
+ }
+ }
+ }
+
+ startHeartbeat()
+ }
+
+ private fun stopTracking() {
+ heartbeatJob?.cancel()
+ heartbeatJob = null
+
+ collectJob?.cancel()
+ collectJob = null
+
+ sessionsUpdateJob?.cancel()
+ sessionsUpdateJob = null
+
+ metadataRefreshJob?.cancel()
+ metadataRefreshJob = null
+
+ if (sessionsListening) {
+ mediaSessionManager.removeOnActiveSessionsChangedListener(sessionsChangedListener)
+ sessionsListening = false
+ }
+
+ activeController?.unregisterCallback(mediaControllerCallback)
+ activeController = null
+
+ _mediaMetadata.value = null
+ _playbackState.value = null
+ }
+
+ private fun startHeartbeat() {
+ heartbeatJob?.cancel()
+ heartbeatJob = scope.launch {
+ while (isActive) {
+ delay(HEARTBEAT_INTERVAL_MS)
+ refreshActiveSessions()
+ }
+ }
+ }
+
+ fun refreshActiveSessions() {
+ if (listeners.isEmpty() || !sessionsListening) return
+ sessionsChangedListener.onActiveSessionsChanged(
+ mediaSessionManager.getActiveSessions(null)
+ )
+ }
+
+ private fun notifyListeners(action: MediaMetadataListener.() -> Unit) {
+ listeners.forEach { it.action() }
+ }
+
+ private fun isEligibleState(state: Int?): Boolean {
+ return when (state) {
+ null,
+ PlaybackState.STATE_NONE,
+ PlaybackState.STATE_STOPPED,
+ PlaybackState.STATE_ERROR -> false
+ else -> true
+ }
+ }
+
+ private fun pickBestController(controllers: List): MediaController? {
+ var localController: MediaController? = null
+ val remoteSessions = HashSet()
+
+ controllers
+ .asSequence()
+ .filter { controller ->
+ isEligibleState(controller.playbackState?.state) && controller.playbackInfo != null
+ }
+ .sortedWith(
+ compareByDescending { it.playbackState?.state == PlaybackState.STATE_PLAYING }
+ .thenByDescending { it.playbackState?.state == PlaybackState.STATE_BUFFERING }
+ .thenByDescending { it.playbackState?.state == PlaybackState.STATE_PAUSED }
+ )
+ .forEach { controller ->
+ when (controller.playbackInfo?.playbackType) {
+ MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE -> {
+ remoteSessions.add(controller.packageName)
+ if (localController?.packageName == controller.packageName) {
+ localController = null
+ }
+ }
+ MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL -> {
+ if (!remoteSessions.contains(controller.packageName)) {
+ localController = localController ?: controller
+ }
+ }
+ }
+ }
+
+ return localController
+ }
+
+ fun refreshActiveControllerMetadata() {
+ metadataRefreshJob?.cancel()
+ metadataRefreshJob = scope.launch {
+ repeat(METADATA_REFRESH_RETRY_COUNT) { attempt ->
+ val freshMetadata = activeController?.metadata
+ if (freshMetadata != null && _mediaMetadata.value != freshMetadata) {
+ _mediaMetadata.value = freshMetadata
+ Log.d(TAG, "Metadata refreshed successfully on attempt ${attempt + 1}")
+ return@launch
+ }
+ if (attempt < METADATA_REFRESH_RETRY_COUNT - 1) {
+ delay(METADATA_REFRESH_RETRY_DELAY_MS)
+ }
+ }
+ Log.w(TAG, "Failed to refresh metadata after $METADATA_REFRESH_RETRY_COUNT attempts")
+ }
+ }
+
+ fun seekTo(time: Long) {
+ activeController?.transportControls?.seekTo(time)
+ }
+
+ fun getTotalDuration(): Long =
+ mediaMetadata.value?.getLong(MediaMetadata.METADATA_KEY_DURATION) ?: 0L
+
+ private fun saveLastNonNullPackageName() {
+ activeController?.packageName?.takeIf { it.isNotEmpty() }?.let { pkg ->
+ if (pkg != lastSavedPackageName) {
+ lastSavedPackageName = pkg
+ }
+ }
+ }
+
+ fun getCurrentMediaMetadata(): MediaMetadata? = mediaMetadata.value
+
+ fun getMediaAppIcon(): Drawable? {
+ val packageName = activeController?.packageName ?: return null
+ return try {
+ context.packageManager.getApplicationIcon(packageName)
+ } catch (_: PackageManager.NameNotFoundException) {
+ null
+ }
+ }
+
+ fun isMediaControllerAvailable(): Boolean =
+ activeController?.packageName?.isNotEmpty() ?: false
+
+ fun isMediaSessionActive(): Boolean {
+ val controller = activeController ?: return false
+ val st = controller.playbackState?.state
+ return st != null &&
+ st != PlaybackState.STATE_NONE &&
+ st != PlaybackState.STATE_STOPPED &&
+ st != PlaybackState.STATE_ERROR
+ }
+
+ fun isMediaPaused(): Boolean = playbackState.value?.state == PlaybackState.STATE_PAUSED
+
+ fun isMediaPlaying(): Boolean = playbackState.value?.state == PlaybackState.STATE_PLAYING
+
+ fun getMediaControllerPlaybackState(): PlaybackState? = activeController?.playbackState
+
+ private fun sameSessions(a: MediaController?, b: MediaController?): Boolean {
+ if (a == b) return true
+ if (a == null || b == null) return false
+ return a.controlsSameSession(b)
+ }
+
+ private fun dispatchMediaKeyWithWakeLockToMediaSession(keycode: Int) {
+ val helper = MediaSessionLegacyHelper.getHelper(context) ?: return
+ var event = KeyEvent(
+ SystemClock.uptimeMillis(),
+ SystemClock.uptimeMillis(),
+ KeyEvent.ACTION_DOWN,
+ keycode,
+ 0
+ )
+ helper.sendMediaButtonEvent(event, true)
+ event = KeyEvent.changeAction(event, KeyEvent.ACTION_UP)
+ helper.sendMediaButtonEvent(event, true)
+ }
+
+ fun prevSong() {
+ dispatchMediaKeyWithWakeLockToMediaSession(KeyEvent.KEYCODE_MEDIA_PREVIOUS)
+ }
+
+ fun nextSong() {
+ dispatchMediaKeyWithWakeLockToMediaSession(KeyEvent.KEYCODE_MEDIA_NEXT)
+ }
+
+ fun toggleMediaPlaybackState() {
+ if (isMediaPlaying()) {
+ dispatchMediaKeyWithWakeLockToMediaSession(KeyEvent.KEYCODE_MEDIA_PAUSE)
+ } else {
+ dispatchMediaKeyWithWakeLockToMediaSession(KeyEvent.KEYCODE_MEDIA_PLAY)
+ }
+ }
+
+ fun launchMediaApp() {
+ lastSavedPackageName?.takeIf { it.isNotEmpty() }?.let {
+ launchMediaPlayerApp(it)
+ }
+ }
+
+ fun launchMediaPlayerApp(packageName: String) {
+ if (packageName.isEmpty()) return
+ val launchIntent = context.packageManager.getLaunchIntentForPackage(packageName) ?: return
+ context.startActivity(launchIntent)
+ }
+
+ companion object {
+ private const val TAG = "MediaSessionManagerHelper"
+
+ private const val METADATA_REFRESH_RETRY_COUNT = 5
+ private const val METADATA_REFRESH_RETRY_DELAY_MS = 300L
+
+ /** Periodic re-evaluation interval to catch stale sessions. */
+ private const val HEARTBEAT_INTERVAL_MS = 30_000L
+
+ @Volatile
+ private var instance: MediaSessionManagerHelper? = null
+
+ fun getInstance(context: Context): MediaSessionManagerHelper =
+ instance ?: synchronized(this) {
+ instance ?: MediaSessionManagerHelper(context).also { instance = it }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/util/OnboardingPrefs.kt b/src/com/android/launcher3/util/OnboardingPrefs.kt
index 771594ea9d2..9634a00913a 100644
--- a/src/com/android/launcher3/util/OnboardingPrefs.kt
+++ b/src/com/android/launcher3/util/OnboardingPrefs.kt
@@ -68,7 +68,7 @@ object OnboardingPrefs {
@JvmField val HOME_BOUNCE_COUNT = CountedItem("launcher.home_bounce_count", 3)
@JvmField
- val HOTSEAT_DISCOVERY_TIP_COUNT = CountedItem("launcher.hotseat_discovery_tip_count", 5)
+ val HOTSEAT_DISCOVERY_TIP_COUNT = CountedItem("launcher.hotseat_discovery_tip_count", 0)
@JvmField val ALL_APPS_VISITED_COUNT = CountedItem("launcher.all_apps_visited_count", 20)
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index 3d01f246ec3..e9965682096 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -22,10 +22,13 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Process;
@@ -48,6 +51,7 @@
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
+import java.net.URISyntaxException;
import java.util.List;
import java.util.Objects;
@@ -112,6 +116,22 @@ public LauncherActivityInfo getAppLaunchInfo(@Nullable final String pkg,
return activities.isEmpty() ? null : activities.get(0);
}
+ /**
+ * Creates a new market search intent.
+ */
+ public static Intent getMarketSearchIntent(Context context, String query) {
+ try {
+ Intent intent = Intent.parseUri("market://search?c=apps", 0);
+ if (!TextUtils.isEmpty(query)) {
+ intent.setData(
+ intent.getData().buildUpon().appendQueryParameter("q", query).build());
+ }
+ return intent;
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
/**
* Starts the details activity for {@code info}
*/
@@ -144,6 +164,47 @@ public static void startDetailsActivityForInfo(Context context, ItemInfo info,
}
}
+ public static boolean isSystemApp(@NonNull final Context context, String pkgName) {
+ return isSystemApp(context, null, pkgName);
+ }
+
+ public static boolean isSystemApp(@NonNull final Context context, Intent intent) {
+ return isSystemApp(context, intent, null);
+ }
+
+ public static boolean isSystemApp(@NonNull final Context context, Intent intent, String pkgName) {
+ PackageManager pm = context.getPackageManager();
+ String packageName = null;
+ // If the intent is not null, let's get the package name from the intent.
+ if (intent != null) {
+ ComponentName cn = intent.getComponent();
+ if (cn == null) {
+ ResolveInfo info = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
+ if ((info != null) && (info.activityInfo != null)) {
+ packageName = info.activityInfo.packageName;
+ }
+ } else {
+ packageName = cn.getPackageName();
+ }
+ }
+ // Otherwise we have the package name passed from the method.
+ else {
+ packageName = pkgName;
+ }
+ // Check if the provided package is a system app.
+ if (packageName != null) {
+ try {
+ PackageInfo info = pm.getPackageInfo(packageName, 0);
+ return (info != null) && (info.applicationInfo != null) &&
+ ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
+ } catch (NameNotFoundException e) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
/**
* Returns true if the intent is a valid launch intent for a launcher activity of an app.
* This is used to identify shortcuts which are different from the ones exposed by the
diff --git a/src/com/android/launcher3/util/ScreenOnTracker.java b/src/com/android/launcher3/util/ScreenOnTracker.java
index cc3a34d93fd..862b9d228ff 100644
--- a/src/com/android/launcher3/util/ScreenOnTracker.java
+++ b/src/com/android/launcher3/util/ScreenOnTracker.java
@@ -19,6 +19,8 @@
import static android.content.Intent.ACTION_SCREEN_ON;
import static android.content.Intent.ACTION_USER_PRESENT;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR;
import static com.android.launcher3.util.SimpleBroadcastReceiver.actionsFilter;
import android.content.Context;
@@ -82,17 +84,33 @@ void onReceive(Intent intent) {
String action = intent.getAction();
if (ACTION_SCREEN_ON.equals(action)) {
mIsScreenOn = true;
- dispatchScreenOnChanged();
+ THREAD_POOL_EXECUTOR.execute(() -> {
+ dispatchScreenOnChanged();
+ });
} else if (ACTION_SCREEN_OFF.equals(action)) {
mIsScreenOn = false;
- dispatchScreenOnChanged();
+ THREAD_POOL_EXECUTOR.execute(() -> {
+ dispatchScreenOnChanged();
+ });
} else if (ACTION_USER_PRESENT.equals(action)) {
- mListeners.forEach(ScreenOnListener::onUserPresent);
+ THREAD_POOL_EXECUTOR.execute(() -> {
+ dispatchUserPresent();
+ });
}
}
private void dispatchScreenOnChanged() {
- mListeners.forEach(l -> l.onScreenOnChanged(mIsScreenOn));
+ for (ScreenOnListener listener : mListeners) {
+ MAIN_EXECUTOR.execute(() -> {
+ listener.onScreenOnChanged(mIsScreenOn);
+ });
+ }
+ }
+
+ private void dispatchUserPresent() {
+ for (ScreenOnListener listener : mListeners) {
+ MAIN_EXECUTOR.execute(listener::onUserPresent);
+ }
}
/** Returns if the screen is on or not */
diff --git a/src/com/android/launcher3/util/SettingsCache.java b/src/com/android/launcher3/util/SettingsCache.java
index 277a276e146..4ba9dbf846d 100644
--- a/src/com/android/launcher3/util/SettingsCache.java
+++ b/src/com/android/launcher3/util/SettingsCache.java
@@ -81,6 +81,9 @@ public class SettingsCache extends ContentObserver {
private static final String SYSTEM_URI_PREFIX = Settings.System.CONTENT_URI.toString();
private static final String GLOBAL_URI_PREFIX = Settings.Global.CONTENT_URI.toString();
+ private static final String LINEAGE_SYSTEM_URI_PREFIX =
+ Settings.System.CONTENT_URI.toString();
+
private final Function> mListenerMapper = uri -> {
registerUriAsync(uri);
return new CopyOnWriteArrayList<>();
@@ -90,6 +93,7 @@ public class SettingsCache extends ContentObserver {
* Caches the last seen value for registered keys.
*/
private final Map mKeyCache = new ConcurrentHashMap<>();
+ private final Map mIntKeyCache = new ConcurrentHashMap<>();
private final Map> mListenerMap =
new ConcurrentHashMap<>();
private final Set mUrisEnabledByDefault;
@@ -148,6 +152,26 @@ private void registerUriAsync(Uri uri) {
() -> mResolver.registerContentObserver(uri, false, this));
}
+ /**
+ * Returns the int value for this classes key from the cache. If not in cache, will call
+ * {@link #updateIntValue(Uri, int)} to fetch.
+ */
+ public int getIntValue(Uri keySetting) {
+ return getIntValue(keySetting, 1);
+ }
+
+ /**
+ * Returns the int value for this classes key from the cache. If not in cache, will call
+ * {@link #updateIntValue(Uri, int)} to fetch.
+ */
+ public int getIntValue(Uri keySetting, int defaultValue) {
+ if (mIntKeyCache.containsKey(keySetting)) {
+ return mIntKeyCache.get(keySetting);
+ } else {
+ return updateIntValue(keySetting, defaultValue);
+ }
+ }
+
/**
* Does not de-dupe if you add same listeners for the same key multiple times.
* Unregister once complete using {@link #unregister(Uri, OnChangeListener)}
@@ -165,6 +189,8 @@ private boolean updateValue(Uri keyUri) {
newVal = Settings.System.getInt(mResolver, key, defaultValue) == 1;
} else if (keyUri.toString().startsWith(GLOBAL_URI_PREFIX)) {
newVal = Settings.Global.getInt(mResolver, key, defaultValue) == 1;
+ } else if (keyUri.toString().startsWith(LINEAGE_SYSTEM_URI_PREFIX)) {
+ newVal = Settings.System.getInt(mResolver, key, defaultValue) == 1;
} else { // SETTING_SECURE
newVal = Settings.Secure.getInt(mResolver, key, defaultValue) == 1;
}
@@ -173,15 +199,37 @@ private boolean updateValue(Uri keyUri) {
return newVal;
}
+ private int updateIntValue(Uri keyUri, int defaultValue) {
+ String key = keyUri.getLastPathSegment();
+ int newVal;
+ if (keyUri.toString().startsWith(SYSTEM_URI_PREFIX)) {
+ newVal = Settings.System.getInt(mResolver, key, defaultValue);
+ } else if (keyUri.toString().startsWith(GLOBAL_URI_PREFIX)) {
+ newVal = Settings.Global.getInt(mResolver, key, defaultValue);
+ } else if (keyUri.toString().startsWith(LINEAGE_SYSTEM_URI_PREFIX)) {
+ newVal = Settings.System.getInt(mResolver, key, defaultValue);
+ } else { // SETTING_SECURE
+ newVal = Settings.Secure.getInt(mResolver, key, defaultValue);
+ }
+
+ mIntKeyCache.put(keyUri, newVal);
+ return newVal;
+ }
+
/**
* Call to stop receiving updates on the given {@param listener}.
* This Uri/Listener pair must correspond to the same pair called with for
* {@link #register(Uri, OnChangeListener)}
*/
public void unregister(Uri uri, OnChangeListener listener) {
- List listenersToRemoveFrom = mListenerMap.get(uri);
- if (listenersToRemoveFrom != null) {
- listenersToRemoveFrom.remove(listener);
+ List listeners = mListenerMap.get(uri);
+ if (listeners != null) {
+ listeners.remove(listener);
+ if (listeners.isEmpty()) {
+ mKeyCache.remove(uri);
+ mIntKeyCache.remove(uri);
+ mListenerMap.remove(uri);
+ }
}
}
diff --git a/src/com/android/launcher3/util/SimpleBroadcastReceiver.kt b/src/com/android/launcher3/util/SimpleBroadcastReceiver.kt
index 7e4df5b6be7..70693a50605 100644
--- a/src/com/android/launcher3/util/SimpleBroadcastReceiver.kt
+++ b/src/com/android/launcher3/util/SimpleBroadcastReceiver.kt
@@ -35,6 +35,8 @@ constructor(
private val intentConsumer: Consumer,
) : BroadcastReceiver(), SafeCloseable {
+ private var closed = false
+
override fun onReceive(context: Context, intent: Intent) {
intentConsumer.accept(intent)
}
@@ -56,6 +58,7 @@ constructor(
completionCallback: Runnable? = null,
) = apply {
executor.execute {
+ if (closed) return@execute
context.registerReceiver(this, filter, permission, callbackExecutor.handler, flags)
if (completionCallback != null) {
@@ -67,6 +70,7 @@ constructor(
/** Unregister broadcast receiver */
override fun close() {
executor.execute {
+ closed = true
try {
context.unregisterReceiver(this)
} catch (e: IllegalArgumentException) {
diff --git a/src/com/android/launcher3/util/TaskSchedule.java b/src/com/android/launcher3/util/TaskSchedule.java
new file mode 100644
index 00000000000..b0b6eb77a34
--- /dev/null
+++ b/src/com/android/launcher3/util/TaskSchedule.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util;
+
+import android.os.SystemClock;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.concurrent.Executor;
+
+public class TaskSchedule {
+
+ static final String TAG = "TaskSchedule";
+ public static void runAllTasksInExecutor(ArrayList runnableList, Executor executor, int parallelTasks) {
+ int taskSize = runnableList.size();
+ parallelTasks = Math.min(taskSize, parallelTasks);
+ Object LOCK = new Object();
+ for (int i = 0; i < parallelTasks; ++i) {
+ executor.execute(() -> {
+ while (true) {
+ SafelyRunnable runnable = getTask(runnableList, LOCK);
+ if (runnable == null) {
+ break;
+ }
+ runnable.run();
+ }
+ });
+ }
+ }
+
+ public static void runTasks(ArrayList runnableList, Executor executor, int parallelTasks, long timeOut) {
+ int taskSize = runnableList.size();
+ parallelTasks = Math.min(taskSize - 1, parallelTasks);
+ Object LOCK = new Object();
+ final IntCounter intCounter = new IntCounter();
+ for (int i = 0; i < parallelTasks; ++i) {
+ executor.execute(() -> {
+ while (true) {
+ SafelyRunnable runnable = getTask(runnableList, LOCK);
+ if (runnable == null) {
+ break;
+ }
+ runnable.run();
+ synchronized (LOCK) {
+ intCounter.add();
+ }
+ }
+ });
+ }
+ long taskDoneTime = -1;
+ while (true) {
+ SafelyRunnable runnable = getTask(runnableList, LOCK);
+ if (runnable == null) {
+ if (intCounter.isEqual(taskSize)) {
+ break;
+ }
+ if (taskDoneTime == -1) {
+ taskDoneTime = SystemClock.uptimeMillis();
+ }
+ try {
+ Thread.sleep(10);
+ } catch (InterruptedException e) {
+ Log.e(TAG, "InterruptedException-e:" + e.getMessage());
+ }
+ long now = SystemClock.uptimeMillis();
+ if (now - taskDoneTime > timeOut) {
+ Log.e(TAG, "runTasks>>>>>>>>>>>>>>>>>>>>>>>time out");
+ break;
+ }
+ continue;
+ }
+ runnable.run();
+ synchronized (LOCK) {
+ intCounter.add();
+ }
+ }
+ }
+
+ static SafelyRunnable getTask(ArrayList runnableList, final Object LOCK) {
+ synchronized (LOCK) {
+ if (runnableList.isEmpty()) {
+ return null;
+ }
+ int size = runnableList.size();
+ return runnableList.remove(size - 1);
+ }
+ }
+
+ public static abstract class SafelyRunnable implements Runnable {
+
+ @Override
+ public final void run() {
+ try {
+ onTaskRun();
+ } catch (Throwable e) {
+ Log.e(TAG,"SafelyRunnable-Throwable:" + e.getMessage());
+ }
+ }
+
+ public abstract void onTaskRun();
+ }
+
+ public static class IntCounter {
+ public int mCounter = 0;
+
+ public void add() {
+ this.mCounter += 1;
+ }
+
+ public boolean isEqual(int value) {
+ return this.mCounter == value;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/util/VibratorWrapper.java b/src/com/android/launcher3/util/VibratorWrapper.java
index ef92ec301f6..5a885a201cc 100644
--- a/src/com/android/launcher3/util/VibratorWrapper.java
+++ b/src/com/android/launcher3/util/VibratorWrapper.java
@@ -78,7 +78,7 @@ public VibratorWrapper(@ApplicationContext Context context, SettingsCache settin
DaggerSingletonTracker tracker) {
mVibrator = context.getSystemService(Vibrator.class);
- mHasVibrator = mVibrator.hasVibrator();
+ mHasVibrator = mVibrator != null && mVibrator.hasVibrator();
if (mHasVibrator) {
MAIN_EXECUTOR.execute(
() -> settingsCache.register(HAPTIC_FEEDBACK_URI, mHapticChangeListener));
@@ -105,6 +105,10 @@ public void vibrate(VibrationEffect vibrationEffect) {
}
}
+ public boolean hasVibrator() {
+ return mHasVibrator;
+ }
+
/**
* Vibrates with a single primitive, if supported, or use a fallback effect instead. This only
* vibrates if haptic feedback is available and enabled.
diff --git a/src/com/android/launcher3/util/WallpaperColorHints.kt b/src/com/android/launcher3/util/WallpaperColorHints.kt
index 29fe31ae3d7..6d533011ace 100644
--- a/src/com/android/launcher3/util/WallpaperColorHints.kt
+++ b/src/com/android/launcher3/util/WallpaperColorHints.kt
@@ -26,10 +26,15 @@ import androidx.annotation.VisibleForTesting
import com.android.launcher3.dagger.ApplicationContext
import com.android.launcher3.dagger.LauncherAppComponent
import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.data.wallpaper.service.WallpaperService
import com.android.launcher3.util.Executors.MAIN_EXECUTOR
import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+
/**
* This class caches the system's wallpaper color hints for use by other classes as a performance
* enhancer. It also centralizes all the WallpaperManager color hint code in one location.
@@ -62,6 +67,14 @@ constructor(@ApplicationContext private val context: Context, tracker: DaggerSin
wallpaperManager.removeOnColorsChangedListener(onColorsChangedListener)
}
}
+ CoroutineScope(Dispatchers.IO).launch {
+ val service = WallpaperService.INSTANCE.get(context)
+ val wallpapers = runCatching { service.getTopWallpapers() }
+ .getOrDefault(emptyList())
+ if (wallpapers.isEmpty()) {
+ service.saveWallpaper(wallpaperManager)
+ }
+ }
}
@MainThread
@@ -72,6 +85,9 @@ constructor(@ApplicationContext private val context: Context, tracker: DaggerSin
hints = newHints
onColorHintsChangedListeners.forEach { it.onColorHintsChanged(newHints) }
}
+ CoroutineScope(Dispatchers.IO).launch {
+ WallpaperService.INSTANCE.get(context).saveWallpaper(wallpaperManager)
+ }
}
}
diff --git a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
index 22e434f6ba0..8fd10fd7eaf 100644
--- a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
+++ b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
@@ -7,6 +7,7 @@
import android.app.WallpaperManager;
import android.content.Context;
+import android.content.SharedPreferences;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
@@ -18,21 +19,20 @@
import androidx.annotation.WorkerThread;
import com.android.app.animation.Interpolators;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
/**
* Utility class to handle wallpaper scrolling along with workspace.
*/
-public class WallpaperOffsetInterpolator {
+public class WallpaperOffsetInterpolator implements
+ SharedPreferences.OnSharedPreferenceChangeListener {
private static final int[] sTempInt = new int[2];
private static final String TAG = "WPOffsetInterpolator";
private static final int ANIMATION_DURATION = 250;
- // Don't use all the wallpaper for parallax until you have at least this many pages
- private static final int MIN_PARALLAX_PAGE_SPAN = 4;
-
private final SimpleBroadcastReceiver mWallpaperChangeReceiver;
private final Context mContext;
private final Workspace> mWorkspace;
@@ -45,6 +45,9 @@ public class WallpaperOffsetInterpolator {
private boolean mLockedToDefaultPage;
private int mNumScreens;
+ private SharedPreferences mPrefs;
+
+ private boolean mAllowScrolling;
public WallpaperOffsetInterpolator(Workspace> workspace) {
mContext = workspace.getContext();
@@ -53,6 +56,9 @@ public WallpaperOffsetInterpolator(Workspace> workspace) {
workspace.getContext(), UI_HELPER_EXECUTOR, i -> onWallpaperChanged());
mIsRtl = Utilities.isRtl(workspace.getResources());
mHandler = new OffsetHandler(workspace.getContext());
+ mAllowScrolling = LauncherPrefs.WALLPAPER_SCROLLING.get(workspace.getContext());
+ mPrefs = LauncherPrefs.getPrefs(workspace.getContext());
+ mPrefs.registerOnSharedPreferenceChangeListener(this);
}
/**
@@ -66,6 +72,26 @@ public boolean isLockedToDefaultPage() {
return mLockedToDefaultPage;
}
+ public void destroy() {
+ if (mRegistered) {
+ mWallpaperChangeReceiver.close();
+ mRegistered = false;
+ }
+ mHandler.removeCallbacksAndMessages(null);
+ mPrefs.unregisterOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+ if (LauncherPrefs.WALLPAPER_SCROLLING.getSharedPrefKey().equals(key)) {
+ mAllowScrolling = prefs.getBoolean(key, true);
+ }
+ }
+
+ private int getMinimumScrollableScreensForParallax() {
+ return LauncherPrefs.SINGLE_PAGE_CENTER.get(mWorkspace.getContext()) ? 0 : 1;
+ }
+
/**
* Computes the wallpaper offset as an int ratio (out[0] / out[1])
*
@@ -76,15 +102,14 @@ private void wallpaperOffsetForScroll(int scroll, int numScrollableScreens, fina
// To match the default wallpaper behavior in the system, we default to either the left
// or right edge on initialization
- if (mLockedToDefaultPage || numScrollableScreens <= 1) {
- out[0] = mIsRtl ? 1 : 0;
+ out[0] = mIsRtl ? 1 : 0;
+ if (!mAllowScrolling || mLockedToDefaultPage || numScrollableScreens <= getMinimumScrollableScreensForParallax()) {
return;
}
- // Distribute the wallpaper parallax over a minimum of MIN_PARALLAX_PAGE_SPAN workspace
- // screens, not including the custom screen, and empty screens (if > MIN_PARALLAX_PAGE_SPAN)
- int numScreensForWallpaperParallax = mWallpaperIsLiveWallpaper ? numScrollableScreens :
- Math.max(MIN_PARALLAX_PAGE_SPAN, numScrollableScreens);
+ // Distribute the wallpaper parallax over a minimum of getMinParallaxPageSpan() workspace
+ // screens, not including the custom screen, and empty screens (if > getMinParallaxPageSpan())
+ int numScreensForWallpaperParallax = getNumPagesForWallpaperParallax();
// Offset by the custom screen
@@ -108,7 +133,10 @@ private void wallpaperOffsetForScroll(int scroll, int numScrollableScreens, fina
int rightPageScrollX = mWorkspace.getScrollForPage(rightPageIndex);
int scrollRange = rightPageScrollX - leftPageScrollX;
if (scrollRange <= 0) {
- out[0] = 0;
+ if (getMinimumScrollableScreensForParallax() == 0) {
+ out[0] = 1;
+ out[1] = 2;
+ }
return;
}
@@ -158,7 +186,7 @@ private int getNumScrollableScreensExcludingEmpty() {
*/
private int getNumPagesExcludingEmpty() {
int numOfPages = mWorkspace.getChildCount();
- if (numOfPages >= MIN_PARALLAX_PAGE_SPAN && mWorkspace.hasExtraEmptyScreens()) {
+ if (numOfPages >= getMinParallaxPageSpan() && mWorkspace.hasExtraEmptyScreens()) {
return numOfPages - mWorkspace.getPanelCount();
} else {
return numOfPages;
@@ -186,10 +214,15 @@ public int getNumPagesForWallpaperParallax() {
if (mWallpaperIsLiveWallpaper) {
return mNumScreens;
} else {
- return Math.max(MIN_PARALLAX_PAGE_SPAN, mNumScreens);
+ return Math.max(getMinParallaxPageSpan(), mNumScreens);
}
}
+ private int getMinParallaxPageSpan() {
+ // Don't use all the wallpaper for parallax until you have at least this many pages
+ return LauncherPrefs.SHORT_PARALLAX.get(mWorkspace.getContext()) ? 1 : 4;
+ }
+
@AnyThread
private void updateOffset() {
Message.obtain(mHandler, MSG_SET_NUM_PARALLAX, getNumPagesForWallpaperParallax(), 0,
@@ -205,6 +238,7 @@ public void setWindowToken(IBinder token) {
if (mWindowToken == null && mRegistered) {
mWallpaperChangeReceiver.close();
mRegistered = false;
+ mHandler.removeCallbacksAndMessages(null);
} else if (mWindowToken != null && !mRegistered) {
mWallpaperChangeReceiver.register(
actionsFilter(ACTION_WALLPAPER_CHANGED),
@@ -316,4 +350,4 @@ private void setOffsetSafely(IBinder token) {
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/com/android/launcher3/util/window/WindowManagerProxy.java b/src/com/android/launcher3/util/window/WindowManagerProxy.java
index 56b85545a0b..68ed6705092 100644
--- a/src/com/android/launcher3/util/window/WindowManagerProxy.java
+++ b/src/com/android/launcher3/util/window/WindowManagerProxy.java
@@ -480,7 +480,7 @@ public Display getDisplay(Context displayInfoContext) {
DEFAULT_DISPLAY);
}
- private int getDisplayId(Context displayInfoContext) {
+ public int getDisplayId(Context displayInfoContext) {
try {
return displayInfoContext.getDisplay().getDisplayId();
} catch (UnsupportedOperationException e) {
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index 38406620f01..916c0cf734a 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -461,8 +461,10 @@ public void mapCoordInSelfToDescendant(View descendant, float[] coord) {
public void mapCoordInSelfToDescendant(View descendant, int[] coord) {
mTmpXY[0] = coord[0];
mTmpXY[1] = coord[1];
- Utilities.mapCoordInSelfToDescendant(descendant, this, mTmpXY);
- Utilities.roundArray(mTmpXY, coord);
+ if (descendant != null) {
+ Utilities.mapCoordInSelfToDescendant(descendant, this, mTmpXY);
+ Utilities.roundArray(mTmpXY, coord);
+ }
}
public void getViewRectRelativeToSelf(View v, Rect r) {
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index 5e77071cfda..f3d57b23159 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -575,7 +575,9 @@ public static IconLoadResult fetchIcon(Launcher l, View v, ItemInfo info, boolea
} else {
btvIcon = btv.getIcon();
// Clone when needed
- btvDrawableSupplier = () -> btvIcon.getConstantState().newDrawable();
+ btvDrawableSupplier = () -> (btvIcon != null && btvIcon.getConstantState() != null)
+ ? btvIcon.getConstantState().newDrawable()
+ : null;
}
} else {
btvIcon = null;
diff --git a/src/com/android/launcher3/views/IconFrame.kt b/src/com/android/launcher3/views/IconFrame.kt
new file mode 100644
index 00000000000..9f3707cc1b2
--- /dev/null
+++ b/src/com/android/launcher3/views/IconFrame.kt
@@ -0,0 +1,73 @@
+package com.android.launcher3.views
+
+import android.content.Context
+import android.graphics.drawable.GradientDrawable
+import android.util.AttributeSet
+import android.widget.FrameLayout
+import android.widget.ImageView
+import androidx.annotation.DrawableRes
+import androidx.core.content.ContextCompat
+import com.android.launcher3.R
+import com.android.launcher3.util.Themes
+
+class IconFrame @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+) : FrameLayout(context, attrs, defStyleAttr) {
+
+ private val imageView: ImageView
+
+ init {
+ layoutParams = LayoutParams(
+ LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT,
+ )
+
+ imageView = ImageView(context).apply {
+ layoutParams = LayoutParams(
+ LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT,
+ )
+ setPadding(12.dpToPx(context), 12.dpToPx(context), 12.dpToPx(context), 12.dpToPx(context))
+ }
+ addView(imageView)
+
+ setBackgroundWithRadius(
+ bgColor = ContextCompat.getColor(context, R.color.accent_primary_device_default),
+ cornerRadius = Themes.getDialogCornerRadius(context),
+ )
+ }
+
+ /**
+ * Convert dp to pixels for consistent padding across devices.
+ */
+ private fun Int.dpToPx(context: Context): Int {
+ val density = context.resources.displayMetrics.density
+ return (this * density).toInt()
+ }
+
+ /**
+ * Set the vector drawable for the ImageView.
+ *
+ * @param drawableRes The resource ID of the vector drawable.
+ */
+ fun setIcon(@DrawableRes drawableRes: Int) {
+ imageView.setImageResource(drawableRes)
+ }
+
+ /**
+ * Set the background color and corner radius of the FrameLayout.
+ *
+ * @param bgColor The background color.
+ * @param cornerRadius The corner radius in pixels.
+ */
+ fun setBackgroundWithRadius(bgColor: Int, cornerRadius: Float) {
+ val backgroundDrawable = GradientDrawable().apply {
+ shape = GradientDrawable.RECTANGLE
+ setColor(bgColor)
+ this.cornerRadius = cornerRadius
+ }
+ background = backgroundDrawable
+ }
+}
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index 902c7d8274e..cb61dbbe99d 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -32,6 +32,7 @@
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
@@ -47,6 +48,7 @@
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.data.wallpaper.service.WallpaperService;
import com.android.launcher3.logging.StatsLogManager.EventEnum;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.ArrowPopup;
@@ -182,13 +184,27 @@ private static OptionsPopupView show(
if (activityContext == null) {
return null;
}
+
+ final Context context = (Context) activityContext;
+
+ var layout = R.layout.longpress_options_menu;
+ boolean isEmpty = true;
+ if (LauncherPrefs.WALLPAPER_CAROUSEL.get(context)) {
+ final List wallpapers =
+ WallpaperService.INSTANCE.get(context).getTopWallpapersBlocking();
+ isEmpty = wallpapers.isEmpty();
+ Log.d("OptionsPopupView", "Wallpapers in DB: " + wallpapers.size() + ", isEmpty: " + isEmpty);
+ layout = isEmpty ? R.layout.longpress_options_menu : R.layout.wallpaper_options_popup;
+ }
+
OptionsPopupView popup = (OptionsPopupView) activityContext.getLayoutInflater()
- .inflate(R.layout.longpress_options_menu, activityContext.getDragLayer(), false);
+ .inflate(layout, activityContext.getDragLayer(), false);
popup.mTargetRect = targetRect;
popup.setShouldAddArrow(shouldAddArrow);
for (OptionItem item : items) {
- DeepShortcutView view = popup.inflateAndAdd(R.layout.system_shortcut, popup);
+ var deepLayout = isEmpty ? R.layout.system_shortcut : R.layout.wallpaper_options_popup_item;
+ DeepShortcutView view = popup.inflateAndAdd(deepLayout, popup);
if (width > 0) {
view.getLayoutParams().width = width;
}
@@ -209,10 +225,10 @@ private static OptionsPopupView show(
public static ArrayList getOptions(Launcher launcher) {
ArrayList options = new ArrayList<>();
options.add(new OptionItem(launcher,
- R.string.styles_wallpaper_button_text,
- R.drawable.ic_palette,
- IGNORE,
- OptionsPopupView::startWallpaperPicker));
+ R.string.settings_title,
+ R.drawable.ic_setting,
+ LAUNCHER_SETTINGS_BUTTON_TAP_OR_LONGPRESS,
+ OptionsPopupView::startSettings));
if (WIDGETS_ENABLED && !LauncherPrefs.WORKSPACE_LOCK.get(launcher)) {
options.add(new OptionItem(launcher,
R.string.widget_button_text,
@@ -233,10 +249,10 @@ public static ArrayList getOptions(Launcher launcher) {
LAUNCHER_ALL_APPS_TAP_OR_LONGPRESS,
OptionsPopupView::enterAllApps));
options.add(new OptionItem(launcher,
- R.string.settings_title,
- R.drawable.ic_setting,
- LAUNCHER_SETTINGS_BUTTON_TAP_OR_LONGPRESS,
- OptionsPopupView::startSettings));
+ R.string.styles_wallpaper_button_text,
+ R.drawable.ic_palette,
+ IGNORE,
+ OptionsPopupView::startWallpaperPicker));
return options;
}
@@ -282,16 +298,13 @@ private static boolean startWallpaperPicker(View v) {
Toast.makeText(launcher, message, Toast.LENGTH_SHORT).show();
return false;
}
- Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER)
- .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
- .putExtra(EXTRA_WALLPAPER_OFFSET,
- launcher.getWorkspace().getWallpaperOffsetForCenterPage())
- .putExtra(EXTRA_WALLPAPER_LAUNCH_SOURCE, "app_launched_launcher")
- .putExtra(EXTRA_WALLPAPER_FLAVOR, "focus_wallpaper");
- String pickerPackage = launcher.getString(R.string.wallpaper_picker_package);
- if (!TextUtils.isEmpty(pickerPackage)) {
- intent.setPackage(pickerPackage);
- }
+ Intent intent = new Intent();
+ intent.setClassName(
+ "com.android.alpha.themepicker",
+ "com.android.alpha.themepicker.ui.MainActivity"
+ );
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ intent.putExtra("is_launch_extra", true);
return launcher.startActivitySafely(v, intent, placeholderInfo(intent)) != null;
}
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
index cc66c1c341f..9265e734c37 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
@@ -103,7 +103,7 @@ protected LauncherAppWidgetProviderInfo(Parcel in) {
public void initSpans(Context context, InvariantDeviceProfile idp) {
mPM = context.getApplicationContext().getPackageManager();
int minSpanX = 0;
- int minSpanY = 0;
+ int minSpanY = 1;
int maxSpanX = idp.numColumns;
int maxSpanY = idp.numRows;
int spanX = 0;
@@ -125,8 +125,8 @@ public void initSpans(Context context, InvariantDeviceProfile idp) {
getSpanX(widgetPadding, minResizeWidth,
dp.getWorkspaceIconProfile().getCellLayoutBorderSpacePx().x,
cellSize.x));
- minSpanY = Math.max(minSpanY,
- getSpanY(widgetPadding, minResizeHeight,
+ minSpanY = Math.min(minSpanY,
+ getSpanY(new Rect(), minResizeHeight,
dp.getWorkspaceIconProfile().getCellLayoutBorderSpacePx().y,
cellSize.y));
diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
index 1f86c8e2e95..615addacd69 100644
--- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
@@ -54,12 +54,14 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.icons.FastBitmapDrawable;
+import com.android.launcher3.graphics.DrawableFactory;
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -294,6 +296,7 @@ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
@Override
public void reapplyItemInfo(ItemInfoWithIcon info) {
+ DrawableFactory drawableFactory = DrawableFactory.INSTANCE.get(getContext());
if (mCenterDrawable != null) {
mCenterDrawable.setCallback(null);
mCenterDrawable = null;
@@ -305,13 +308,13 @@ public void reapplyItemInfo(ItemInfoWithIcon info) {
// 2) Preload icon in the center
// 3) App icon in the center with a setup icon on the top left corner.
if (mDisabledForSafeMode) {
- FastBitmapDrawable disabledIcon = info.newIcon(getContext());
+ FastBitmapDrawable disabledIcon = drawableFactory.newIcon(getContext(), info);
disabledIcon.setDisabled(true);
mCenterDrawable = disabledIcon;
mSettingIconDrawable = null;
} else if (isReadyForClickSetup()) {
- mCenterDrawable = info.newIcon(getContext());
- mSettingIconDrawable = getResources().getDrawable(R.drawable.ic_setting).mutate();
+ mCenterDrawable = drawableFactory.newIcon(getContext(), info);
+ mSettingIconDrawable = ContextCompat.getDrawable(getContext(), R.drawable.ic_setting).mutate();
updateSettingColor(info.bitmap.color);
mDragFlags |= FLAG_DRAW_SETTINGS | FLAG_DRAW_LABEL;
diff --git a/src/com/android/launcher3/widget/TintDrawable.java b/src/com/android/launcher3/widget/TintDrawable.java
deleted file mode 100644
index 73d070e978b..00000000000
--- a/src/com/android/launcher3/widget/TintDrawable.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * 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.widget;
-
-
-import android.content.res.ColorStateList;
-import android.content.res.Resources;
-import android.content.res.Resources.Theme;
-import android.content.res.TypedArray;
-import android.graphics.drawable.DrawableWrapper;
-import android.util.AttributeSet;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.R;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-
-/**
- * A Drawable that tints a contained Drawable, overriding the existing tint specified in the
- * underlying drawable. This class should only be used in XML.
- *
- * @attr ref android.R.styleable#DrawableWrapper_drawable
- * @attr ref android.R.styleable#TintDrawable_tint
- */
-public class TintDrawable extends DrawableWrapper {
- private ColorStateList mTint;
- private int[] mThemeAttrs;
-
- /** No-arg constructor used by drawable inflation. */
- public TintDrawable() {
- super(null);
- }
-
- @Override
- public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
- @NonNull AttributeSet attrs, @Nullable Theme theme)
- throws XmlPullParserException, IOException {
- final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.TintDrawable);
-
- super.inflate(r, parser, attrs, theme);
-
- mThemeAttrs = a.extractThemeAttrs();
- updateStateFromTypedArray(a);
- a.recycle();
-
- applyTint();
- }
-
- @Override
- public void applyTheme(Theme t) {
- super.applyTheme(t);
-
- if (mThemeAttrs != null) {
- final TypedArray a = t.resolveAttributes(mThemeAttrs, R.styleable.TintDrawable);
- updateStateFromTypedArray(a);
- a.recycle();
- }
-
- // Ensure tint is reapplied after applying the theme to ensure this drawables'
- // tint overrides the underlying drawables' tint.
- applyTint();
- }
-
- @Override
- public boolean canApplyTheme() {
- return (mThemeAttrs != null && mThemeAttrs.length > 0) || super.canApplyTheme();
- }
-
- private void updateStateFromTypedArray(@NonNull TypedArray a) {
- if (a.hasValue(R.styleable.TintDrawable_android_drawable)) {
- setDrawable(a.getDrawable(R.styleable.TintDrawable_android_drawable));
- }
- if (a.hasValue(R.styleable.TintDrawable_android_tint)) {
- mTint = a.getColorStateList(R.styleable.TintDrawable_android_tint);
- }
- }
-
- private void applyTint() {
- if (getDrawable() != null && mTint != null) {
- getDrawable().mutate().setTintList(mTint);
- }
- }
-}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
index 460a9aa7588..3f9b9eeb0b6 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
@@ -204,7 +204,7 @@ private boolean onShowAllWidgetsMenuItemClick(MenuItem menuItem) {
onWidgetsBound();
if (mIsInSearchMode) {
mSearchBar.reset();
- } else if (!mSuggestedWidgetsPackageUserKey.equals(mSelectedHeader)) {
+ } else if (mSelectedHeader != null && mSelectedHeader != mSuggestedWidgetsPackageUserKey) {
mAdapters.get(mActivePage).mWidgetsListAdapter.selectFirstHeaderEntry();
mAdapters.get(mActivePage).mWidgetsRecyclerView.scrollToTop();
}
diff --git a/src_build_config/com/android/launcher3/BuildConfig.java b/src_build_config/com/android/launcher3/BuildConfig.java
index 96a8ab659d2..032b84400a0 100644
--- a/src_build_config/com/android/launcher3/BuildConfig.java
+++ b/src_build_config/com/android/launcher3/BuildConfig.java
@@ -24,7 +24,7 @@ public final class BuildConfig {
* Flag to state if the QSB is on the first screen and placed on the top,
* this can be overwritten in other launchers with a different value, if needed.
*/
- public static final boolean QSB_ON_FIRST_SCREEN = false;
+ public static final boolean USE_QUICKSPACE_VIEW = true;
/**
* Flag to control various developer centric features
diff --git a/src_no_quickstep/com/android/launcher3/uioverrides/states/AllAppsState.java b/src_no_quickstep/com/android/launcher3/uioverrides/states/AllAppsState.java
index 8a94188820b..4615dcb9440 100644
--- a/src_no_quickstep/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/src_no_quickstep/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -20,7 +20,10 @@
import android.graphics.Color;
+import androidx.core.graphics.ColorUtils;
+
import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherUiState;
import com.android.launcher3.R;
@@ -105,10 +108,21 @@ public float getVerticalProgress(Launcher launcher) {
@Override
public ScrimColors getWorkspaceScrimColor(Launcher launcher) {
+ int color;
+ if (LauncherPrefs.APP_DRAWER_CUSTOM_COLOR_ENABLED.get(launcher)) {
+ color = com.android.launcher3.Utilities.isDarkTheme(launcher)
+ ? LauncherPrefs.APP_DRAWER_CUSTOM_COLOR_DARK.get(launcher)
+ : LauncherPrefs.APP_DRAWER_CUSTOM_COLOR_LIGHT.get(launcher);
+ } else {
+ color = launcher.getDeviceProfile().getDeviceProperties().isTablet()
+ ? launcher.getResources().getColor(R.color.widgets_picker_scrim)
+ : Themes.getAttrColor(launcher, R.attr.allAppsScrimColor);
+ }
+ int scrimColor = ColorUtils.setAlphaComponent(
+ color,
+ LauncherPrefs.APP_DRAWER_OPACITY.get(launcher) * 255 / 100);
return new ScrimColors(
- /* backgroundColor */ launcher.getDeviceProfile().getDeviceProperties().isTablet()
- ? launcher.getResources().getColor(R.color.widgets_picker_scrim)
- : Themes.getAttrColor(launcher, R.attr.allAppsScrimColor),
+ /* backgroundColor */ scrimColor,
/* foregroundColor */ Color.TRANSPARENT);
}
}
diff --git a/src_no_quickstep/com/android/launcher3/uioverrides/states/OverviewState.java b/src_no_quickstep/com/android/launcher3/uioverrides/states/OverviewState.java
index 96279326199..b9bf9a1c6c7 100644
--- a/src_no_quickstep/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/src_no_quickstep/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -18,7 +18,11 @@
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
import android.graphics.Color;
+
+import androidx.core.graphics.ColorUtils;
+
import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.util.Themes;
@@ -63,8 +67,11 @@ public static OverviewState newSplitSelectState(int id) {
@Override
public ScrimColors getWorkspaceScrimColor(Launcher launcher) {
+ int scrimColor = ColorUtils.setAlphaComponent(
+ Themes.getAttrColor(launcher, R.attr.overviewScrimColor),
+ LauncherPrefs.RECENTS_OPACITY.get(launcher) * 255 / 100);
return new ScrimColors(
- /* backgroundColor */ Themes.getAttrColor(launcher, R.attr.overviewScrimColor),
+ /* backgroundColor */ scrimColor,
/* foregroundColor */ Color.TRANSPARENT);
}
}
diff --git a/src_no_quickstep/com/android/launcher3/util/StateManagerProtoLogProxy.java b/src_no_quickstep/com/android/launcher3/util/StateManagerProtoLogProxy.java
index 56dc703e376..d199410cf10 100644
--- a/src_no_quickstep/com/android/launcher3/util/StateManagerProtoLogProxy.java
+++ b/src_no_quickstep/com/android/launcher3/util/StateManagerProtoLogProxy.java
@@ -21,6 +21,10 @@
*/
public class StateManagerProtoLogProxy {
+ public static boolean isEnabled() {
+ return false;
+ }
+
public static boolean isLoggingToLogcat() {
return false;
}