From 5a03ade7945a51f731a1d602d2cc6c918e9b1507 Mon Sep 17 00:00:00 2001 From: vadimt Date: Tue, 15 Jun 2021 19:34:24 -0700 Subject: [PATCH 01/13] Improving diagnostics when Launcher dies while in ContentProvider call Test: presubmit Bug: 191059619 Change-Id: Ib87a41663a63729024b41603973b782cf4763551 --- .../com/android/launcher3/tapl/LauncherInstrumentation.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java index 426af19084..bffd3509ea 100644 --- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java +++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java @@ -39,6 +39,7 @@ import android.graphics.Point; import android.graphics.Rect; import android.net.Uri; import android.os.Bundle; +import android.os.DeadObjectException; import android.os.Parcelable; import android.os.RemoteException; import android.os.SystemClock; @@ -269,6 +270,9 @@ public final class LauncherInstrumentation { try (ContentProviderClient client = getContext().getContentResolver() .acquireContentProviderClient(mTestProviderUri)) { return client.call(request, null, null); + } catch (DeadObjectException e) { + fail("Launcher crashed"); + return null; } catch (RemoteException e) { throw new RuntimeException(e); } From 58ffbef8f0a9a71d4aa264e0af0cdecab1fddd50 Mon Sep 17 00:00:00 2001 From: Vinit Nayak Date: Tue, 15 Jun 2021 13:14:41 -0700 Subject: [PATCH 02/13] Enable 3 button nav for taskbar Bug: 180046394 Test: Switched between gesture and button nav on phone and tablet. Change-Id: Iac2357a3256f8b6b36ca49d3680594a91e824a5f --- .../launcher3/taskbar/NavbarButtonsViewController.java | 2 +- .../android/launcher3/taskbar/TaskbarActivityContext.java | 6 +++--- .../com/android/launcher3/taskbar/TaskbarControllers.java | 2 +- .../launcher3/taskbar/TaskbarDragLayerController.java | 2 +- .../launcher3/taskbar/TaskbarKeyguardController.java | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java index d40242c42c..2facd4436c 100644 --- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java @@ -105,7 +105,7 @@ public class NavbarButtonsViewController { return true; }; - if (mContext.canShowNavButtons()) { + if (mContext.isThreeButtonNav()) { initButtons(mStartContainer, mEndContainer, mControllers.navButtonController); // Animate taskbar background when IME shows diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java index 000799b73b..59586abee7 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -161,8 +161,8 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ mWindowManager.addView(mDragLayer, mWindowLayoutParams); } - public boolean canShowNavButtons() { - return ENABLE_THREE_BUTTON_TASKBAR && mNavMode == Mode.THREE_BUTTONS; + public boolean isThreeButtonNav() { + return mNavMode == Mode.THREE_BUTTONS; } @Override @@ -214,7 +214,7 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ } public void updateSysuiStateFlags(int systemUiStateFlags, boolean forceUpdate) { - if (!canShowNavButtons()) { + if (!isThreeButtonNav()) { return; } mControllers.navbarButtonsViewController.updateStateForSysuiFlags( diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java index 157053efdd..c48c28b9e5 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java @@ -61,7 +61,7 @@ public class TaskbarControllers { */ public void init() { navbarButtonsViewController.init(this); - if (taskbarActivityContext.canShowNavButtons()) { + if (taskbarActivityContext.isThreeButtonNav()) { rotationButtonController.init(); } taskbarDragLayerController.init(this); diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java index 743619b6be..db5c387770 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java @@ -93,7 +93,7 @@ public class TaskbarDragLayerController { */ public void updateInsetsTouchability(InsetsInfo insetsInfo) { insetsInfo.touchableRegion.setEmpty(); - if (mActivity.canShowNavButtons()) { + if (mActivity.isThreeButtonNav()) { // Always have nav buttons be touchable mControllers.navbarButtonsViewController.addVisibleButtonsRegion( mTaskbarDragLayer, insetsInfo.touchableRegion); diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarKeyguardController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarKeyguardController.java index b7799d8149..2936bd2146 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarKeyguardController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarKeyguardController.java @@ -57,7 +57,7 @@ public class TaskbarKeyguardController { mKeyguardSysuiFlags = interestingKeyguardFlags; mBouncerShowing = bouncerShowing; - if (!mContext.canShowNavButtons()) { + if (!mContext.isThreeButtonNav()) { // For gesture nav we don't need to deal with bouncer or showing taskbar when locked return; } From d410a2c168535339272214453683b5cd2eb9b569 Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Wed, 16 Jun 2021 16:23:33 -0700 Subject: [PATCH 03/13] Ensure widgets header is out of the gesture region before clicking This way, NexusLauncher won't appear during the Launcher3 test. Test: AddConfigWidgetTest#testConfigCancelled Bug: 188450580 Change-Id: Iedf0e0e17eab2473ecd2d17a4391cf9db3e80599 --- tests/tapl/com/android/launcher3/tapl/Widgets.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java index 51e331de42..99d988926a 100644 --- a/tests/tapl/com/android/launcher3/tapl/Widgets.java +++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java @@ -185,8 +185,12 @@ public final class Widgets extends LauncherInstrumentation.VisibleContainer { targetAppSelector); if (headerTitle != null) { // If we find the header and it has not been expanded, let's click it to see the - // widgets list. - if (!hasHeaderExpanded) { + // widgets list. Note that we wait until the header is out of the gesture region at + // the bottom of the screen, because tapping there in Launcher3 causes NexusLauncher + // to briefly appear to handle the gesture, which can break our test. + boolean isHeaderOutOfGestureRegion = headerTitle.getVisibleCenter().y + < mLauncher.getBottomGestureStartOnScreen(); + if (!hasHeaderExpanded && isHeaderOutOfGestureRegion) { log("Header has not been expanded. Click to expand."); hasHeaderExpanded = true; mLauncher.clickLauncherObject(headerTitle); From e91e4f0328f9e43ad2992a04b20962e5d9ddf4c9 Mon Sep 17 00:00:00 2001 From: Vinit Nayak Date: Thu, 17 Jun 2021 12:37:02 -0700 Subject: [PATCH 04/13] Default to requesting command type TOGGLE for OverviewCommandHelper Parity with how sysui switches between apps for double tapping recents button. Fixes: 190660016 Test: Double tap to switch works, tapping fast also works Change-Id: If6dda377d8accf564f3385e78c6721c677310361 --- .../launcher3/taskbar/TaskbarNavButtonController.java | 5 +---- .../src/com/android/quickstep/OverviewCommandHelper.java | 5 ----- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java index 8549ca2ac5..8b0f426699 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java @@ -94,10 +94,7 @@ public class TaskbarNavButtonController { } private void navigateToOverview() { - int commandType = mService.getOverviewCommandHelper().isOverviewVisible() ? - OverviewCommandHelper.TYPE_TOGGLE : - OverviewCommandHelper.TYPE_SHOW; - mService.getOverviewCommandHelper().addCommand(commandType); + mService.getOverviewCommandHelper().addCommand(OverviewCommandHelper.TYPE_TOGGLE); } private void executeBack() { diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java index 728fec2732..2beef0a7ce 100644 --- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java +++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java @@ -136,11 +136,6 @@ public class OverviewCommandHelper { } } - public boolean isOverviewVisible() { - BaseActivityInterface activityInterface = - mOverviewComponentObserver.getActivityInterface(); - return activityInterface.getVisibleRecentsView() != null; - } /** * Executes the task and returns true if next task can be executed. If false, then the next * task is deferred until {@link #scheduleNextTask} is called From b50d747d208a2338c98e79949e8a9d41dcac8754 Mon Sep 17 00:00:00 2001 From: Steven Ng Date: Thu, 17 Jun 2021 18:07:43 +0100 Subject: [PATCH 05/13] Log widget dimensions before deleting a widget violating min size Test: N/A Bug: 191223844 Change-Id: Id909ee19be1a1c7d70a09643422d9ed62a2d82a9 --- .../android/launcher3/model/LoaderTask.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index 79396b1fc9..318496ad19 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -28,6 +28,7 @@ import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission; import static com.android.launcher3.util.PackageManagerHelper.isSystemApp; +import android.annotation.SuppressLint; import android.appwidget.AppWidgetProviderInfo; import android.content.ComponentName; import android.content.ContentResolver; @@ -40,6 +41,7 @@ import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageManager; import android.content.pm.ShortcutInfo; +import android.graphics.Point; import android.net.Uri; import android.os.Bundle; import android.os.UserHandle; @@ -50,6 +52,8 @@ import android.util.Log; import android.util.LongSparseArray; import android.util.TimingLogger; +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherModel; import com.android.launcher3.LauncherSettings; @@ -754,6 +758,9 @@ public class LoaderTask implements Runnable { if (widgetProviderInfo != null && (appWidgetInfo.spanX < widgetProviderInfo.minSpanX || appWidgetInfo.spanY < widgetProviderInfo.minSpanY)) { + logDeleteWidgetInfo(mApp.getInvariantDeviceProfile(), + widgetProviderInfo); + // This can happen when display size changes. c.markDeleted("Widget removed, min sizes not met: " + "span=" + appWidgetInfo.spanX + "x" @@ -980,6 +987,51 @@ public class LoaderTask implements Runnable { && (provider.provider.getPackageName() != null); } + @SuppressLint("NewApi") // Already added API check. + private static void logDeleteWidgetInfo(InvariantDeviceProfile idp, + LauncherAppWidgetProviderInfo widgetProviderInfo) { + FileLog.d(TAG, "Deleting " + widgetProviderInfo.getComponent() + + " due to min size constraint"); + Point cellSize = new Point(); + for (DeviceProfile deviceProfile : idp.supportedProfiles) { + deviceProfile.getCellSize(cellSize); + FileLog.d(TAG, "DeviceProfile available width: " + deviceProfile.availableWidthPx + + ", available height: " + deviceProfile.availableHeightPx + + ", cellLayoutBorderSpacingPx: " + deviceProfile.cellLayoutBorderSpacingPx + + ", cellSize: " + cellSize); + } + + StringBuilder widgetDimension = new StringBuilder(); + widgetDimension.append("Widget dimensions:\n") + .append("minResizeWidth: ") + .append(widgetProviderInfo.minResizeWidth) + .append("\n") + .append("minResizeHeight: ") + .append(widgetProviderInfo.minResizeHeight) + .append("\n") + .append("defaultWidth: ") + .append(widgetProviderInfo.minWidth) + .append("\n") + .append("defaultHeight: ") + .append(widgetProviderInfo.minHeight) + .append("\n"); + if (Utilities.ATLEAST_S) { + widgetDimension.append("targetCellWidth: ") + .append(widgetProviderInfo.targetCellWidth) + .append("\n") + .append("targetCellHeight: ") + .append(widgetProviderInfo.targetCellHeight) + .append("\n") + .append("maxResizeWidth: ") + .append(widgetProviderInfo.maxResizeWidth) + .append("\n") + .append("maxResizeHeight: ") + .append(widgetProviderInfo.maxResizeHeight) + .append("\n"); + } + FileLog.d(TAG, widgetDimension.toString()); + } + private static void logASplit(final TimingLogger logger, final String label) { logger.addSplit(label); if (DEBUG) { From 5f5b2e2558bf23a176f1677bd2ef6483f8266c74 Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Thu, 17 Jun 2021 21:32:13 +0000 Subject: [PATCH 06/13] Import translations. DO NOT MERGE ANYWHERE Auto-generated-cl: translation import Change-Id: Ie7cf7ee0a2fc1095e8e59bd41c7ebc5e0fec3678 --- res/values-af/strings.xml | 9 +++++---- res/values-am/strings.xml | 9 +++++---- res/values-ar/strings.xml | 9 +++++---- res/values-as/strings.xml | 9 +++++---- res/values-az/strings.xml | 9 +++++---- res/values-b+sr+Latn/strings.xml | 9 +++++---- res/values-be/strings.xml | 9 +++++---- res/values-bg/strings.xml | 9 +++++---- res/values-bn/strings.xml | 12 ++++++------ res/values-bs/strings.xml | 9 +++++---- res/values-ca/strings.xml | 9 +++++---- res/values-cs/strings.xml | 9 +++++---- res/values-da/strings.xml | 9 +++++---- res/values-de/strings.xml | 6 ++++-- res/values-el/strings.xml | 9 +++++---- res/values-en-rAU/strings.xml | 9 +++++---- res/values-en-rCA/strings.xml | 9 +++++---- res/values-en-rGB/strings.xml | 9 +++++---- res/values-en-rIN/strings.xml | 9 +++++---- res/values-en-rXC/strings.xml | 9 +++++---- res/values-es-rUS/strings.xml | 9 +++++---- res/values-es/strings.xml | 9 +++++---- res/values-et/strings.xml | 9 +++++---- res/values-eu/strings.xml | 9 +++++---- res/values-fa/strings.xml | 9 +++++---- res/values-fi/strings.xml | 9 +++++---- res/values-fr-rCA/strings.xml | 9 +++++---- res/values-fr/strings.xml | 9 +++++---- res/values-gl/strings.xml | 9 +++++---- res/values-gu/strings.xml | 12 ++++++------ res/values-hi/strings.xml | 9 +++++---- res/values-hr/strings.xml | 9 +++++---- res/values-hu/strings.xml | 9 +++++---- res/values-hy/strings.xml | 9 +++++---- res/values-in/strings.xml | 9 +++++---- res/values-is/strings.xml | 9 +++++---- res/values-it/strings.xml | 9 +++++---- res/values-iw/strings.xml | 9 +++++---- res/values-ja/strings.xml | 9 +++++---- res/values-ka/strings.xml | 9 +++++---- res/values-kk/strings.xml | 9 +++++---- res/values-km/strings.xml | 9 +++++---- res/values-kn/strings.xml | 12 ++++++------ res/values-ko/strings.xml | 9 +++++---- res/values-ky/strings.xml | 9 +++++---- res/values-lo/strings.xml | 9 +++++---- res/values-lt/strings.xml | 9 +++++---- res/values-lv/strings.xml | 9 +++++---- res/values-mk/strings.xml | 9 +++++---- res/values-ml/strings.xml | 9 +++++---- res/values-mn/strings.xml | 9 +++++---- res/values-mr/strings.xml | 27 +++++++++++---------------- res/values-ms/strings.xml | 9 +++++---- res/values-my/strings.xml | 9 +++++---- res/values-nb/strings.xml | 9 +++++---- res/values-ne/strings.xml | 15 +++++++-------- res/values-nl/strings.xml | 9 +++++---- res/values-or/strings.xml | 6 ++++-- res/values-pa/strings.xml | 12 ++++++------ res/values-pl/strings.xml | 9 +++++---- res/values-pt-rPT/strings.xml | 9 +++++---- res/values-pt/strings.xml | 9 +++++---- res/values-ro/strings.xml | 9 +++++---- res/values-ru/strings.xml | 9 +++++---- res/values-si/strings.xml | 9 +++++---- res/values-sk/strings.xml | 9 +++++---- res/values-sl/strings.xml | 9 +++++---- res/values-sq/strings.xml | 9 +++++---- res/values-sr/strings.xml | 9 +++++---- res/values-sv/strings.xml | 9 +++++---- res/values-sw/strings.xml | 9 +++++---- res/values-ta/strings.xml | 9 +++++---- res/values-te/strings.xml | 12 ++++++------ res/values-th/strings.xml | 9 +++++---- res/values-tl/strings.xml | 9 +++++---- res/values-tr/strings.xml | 9 +++++---- res/values-uk/strings.xml | 9 +++++---- res/values-ur/strings.xml | 15 +++++++-------- res/values-uz/strings.xml | 9 +++++---- res/values-vi/strings.xml | 9 +++++---- res/values-zh-rCN/strings.xml | 9 +++++---- res/values-zh-rHK/strings.xml | 9 +++++---- res/values-zh-rTW/strings.xml | 9 +++++---- res/values-zu/strings.xml | 9 +++++---- 84 files changed, 433 insertions(+), 362 deletions(-) diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml index 2703eb5e98..7f36dd2cc4 100644 --- a/res/values-af/strings.xml +++ b/res/values-af/strings.xml @@ -82,8 +82,10 @@ "skryf Tuis-instellings en -kortpaaie" "Laat die program toe om die instellings en kortpaaie in Tuis te verander." "%1$s word nie toegelaat om foonoproepe te maak nie" - "Kon nie legstuk laai nie" - "Stel op" + + + + "Dit is \'n stelselprogram en kan nie gedeïnstalleer word nie." "Wysig naam" "Het %1$s gedeaktiveer" @@ -156,8 +158,7 @@ "Persoonlik" "Werk" "Werkprofiel" - - + "Werkprogramme het \'n kenteken en is sigbaar vir jou IT-administrateur" "Het dit" "Werkprogramme is af" "Jou werkprogramme kan nie vir jou kennisgewings stuur, jou battery gebruik of toegang tot jou ligging kry nie" diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml index 846de43d4a..fe9716358a 100644 --- a/res/values-am/strings.xml +++ b/res/values-am/strings.xml @@ -82,8 +82,10 @@ "የመነሻ ቅንብሮችን እና አቋራጮችን ይጽፋል" "መተግብሪያው ቅንብሮችን እና አቋራጮችን በመነሻ ውስጥ እንዲቀይራቸው ያስችለዋል።" "%1$s የስልክ ጥሪዎችን ለማድረግ አልተፈቀደለትም" - "ፍርግም የመጫን ችግር" - "ማዋቀሪያ" + + + + "ይህ የስርዓት መተግበሪያ ነው እና ማራገፍ አይቻልም።" "ስም ያርትዑ" "%1$s ተሰናክሏል" @@ -156,8 +158,7 @@ "የግል" "ሥራ" "የሥራ መገለጫ" - - + "የሥራ መተግበሪያዎች ባጅ የተደረገባቸው እና ለእርስዎ የአይቲ አስተዳዳሪ የሚታዩ ናቸው" "ገባኝ" "የሥራ መተግበሪያዎች ጠፍተዋል" "የስራ መተግበሪያዎችዎ ማሳወቂያዎችን ሊልክልዎ፣ ባትሪዎን መጠቀም ወይም አካባቢዎን መድረስ አይችሉም" diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml index 18ce18c23b..ff2f51db3b 100644 --- a/res/values-ar/strings.xml +++ b/res/values-ar/strings.xml @@ -90,8 +90,10 @@ "كتابة إعدادات واختصارات الشاشة الرئيسية" "للسماح للتطبيق بتغيير الإعدادات والاختصارات في الشاشة الرئيسية." "%1$s غير مسموح به لإجراء مكالمات هاتفية" - "حدثت مشكلة أثناء تحميل الأداة" - "الإعداد" + + + + "هذا تطبيق نظام وتتعذر إزالته." "تعديل الاسم" "تم إيقاف %1$s" @@ -168,8 +170,7 @@ "شخصية" "للعمل" "الملف الشخصي للعمل" - - + "تحمل تطبيقات العمل شارة وتكون مرئية لمشرف تكنولوجيا المعلومات." "حسنًا" "تطبيقات العمل غير مفعّلة" "لا يمكن لتطبيقات العمل إرسال إشعارات إليك أو استخدام بطاريتك أو الوصول إلى موقعك الجغرافي." diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml index 1f217cf7b6..43927f53e2 100644 --- a/res/values-as/strings.xml +++ b/res/values-as/strings.xml @@ -82,8 +82,10 @@ "গৃহ ছেটিং আৰু শ্বৰ্টকাটবোৰ লিখিব পাৰে" "এপটোক গৃহ পৃষ্ঠাত ছেটিং আৰু শ্বৰ্টকাটসমূহ সলনি কৰাৰ অনুমতি দিয়ে।" "%1$sক ফ\'ন কলবোৰ কৰাৰ অনুমতি দিয়া হোৱা নাই" - "ৱিজেট ল\'ড কৰাত সমস্য়া" - "ছেটআপ কৰক" + + + + "এইটো এটা ছিষ্টেম এপ আৰু ইয়াক আনইনষ্টল কৰিব নোৱৰি" "নাম সম্পাদনা কৰক" "%1$s অক্ষম কৰা হ’ল" @@ -156,8 +158,7 @@ "ব্যক্তিগত" "কৰ্মস্থান" "কৰ্মস্থানৰ প্ৰ\'ফাইল" - - + "কৰ্মস্থানৰ এপ্‌সমূহ প্ৰতীকেৰে চিহ্নিত কৰা হয় আৰু সেইবোৰ আপোনাৰ আইটি প্ৰশাসকৰ বাবে দৃশ্যমান হয়" "বুজি পালোঁ" "কৰ্মস্থানৰ এপ্‌সমূহ অফ হৈ আছে" "আপোনাৰ কৰ্মস্থানৰ এপ্‌সমূহে আপোনালৈ জাননী পঠিয়াব, আপোনাৰ বেটাৰী ব্যৱহাৰ কৰিব অথবা আপোনাৰ অৱস্থান এক্সেছ কৰিব নোৱাৰে" diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml index f73e500c4f..7685330860 100644 --- a/res/values-az/strings.xml +++ b/res/values-az/strings.xml @@ -82,8 +82,10 @@ "Əsas Səhifə ayarlarını və qısayolları yazın" "Tətbiqə Əsas Səhifədə ayarları və qısayolları dəyişməyə icazə verir." "%1$s tətbiqinə telefon zəngləri etmək üçün icazə verilmir" - "Vidcet yükləmə problemi" - "Quraşdırma" + + + + "Bu sistem tətbiqi olduğu üçün sistemdən silinə bilməz." "Adı redaktə edin" "%1$s deaktiv edildi" @@ -156,8 +158,7 @@ "Şəxsi" "İş" "İş profili" - - + "İş tətbiqləri nişanlanıb və İT administratorunuza görünür" "Anladım" "İş tətbiqləri deaktivdir" "İş tətbiqləriniz sizə bildirişlər göndərə, batareyanızdan istifadə edə və ya məkanınıza daxil ola bilməz" diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml index a5000765fb..3e43c50b93 100644 --- a/res/values-b+sr+Latn/strings.xml +++ b/res/values-b+sr+Latn/strings.xml @@ -84,8 +84,10 @@ "upisivanje podešavanja i prečica na početnom ekranu" "Dozvoljava aplikaciji da menja podešavanja i prečice na početnom ekranu." "%1$s nema dozvolu za upućivanje telefonskih poziva" - "Problem pri učitavanju vidžeta" - "Podešavanje" + + + + "Ovo je sistemska aplikacija i ne može da se deinstalira." "Izmenite naziv" "Aplikacija %1$s je onemogućena" @@ -159,8 +161,7 @@ "Lične" "Poslovne" "Poslovni profil" - - + "Poslovne aplikacije su označene značkom i IT administrator može da ih vidi" "Važi" "Poslovne aplikacije su isključene" "Poslovne aplikacije ne mogu da vam šalju obaveštenja, koriste bateriju niti pristupaju lokaciji" diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml index aa8acd65d0..d99972996d 100644 --- a/res/values-be/strings.xml +++ b/res/values-be/strings.xml @@ -86,8 +86,10 @@ "запісваць налады і ярлыкі на галоўнай старонцы" "Дазваляе праграме змяняць налады і ярлыкі на Галоўнай старонцы." "%1$s не мае дазволу на здзяйсненне тэлефонных званкоў" - "Праблема загрузкі віджэта" - "Наладжванне" + + + + "Гэта сістэмная праграма, яе нельга выдаліць." "Змяніць назву" "%1$s адключана" @@ -162,8 +164,7 @@ "Асабістыя" "Працоўныя" "Працоўны профіль" - - + "Працоўныя праграмы маюць значкі і бачныя IT-адміністратару" "Зразумела" "Працоўныя праграмы выключаны" "Працоўныя праграмы не могуць адпраўляць вам апавяшчэнні, выкарыстоўваць акумулятар або атрымліваць доступ да даных пра ваша месцазнаходжанне" diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml index 117ee45e7b..a104988e2d 100644 --- a/res/values-bg/strings.xml +++ b/res/values-bg/strings.xml @@ -82,8 +82,10 @@ "запис на настройките и преките пътища в Начало" "Разрешава на приложението да променя настройките и преките пътища в Начало." "%1$s няма разрешение да извършва телефонни обаждания" - "Проблем при зареждане на приспособлението" - "Настройване" + + + + "Това е системно приложение и не може да се деинсталира." "Редактиране на името" "Деактивирахте %1$s" @@ -156,8 +158,7 @@ "Лични" "Служебни" "Служебен потребителски профил" - - + "Служебните приложения са означени със значка и са видими за системния администратор" "Разбрах" "Служебните ви приложения са изключени" "Служебните ви приложения не могат да ви изпращат известия, да използват батерията или да осъществяват достъп до местоположението ви" diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml index e88e4b923d..f89aecbadd 100644 --- a/res/values-bn/strings.xml +++ b/res/values-bn/strings.xml @@ -53,11 +53,9 @@ "কথোপকথন" "সহজেই দরকারি তথ্য পান" "অ্যাপ না খুলে তথ্য পাওয়ার জন্য, আপনার হোম স্ক্রিনে উইজেট যোগ করতে পারেন" - - + "উইজেট সেটিংস পরিবর্তন করতে ট্যাপ করুন" "বুঝেছি" - - + "উইজেট সেটিংস পরিবর্তন করুন" "অ্যাপ খুঁজুন" "অ্যাপ লোড হচ্ছে…" "\"%1$s\" এর সাথে মেলে এমন কোনো অ্যাপ পাওয়া যায়নি" @@ -84,8 +82,10 @@ "হোম সেটিংস এবং শর্টকাটগুলি লেখে" "হোমে অ্যাপ্লিকেশানটিকে সেটিংস এবং শর্টকাটগুলি পরিবর্তন করতে দেয়৷" "ফোন কলগুলি করার জন্য %1$s এর অনুমতি নেই" - "উইজেট লোড হতে সমস্যা হয়েছে" - "সেটআপ" + + + + "এটি একটি সিস্টেম অ্যাপ্লিকেশান এবং আনইনস্টল করা যাবে না৷" "নাম এডিট করুন" "%1$s অক্ষম করা হয়েছে" diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml index ed7050fa30..b265d841e4 100644 --- a/res/values-bs/strings.xml +++ b/res/values-bs/strings.xml @@ -84,8 +84,10 @@ "zapisuj postavke na početnom ekranu i prečice" "Dopušta aplikaciji promjenu postavki i prečica na početnom ekranu." "%1$s nema odobrenje da uspostavlja telefonske pozive" - "Problem pri učitavanju dodatka" - "Postavljanje" + + + + "Ovo je sistemska aplikacija i ne može se deinstalirati." "Uređivanje naziva" "Aplikacija %1$s je onemogućena" @@ -159,8 +161,7 @@ "Lične" "Poslovne" "Radni profil" - - + "Poslovne aplikacije su označene i vaš IT administrator ih može vidjeti" "Razumijem" "Poslovne aplikacije su isključene" "Poslovne aplikacije vam ne mogu slati obavještenja, koristiti bateriju niti pristupiti vašoj lokaciji" diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml index 306e0c220f..9cd23ba2cb 100644 --- a/res/values-ca/strings.xml +++ b/res/values-ca/strings.xml @@ -82,8 +82,10 @@ "escriu la configuració i les dreceres de la pantalla d\'inici" "Permet que l\'aplicació canviï la configuració i les dreceres de la pantalla d\'inici." "%1$s no té permís per fer trucades telefòniques" - "S\'ha produït un problema en carregar el widget" - "Configuració" + + + + "Aquesta aplicació és una aplicació del sistema i no es pot desinstal·lar." "Edita el nom" "S\'ha desactivat %1$s" @@ -156,8 +158,7 @@ "Personal" "Treball" "Perfil de treball" - - + "Les aplicacions de treball tenen una insígnia i són visibles per al teu administrador de TI" "Entesos" "Les aplicacions de treball estan desactivades" "Les aplicacions de treball no poden enviar-te notificacions, consumir bateria ni accedir a la teva ubicació" diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml index 70c511fd95..9785754d58 100644 --- a/res/values-cs/strings.xml +++ b/res/values-cs/strings.xml @@ -86,8 +86,10 @@ "zápis nastavení a odkazů plochy" "Umožňuje aplikaci změnit nastavení a odkazy na ploše." "Aplikace %1$s nemá oprávnění telefonovat" - "Problém s načtením widgetu" - "Nastavení" + + + + "Toto je systémová aplikace a nelze ji odinstalovat." "Upravit název" "Aplikace %1$s je zakázána" @@ -162,8 +164,7 @@ "Osobní" "Pracovní" "Pracovní profil" - - + "Pracovní aplikace jsou označené a viditelné vašemu administrátorovi IT" "Rozumím" "Pracovní aplikace jsou vypnuté" "Pracovní aplikace vám nemohou zasílat oznámení, používat vaši baterii ani získat přístup k vaší poloze" diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml index 5087ac4fb0..d757b0ecd2 100644 --- a/res/values-da/strings.xml +++ b/res/values-da/strings.xml @@ -82,8 +82,10 @@ "skrive indstillinger og genveje for startskærmen" "Tillader, at appen ændrer indstillingerne og genvejene på startskærmen." "%1$s har ikke tilladelse til at foretage telefonopkald" - "Der er problemer med indlæsning af widgetten" - "Konfigurer" + + + + "Dette er en systemapp, som ikke kan afinstalleres." "Rediger navn" "%1$s er deaktiveret" @@ -156,8 +158,7 @@ "Personlige" "Arbejde" "Arbejdsprofil" - - + "Arbejdsapps har badges og kan ses af din it-administrator" "OK" "Arbejdsapps er deaktiveret" "Arbejdsapps kan ikke sende dig notifikationer, bruge dit batteri eller få adgang til din lokation" diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index 6ff90222ae..ef646245e4 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -82,8 +82,10 @@ "Einstellungen und Verknüpfungen für den Startbildschirm schreiben" "Ermöglicht der App, die Einstellungen und Verknüpfungen auf dem Startbildschirm zu ändern" "%1$s darf keine Telefonanrufe tätigen." - "Problem beim Laden des Widgets" - "Einrichten" + + + + "Dies ist eine Systemanwendung, die nicht deinstalliert werden kann." "Name bearbeiten" "%1$s deaktiviert" diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml index b6b44f03e1..e9ea0eae46 100644 --- a/res/values-el/strings.xml +++ b/res/values-el/strings.xml @@ -82,8 +82,10 @@ "εγγραφή ρυθμίσεων και συντομεύσεων αρχικής οθόνης" "Επιτρέπει στην εφαρμογή την αλλαγή των ρυθμίσεων και των συντομεύσεων στην Αρχική οθόνη." "Η εφαρμογή %1$s δεν επιτρέπεται να πραγματοποιεί τηλεφωνικές κλήσεις" - "Παρουσιάστηκε πρόβλημα στη φόρτωση του γραφικού στοιχείου" - "Ρύθμιση" + + + + "Αυτή είναι μια εφαρμογή συστήματος και δεν είναι δυνατή η κατάργηση της εγκατάστασής της." "Επεξεργασία ονόματος" "Η εφαρμογή %1$s είναι απενεργοποιημένη" @@ -156,8 +158,7 @@ "Προσωπικές" "Εργασίας" "Προφίλ εργασίας" - - + "Οι εφαρμογές εργασιών φέρουν σήμα και είναι ορατές στον διαχειριστή IT σας" "Το κατάλαβα" "Οι εφαρμογές εργασιών είναι απενεργοποιημένες" "Οι εφαρμογές εργασιών δεν έχουν τη δυνατότητα αποστολής ειδοποιήσεων, χρήσης της μπαταρίας ή πρόσβασης στην τοποθεσία σας" diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml index 0e585f1032..c704585074 100644 --- a/res/values-en-rAU/strings.xml +++ b/res/values-en-rAU/strings.xml @@ -82,8 +82,10 @@ "write Home settings and shortcuts" "Allows the app to change the settings and shortcuts in Home." "%1$s is not allowed to make phone calls" - "Problem loading widget" - "Setup" + + + + "This is a system app and can\'t be uninstalled." "Edit Name" "Disabled %1$s" @@ -156,8 +158,7 @@ "Personal" "Work" "Work profile" - - + "Work apps are badged and visible to your IT admin" "OK" "Work apps are off" "Your work apps can’t send you notifications, use your battery or access your location" diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml index 0e585f1032..c704585074 100644 --- a/res/values-en-rCA/strings.xml +++ b/res/values-en-rCA/strings.xml @@ -82,8 +82,10 @@ "write Home settings and shortcuts" "Allows the app to change the settings and shortcuts in Home." "%1$s is not allowed to make phone calls" - "Problem loading widget" - "Setup" + + + + "This is a system app and can\'t be uninstalled." "Edit Name" "Disabled %1$s" @@ -156,8 +158,7 @@ "Personal" "Work" "Work profile" - - + "Work apps are badged and visible to your IT admin" "OK" "Work apps are off" "Your work apps can’t send you notifications, use your battery or access your location" diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml index 0e585f1032..c704585074 100644 --- a/res/values-en-rGB/strings.xml +++ b/res/values-en-rGB/strings.xml @@ -82,8 +82,10 @@ "write Home settings and shortcuts" "Allows the app to change the settings and shortcuts in Home." "%1$s is not allowed to make phone calls" - "Problem loading widget" - "Setup" + + + + "This is a system app and can\'t be uninstalled." "Edit Name" "Disabled %1$s" @@ -156,8 +158,7 @@ "Personal" "Work" "Work profile" - - + "Work apps are badged and visible to your IT admin" "OK" "Work apps are off" "Your work apps can’t send you notifications, use your battery or access your location" diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml index 0e585f1032..c704585074 100644 --- a/res/values-en-rIN/strings.xml +++ b/res/values-en-rIN/strings.xml @@ -82,8 +82,10 @@ "write Home settings and shortcuts" "Allows the app to change the settings and shortcuts in Home." "%1$s is not allowed to make phone calls" - "Problem loading widget" - "Setup" + + + + "This is a system app and can\'t be uninstalled." "Edit Name" "Disabled %1$s" @@ -156,8 +158,7 @@ "Personal" "Work" "Work profile" - - + "Work apps are badged and visible to your IT admin" "OK" "Work apps are off" "Your work apps can’t send you notifications, use your battery or access your location" diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml index 9add9f4820..930eb44567 100644 --- a/res/values-en-rXC/strings.xml +++ b/res/values-en-rXC/strings.xml @@ -82,8 +82,10 @@ "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‎‎‎‏‏‎‎‏‏‎‏‎‎‎‏‎‏‎‎‎‎‏‏‎‏‎‏‏‏‏‎‏‏‏‎‎‏‏‏‎‎‏‏‎‏‎‎‏‎‏‎‏‎‎‏‎‏‎‏‏‎write Home settings and shortcuts‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‏‎‎‏‎‏‏‏‎‎‏‎‎‎‏‎‎‎‏‏‏‏‏‏‏‎‏‎‎‏‎‏‎‏‎‎‏‏‏‎‎‏‎‎‎‎‏‎Allows the app to change the settings and shortcuts in Home.‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‎‏‏‏‎‏‎‏‏‎‏‎‎‏‏‏‎‏‏‎‏‎‏‎‎‏‏‎‎‎‎‏‏‏‎‎‏‎‏‎‏‎‏‏‎‏‏‎‎‏‎‎‏‏‎%1$s‎‏‎‎‏‏‏‎ is not allowed to make phone calls‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‏‏‎‎‏‎‎‎‏‎‏‏‎‏‎‏‏‎‎‏‎‏‏‎‏‎‎‏‎‏‏‏‎‏‎‎‎‏‎‎‎‏‎‏‎‏‎‏‎‎‏‏‏‏‏‎Problem loading widget‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‏‎‎‏‏‎‎‏‎‏‎‏‎‎‎‎‏‎‏‎‎‎‏‏‏‎‎‎‎‏‎‎‏‎‎‎‏‏‎‏‎‏‎‏‏‏‎‏‎‏‎‎‎‎Setup‎‏‎‎‏‎" + + + + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‏‏‎‎‏‏‎‎‎‎‏‎‏‎‏‏‏‎‏‎‎‎‎‏‎‏‎‏‎‏‎‎‏‏‏‏‎‏‏‎‎‏‎‎‎‏‎‏‏‎‏‎‏‎‎‎This is a system app and can\'t be uninstalled.‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‏‏‎‏‎‎‎‎‏‎‏‏‏‏‎‎‎‏‎‎‏‎‏‎‏‏‎‏‎‎‎‏‎‏‎‎‏‎‎‎‎‏‏‏‎‏‏‎‏‎‏‏‎‎‎‎‎Edit Name‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‎‎‏‏‎‏‏‏‎‏‏‎‏‏‏‎‏‏‏‏‏‎‏‏‏‏‎‏‎‏‏‎‎‎‎‎‏‏‎‏‎‏‏‎‏‏‏‏‎‎‏‏‏‎‎‎Disabled ‎‏‎‎‏‏‎%1$s‎‏‎‎‏‏‏‎‎‏‎‎‏‎" @@ -156,8 +158,7 @@ "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‎‏‎‎‏‏‎‏‏‎‎‎‏‎‎‏‎‏‎‎‎‎‎‎‏‏‏‎‎‎‏‎‎‎‎‏‎‏‎‏‎‎‏‎‎‏‎‏‎‏‏‏‎‏‎‎Personal‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‏‎‎‏‎‏‎‎‏‏‎‎‎‎‎‎‏‏‏‎‎‏‏‏‏‎‎‎‎‏‏‎‏‏‏‏‏‏‎‎‎‏‎‏‎‎‏‏‏‎‎‏‏‏‎‎Work‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‎‎‎‎‏‎‎‎‎‎‎‏‎‎‎‏‏‎‎‎‏‎‎‏‏‎‏‎‎‎‎‎‏‎‎‏‏‎‎‎‏‏‎‏‎‏‎‎‏‏‏‎‏‎‎Work profile‎‏‎‎‏‎" - - + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‎‏‎‎‏‎‎‏‎‏‎‏‏‎‏‏‎‏‎‏‏‎‏‎‎‏‏‏‎‏‎‏‏‎‏‎‎‎‎‏‎‎‎‎‏‎‎‏‎‏‏‎‎‎‎Work apps are badged and visible to your IT admin‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‎‏‏‏‏‎‎‎‎‏‏‏‎‎‎‎‎‎‏‏‎‏‎‎‎‎‏‏‎‎‏‎‏‏‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‏‏‏‎Got it‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‏‎‎‏‏‎‏‏‏‏‎‎‏‎‏‎‏‏‏‎‏‏‏‏‎‎‏‏‏‏‎‏‎‎‏‎‎‎‎‎‏‏‏‎‏‏‎‎‏‎‎‎‏‏‎‎‏‎‎‎‎Work apps are off‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‎‏‎‏‏‏‎‏‎‎‎‎‏‏‎‎‎‎‎‏‏‎‏‏‎‏‎‏‏‎‏‏‏‏‎‏‎‎‎‎‎‎‎‎‏‎‎‎‎‎‎‎‏‎‎‎‎‏‎‏‎‎Your work apps can’t send you notifications, use your battery, or access your location‎‏‎‎‏‎" diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml index 53e7637707..fbb616aeb7 100644 --- a/res/values-es-rUS/strings.xml +++ b/res/values-es-rUS/strings.xml @@ -82,8 +82,10 @@ "escribir configuración y accesos directos de la pantalla principal" "Permite que la aplicación cambie la configuración y los accesos directos de la pantalla principal." "%1$s no puede realizar llamadas telefónicas" - "Problema al cargar el widget" - "Configuración" + + + + "Esta es una aplicación del sistema y no se puede desinstalar." "Editar nombre" "Se inhabilitó %1$s" @@ -156,8 +158,7 @@ "Personales" "De trabajo" "Perfil de trabajo" - - + "Las apps de trabajo tienen una insignia y el administrador de IT las puede ver" "Entendido" "Las apps de trabajo están desactivadas" "Las apps de trabajo no pueden enviarte notificaciones, usar la batería ni acceder a tu ubicación" diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml index 61bd39d38c..03ac6d12e5 100644 --- a/res/values-es/strings.xml +++ b/res/values-es/strings.xml @@ -82,8 +82,10 @@ "escribir información de accesos directos y de ajustes de la pantalla de inicio" "Permite que las aplicaciones cambien los ajustes y los accesos directos de la pantalla de inicio." "%1$s no puede hacer llamadas" - "Problema al cargar el widget" - "Configuración" + + + + "Esta aplicación es del sistema y no se puede desinstalar." "Editar nombre" "Se ha inhabilitado %1$s" @@ -156,8 +158,7 @@ "Personal" "Trabajo" "Perfil de trabajo" - - + "Las aplicaciones de trabajo tienen una insignia, y tu administrador de TI las puede ver" "Entendido" "Las aplicaciones de trabajo están desactivadas" "Tus aplicaciones de trabajo no pueden enviarte notificaciones, consumir batería ni acceder a tu ubicación" diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml index e0c8733820..9ec12fce6a 100644 --- a/res/values-et/strings.xml +++ b/res/values-et/strings.xml @@ -82,8 +82,10 @@ "avakuva seadete ja otseteede kirjutamine" "Võimaldab rakendusel muuta avaekraanil seadeid ja otseteid." "Rakendusel %1$s pole lubatud helistada" - "Probleem vidina laadimisel" - "Seadistamine" + + + + "See on süsteemirakendus ja seda ei saa desinstallida." "Muuda nime" "Rakendus %1$s on keelatud" @@ -156,8 +158,7 @@ "Isiklik" "Töö" "Tööprofiil" - - + "Töörakendustel on märk ja need on teie IT-administraatorile nähtavad" "Selge" "Töörakendused on välja lülitatud" "Töörakendused ei saa teile märguandeid saata, akut kasutada ega teie asukohale juurde pääseda" diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml index eb038bae2b..0df92adf24 100644 --- a/res/values-eu/strings.xml +++ b/res/values-eu/strings.xml @@ -82,8 +82,10 @@ "Idatzi hasierako ezarpenak eta lasterbideak" "Hasierako pantailako ezarpenak eta lasterbideak aldatzeko baimena ematen die aplikazioei." "%1$s aplikazioak ez du telefono-deiak egiteko baimenik" - "Arazo bat izan da widgeta kargatzean" - "Konfigurazioa" + + + + "Sistema-aplikazioa da hau eta ezin da desinstalatu." "Editatu izena" "%1$s desgaituta dago" @@ -156,8 +158,7 @@ "Pertsonalak" "Lanekoak" "Laneko profila" - - + "Laneko aplikazioek bereizgarriak dituzte, eta IKT saileko administratzaileak ikus ditzake" "Ados" "Laneko aplikazioak desaktibatuta daude" "Laneko aplikazioek ezin dute jakinarazpenik bidali, bateria erabili edo kokapena atzitu" diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml index 0926e48f81..9da53ca986 100644 --- a/res/values-fa/strings.xml +++ b/res/values-fa/strings.xml @@ -82,8 +82,10 @@ "نوشتن تنظیمات و میان‌برهای صفحه اصلی" "به برنامه اجازه می‌دهد تنظیمات و میان‌برها را در صفحه اصلی تغییر دهد." "%1$s مجاز نیست تماس تلفنی برقرار کند" - "مشکل در بارگیری ابزارک" - "تنظیم" + + + + "این برنامه سیستمی است و حذف نصب نمی‌شود." "ویرایش نام" "%1$s غیرفعال شد" @@ -156,8 +158,7 @@ "شخصی" "کاری" "نمایه کاری" - - + "برنامه‌های کاری دارای نشان هستند و سرپرست سیستم می‌تواند آن‌ها را ببیند" "متوجه‌ام" "برنامه‌های کاری خاموش است" "برنامه‌های کاری نمی‌توانند برای شما اعلان ارسال کنند، از باتری استفاده کنند، یا به مکانتان دسترسی داشته باشند" diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml index 4ebc8d279b..fd8b67168f 100644 --- a/res/values-fi/strings.xml +++ b/res/values-fi/strings.xml @@ -82,8 +82,10 @@ "kirjoita aloitusruudun asetuksia ja pikakuvakkeita" "Antaa sovelluksen muuttaa aloitusruudun asetuksia ja pikakuvakkeita." "%1$s ei saa soittaa puheluita." - "Ongelma ladattaessa widgetiä" - "Asetus" + + + + "Tämä on järjestelmäsovellus, eikä sitä voi poistaa." "Muokkaa nimeä" "%1$s poistettiin käytöstä" @@ -156,8 +158,7 @@ "Henkilökohtaiset" "Työsovellukset" "Työprofiili" - - + "Työsovellukset on merkitty ja ne näkyvät IT-järjestelmänvalvojalle" "Selvä" "Työsovellukset ovat pois päältä" "Työsovellukset eivät voi lähettää sinulle ilmoituksia eivätkä käyttää akkuasi tai paikantaa sijaintiasi" diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml index 0a66787554..fb358eaf58 100644 --- a/res/values-fr-rCA/strings.xml +++ b/res/values-fr-rCA/strings.xml @@ -82,8 +82,10 @@ "enregistrer les paramètres de la page d\'accueil et des raccourcis" "Permet à l\'application de modifier les paramètres et les raccourcis de l\'écran d\'accueil." "L\'application %1$s n\'est pas autorisée à faire des appels téléphoniques" - "Problème lors du chargement du widget" - "Configuration" + + + + "Impossible de désinstaller cette application, car il s\'agit d\'une application système." "Modifier le nom" "L\'application %1$s est désactivée" @@ -156,8 +158,7 @@ "Personnel" "Travail" "Profil professionnel" - - + "Les applications professionnelles sont indiquées par un badge et elles sont visibles pour votre administrateur informatique" "OK" "Les applications professionnelles sont désactivées" "Les applications professionnelles ne peuvent ni vous envoyer de notifications, ni utiliser la pile, ni accéder à votre position" diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index f08f7eb0ba..098ce6f3e0 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -82,8 +82,10 @@ "modifier les paramètres et les raccourcis de l\'écran d\'accueil" "Permettre à l\'application de modifier les paramètres et les raccourcis de l\'écran d\'accueil" "L\'application %1$s n\'est pas autorisée à passer des appels téléphoniques." - "Problème lors du chargement du widget." - "Configuration" + + + + "Impossible de désinstaller cette application, car il s\'agit d\'une application système." "Modifier le nom" "%1$s est désactivé." @@ -156,8 +158,7 @@ "Personnelles" "Professionnelles" "Profil professionnel" - - + "Les applis professionnelles sont identifiées par un badge et votre administrateur informatique peut les voir" "OK" "Les applis professionnelles sont désactivées" "Vos applis professionnelles ne peuvent pas vous envoyer de notifications, utiliser votre batterie ni accéder à votre position" diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml index 6a7a6ef10e..8cc93612a2 100644 --- a/res/values-gl/strings.xml +++ b/res/values-gl/strings.xml @@ -82,8 +82,10 @@ "modificar a configuración e os atallos da pantalla de inicio" "Permite a unha aplicación cambiar a configuración e os atallos da pantalla de inicio." "%1$s non ten permiso para facer chamadas telefónicas" - "Produciuse un problema ao cargar o widget" - "Configuración" + + + + "Esta aplicación é do sistema e non se pode desinstalar." "Edita o nome" "Desactivouse %1$s" @@ -156,8 +158,7 @@ "Persoal" "Traballo" "Perfil de traballo" - - + "O administrador de TI pode ver as aplicacións do traballo e engadirlles indicadores" "Entendido" "As aplicacións do traballo están desactivadas" "As aplicacións do traballo non poden enviarche notificacións, utilizar a batería nin acceder á túa localización" diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml index 4b3821ce03..2e0171dad6 100644 --- a/res/values-gu/strings.xml +++ b/res/values-gu/strings.xml @@ -53,11 +53,9 @@ "વાતચીતો" "ઉપયોગી માહિતી તમારી આંગળીના ટેરવે" "ઍપને ખોલ્યા વિના માહિતી મેળવવા માટે, તમે તમારી હોમ સ્ક્રીન પર વિજેટ ઉમેરી શકો છો" - - + "વિજેટના સેટિંગ બદલવા માટે ટૅપ કરો" "સમજાઈ ગયું" - - + "વિજેટના સેટિંગ બદલો" "શોધ ઍપ્લિકેશનો" "ઍપ્લિકેશનો લોડ કરી રહ્યું છે…" "\"%1$s\"થી મેળ ખાતી કોઈ ઍપ્લિકેશનો મળી નથી" @@ -84,8 +82,10 @@ "હોમ સેટિંગ્સ અને શોર્ટકટ્સ લખો" "એપ્લિકેશનને હોમમાં સેટિંગ્સ અને શોર્ટકટ્સ બદલવાની મંજૂરી આપે છે." "%1$s ને ફોન કૉલ્સ કરવાની મંજૂરી નથી" - "વિજેટ લોડ કરવામાં સમસ્યા" - "સેટઅપ" + + + + "આ એક સિસ્ટમ ઍપ્લિકેશન છે અને અનઇન્સ્ટોલ કરી શકાતી નથી." "નામમાં ફેરફાર કરો" "%1$s અક્ષમ કરી" diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml index 8af8f066ad..fbadca6ba7 100644 --- a/res/values-hi/strings.xml +++ b/res/values-hi/strings.xml @@ -82,8 +82,10 @@ "होम पेज की सेटिंग और शॉर्टकट लिखें" "ऐप्लिकेशन को होम पेज में सेटिंग और शॉर्टकट बदलने देती है." "%1$s को फ़ोन कॉल करने की अनुमति नहीं है" - "विजेट लोड करने में समस्‍या" - "सेटअप" + + + + "यह एक सिस्टम ऐप्लिकेशन है और इसे अनइंस्टॉल नहीं किया जा सकता." "नाम में बदलाव करें" "%1$s अक्षम है" @@ -156,8 +158,7 @@ "निजी ऐप" "काम से जुड़े ऐप" "वर्क प्रोफ़ाइल" - - + "ऑफ़िस के काम से जुड़े ऐप्लिकेशन, बैज किए गए हैं और आईटी एडमिन को दिख रहे हैं" "ठीक है" "ऑफ़िस के काम से जुड़े ऐप्लिकेशन बंद हैं" "ऑफ़िस के काम से जुड़े आपके ऐप्लिकेशन, आपको सूचनाएं नहीं भेज सकते. साथ ही, आपकी बैटरी का इस्तेमाल या आपकी जगह की जानकारी को ऐक्सेस भी नहीं कर सकते" diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml index 3f1e1b0762..09d315f928 100644 --- a/res/values-hr/strings.xml +++ b/res/values-hr/strings.xml @@ -84,8 +84,10 @@ "pisanje postavki početnog zaslona i prečaca" "Aplikaciji omogućuje promjenu postavki i prečaca na početnom zaslonu." "%1$s nema dopuštenje za telefonske pozive" - "Problem pri učitavanju widgeta" - "Postavljanje" + + + + "Ovo je aplikacija sustava i ne može se ukloniti." "Uređivanje naziva" "Aplikacija %1$s onemogućena" @@ -159,8 +161,7 @@ "Osobno" "Posao" "Poslovni profil" - - + "Poslovne su aplikacije označene i vidljive vašem IT administratoru" "Shvaćam" "Poslovne aplikacije su isključene" "Poslovne aplikacije ne mogu vam slati obavijesti, trošiti bateriju niti pristupiti vašoj lokaciji" diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml index 09c4d5093a..2167540d38 100644 --- a/res/values-hu/strings.xml +++ b/res/values-hu/strings.xml @@ -82,8 +82,10 @@ "Főoldal beállításainak és parancsikonjainak írása" "Lehetővé teszi az alkalmazás számára, hogy módosítsa a kezdőképernyő beállításait és parancsikonjait." "A(z) %1$s nem kezdeményezhet telefonhívásokat" - "Probléma történt a modul betöltésekor" - "Beállítás" + + + + "Ez egy rendszeralkalmazás, és nem lehet eltávolítani." "Név módosítása" "A(z) %1$s letiltva" @@ -156,8 +158,7 @@ "Személyes" "Munkahelyi" "Munkaprofil" - - + "A munkahelyi alkalmazások jelvénnyel vannak megjelölve, és láthatók a rendszergazda számára" "Értem" "A munkahelyi alkalmazások ki vannak kapcsolva" "A munkahelyi alkalmazások nem küldhetnek értesítéseket, nem használhatják az akkumulátort, és nem férhetnek hozzá az Ön tartózkodási helyéhez." diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml index ef87d0619c..b261b02e7a 100644 --- a/res/values-hy/strings.xml +++ b/res/values-hy/strings.xml @@ -82,8 +82,10 @@ "ստեղծել հիմնաէջի կարգավորումներ ու դյուրանցումներ" "Ծրագրին թույլ է տալիս փոփոխել հիմնաէջի կարգավորումներն ու դյուրանցումները:" "%1$s հավելվածին չի թույլատրվում հեռախոսազանգեր կատարել" - "Վիջեթի բեռնման խնդիր կա" - "Կարգավորում" + + + + "Սա համակարգային ծրագիր է և չի կարող ապատեղադրվել:" "Փոխել անունը" "%1$s հավելվածն անջատված է" @@ -156,8 +158,7 @@ "Անձնական" "Աշխատանքային" "Աշխատանքային պրոֆիլ" - - + "Աշխատանքային հավելվածները նշանակներ ունեն և տեսանելի են ՏՏ ադմինիստրատորին" "Եղավ" "Աշխատանքային հավելվածներն անջատված են" "Աշխատանքային հավելվածները չեն կարող ծանուցումներ ուղարկել ձեզ, օգտագործել մարտկոցը և ձեր տեղադրության մասին տվյալներ ստանալ։" diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml index c3cdcfcf6a..8b45632d4c 100644 --- a/res/values-in/strings.xml +++ b/res/values-in/strings.xml @@ -82,8 +82,10 @@ "menulis setelan dan pintasan layar Utama" "Mengizinkan aplikasi mengubah setelan dan pintasan di layar Utama." "%1$s tidak diizinkan untuk melakukan panggilan telepon" - "Masalah memuat widget" - "Siapkan" + + + + "Ini adalah aplikasi sistem dan tidak dapat dicopot pemasangannya." "Sunting Nama" "%1$s dinonaktifkan" @@ -156,8 +158,7 @@ "Pribadi" "Kerja" "Profil kerja" - - + "Aplikasi kerja diberi badge dan terlihat oleh admin IT" "Oke" "Aplikasi kerja dinonaktifkan" "Aplikasi kerja tidak dapat mengirimkan notifikasi, menggunakan baterai, atau mengakses lokasi Anda" diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml index d3774ed827..baa4a87933 100644 --- a/res/values-is/strings.xml +++ b/res/values-is/strings.xml @@ -82,8 +82,10 @@ "skrifa stillingar og flýtileiðir heimaskjás" "Leyfir forriti að breyta stillingum og flýtileiðum heimaskjás." "%1$s hefur ekki leyfi til að hringja símtöl" - "Vandamál við að hlaða græju" - "Uppsetning" + + + + "Þetta er kerfisforrit sem ekki er hægt að fjarlægja." "Breyta nafni" "Óvirkt %1$s" @@ -156,8 +158,7 @@ "Persónulegt" "Vinna" "Vinnusnið" - - + "Vinnuforrit eru merkt og kerfisstjórinn getur séð þau" "Ég skil" "Slökkt er á vinnuforritum" "Vinnuforrit geta ekki sent þér tilkynningar, notað rafhlöðuorku eða fengið aðgang að staðsetningu þinni" diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml index 58135218c0..54a6560d04 100644 --- a/res/values-it/strings.xml +++ b/res/values-it/strings.xml @@ -82,8 +82,10 @@ "creazione di impostazioni e scorciatoie in Home" "Consente all\'app di modificare le impostazioni e le scorciatoie in Home." "L\'app %1$s non è autorizzata a effettuare telefonate" - "Errore durante il caricamento del widget" - "Configurazione" + + + + "Questa è un\'app di sistema e non può essere disinstallata." "Modifica nome" "App %1$s disattivata" @@ -156,8 +158,7 @@ "Personali" "Lavoro" "Profilo di lavoro" - - + "Le app di lavoro sono contrassegnate con un badge e visibili all\'amministratore IT" "OK" "Le app di lavoro non sono attive" "Le app di lavoro non possono inviarti notifiche, usare la batteria o accedere alla tua posizione" diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml index f07b4e554d..6291e308f4 100644 --- a/res/values-iw/strings.xml +++ b/res/values-iw/strings.xml @@ -86,8 +86,10 @@ "כתיבת הגדרות וקיצורי דרך של דף הבית" "מאפשר לאפליקציה לשנות את ההגדרות וקיצורי הדרך בדף הבית." "%1$s אינו רשאי להתקשר" - "בעיה בטעינת ווידג\'ט" - "הגדרה" + + + + "זוהי אפליקציית מערכת ולא ניתן להסיר את התקנתה." "עריכת השם" "%1$s מושבתת" @@ -162,8 +164,7 @@ "אישיות" "עבודה" "פרופיל עבודה" - - + "‏האפליקציות לעבודה מתויגות ומוצגות למנהל ה-IT" "הבנתי" "האפליקציות לעבודה מושבתות" "האפליקציות לעבודה לא יכולות לשלוח לך התראות, להשתמש בסוללה או לגשת למיקום שלך" diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml index 0d60f6d656..3586843a2f 100644 --- a/res/values-ja/strings.xml +++ b/res/values-ja/strings.xml @@ -82,8 +82,10 @@ "ホームの設定とショートカットの書き込み" "ホームの設定とショートカットの変更をアプリに許可します。" "「%1$s」から電話をかけることはできません" - "ウィジェットを表示できません" - "セットアップ" + + + + "このシステムアプリはアンインストールできません。" "名前の編集" "「%1$s」は無効です" @@ -156,8 +158,7 @@ "個人用" "仕事用" "仕事用プロファイル" - - + "仕事用アプリはバッジが付き、IT 管理者に公開されます" "OK" "仕事用アプリは OFF になっています" "仕事用アプリは、通知の送信、バッテリーの使用、位置情報の取得を行えません" diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml index 51246be30b..a8cbdeda02 100644 --- a/res/values-ka/strings.xml +++ b/res/values-ka/strings.xml @@ -82,8 +82,10 @@ "მთავარი ეკრანის პარამეტრებისა და მალსახმობების ჩაწერა" "აპისთვის მთავარი ეკრანის პარამეტრებისა და მალსახმობების შეცვლის უფლების მიცემა." "%1$s-ს არ აქვს სატელეფონო ზარების განხორციელების უფლება" - "პრობლემა ვიჯეტის ჩატვირთვისას" - "დაყენება" + + + + "ეს სისტემური აპია და მისი წაშლა შეუძლებელია." "სახელის რედაქტირება" "%1$s გაითიშა" @@ -156,8 +158,7 @@ "პირადი" "სამსახური" "სამსახურის პროფილი" - - + "სამსახურის აპები ბეჯით არის მონიშნული და ხილულია IT ადმინისტრატორისთვის" "გასაგებია" "სამსახურის აპები გამორთულია" "თქვენს სამსახურის აპებს არ შეუძლია თქვენთვის შეტყობინებების გამოგზავნა, თქვენი ბატარეის გამოყენება, ან თქვენს მდებარეობაზე წვდომა" diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml index b2753a2e88..057ca353cb 100644 --- a/res/values-kk/strings.xml +++ b/res/values-kk/strings.xml @@ -82,8 +82,10 @@ "Негізгі экран параметрлері мен төте пернелерін жазу" "Қолданбаға Негізгі экрандағы параметрлер мен төте пернелерді өзгерту мүмкіндігін береді." "%1$s арқылы телефон қоңырауларын соғуға рұқсат етілмеген" - "Виджетті жүктеу барысында мәселе орын алды" - "Орнату" + + + + "Бұл жүйе қолданбасы, сондықтан оны алу мүмкін емес." "Атын өңдеу" "%1$s өшірілді" @@ -156,8 +158,7 @@ "Жеке" "Жұмыс" "Жұмыс профилі" - - + "Жұмыс қолданбаларының танымбелгілері бар және олар әкімшіңізге көрінеді." "Түсінікті" "Жұмыс қолданбалары өшірулі" "Жұмыс қолданбалары сізге хабарландырулар жібере, батареяңызды немесе локацияңызды пайдалана алмайды." diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml index 82c9a36123..08f9d0a5e9 100644 --- a/res/values-km/strings.xml +++ b/res/values-km/strings.xml @@ -82,8 +82,10 @@ "សរសេរ​ការ​កំណត់ ​និង​ផ្លូវកាត់​​លើ​អេក្រង់​ដើម" "អនុញ្ញាត​ឲ្យ​កម្មវិធី​ប្ដូរ​ការ​កំណត់ និង​ផ្លូវ​កាត់​ក្នុង​អេក្រង់​ដើម។" "%1$s មិនត្រូវបានអនុញ្ញាតឲ្យធ្វើការហៅទូរស័ព្ទទេ" - "បញ្ហា​ក្នុង​ការ​ផ្ទុក​ធាតុ​​ក្រាហ្វិក" - "រៀបចំ" + + + + "នេះ​​​ជា​កម្មវិធី​ប្រព័ន្ធ មិន​អាច​លុប​បាន​ទេ។" "កែ​ឈ្មោះ" "បានបិទដំណើរការ %1$s" @@ -156,8 +158,7 @@ "ផ្ទាល់ខ្លួន" "ការងារ" "កម្រងព័ត៌មានការងារ" - - + "កម្មវិធីការងារ​ត្រូវបានដាក់​គ្រឿងសម្គាល់ ហើយ​អ្នកគ្រប់គ្រង​ផ្នែកព័ត៌មានវិទ្យា​របស់អ្នក​អាចមើលឃើញ" "យល់ហើយ" "កម្មវិធីការងារ​ត្រូវបានបិទ" "កម្មវិធី​ការងារ​របស់អ្នក​មិនអាចផ្ញើ​ការជូនដំណឹង​ទៅអ្នក ប្រើប្រាស់ថ្ម​របស់អ្នក ឬ​ចូលប្រើ​ទីតាំង​របស់អ្នក​បានទេ" diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml index 07a0cc9238..c46e732a79 100644 --- a/res/values-kn/strings.xml +++ b/res/values-kn/strings.xml @@ -53,11 +53,9 @@ "ಸಂವಾದಗಳು" "ನಿಮ್ಮ ಬೆರಳ ತುದಿಯಲ್ಲಿ ಉಪಯುಕ್ತ ಮಾಹಿತಿ" "ಆ್ಯಪ್‌ಗಳನ್ನು ತೆರೆಯದೆಯೇ ಮಾಹಿತಿಯನ್ನು ಪಡೆಯಲು, ನಿಮ್ಮ ಹೋಮ್ ಸ್ಕ್ರೀನ್‌ನಲ್ಲಿ ನೀವು ವಿಜೆಟ್‌ಗಳನ್ನು ಸೇರಿಸಬಹುದು" - - + "ವಿಜೆಟ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಬದಲಾಯಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ" "ಅರ್ಥವಾಯಿತು" - - + "ವಿಜೆಟ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಬದಲಾಯಿಸಿ" "ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಹುಡುಕಿ" "ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಲೋಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ..." "\"%1$s\" ಹೊಂದಿಕೆಯ ಯಾವುದೇ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಕಂಡುಬಂದಿಲ್ಲ" @@ -84,8 +82,10 @@ "ಮುಖಪುಟದ ಸೆಟ್ಟಿಂಗ್‌ಗಳು ಮತ್ತು ಶಾರ್ಟ್‌ಕಟ್‌ಗಳನ್ನು ಬರೆಯಿರಿ" "ಮುಖಪುಟದಲ್ಲಿ ಸೆಟ್ಟಿಂಗ್‌ಗಳು ಮತ್ತು ಶಾರ್ಟ್‌ಕಟ್‌ಗಳನ್ನು ಬದಲಾಯಿಸಲು ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಅನುಮತಿ ನೀಡುತ್ತದೆ." "ಫೋನ್ ಕರೆಗಳನ್ನು ಮಾಡಲು %1$s ಅಪ್ಲಿಕೇಶನ್‌‌ಗೆ ಅನುಮತಿಸಲಾಗುವುದಿಲ್ಲ" - "ವಿಜೆಟ್ ಲೋಡ್‌ ಮಾಡುವಲ್ಲಿ ಸಮಸ್ಯೆ" - "ಸೆಟಪ್" + + + + "ಇದೊಂದು ಅಪ್ಲಿಕೇಶನ್ ಆಗಿದೆ ಮತ್ತು ಅಸ್ಥಾಪಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ." "ಹೆಸರನ್ನು ಎಡಿಟ್ ಮಾಡಿ" "%1$s ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ" diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml index 68589d8ea9..c06d33753e 100644 --- a/res/values-ko/strings.xml +++ b/res/values-ko/strings.xml @@ -82,8 +82,10 @@ "홈 설정 및 바로가기 쓰기" "앱이 홈에 있는 설정 및 바로가기를 변경할 수 있도록 합니다." "%1$s에서 전화를 걸 수 없습니다." - "위젯을 로드하는 중 문제가 발생했습니다." - "설정" + + + + "시스템 앱은 제거할 수 없습니다." "이름 수정" "%1$s 사용 안함" @@ -156,8 +158,7 @@ "개인" "직장" "직장 프로필" - - + "직장 앱에는 배지가 있으며, IT 관리자는 직장 앱을 확인할 수 있습니다" "확인" "직장 앱이 꺼져 있음" "직장 앱에서 알림을 보내거나 배터리를 사용하거나 위치 정보에 액세스할 수 없습니다." diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml index 919fe45bba..122beaca13 100644 --- a/res/values-ky/strings.xml +++ b/res/values-ky/strings.xml @@ -82,8 +82,10 @@ "Үйдүн тууралоолорун жана тез чакырмаларын жазуу" "Колдонмого Үйдүн тууралоолорун жана тез чакырмаларын өзгөртүүгө уруксат берет." "%1$s телефон чалууларды аткарууга уруксаты жок" - "Виджетти жүктөөдө маселе бар" - "Орнотуу" + + + + "Бул системдик колдонмо жана аны чечкенге болбойт." "Аталышын түзөтүү" "%1$s өчүрүлгөн" @@ -156,8 +158,7 @@ "Жеке колдонмолор" "Жумуш колдонмолору" "Жумуш профили" - - + "Жумуш колдонмолору белгиленип, аларды IT администраторлору көрөт" "Түшүндүм" "Жумуш колдонмолору өчүк" "Жумуш колдонмолору билдирмелерди жөнөтүп, түзмөгүңүздүн батареясын керектеп же кайда жүргөнүңүздү көрө албайт" diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml index b91da7d67e..73d9fc4802 100644 --- a/res/values-lo/strings.xml +++ b/res/values-lo/strings.xml @@ -82,8 +82,10 @@ "ຂຽນການຕັ້ງຄ່າໜ້າຫຼັກ ແລະທາງລັດ" "ອະນຸຍາດໃຫ້ແອັບຯດັ່ງກ່າວ ປ່ຽນການຕັ້ງຄ່າ ແລະທາງລັດໃນໜ້າຫຼັກ." "%1$s ບໍ່​ໄດ້​ຮັບ​ອະ​ນຸ​ຍາດ​ໃຫ້​ໂທ" - "ມີບັນຫາໃນການໂຫລດວິດເຈັດ" - "ຕິດຕັ້ງ" + + + + "ນີ້ແມ່ນແອັບຯຂອງລະບົບ ແລະບໍ່ສາມາດຖອນການຕິດຕັ້ງອອກໄດ້." "ແກ້ໄຂຊື່" "ປິດການນຳໃຊ້ %1$s ແລ້ວ" @@ -156,8 +158,7 @@ "ສ່ວນຕົວ" "ວຽກ" "ໂປຣໄຟລ໌ບ່ອນເຮັດວຽກ" - - + "ແອັບບ່ອນເຮັດວຽກແມ່ນຖືກຕິດປ້າຍ ແລະ ສະແດງໃຫ້ຜູ້ເບິ່ງແຍງໄອທີຂອງທ່ານເຫັນ" "ເຂົ້າໃຈແລ້ວ" "ແອັບບ່ອນເຮັດວຽກຖືກປິດໄວ້ຢູ່" "ແອັບບ່ອນເຮັດວຽກຂອງທ່ານຈະບໍ່ສາມາດສົ່ງການແຈ້ງເຕືອນຫາທ່ານ, ໃຊ້ແບັດເຕີຣີຂອງທ່ານ ຫຼື ເຂົ້າເຖິງສະຖານທີ່ຂອງທ່ານໄດ້" diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml index 84414ca486..6843530cfb 100644 --- a/res/values-lt/strings.xml +++ b/res/values-lt/strings.xml @@ -86,8 +86,10 @@ "rašyti pagrindinio puslapio nustatymus ir sparčiuosius klavišus" "Programai leidžiama keisti pagrindinio puslapio nustatymus ir sparčiuosius klavišus." "„%1$s“ neleidžiama skambinti" - "Problema įkeliant valdiklį" - "Sąranka" + + + + "Tai sistemos programa ir jos negalima pašalinti." "Pavadinimo redagavimas" "„%1$s“ išjungta" @@ -162,8 +164,7 @@ "Asmeninės" "Darbo" "Darbo profilis" - - + "Darbo programos yra pažymėtos ženkleliu ir matomos IT administratoriui" "Supratau" "Darbo programos išjungtos" "Darbo programos negali siųsti jums pranešimų, naudoti jūsų akumuliatoriaus ar pasiekti jūsų vietovės" diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml index 0b283d9b48..ed6ebf610a 100644 --- a/res/values-lv/strings.xml +++ b/res/values-lv/strings.xml @@ -84,8 +84,10 @@ "rakstīt sākuma ekrāna iestatījumus un saīsnes" "Ļauj lietotnei mainīt iestatījumus un saīsnes sākuma ekrānā." "Lietotnei %1$s nav atļauts veikt tālruņa zvanus." - "Ielādējot logrīku, radās problēma." - "Notiek iestatīšana" + + + + "Šī ir sistēmas lietotne, un to nevar atinstalēt." "Nosaukuma rediģēšana" "Lietotne %1$s ir atspējota" @@ -159,8 +161,7 @@ "Personīgās lietotnes" "Darba lietotnes" "Darba profils" - - + "Darba lietotnēm ir pievienota emblēma, un tās ir redzamas jūsu IT administratoram" "Labi" "Darba lietotnes ir izslēgtas" "Jūsu darba lietotnes nevar sūtīt jums paziņojumus, izmantot akumulatoru un piekļūt jūsu atrašanās vietai." diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml index 29acbb87b6..2d5381171a 100644 --- a/res/values-mk/strings.xml +++ b/res/values-mk/strings.xml @@ -82,8 +82,10 @@ "пишува поставки и кратенки на почетна страница" "Овозможува апликацијата да ги менува подесувањата и кратенките на почетната страница." "%1$s нема дозвола за телефонски повици" - "Проблем при вчитувањето на виџетот" - "Поставување" + + + + "Ова е системска апликација и не може да се деинсталира." "Изменете го името" "%1$s е оневозможена" @@ -156,8 +158,7 @@ "Лично" "За работа" "Работен профил" - - + "Работните апликации имаат значка и се видливи за IT-администраторот" "Сфатив" "Работните апликации се исклучени" "Работните апликации не може да ви испраќаат известувања, да ја користат батеријата или да пристапуваат до вашата локација" diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml index 53679f15c4..f323d8fcfd 100644 --- a/res/values-ml/strings.xml +++ b/res/values-ml/strings.xml @@ -82,8 +82,10 @@ "ഹോം ക്രമീകരണങ്ങളും കുറുക്കുവഴികളും റൈറ്റുചെയ്യുക" "ഹോമിലെ ക്രമീകരണങ്ങളും കുറുക്കുവഴികളും മാറ്റാൻ അപ്ലിക്കേഷനെ അനുവദിക്കുന്നു." "ഫോൺ കോൾ ചെയ്യാൻ %1$s എന്നതിനെ അനുവദിച്ചിട്ടില്ല" - "വിജറ്റ് ലോഡുചെയ്യുന്നതിൽ പ്രശ്നമുണ്ട്" - "സജ്ജീകരിക്കുക" + + + + "ഇതൊരു സിസ്‌റ്റം അപ്ലിക്കേഷനായതിനാൽ അൺഇൻസ്‌റ്റാളുചെയ്യാനാവില്ല." "പേര് എഡിറ്റ് ചെയ്യുക" "%1$s പ്രവർത്തനരഹിതമാക്കി" @@ -156,8 +158,7 @@ "വ്യക്തിപരം" "ഔദ്യോഗികം" "ഔദ്യോഗിക പ്രൊഫൈൽ" - - + "ഔദ്യോഗിക ആപ്പുകൾക്ക് ബാഡ്‌ജ് നൽകിയിരിക്കുന്നു, അവ നിങ്ങളുടെ ഐടി അഡ്‌മിന് കാണാനുമാകും" "മനസ്സിലായി" "ഔദ്യോഗിക ആപ്പുകൾ ഓഫാണ്" "നിങ്ങൾക്ക് അറിയിപ്പുകൾ അയയ്ക്കാനോ ബാറ്ററി ഉപയോഗിക്കാനോ ലൊക്കേഷൻ ആക്‌സസ് ചെയ്യാനോ നിങ്ങളുടെ ഔദ്യോഗിക ആപ്പുകൾക്ക് കഴിയില്ല" diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml index dc30f552a2..fc9f772bbe 100644 --- a/res/values-mn/strings.xml +++ b/res/values-mn/strings.xml @@ -82,8 +82,10 @@ "Нүүрний тохиргоо болон товчлолыг бичих" "Апп нь Нүүрэндэх товчлол болон тохиргоог өөрчилж чадна." "%1$s утасны дуудлага хийх боломжгүй" - "Виджет ачаалахад асуудал гарав" - "Тохируулга" + + + + "Энэ апп нь системийн апп ба устгах боломжгүй." "Нэр засах" "%1$s-г идэвхгүй болгосон" @@ -156,8 +158,7 @@ "Хувийн" "Ажил" "Ажлын профайл" - - + "Ажлын аппуудыг тэмдэглэсэн бөгөөд танай IT админд харагдана" "Ойлголоо" "Ажлын аппууд унтраалттай байна" "Таны ажлын аппууд танд мэдэгдэл илгээх, таны батарейг ашиглах эсвэл байршилд тань хандах боломжгүй" diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml index 38acebffad..4e1cdfb896 100644 --- a/res/values-mr/strings.xml +++ b/res/values-mr/strings.xml @@ -53,11 +53,9 @@ "संभाषणे" "तुमच्यासाठी सहज उपलब्ध असलेली माहिती" "ॲप्स न उघडता माहिती मिळवण्यासाठी, तुम्ही तुमच्या होम स्क्रीनवर विजेट जोडू शकता" - - + "विजेट सेटिंग्ज बदलण्यासाठी टॅप करा" "समजले" - - + "विजेट सेटिंग्ज बदला" "अ‍ॅप्स शोधा" "अ‍ॅप्स लोड करत आहे…" "\"%1$s\" शी जुळणारे कोणतेही अ‍ॅप्स आढळले नाहीत" @@ -84,8 +82,10 @@ "होम सेटिंग्ज आणि शॉर्टकट लिहा" "मुख्यपृष्ठातील सेटिंग्ज आणि शॉर्टकट बदलण्यास अ‍ॅप ला अनुमती देते." "%1$s ला फोन कॉल करण्याची अनुमती नाही" - "विजेट लोड करण्यात समस्या" - "सेटअप" + + + + "हा सिस्टम अ‍ॅप आहे आणि अनइंस्टॉल केला जाऊ शकत नाही." "नाव संपादित करा" "%1$s अक्षम केला आहे" @@ -161,18 +161,13 @@ "समजले" - - - - - - + "कामाशी संबंधित ॲप्स बंद आहेत" + "तुमचे कामाशी संबंधित ॲप्स तुम्हाला सूचना पाठवू शकत नाहीत, तुमची बॅटरी वापरू शकत नाहीत किंवा तुमचे स्थान अ‍ॅक्सेस करू शकत नाहीत" + "कामाशी संबंधित ॲप्स बंद आहेत. तुमचे कामाशी संबंधित ॲप्स तुम्हाला सूचना पाठवू शकत नाहीत, तुमची बॅटरी वापरू शकत नाहीत किंवा तुमचे स्थान अ‍ॅक्सेस करू शकत नाहीत" "Work apps ही बॅज केलेली असून तुमच्या IT ॲडमिनला दृश्यमान आहेत" "समजले" - - - - + "कामाशी संबंधित ॲप्स बंद करा" + "कामाशी संबंधित ॲप्स सुरू करा" "फिल्टर" "हे करता आले नाही: %1$s" diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml index be9496203f..07b414a1d1 100644 --- a/res/values-ms/strings.xml +++ b/res/values-ms/strings.xml @@ -82,8 +82,10 @@ "tulis tetapan dan pintasan Laman Utama" "Membenarkan apl menukar tetapan dan pintasan di Laman Utama." "%1$s tidak dibenarkan membuat panggilan telefon" - "Masalah memuatkan widget" - "Persediaan" + + + + "Ini ialah apl sistem dan tidak boleh dinyahpasang." "Edit Nama" "%1$s dilumpuhkan" @@ -156,8 +158,7 @@ "Peribadi" "Kerja" "Profil kerja" - - + "Apl kerja mempunyai lencana dan kelihatan kepada pentadbir IT anda" "OK" "Apl kerja dimatikan" "Apl kerja anda tidak boleh menghantar pemberitahuan kepada anda, menggunakan bateri anda atau mengakses lokasi anda" diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml index 480310064f..e9f42e4014 100644 --- a/res/values-my/strings.xml +++ b/res/values-my/strings.xml @@ -82,8 +82,10 @@ "ပင်မမျက်နှာစာ အပြင်အဆင် နှင့် အတိုကောက်မှတ်သားမှုများအား ရေးသားခြင်း" "ပင်မမျက်နှာစာတွင် ရှိသော အပြင်အဆင် နှင့် အတိုကောက်မှတ်သားမှုများ ကို အပ်ပလီကေးရှင်းအား ပြောင်းခွင့်ပြုခြင်း" "%1$sသည် ဖုန်းခေါ်ဆိုခွင့် မရှိပါ" - "ဝဒ်ဂျက် တင်ရာတွင် ပြသနာ ရှိပါသည်" - "စဖွင့်သတ်မှတ်ရန်" + + + + "ဤအပ်ပလီကေးရှင်းမှာ စစ်စတန်ပိုင်းဆိုင်ရာ အပ်ပလီကေးရှင်းဖြစ်ပါသည်။ ထုတ်ပစ်၍ မရပါ" "အမည်ကို တည်းဖြတ်ပါ" "%1$s ကို ပိတ်ထားသည်" @@ -156,8 +158,7 @@ "ကိုယ်ပိုင်" "အလုပ်" "အလုပ်ပရိုဖိုင်" - - + "အလုပ်သုံးအက်ပ်များကို တံဆိပ်တပ်ထားပြီး သင်၏ IT စီမံခန့်ခွဲသူက မြင်နိုင်ပါသည်" "ရပါပြီ" "အလုပ်သုံးအက်ပ်များ ပိတ်ထားသည်" "သင်၏ အလုပ်သုံးအက်ပ်များက အကြောင်းကြားချက်များ ပို့ခြင်း၊ သင့်ဘက်ထရီ သုံးခြင်း (သို့) သင့်တည်နေရာ သုံးခြင်းတို့ မပြုလုပ်နိုင်ပါ" diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml index 55ef7a4cd1..046f4adbbd 100644 --- a/res/values-nb/strings.xml +++ b/res/values-nb/strings.xml @@ -82,8 +82,10 @@ "angi startsideinnstillinger og -snarveier" "Lar appen endre innstillingene og snarveiene på startsiden." "%1$s har ikke tillatelse til å ringe" - "Problem ved innlasting av modul" - "Konfigurering" + + + + "Dette er en systemapp som ikke kan avinstalleres." "Rediger navn" "Slo av %1$s" @@ -156,8 +158,7 @@ "Personlig" "Jobb" "Jobbprofil" - - + "Jobbapper er merket og synlige for IT-administratoren din" "Greit" "Jobbapper er av" "Jobbapper kan ikke sende deg varsler, bruke batteriet eller få tilgang til posisjonen din" diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml index 14dcec8aa2..f7dcd964d7 100644 --- a/res/values-ne/strings.xml +++ b/res/values-ne/strings.xml @@ -53,11 +53,9 @@ "वार्तालापहरू" "उपयोगी जानकारी सजिलै प्राप्त गर्नुहोस्" "एपहरू नखोलिकनै तिनका बारेमा जानकारी प्राप्त गर्न तपाईं आफ्नो होम स्क्रिनमा विजेटहरू हाल्न सक्नुहुन्छ" - - + "विजेटका सेटिङ बदल्न ट्याप गर्नुहोस्" "बुझेँ" - - + "विजेटका सेटिङ बदल्नुहोस्" "खोजसम्बन्धी एपहरू" "एपहरू लोड गर्दै…" "\"%1$s\" सँग मिल्दो कुनै एप भेटिएन" @@ -84,8 +82,10 @@ "गृह सेटिङहरू र सर्टकटहरू लेख्नुहोस्" "गृहमा एउटा एपलाई सेटिङ र सर्टकट बदल्न अनुमति दिनुहोस्।" "%1$s ले फोन कलहरू गर्न अनुमति छैन" - "समस्या लोडिङ गर्ने विजेट" - "सेटअप" + + + + "यो प्रणाली एप हो र यसलाई स्थापना रद्द गर्न सकिँदैन।" "नाम सम्पादन गर्नुहोस्" "असक्षम पारिएको %1$s" @@ -158,8 +158,7 @@ "व्यक्तिगत" "कार्यसम्बन्धी" "कार्य प्रोफाइल" - - + "कामसम्बन्धी एपहरूमा ब्याज अङ्कित हुन्छ र तपाईंका IT एड्मिन ती एप हेर्न सक्छन्" "बुझेँ" "कामसम्बन्धी एपहरू अफ गरिएका छन्" "तपाईंका कामसम्बन्धी एपहरूले तपाईंलाई सूचना पठाउन, तपाईंको डिभाइसको ब्याट्री प्रयोग गर्न वा तपाईंको लोकेसन हेर्न सक्दैनन्" diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml index c9f204bc2b..8fa7c0253f 100644 --- a/res/values-nl/strings.xml +++ b/res/values-nl/strings.xml @@ -82,8 +82,10 @@ "instellingen en snelkoppelingen op startscherm zetten" "De app toestaan de instellingen en snelkoppelingen op de homepage te wijzigen." "%1$s mag niet bellen" - "Probleem bij het laden van widget" - "Configuratie" + + + + "Dit is een systeemapp die niet kan worden verwijderd." "Naam bewerken" "%1$s staat uit" @@ -156,8 +158,7 @@ "Privé" "Werk" "Werkprofiel" - - + "Werk-apps hebben badges en zijn zichtbaar voor je IT-beheerder" "OK" "Werk-apps staan uit" "Je werk-apps kunnen je geen meldingen sturen, je batterij niet gebruiken en geen toegang krijgen tot je locatie" diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml index 81b66c6ba1..9ef1ace198 100644 --- a/res/values-or/strings.xml +++ b/res/values-or/strings.xml @@ -82,8 +82,10 @@ "ହୋମ୍‌ ସେଟିଙ୍ଗ ଏବଂ ଶର୍ଟକଟ୍‌ ଲେଖନ୍ତୁ" "ହୋମରେ ସେଟିଙ୍ଗ ଏବଂ ଶର୍ଟକଟ୍‌ ପରିବର୍ତ୍ତନ କରିବାକୁ ଆପକୁ ଅନୁମତି ଦିଏ।" "ଫୋନ୍‌ କଲ୍‌ କରିବାକୁ %1$sକୁ ଅନୁମତି ଦିଆଯାଇ ନାହିଁ" - "ୱିଜେଟ୍‌ ଲୋଡ୍‌ ହେବାରେ ସମସ୍ୟା ଅଛି" - "ସେଟ୍ ଅପ୍ କରନ୍ତୁ" + + + + "ଏହା ଏକ ସିଷ୍ଟମ୍‌ ଆପ୍‌ ଅଟେ ଏବଂ ଏହା ଅନଇନଷ୍ଟଲ୍‌ କରାଯାଇ ପାରିବ ନାହିଁ।" "ନାମ ସମ୍ପାଦନ କରନ୍ତୁ" "%1$s ଅକ୍ଷମ କରାଗଲା" diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml index 993b16a11b..ba3c39976d 100644 --- a/res/values-pa/strings.xml +++ b/res/values-pa/strings.xml @@ -53,11 +53,9 @@ "ਗੱਲਾਂਬਾਤਾਂ" "ਤੁਹਾਡੀਆਂ ਉਂਗਲਾਂ \'ਤੇ ਲਾਹੇਵੰਦ ਜਾਣਕਾਰੀ" "ਐਪਾਂ ਨੂੰ ਖੋਲ੍ਹੇ ਬਿਨਾਂ ਜਾਣਕਾਰੀ ਪ੍ਰਾਪਤ ਕਰਨ ਲਈ, ਤੁਸੀਂ ਆਪਣੀ ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਵਿਜੇਟ ਸ਼ਾਮਲ ਕਰ ਸਕਦੇ ਹੋ" - - + "ਵਿਜੇਟ ਸੈਟਿੰਗਾਂ ਨੂੰ ਬਦਲਣ ਲਈ ਟੈਪ ਕਰੋ" "ਸਮਝ ਲਿਆ" - - + "ਵਿਜੇਟ ਸੈਟਿੰਗਾਂ ਬਦਲੋ" "ਐਪਾਂ ਖੋਜੋ" "ਐਪਾਂ ਨੂੰ ਲੋਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ..." "\"%1$s\" ਨਾਲ ਮੇਲ ਖਾਂਦੀਆਂ ਕੋਈ ਐਪਾਂ ਨਹੀਂ ਮਿਲੀਆਂ" @@ -84,8 +82,10 @@ "ਹੋਮ ਸੈਟਿੰਗਾਂ ਅਤੇ ਸ਼ਾਰਟਕੱਟ ਲਿਖੋ" "ਐਪ ਨੂੰ ਹੋਮ ਵਿੱਚ ਸੈਟਿੰਗਾਂ ਅਤੇ ਸ਼ਾਰਟਕੱਟ ਬਦਲਣ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ।" "%1$s ਨੂੰ ਫ਼ੋਨ ਕਾਲਾਂ ਕਰਨ ਦੀ ਆਗਿਆ ਨਹੀਂ ਹੈ" - "ਵਿਜੇਟ ਲੋਡ ਕਰਨ ਵਿੱਚ ਸਮੱਸਿਆ" - "ਸਥਾਪਤ ਕਰੋ" + + + + "ਇਹ ਇੱਕ ਸਿਸਟਮ ਐਪ ਹੈ ਅਤੇ ਇਸਨੂੰ ਅਣਇੰਸਟੌਲ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ।" "ਨਾਮ ਦਾ ਸੰਪਾਦਨ ਕਰੋ" "%1$s ਨੂੰ ਅਯੋਗ ਬਣਾਇਆ ਗਿਆ" diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml index d55f6f5173..bb54c2f8e7 100644 --- a/res/values-pl/strings.xml +++ b/res/values-pl/strings.xml @@ -86,8 +86,10 @@ "zapisywanie ustawień i skrótów na ekranie głównym" "Umożliwia aplikacji zmianę ustawień i skrótów na ekranie głównym." "%1$s nie może wykonywać połączeń telefonicznych" - "Problem podczas ładowania widżetu" - "Konfiguracja" + + + + "To aplikacja systemowa i nie można jej odinstalować." "Edytuj nazwę" "Aplikacja %1$s jest wyłączona" @@ -162,8 +164,7 @@ "Osobiste" "Służbowe" "Profil służbowy" - - + "Aplikacje służbowe mają plakietki i są widoczne dla administratora IT" "OK" "Aplikacje służbowe są wyłączone" "Aplikacje służbowe nie mogą wysyłać powiadomień, używać baterii ani uzyskiwać dostępu do Twojej lokalizacji" diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml index d844da3c22..2ffb341198 100644 --- a/res/values-pt-rPT/strings.xml +++ b/res/values-pt-rPT/strings.xml @@ -82,8 +82,10 @@ "escrever definições e atalhos do Ecrã principal" "Permite à app alterar as definições e os atalhos no Ecrã Principal." "O %1$s não tem autorização para efetuar chamadas telefónicas" - "Problema ao carregar o widget" - "Configuração" + + + + "É uma app de sistema e não pode ser desinstalada." "Edite o nome" "%1$s desativado" @@ -156,8 +158,7 @@ "Pessoal" "Trabalho" "Perfil de trabalho" - - + "As apps de trabalho têm um emblema e estão visíveis para o seu administrador de TI" "OK" "As apps de trabalho estão desativadas" "As apps de trabalho não podem enviar-lhe notificações, utilizar a bateria ou aceder à sua localização" diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml index a29cc4968f..76a00ae628 100644 --- a/res/values-pt/strings.xml +++ b/res/values-pt/strings.xml @@ -82,8 +82,10 @@ "gravar configurações e atalhos da tela inicial" "Permite que o app altere as configurações e os atalhos na tela inicial." "%1$s não tem permissão para fazer chamadas" - "Problema ao carregar o widget" - "Configuração" + + + + "Este é um app do sistema e não pode ser desinstalado." "Editar nome" "%1$s desativado" @@ -156,8 +158,7 @@ "Pessoais" "Trabalho" "Perfil de trabalho" - - + "Os apps de trabalho têm um ícone e ficam visíveis para o administrador de TI" "Ok" "Os apps de trabalho estão desativados" "Os apps de trabalho não podem enviar notificações, usar a bateria nem acessar o local" diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml index 6fb48aa000..49c787cfc9 100644 --- a/res/values-ro/strings.xml +++ b/res/values-ro/strings.xml @@ -84,8 +84,10 @@ "scrie setări și comenzi rapide pentru ecranul de pornire" "Permite aplicației să modifice setările și comenzile rapide din ecranul de pornire." "%1$s nu are permisiunea de a apela" - "Problemă la încărcarea widgetului" - "Configurați" + + + + "Aceasta este o aplicație de sistem și nu poate fi dezinstalată." "Modificați numele" "S-a dezactivat %1$s" @@ -159,8 +161,7 @@ "Personale" "Profesionale" "Profil de serviciu" - - + "Aplicațiile pentru lucru sunt marcate și vizibile pentru administratorul IT" "OK" "Aplicațiile pentru lucru sunt dezactivate" "Aplicațiile pentru lucru nu pot să vă trimită notificări, să folosească bateria sau să vă acceseze locația" diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml index 5adf04b3b1..12db0e2543 100644 --- a/res/values-ru/strings.xml +++ b/res/values-ru/strings.xml @@ -86,8 +86,10 @@ "Изменение настроек и ярлыков главного экрана" "Приложение сможет изменять настройки и ярлыки на главном экране." "Приложение %1$s не может делать телефонные звонки" - "Не удалось загрузить виджет" - "Настройка" + + + + "Это системное приложение, его нельзя удалить." "Измените название" "Приложение %1$s отключено" @@ -162,8 +164,7 @@ "Личные" "Рабочие" "Рабочий профиль" - - + "У рабочих приложений есть специальная пометка. Они видны системному администратору." "ОК" "Рабочие приложения отключены" "Рабочие приложения не могут отправлять уведомления, расходовать заряд батареи и получать доступ к данным о вашем местоположении." diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml index e9e1062b73..946e12c319 100644 --- a/res/values-si/strings.xml +++ b/res/values-si/strings.xml @@ -82,8 +82,10 @@ "මුල් පිටු සැකසීම් සහ කෙටිමං ලියන්න" "මුල් පිටුවේ සැකසීම් සහ කෙටිමං ඉවත් කිරීමට යෙදුමට අවසර දෙයි." "%1$s හට දුරකථන ඇමතුම් සිදු කිරීමට ඉඩ නොදේ" - "ගැටලු පූරණ විජට් එක" - "ස්ථාපනය කරන්න" + + + + "මෙය පද්ධති යෙදුමක් වන අතර අස්ථාපනය කළ නොහැක." "නම සංස්කරණය කරන්න" "%1$s අබල කෙරිණි" @@ -156,8 +158,7 @@ "පුද්ගලික" "කාර්යාලය" "කාර්යාල පැතිකඩ" - - + "කාර්යාල යෙදුම්වලට ලාංඡන යොදා ඇති අතර ඔබගේ IT පරිපාලකට දෘශ්‍යමාන වේ" "තේරුණා" "කාර්යාල යෙදුම් ක්‍රියාවිරහිතයි" "ඔබගේ කාර්යාල යෙදුම්වලට ඔබට දැනුම්දීම් එවීමට, ඔබගේ බැටරිය භාවිත කිරීමට හෝ ඔබගේ ස්ථානයට ප්‍රවේශ වීමට නොහැකිය" diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml index 56637aed78..d4f2e00ba0 100644 --- a/res/values-sk/strings.xml +++ b/res/values-sk/strings.xml @@ -86,8 +86,10 @@ "zápis nastavení a odkazov plochy" "Povoľuje aplikácii zmeniť nastavenia a odkazy na ploche." "Aplikácia %1$s nemá povolenie uskutočňovať telefonické hovory" - "Problém s načítaním miniaplikácií" - "Nastavenie" + + + + "Toto je systémová aplikácia a nedá sa odinštalovať." "Úprava názvu" "Aplikácia %1$s je deaktivovaná" @@ -162,8 +164,7 @@ "Osobné" "Pracovné" "Pracovný profil" - - + "Pracovné aplikácie majú odznak a zobrazujú sa správcovi IT" "Dobre" "Pracovné aplikácie sú vypnuté" "Pracovné aplikácie nemôžu posielať upozornenia ani používať batériu či polohu." diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml index 3cf34d4a7a..d5d9f028fb 100644 --- a/res/values-sl/strings.xml +++ b/res/values-sl/strings.xml @@ -86,8 +86,10 @@ "zapis nastavitev in bližnjic na začetnem zaslonu" "Aplikaciji dovoli spreminjanje nastavitev in bližnjic na začetnem zaslonu." "Aplikaciji %1$s ni dovoljeno opravljanje klicev" - "Težava pri nalaganju pripomočka" - "Nastavitev" + + + + "To je sistemska aplikacija in je ni mogoče odstraniti." "Urejanje imena" "Aplikacija %1$s je onemogočena" @@ -162,8 +164,7 @@ "Osebno" "Delo" "Delovni profil" - - + "Delovne aplikacije so označene z značko in vidne skrbniku za IT." "Razumem" "Delovne aplikacije so izklopljene." "Delovne aplikacije ne smejo pošiljati obvestil, porabljati energije baterije ali dostopati do lokacije." diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml index 62537f2312..f3a4649c28 100644 --- a/res/values-sq/strings.xml +++ b/res/values-sq/strings.xml @@ -82,8 +82,10 @@ "shkruaj cilësimet dhe shkurtoret e ekranit bazë" "Lejon aplikacionin të ndryshojë cilësimet dhe shkurtoret në ekranin bazë." "%1$s nuk lejohet të kryejë telefonata" - "Problem në ngarkimin e miniaplikacionit" - "Konfiguro" + + + + "Ky është aplikacion sistemi dhe nuk mund të çinstalohet." "Redakto emrin" "%1$s u çaktivizua" @@ -156,8 +158,7 @@ "Personale" "Punë" "Profili i punës" - - + "Aplikacionet e punës janë të shënuara dhe të dukshme për administratorin e teknologjisë së informacionit" "E kuptova" "Aplikacionet e punës janë joaktive" "Aplikacionet e tua të punës nuk mund të të dërgojnë njoftime, të përdorin baterinë tënde apo të kenë qasje në vendndodhjen tënde" diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml index d5f8598b1a..b47a148072 100644 --- a/res/values-sr/strings.xml +++ b/res/values-sr/strings.xml @@ -84,8 +84,10 @@ "уписивање подешавања и пречица на почетном екрану" "Дозвољава апликацији да мења подешавања и пречице на почетном екрану." "%1$s нема дозволу за упућивање телефонских позива" - "Проблем при учитавању виџета" - "Подешавање" + + + + "Ово је системска апликација и не може да се деинсталира." "Измените назив" "Апликација %1$s је онемогућена" @@ -159,8 +161,7 @@ "Личне" "Пословне" "Пословни профил" - - + "Пословне апликације су означене значком и ИТ администратор може да их види" "Важи" "Пословне апликације су искључене" "Пословне апликације не могу да вам шаљу обавештења, користе батерију нити приступају локацији" diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml index 03872280ba..415e7bea76 100644 --- a/res/values-sv/strings.xml +++ b/res/values-sv/strings.xml @@ -82,8 +82,10 @@ "skriva inställningar och genvägar för startsidan" "Tillåter att appen ändrar inställningar och genvägar på startsidan." "%1$s har inte behörighet att ringa samtal" - "Det gick inte att läsa in widgeten" - "Konfiguration" + + + + "Det här är en systemapp som inte kan avinstalleras." "Redigera namn" "%1$s har inaktiverats" @@ -156,8 +158,7 @@ "Privat" "Arbete" "Jobbprofil" - - + "Jobbappar är märkta och synliga för IT-administratören" "OK" "Jobbappar är inaktiverade" "Dina jobbappar kan inte skicka aviseringar, använda batteriet eller komma åt din plats" diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml index c8463499db..7e1c724562 100644 --- a/res/values-sw/strings.xml +++ b/res/values-sw/strings.xml @@ -82,8 +82,10 @@ "andika mipangilio ya skrini ya Mwanzo na njia za mkato" "Huruhusu programu kubadilisha mipangilio na njia za mkato katika skrini ya Mwanzo." "%1$s hairuhusiwi kupiga simu" - "Tatizo la kupakia wijeti" - "Sanidi" + + + + "Hii ni programu ya mfumo na haiwezi kuondolewa." "Badilisha Jina" "%1$s imezimwa" @@ -156,8 +158,7 @@ "Binafsi" "Kazini" "Wasifu wa kazini" - - + "Programu za kazini zina beji na zinaonekana kwa msimamizi wako wa TEHAMA" "Nimeelewa" "Programu za kazini zimezimwa" "Programu zako za kazini haziwezi kukutumia arifa, kutumia betri yako au kufikia maelezo ya mahali ulipo" diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml index cfd4b3660a..877d92d09e 100644 --- a/res/values-ta/strings.xml +++ b/res/values-ta/strings.xml @@ -82,8 +82,10 @@ "முகப்பின் அமைப்பு மற்றும் குறுக்குவழிகளை எழுதுதல்" "முகப்பில் உள்ள அமைப்பு மற்றும் குறுக்குவழிகளை மாற்ற ஆப்ஸை அனுமதிக்கிறது." "ஃபோன் அழைப்புகள் செய்ய, %1$s அனுமதிக்கப்படவில்லை" - "விட்ஜெட்டை ஏற்றுவதில் சிக்கல்" - "அமைவு" + + + + "இது அமைப்பு ஆப்ஸ் என்பதால் நிறுவல் நீக்கம் செய்ய முடியாது." "பெயரைத் திருத்துதல்" "%1$s முடக்கப்பட்டது" @@ -156,8 +158,7 @@ "தனிப்பட்டவை" "பணி" "பணிக் கணக்கு" - - + "பணி ஆப்ஸில் பேட்ஜ் இடப்பட்டுள்ளன. உங்கள் IT நிர்வாகியால் அவற்றைப் பார்க்க முடியும்" "முடிந்தது" "பணி ஆப்ஸ் முடக்கப்பட்டுள்ளன" "பணி ஆப்ஸால் உங்களுக்கு அறிவிப்புகளை அனுப்பவோ பேட்டரியைப் பயன்படுத்தவோ உங்கள் இருப்பிடத்தை அணுகவோ முடியாது" diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml index b42b242169..e5bd96ee79 100644 --- a/res/values-te/strings.xml +++ b/res/values-te/strings.xml @@ -53,11 +53,9 @@ "సంభాషణలు" "మీ చేతివేళ్ల మీద ఉపయోగకరమైన సమాచారం" "యాప్‌లను తెరవకుండా సమాచారం పొందడానికి, మీరు మీ మొదటి స్క్రీన్‌కు విడ్జెట్‌లను జోడించవచ్చు" - - + "విడ్జెట్ సెట్టింగ్‌లను మార్చడానికి ట్యాప్ చేయండి" "అర్థమైంది" - - + "విడ్జెట్ సెట్టింగ్‌లను మార్చండి" "అప్లికేషన్‌లను శోధించండి" "అప్లికేషన్‌లను లోడ్ చేస్తోంది…" "\"%1$s\"కి సరిపోలే అప్లికేషన్‌లేవీ కనుగొనబడలేదు" @@ -84,8 +82,10 @@ "హోమ్ సెట్టింగ్‌లు మరియు సత్వరమార్గాలను వ్రాయడం" "హోమ్‌లో సెట్టింగ్‌లు మరియు సత్వరమార్గాలను మార్చడానికి యాప్‌ను అనుమతిస్తుంది." "ఫోన్ కాల్‌లను చేసేందుకు %1$sకి అనుమతి లేదు" - "విడ్జెట్‌ను లోడ్ చేయడంలో సమస్య" - "సెటప్ చేయి" + + + + "ఇది సిస్టమ్ యాప్ మరియు దీన్ని అన్‌ఇన్‌స్టాల్ చేయడం సాధ్యపడదు." "పేరును ఎడిట్ చేయండి" "%1$s నిలిపివేయబడింది" diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml index cbb7d2a953..795bc7d929 100644 --- a/res/values-th/strings.xml +++ b/res/values-th/strings.xml @@ -82,8 +82,10 @@ "เขียนการตั้งค่าและทางลัดหน้าแรกแล้ว" "อนุญาตให้แอปเปลี่ยนการตั้งค่าและทางลัดในหน้าแรก" "%1$s ไม่ได้รับอนุญาตให้โทรออก" - "มีปัญหาขณะโหลดวิดเจ็ต" - "ตั้งค่า" + + + + "นี่เป็นแอประบบและไม่สามารถถอนการติดตั้งได้" "แก้ไขชื่อ" "ปิดใช้ %1$s" @@ -156,8 +158,7 @@ "ส่วนตัว" "งาน" "โปรไฟล์งาน" - - + "แอปงานจะติดป้ายไว้และผู้ดูแลระบบไอทีจะมองเห็น" "รับทราบ" "แอปงานปิดอยู่" "แอปงานจะส่งการแจ้งเตือน ใช้แบตเตอรี่ หรือเข้าถึงตำแหน่งของคุณไม่ได้" diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml index aefb1204ed..558b96bc06 100644 --- a/res/values-tl/strings.xml +++ b/res/values-tl/strings.xml @@ -82,8 +82,10 @@ "magsulat ng mga setting at shortcut ng Home" "Pinapayagan ang app na baguhin ang mga setting at shortcut sa Home." "Hindi pinahihintulutang tumawag ang %1$s" - "Problema sa pag-load ng widget" - "I-setup" + + + + "Isa itong app ng system at hindi maaaring i-uninstall." "I-edit ang Pangalan" "Naka-disable ang %1$s" @@ -156,8 +158,7 @@ "Personal" "Trabaho" "Profile sa trabaho" - - + "May badge at nakikita ng iyong IT admin ang mga app para sa trabaho" "OK" "Naka-off ang mga app para sa trabaho" "Ang mga app mo para sa trabaho ay hindi makakapagpadala sa iyo ng mga notification, makakagamit ng baterya mo, o makaka-access ng iyong lokasyon" diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml index 6c4e541f68..c5980a9c87 100644 --- a/res/values-tr/strings.xml +++ b/res/values-tr/strings.xml @@ -82,8 +82,10 @@ "Ana ekran ayarlarını ve kısayollarını yaz" "Uygulamaya, Ana ekrandaki ayarları ve kısayolları değiştirme izni verir." "%1$s uygulamasının telefon etmesine izin verilmiyor" - "Widget yüklenirken sorun oluştu" - "Kurulum" + + + + "Bu bir sistem uygulamasıdır ve yüklemesi kaldırılamaz." "Adı Düzenle" "%1$s devre dışı" @@ -156,8 +158,7 @@ "Kişisel" "İş" "İş profili" - - + "İş uygulamaları rozetle işaretlenmiş olup BT yöneticisi tarafından görülebilir" "Anladım" "İş uygulamaları kapalı" "İş uygulamaları size bildirim gönderemez, pilinizi kullanamaz veya konum bilginize erişemez" diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml index 1677c9e67d..29fe67d98d 100644 --- a/res/values-uk/strings.xml +++ b/res/values-uk/strings.xml @@ -86,8 +86,10 @@ "записувати налаштування та ярлики головного екрана" "Дозволяє програмі змінювати налаштування та ярлики на головному екрані." "Додаток %1$s не має дозволу телефонувати" - "Проблема із завантаженням віджета" - "Налаштування" + + + + "Це системна програма, її неможливо видалити." "Редагувати назву" "%1$s вимкнено" @@ -162,8 +164,7 @@ "Особисті додатки" "Робочі додатки" "Робочий профіль" - - + "Робочі додатки відповідно позначені й видимі системному адміністратору" "OK" "Робочі додатки вимкнено" "Ваші робочі додатки не можуть надсилати сповіщення, використовувати заряд акумулятора й отримувати доступ до геоданих" diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml index 8545f8a9b1..aba11d318a 100644 --- a/res/values-ur/strings.xml +++ b/res/values-ur/strings.xml @@ -53,11 +53,9 @@ "گفتگوئیں" "مفید معلومات کو آسانی سے حاصل کریں" "ایپس کو کھولے بغیر معلومات حاصل کرنے کے لیے آپ اپنی ہوم اسکرین پر ویجیٹس شامل کر سکتے ہیں" - - + "ویجیٹ ترتیبات تبدیل کرنے کے لیے تھپتھپائیں" "سمجھ آ گئی" - - + "ویجیٹ ترتیبات تبدیل کریں" "ایپس تلاش کریں" "ایپس لوڈ کی جا رہی ہیں…" "\"%1$s\" سے مماثل کوئی ایپس نہیں ملیں" @@ -84,8 +82,10 @@ "ہوم ترتیبات اور شارٹ کٹس کو لکھیں" "ایپ کو ہوم میں ترتیبات اور شارٹ کٹس کو تبدیل کرنے کی اجازت دیتا ہے۔" "%1$s کو فون کالیں کرنے کی اجازت نہیں ہے" - "ویجیٹ کو لوڈ کرنے میں مسئلہ" - "ترتیب دیں" + + + + "یہ ایک سسٹم ایپ ہے اور اسے اَن انسٹال نہیں کیا جا سکتا ہے۔" "نام میں ترمیم کریں" "%1$s غیر فعال ہے" @@ -158,8 +158,7 @@ "ذاتی" "دفتری" "دفتری پروفائل" - - + "‏ورک ایپس پر بَیج لگایا گیا ہے اور آپ کا IT منتظم اسے دیکھ سکتا ہے" "سمجھ آ گئی" "ورک ایپس آف ہیں" "آپ کی ورک ایپس آپ کو اطلاعات نہیں بھیج سکتیں، آپ کی بیٹری کا استعمال یا آپ کے مقام تک رسائی حاصل نہیں کر سکتی ہیں" diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml index d9f492339a..022e7487a0 100644 --- a/res/values-uz/strings.xml +++ b/res/values-uz/strings.xml @@ -82,8 +82,10 @@ "Uy sozlamalari va yorliqlarini yozish" "Ilovaga \"Uy\" ekranidagi yorliqlar va sozlamalrni o‘zgartirish uchun ruxsat beradi." "%1$s ilovasiga qo‘ng‘iroqlarni amalga oshirishga ruxsat berilmagan" - "Vidjetni yuklashda muammo" - "Sozlash" + + + + "Bu tizim ilovasi, shuning uchun o‘chirib bo‘lmaydi." "Nomini tahrirlash" "%1$s ilovasi o‘chirib qo‘yildi" @@ -156,8 +158,7 @@ "Shaxsiy" "Ish" "Ish profili" - - + "Ishga oid ilovalar qadalgan va IT administratoringizga koʻrinadi" "OK" "Ishga oid ilovalar faolsizlantirilgan" "Ishga oid ilovalar batareya sarfi haqida bildirishnomalar yubora olmaydi va joylashuv axborotidan foydalana olmaydi" diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml index e15b3235ff..c3e2363b5c 100644 --- a/res/values-vi/strings.xml +++ b/res/values-vi/strings.xml @@ -82,8 +82,10 @@ "ghi cài đặt và lối tắt trên Màn hình chính" "Cho phép ứng dụng thay đổi cài đặt và lối tắt trên Màn hình chính." "%1$s không được phép thực hiện cuộc gọi điện thoại" - "Sự cố khi tải tiện ích" - "Thiết lập" + + + + "Đây là ứng dụng hệ thống và không thể gỡ cài đặt." "Chỉnh sửa tên" "Đã vô hiệu hóa %1$s" @@ -156,8 +158,7 @@ "Cá nhân" "Công việc" "Hồ sơ công việc" - - + "Các ứng dụng công việc được gắn huy hiệu và quản trị viên CNTT sẽ nhìn thấy các ứng dụng này" "OK" "Ứng dụng công việc đang tắt" "Các ứng dụng công việc không thể gửi thông báo cho bạn, sử dụng pin hoặc truy cập thông tin vị trí của bạn" diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml index 2d8162a92f..b5f8e335b3 100644 --- a/res/values-zh-rCN/strings.xml +++ b/res/values-zh-rCN/strings.xml @@ -82,8 +82,10 @@ "写入主屏幕设置和快捷方式" "允许应用更改主屏幕中的设置和快捷方式。" "不允许使用“%1$s”拨打电话" - "加载微件时出现问题" - "设置" + + + + "这是系统应用,无法卸载。" "修改名称" "已停用%1$s" @@ -156,8 +158,7 @@ "个人" "工作" "工作资料" - - + "工作应用带有标志,您的 IT 管理员可以看到此类应用" "知道了" "工作应用已关闭" "工作应用无法向您发送通知、不能耗用电池电量,也无法获取您的位置信息" diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml index 80f9ee9315..0379e5b57b 100644 --- a/res/values-zh-rHK/strings.xml +++ b/res/values-zh-rHK/strings.xml @@ -82,8 +82,10 @@ "寫入主畫面的設定和捷徑" "允許應用程式更改主畫面中的設定和捷徑。" "不允許 %1$s 撥打電話" - "載入小工具時發生問題" - "設定" + + + + "這是系統應用程式,無法將其解除安裝。" "編輯名稱" "「%1$s」已停用" @@ -156,8 +158,7 @@ "個人" "工作" "工作設定檔" - - + "工作應用程式會加上標誌,且你的 IT 管理員可以看到這類應用程式" "知道了" "工作應用程式已關閉" "工作應用程式無法向您傳送通知、使用電池或存取位置" diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml index c868137354..0f6ccd8a91 100644 --- a/res/values-zh-rTW/strings.xml +++ b/res/values-zh-rTW/strings.xml @@ -82,8 +82,10 @@ "寫入主畫面設定和捷徑" "允許應用程式變更主畫面中的設定和捷徑。" "%1$s 無法撥打電話" - "載入小工具時發生問題" - "設定" + + + + "這是系統應用程式,不可解除安裝。" "編輯名稱" "已停用 %1$s" @@ -156,8 +158,7 @@ "個人" "工作" "工作資料夾" - - + "工作應用程式會加上標記,而且你的 IT 管理員可以看到這類應用程式" "我知道了" "工作應用程式已關閉" "工作應用程式不會消耗電量、無法傳送通知,也無法存取你的位置資訊" diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml index 03a451c5c8..5bf5104117 100644 --- a/res/values-zu/strings.xml +++ b/res/values-zu/strings.xml @@ -82,8 +82,10 @@ "bhala izilungiselelo zokuthi Ikhaya nezinqamuleli" "Ivumela uhlelo lokusebenza ukuthi lushintshe izilungiselelo nezinqamuleli Ekhaya." "%1$s ayivunyelwe ukwenza amakholi wefoni" - "Inkinga yokulayisha iwijethi" - "Ukumisa" + + + + "Lolu uhlelo lokusebenza lwesistimu futhi alikwazi ukukhishwa." "Hlela igama" "Kukhutshaziwe %1$s" @@ -156,8 +158,7 @@ "Okomuntu siqu" "Umsebenzi" "Iphrofayela yomsebenzi" - - + "Ama-app omsebenzi anebheji futhi ayabonakala kumphathi wakho we-IT" "Ngiyezwa" "Ama-app omsebenzi avaliwe" "Ama-app akho omsebenzi awakwazi ukukuthumela izaziso, ukusebenzisa ibhethri lakho, noma ukufinyelela indawo yakho" From e79b469986ae9c2b056da0b25145ca0aea6bc19c Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Thu, 17 Jun 2021 11:34:20 -0500 Subject: [PATCH 07/13] Allow AllApps to restore current selected tab on activity recreate This CL also fixes: - Wrong tab selection on RTL - Fixes header protection hydraulic scroll issue - Work toggle button text background contrast Bug: 186005219 Bug: 191250799 Bug: 191227662 Bug: 191349226 Test: Manual Change-Id: I3777474c954ed0d542ac38ff552367904be147d3 --- res/values/config.xml | 3 +++ .../allapps/AllAppsContainerView.java | 24 ++++++++++++++++++- .../launcher3/allapps/WorkModeSwitch.java | 1 + .../launcher3/views/SpringRelativeLayout.java | 2 -- .../PersonalWorkSlidingTabStrip.java | 2 +- 5 files changed, 28 insertions(+), 4 deletions(-) diff --git a/res/values/config.xml b/res/values/config.xml index 299c80ef8b..04c359ede3 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -76,6 +76,9 @@ + + + diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index 8076b27300..67f2a9eb32 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -31,6 +31,7 @@ import android.graphics.Color; import android.graphics.Paint; import android.graphics.Point; import android.graphics.Rect; +import android.os.Bundle; import android.os.Parcelable; import android.os.Process; import android.text.Selection; @@ -82,6 +83,8 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo Insettable, OnDeviceProfileChangeListener, OnActivePageChangedListener, ScrimView.ScrimDrawingController { + private static final String BUNDLE_KEY_CURRENT_PAGE = "launcher.allapps.current_page"; + public static final float PULL_MULTIPLIER = .02f; public static final float FLING_VELOCITY_MULTIPLIER = 1200f; @@ -181,6 +184,23 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo } catch (Exception e) { Log.e("AllAppsContainerView", "restoreInstanceState viewId = 0", e); } + Bundle state = (Bundle) sparseArray.get(R.id.work_tab_state_id, null); + if (state != null) { + int currentPage = state.getInt(BUNDLE_KEY_CURRENT_PAGE, 0); + if (currentPage != 0) { + rebindAdapters(true); + mViewPager.setCurrentPage(currentPage); + } + } + } + + + @Override + protected void dispatchSaveInstanceState(SparseArray container) { + super.dispatchSaveInstanceState(container); + Bundle state = new Bundle(); + state.putInt(BUNDLE_KEY_CURRENT_PAGE, getCurrentPage()); + container.put(R.id.work_tab_state_id, state); } /** @@ -231,7 +251,9 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo private void resetWorkProfile() { boolean isEnabled = !mAllAppsStore.hasModelFlag(FLAG_QUIET_MODE_ENABLED); - mWorkModeSwitch.updateCurrentState(isEnabled); + if (mWorkModeSwitch != null) { + mWorkModeSwitch.updateCurrentState(isEnabled); + } mWorkAdapterProvider.updateCurrentState(isEnabled); mAH[AdapterHolder.WORK].applyPadding(); } diff --git a/src/com/android/launcher3/allapps/WorkModeSwitch.java b/src/com/android/launcher3/allapps/WorkModeSwitch.java index 6d57aa61a9..a800d34758 100644 --- a/src/com/android/launcher3/allapps/WorkModeSwitch.java +++ b/src/com/android/launcher3/allapps/WorkModeSwitch.java @@ -67,6 +67,7 @@ public class WorkModeSwitch extends Button implements Insettable, View.OnClickLi @Override protected void onFinishInflate() { super.onFinishInflate(); + setSelected(true); setOnClickListener(this); if (Utilities.ATLEAST_R) { mKeyboardInsetAnimationCallback = new KeyboardInsetAnimationCallback(this); diff --git a/src/com/android/launcher3/views/SpringRelativeLayout.java b/src/com/android/launcher3/views/SpringRelativeLayout.java index 8e3ac20574..8f814a1949 100644 --- a/src/com/android/launcher3/views/SpringRelativeLayout.java +++ b/src/com/android/launcher3/views/SpringRelativeLayout.java @@ -108,8 +108,6 @@ public class SpringRelativeLayout extends RelativeLayout { switch (direction) { case DIRECTION_TOP: return new EdgeEffectProxy(getContext(), mEdgeGlowTop); - case DIRECTION_BOTTOM: - return new EdgeEffectProxy(getContext(), mEdgeGlowBottom); } return super.createEdgeEffect(view, direction); } diff --git a/src/com/android/launcher3/workprofile/PersonalWorkSlidingTabStrip.java b/src/com/android/launcher3/workprofile/PersonalWorkSlidingTabStrip.java index b35c75b429..df5d465d96 100644 --- a/src/com/android/launcher3/workprofile/PersonalWorkSlidingTabStrip.java +++ b/src/com/android/launcher3/workprofile/PersonalWorkSlidingTabStrip.java @@ -127,7 +127,7 @@ public class PersonalWorkSlidingTabStrip extends LinearLayout implements PageInd @Override public void setActiveMarker(int activePage) { updateTabTextColor(activePage); - updateIndicatorPosition(activePage); + updateIndicatorPosition(mIsRtl ? 1 - activePage : activePage); if (mOnActivePageChangedListener != null && mLastActivePage != activePage) { mOnActivePageChangedListener.onActivePageChanged(activePage); } From deec62378c7e3518d1b8a727880b9c68efafae09 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Thu, 17 Jun 2021 15:18:15 -0700 Subject: [PATCH 08/13] Fixing Taskbar not initialized properly on recreate Bug: 187353581 Test: Manual Change-Id: I6e675937a033a4edcb4fa4e8151c210e14e8f896 --- .../launcher3/taskbar/LauncherTaskbarUIController.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java index 255ba1e054..7d0afe1aba 100644 --- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java @@ -90,12 +90,17 @@ public class LauncherTaskbarUIController extends TaskbarUIController { mHotseatController.init(); mLauncher.setTaskbarUIController(this); mKeyguardController = taskbarControllers.taskbarKeyguardController; + onLauncherResumedOrPaused(mLauncher.hasBeenResumed()); mIconAlignmentForResumedState.finishAnimation(); + onIconAlignmentRatioChanged(); } @Override protected void onDestroy() { + mIconAlignmentForResumedState.finishAnimation(); + mIconAlignmentForGestureState.finishAnimation(); + mHotseatController.cleanup(); setTaskbarViewVisible(true); mLauncher.getHotseat().setIconsAlpha(1f); From 058308098b152554af47645a5e8fba2da3b8d209 Mon Sep 17 00:00:00 2001 From: Steven Ng Date: Thu, 17 Jun 2021 23:19:31 +0100 Subject: [PATCH 09/13] Use relative bounds for app widget color extraction Fix 2 bugs in this CL 1. Before this CL, getBoundsForViewInDragLayer returns absolute bounds for widgets. What we want is the bounds of a widget when it is visible in the drag layer. ViewGroupFocusHelper#viewToRect has taken PagedView into account. This makes it a prefect candidate to calculate relative bounds of a widget. 2. requestLayout at the end of endDrag is problematic due to the drop animation. If the drop animation is still in progress when onLayout is called, the bounds of the widget isn't final. Since the color of app widget right before is already the final color. We don't really need to update the color extraction. Test: Set the wallpaper and clock widget according to the bug. Then, reboot the device. The clock widget color shown right after reboot is the expected color. Long pressing the widget doesn't cause a color change. Fix: 191114337 Change-Id: I466c8102dba0e5b207b3768e0935472cbd523235 --- .../widget/LauncherAppWidgetHostView.java | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java index 50ab422114..70ed02f006 100644 --- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java +++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java @@ -16,9 +16,6 @@ package com.android.launcher3.widget; -import static com.android.launcher3.Utilities.getBoundsForViewInDragLayer; -import static com.android.launcher3.Utilities.setRect; - import android.appwidget.AppWidgetProviderInfo; import android.content.Context; import android.content.res.Configuration; @@ -51,6 +48,7 @@ import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.Workspace; import com.android.launcher3.dragndrop.DragLayer; +import com.android.launcher3.keyboard.ViewGroupFocusHelper; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.LauncherAppWidgetInfo; import com.android.launcher3.util.Executors; @@ -102,7 +100,6 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView private final Rect mCurrentWidgetSize = new Rect(); private final Rect mWidgetSizeAtDrag = new Rect(); - private final float[] mTmpFloatArray = new float[4]; private final RectF mTempRectF = new RectF(); private final Rect mEnforcedRectangle = new Rect(); private final float mEnforcedCornerRadius; @@ -117,6 +114,7 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView } }; private final Object mUpdateLock = new Object(); + private final ViewGroupFocusHelper mDragLayerRelativeCoordinateHelper; private long mDeferUpdatesUntilMillis = 0; private RemoteViews mMostRecentRemoteViews; @@ -137,6 +135,7 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView mColorExtractor.setListener(this); mEnforcedCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(getContext()); + mDragLayerRelativeCoordinateHelper = new ViewGroupFocusHelper(mLauncher.getDragLayer()); } @Override @@ -325,15 +324,9 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView mIsScrollable = checkScrollableRecursively(this); if (!mIsInDragMode && getTag() instanceof LauncherAppWidgetInfo) { - mCurrentWidgetSize.left = left; - mCurrentWidgetSize.right = right; - mCurrentWidgetSize.top = top; - mCurrentWidgetSize.bottom = bottom; LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) getTag(); - getBoundsForViewInDragLayer(mLauncher.getDragLayer(), (View) getParent(), - mCurrentWidgetSize, true, mTmpFloatArray, mTempRectF); - setRect(mTempRectF, mCurrentWidgetSize); + mDragLayerRelativeCoordinateHelper.viewToRect(this, mCurrentWidgetSize); updateColorExtraction(mCurrentWidgetSize, mWorkspace.getPageIndexForScreenId(info.screenId)); } @@ -358,7 +351,6 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView mIsInDragMode = false; mDragListener = null; mWidgetSizeAtDrag.setEmpty(); - requestLayout(); } /** From 54b5424548417769df815da072f95c6af5f872de Mon Sep 17 00:00:00 2001 From: Steven Ng Date: Fri, 18 Jun 2021 09:36:23 +0000 Subject: [PATCH 10/13] Revert "Deduct padding from widget size only if padding has been inset" This reverts commit 71cefb593f99ab2d19d1b70335be8c7886040469. Reason for revert: The default padding is added by the framework not the launcher. We really need to subtract padding from the size. Bug: 190498032 Change-Id: If7f164da34aec3d8e022ef2c752b72292cca0489 --- src/com/android/launcher3/widget/util/WidgetSizes.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/com/android/launcher3/widget/util/WidgetSizes.java b/src/com/android/launcher3/widget/util/WidgetSizes.java index fcc0dde889..5c8ea7299b 100644 --- a/src/com/android/launcher3/widget/util/WidgetSizes.java +++ b/src/com/android/launcher3/widget/util/WidgetSizes.java @@ -99,13 +99,11 @@ public final class WidgetSizes { */ public static Bundle getWidgetSizeOptions(Context context, ComponentName provider, int spanX, int spanY) { - boolean shouldInsetWidgets = - LauncherAppState.getIDP(context).getDeviceProfile(context).shouldInsetWidgets(); ArrayList sizes = getWidgetSizes(context, spanX, spanY); Rect padding = getDefaultPaddingForWidget(context, provider, null); float density = context.getResources().getDisplayMetrics().density; - float xPaddingDips = shouldInsetWidgets ? (padding.left + padding.right) / density : 0; - float yPaddingDips = shouldInsetWidgets ? (padding.top + padding.bottom) / density : 0; + float xPaddingDips = (padding.left + padding.right) / density; + float yPaddingDips = (padding.top + padding.bottom) / density; ArrayList paddedSizes = sizes.stream() .map(size -> new SizeF( From a0935232992a333847f845324327a27748115f64 Mon Sep 17 00:00:00 2001 From: Stevie Kideckel Date: Mon, 14 Jun 2021 14:55:07 +0000 Subject: [PATCH 11/13] Load widget preview images before adding the rows to the adapter This allows for smooth animations as the items will have their full height resolved Bug: 190892517 Test: verified locally Test: CachingWidgetPreviewLoaderTest Change-Id: I86afc6951a4cc82ae45e63795791d259a3bb3cda --- .../CachingWidgetPreviewLoaderTest.java | 409 ++++++++++++++++++ .../picker/WidgetsDiffReporterTest.java | 7 +- .../widget/picker/WidgetsListAdapterTest.java | 4 +- ...WidgetsListHeaderViewHolderBinderTest.java | 4 +- ...sListSearchHeaderViewHolderBinderTest.java | 4 +- .../WidgetsListTableViewHolderBinderTest.java | 8 +- .../android/launcher3/LauncherAppState.java | 7 +- .../android/launcher3/model/WidgetItem.java | 15 + .../widget/CachingWidgetPreviewLoader.java | 289 +++++++++++++ .../DatabaseWidgetPreviewLoader.java} | 136 ++++-- .../android/launcher3/widget/WidgetCell.java | 42 +- .../launcher3/widget/WidgetPreviewLoader.java | 47 ++ .../widget/model/WidgetsListBaseEntry.java | 13 + .../widget/model/WidgetsListHeaderEntry.java | 42 +- .../model/WidgetsListSearchHeaderEntry.java | 40 +- .../widget/picker/WidgetsDiffReporter.java | 2 +- .../widget/picker/WidgetsListAdapter.java | 154 +++++-- .../WidgetsListTableViewHolderBinder.java | 21 +- 18 files changed, 1086 insertions(+), 158 deletions(-) create mode 100644 robolectric_tests/src/com/android/launcher3/widget/CachingWidgetPreviewLoaderTest.java create mode 100644 src/com/android/launcher3/widget/CachingWidgetPreviewLoader.java rename src/com/android/launcher3/{WidgetPreviewLoader.java => widget/DatabaseWidgetPreviewLoader.java} (86%) create mode 100644 src/com/android/launcher3/widget/WidgetPreviewLoader.java diff --git a/robolectric_tests/src/com/android/launcher3/widget/CachingWidgetPreviewLoaderTest.java b/robolectric_tests/src/com/android/launcher3/widget/CachingWidgetPreviewLoaderTest.java new file mode 100644 index 0000000000..c18e26ce34 --- /dev/null +++ b/robolectric_tests/src/com/android/launcher3/widget/CachingWidgetPreviewLoaderTest.java @@ -0,0 +1,409 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.widget; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +import android.content.ComponentName; +import android.graphics.Bitmap; +import android.os.CancellationSignal; +import android.os.UserHandle; +import android.util.Size; + +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.icons.IconCache; +import com.android.launcher3.model.WidgetItem; +import com.android.launcher3.testing.TestActivity; +import com.android.launcher3.widget.WidgetPreviewLoader.WidgetPreviewLoadedCallback; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; + +import java.util.Arrays; +import java.util.Collections; + +@RunWith(RobolectricTestRunner.class) +public class CachingWidgetPreviewLoaderTest { + private static final Size SIZE_10_10 = new Size(10, 10); + private static final Size SIZE_20_20 = new Size(20, 20); + private static final String TEST_PACKAGE = "com.example.test"; + private static final ComponentName TEST_PROVIDER = + new ComponentName(TEST_PACKAGE, ".WidgetProvider"); + private static final ComponentName TEST_PROVIDER2 = + new ComponentName(TEST_PACKAGE, ".WidgetProvider2"); + private static final Bitmap BITMAP = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888); + private static final Bitmap BITMAP2 = Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888); + + + @Mock private CancellationSignal mCancellationSignal; + @Mock private WidgetPreviewLoader mDelegate; + @Mock private IconCache mIconCache; + @Mock private DeviceProfile mDeviceProfile; + @Mock private LauncherAppWidgetProviderInfo mProviderInfo; + @Mock private LauncherAppWidgetProviderInfo mProviderInfo2; + @Mock private WidgetPreviewLoadedCallback mPreviewLoadedCallback; + @Mock private WidgetPreviewLoadedCallback mPreviewLoadedCallback2; + @Captor private ArgumentCaptor mCallbackCaptor; + + private TestActivity mTestActivity; + private CachingWidgetPreviewLoader mLoader; + private WidgetItem mWidgetItem; + private WidgetItem mWidgetItem2; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mLoader = new CachingWidgetPreviewLoader(mDelegate); + + mTestActivity = Robolectric.buildActivity(TestActivity.class).setup().get(); + mTestActivity.setDeviceProfile(mDeviceProfile); + + when(mDelegate.loadPreview(any(), any(), any(), any())).thenReturn(mCancellationSignal); + + mProviderInfo.provider = TEST_PROVIDER; + when(mProviderInfo.getProfile()).thenReturn(new UserHandle(0)); + + mProviderInfo2.provider = TEST_PROVIDER2; + when(mProviderInfo2.getProfile()).thenReturn(new UserHandle(0)); + + InvariantDeviceProfile testProfile = new InvariantDeviceProfile(); + testProfile.numRows = 5; + testProfile.numColumns = 5; + + mWidgetItem = new WidgetItem(mProviderInfo, testProfile, mIconCache); + mWidgetItem2 = new WidgetItem(mProviderInfo2, testProfile, mIconCache); + } + + @Test + public void getPreview_notInCache_shouldReturnNull() { + assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isNull(); + } + + @Test + public void getPreview_notInCache_shouldNotCallDelegate() { + mLoader.getPreview(mWidgetItem, SIZE_10_10); + + verifyZeroInteractions(mDelegate); + } + + @Test + public void getPreview_inCache_shouldReturnCachedBitmap() { + loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP); + + assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isEqualTo(BITMAP); + } + + @Test + public void getPreview_otherSizeInCache_shouldReturnNull() { + loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP); + + assertThat(mLoader.getPreview(mWidgetItem, SIZE_20_20)).isNull(); + } + + @Test + public void getPreview_otherItemInCache_shouldReturnNull() { + loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP); + + assertThat(mLoader.getPreview(mWidgetItem2, SIZE_10_10)).isNull(); + } + + @Test + public void getPreview_shouldStoreMultipleSizesPerItem() { + loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP); + loadPreviewIntoCache(mWidgetItem, SIZE_20_20, BITMAP2); + + assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isEqualTo(BITMAP); + assertThat(mLoader.getPreview(mWidgetItem, SIZE_20_20)).isEqualTo(BITMAP2); + } + + @Test + public void loadPreview_notInCache_shouldStartLoading() { + mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback); + + verify(mDelegate).loadPreview(eq(mTestActivity), eq(mWidgetItem), eq(SIZE_10_10), any()); + verifyZeroInteractions(mPreviewLoadedCallback); + } + + @Test + public void loadPreview_thenLoaded_shouldCallBack() { + mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback); + verify(mDelegate).loadPreview(any(), any(), any(), mCallbackCaptor.capture()); + WidgetPreviewLoadedCallback loaderCallback = mCallbackCaptor.getValue(); + + loaderCallback.onPreviewLoaded(BITMAP); + + verify(mPreviewLoadedCallback).onPreviewLoaded(BITMAP); + } + + @Test + public void loadPreview_thenCancelled_shouldCancelDelegateRequest() { + CancellationSignal cancellationSignal = + mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback); + + cancellationSignal.cancel(); + + verify(mCancellationSignal).cancel(); + verifyZeroInteractions(mPreviewLoadedCallback); + assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isNull(); + } + + @Test + public void loadPreview_thenCancelled_otherCallListening_shouldNotCancelDelegateRequest() { + CancellationSignal cancellationSignal1 = + mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback); + mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback2); + + cancellationSignal1.cancel(); + + verifyZeroInteractions(mCancellationSignal); + } + + @Test + public void loadPreview_thenCancelled_otherCallListening_loaded_shouldCallBackToNonCancelled() { + CancellationSignal cancellationSignal1 = + mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback); + mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback2); + verify(mDelegate).loadPreview(any(), any(), any(), mCallbackCaptor.capture()); + WidgetPreviewLoadedCallback loaderCallback = mCallbackCaptor.getValue(); + + cancellationSignal1.cancel(); + loaderCallback.onPreviewLoaded(BITMAP); + + verifyZeroInteractions(mPreviewLoadedCallback); + verify(mPreviewLoadedCallback2).onPreviewLoaded(BITMAP); + assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isEqualTo(BITMAP); + } + + @Test + public void loadPreview_thenCancelled_bothCallsCancelled_shouldCancelDelegateRequest() { + CancellationSignal cancellationSignal1 = + mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback); + CancellationSignal cancellationSignal2 = + mLoader.loadPreview( + mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback2); + + cancellationSignal1.cancel(); + cancellationSignal2.cancel(); + + verify(mCancellationSignal).cancel(); + verifyZeroInteractions(mPreviewLoadedCallback); + verifyZeroInteractions(mPreviewLoadedCallback2); + assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isNull(); + } + + @Test + public void loadPreview_multipleCallbacks_shouldOnlyCallDelegateOnce() { + mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback); + mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback2); + + verify(mDelegate).loadPreview(any(), any(), any(), any()); + } + + @Test + public void loadPreview_multipleCallbacks_shouldForwardResultToEachCallback() { + mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback); + mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback2); + + verify(mDelegate).loadPreview(any(), any(), any(), mCallbackCaptor.capture()); + WidgetPreviewLoadedCallback loaderCallback = mCallbackCaptor.getValue(); + + loaderCallback.onPreviewLoaded(BITMAP); + + verify(mPreviewLoadedCallback).onPreviewLoaded(BITMAP); + verify(mPreviewLoadedCallback2).onPreviewLoaded(BITMAP); + } + + @Test + public void loadPreview_inCache_shouldCallBackImmediately() { + loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP); + reset(mDelegate); + + mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback); + + verify(mPreviewLoadedCallback).onPreviewLoaded(BITMAP); + verifyZeroInteractions(mDelegate); + } + + @Test + public void loadPreview_thenLoaded_thenCancelled_shouldNotRemovePreviewFromCache() { + CancellationSignal cancellationSignal = + mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback); + verify(mDelegate).loadPreview(any(), any(), any(), mCallbackCaptor.capture()); + WidgetPreviewLoadedCallback loaderCallback = mCallbackCaptor.getValue(); + loaderCallback.onPreviewLoaded(BITMAP); + + cancellationSignal.cancel(); + + assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isEqualTo(BITMAP); + } + + @Test + public void isPreviewLoaded_notLoaded_shouldReturnFalse() { + assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse(); + } + + @Test + public void isPreviewLoaded_otherSizeLoaded_shouldReturnFalse() { + loadPreviewIntoCache(mWidgetItem, SIZE_20_20, BITMAP); + + assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse(); + } + + @Test + public void isPreviewLoaded_otherItemLoaded_shouldReturnFalse() { + loadPreviewIntoCache(mWidgetItem2, SIZE_10_10, BITMAP); + + assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse(); + } + + @Test + public void isPreviewLoaded_loaded_shouldReturnTrue() { + loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP); + + assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isTrue(); + } + + @Test + public void clearPreviews_notInCache_shouldBeNoOp() { + mLoader.clearPreviews(Collections.singletonList(mWidgetItem)); + + assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse(); + } + + @Test + public void clearPreviews_inCache_shouldRemovePreview() { + loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP); + + mLoader.clearPreviews(Collections.singletonList(mWidgetItem)); + + assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse(); + } + + @Test + public void clearPreviews_inCache_multipleSizes_shouldRemoveAllSizes() { + loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP); + loadPreviewIntoCache(mWidgetItem, SIZE_20_20, BITMAP); + + mLoader.clearPreviews(Collections.singletonList(mWidgetItem)); + + assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse(); + assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_20_20)).isFalse(); + } + + @Test + public void clearPreviews_inCache_otherItems_shouldOnlyRemoveSpecifiedItems() { + loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP); + loadPreviewIntoCache(mWidgetItem2, SIZE_10_10, BITMAP); + + mLoader.clearPreviews(Collections.singletonList(mWidgetItem)); + + assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse(); + assertThat(mLoader.isPreviewLoaded(mWidgetItem2, SIZE_10_10)).isTrue(); + } + + @Test + public void clearPreviews_inCache_otherItems_shouldRemoveAllSpecifiedItems() { + loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP); + loadPreviewIntoCache(mWidgetItem2, SIZE_10_10, BITMAP); + + mLoader.clearPreviews(Arrays.asList(mWidgetItem, mWidgetItem2)); + + assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse(); + assertThat(mLoader.isPreviewLoaded(mWidgetItem2, SIZE_10_10)).isFalse(); + } + + @Test + public void clearPreviews_loading_shouldCancelLoad() { + mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback); + + mLoader.clearPreviews(Collections.singletonList(mWidgetItem)); + + verify(mCancellationSignal).cancel(); + } + + @Test + public void clearAll_cacheEmpty_shouldBeNoOp() { + mLoader.clearAll(); + + assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse(); + } + + @Test + public void clearAll_inCache_shouldRemovePreview() { + loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP); + + mLoader.clearAll(); + + assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse(); + } + + @Test + public void clearAll_inCache_multipleSizes_shouldRemoveAllSizes() { + loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP); + loadPreviewIntoCache(mWidgetItem, SIZE_20_20, BITMAP); + + mLoader.clearAll(); + + assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse(); + assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_20_20)).isFalse(); + } + + @Test + public void clearAll_inCache_multipleItems_shouldRemoveAll() { + loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP); + loadPreviewIntoCache(mWidgetItem, SIZE_20_20, BITMAP); + loadPreviewIntoCache(mWidgetItem2, SIZE_20_20, BITMAP); + + mLoader.clearAll(); + + assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse(); + assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_20_20)).isFalse(); + assertThat(mLoader.isPreviewLoaded(mWidgetItem2, SIZE_20_20)).isFalse(); + } + + @Test + public void clearAll_loading_shouldCancelLoad() { + mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback); + + mLoader.clearAll(); + + verify(mCancellationSignal).cancel(); + } + + private void loadPreviewIntoCache(WidgetItem widgetItem, Size size, Bitmap bitmap) { + reset(mDelegate); + mLoader.loadPreview(mTestActivity, widgetItem, size, ignored -> {}); + verify(mDelegate).loadPreview(any(), any(), any(), mCallbackCaptor.capture()); + WidgetPreviewLoadedCallback loaderCallback = mCallbackCaptor.getValue(); + + loaderCallback.onPreviewLoaded(bitmap); + } +} diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsDiffReporterTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsDiffReporterTest.java index cc36f630c9..c946c7218f 100644 --- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsDiffReporterTest.java +++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsDiffReporterTest.java @@ -207,10 +207,9 @@ public final class WidgetsDiffReporterTest { // GIVEN the current list has app headers [A, B, E content]. ArrayList currentList = new ArrayList<>( List.of(mHeaderA, mHeaderB, mContentE)); - // GIVEN the new list has app headers [A, B, E content]. - List newList = List.of(mHeaderA, mHeaderB, mContentE); - // GIVEN the user has interacted with B. - mHeaderB.setIsWidgetListShown(true); + // GIVEN the new list has app headers [A, B, E content] and the user has interacted with B. + List newList = + List.of(mHeaderA, mHeaderB.withWidgetListShown(), mContentE); // WHEN computing the list difference. mWidgetsDiffReporter.process(currentList, newList, COMPARATOR); diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java index e1214ff39f..c730fc0d00 100644 --- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java +++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java @@ -33,13 +33,13 @@ import android.view.LayoutInflater; import androidx.recyclerview.widget.RecyclerView; import com.android.launcher3.InvariantDeviceProfile; -import com.android.launcher3.WidgetPreviewLoader; import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.icons.ComponentWithLabel; import com.android.launcher3.icons.IconCache; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.model.data.PackageItemInfo; import com.android.launcher3.util.PackageUserKey; +import com.android.launcher3.widget.DatabaseWidgetPreviewLoader; import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; import com.android.launcher3.widget.model.WidgetsListBaseEntry; import com.android.launcher3.widget.model.WidgetsListContentEntry; @@ -64,7 +64,7 @@ public final class WidgetsListAdapterTest { private static final String TEST_PACKAGE_PLACEHOLDER = "com.google.test"; @Mock private LayoutInflater mMockLayoutInflater; - @Mock private WidgetPreviewLoader mMockWidgetCache; + @Mock private DatabaseWidgetPreviewLoader mMockWidgetCache; @Mock private RecyclerView.AdapterDataObserver mListener; @Mock private IconCache mIconCache; diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java index 4e2a508394..81b0c5f89c 100644 --- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java +++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java @@ -34,7 +34,6 @@ import android.widget.TextView; import com.android.launcher3.DeviceProfile; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.R; -import com.android.launcher3.WidgetPreviewLoader; import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.icons.ComponentWithLabel; import com.android.launcher3.icons.IconCache; @@ -42,6 +41,7 @@ import com.android.launcher3.model.WidgetItem; import com.android.launcher3.model.data.PackageItemInfo; import com.android.launcher3.testing.TestActivity; import com.android.launcher3.util.PackageUserKey; +import com.android.launcher3.widget.DatabaseWidgetPreviewLoader; import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; import com.android.launcher3.widget.model.WidgetsListHeaderEntry; @@ -79,7 +79,7 @@ public final class WidgetsListHeaderViewHolderBinderTest { @Mock private DeviceProfile mDeviceProfile; @Mock - private WidgetPreviewLoader mWidgetPreviewLoader; + private DatabaseWidgetPreviewLoader mWidgetPreviewLoader; @Mock private OnHeaderClickListener mOnHeaderClickListener; diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java index d6aea554f2..a0ba7c3307 100644 --- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java +++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java @@ -34,7 +34,6 @@ import android.widget.TextView; import com.android.launcher3.DeviceProfile; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.R; -import com.android.launcher3.WidgetPreviewLoader; import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.icons.ComponentWithLabel; import com.android.launcher3.icons.IconCache; @@ -42,6 +41,7 @@ import com.android.launcher3.model.WidgetItem; import com.android.launcher3.model.data.PackageItemInfo; import com.android.launcher3.testing.TestActivity; import com.android.launcher3.util.PackageUserKey; +import com.android.launcher3.widget.DatabaseWidgetPreviewLoader; import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry; @@ -79,7 +79,7 @@ public final class WidgetsListSearchHeaderViewHolderBinderTest { @Mock private DeviceProfile mDeviceProfile; @Mock - private WidgetPreviewLoader mWidgetPreviewLoader; + private DatabaseWidgetPreviewLoader mWidgetPreviewLoader; @Mock private OnHeaderClickListener mOnHeaderClickListener; diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java index 2f1326fdc0..8f9d132866 100644 --- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java +++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java @@ -38,13 +38,14 @@ import android.widget.TextView; import com.android.launcher3.DeviceProfile; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.R; -import com.android.launcher3.WidgetPreviewLoader; import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.icons.ComponentWithLabel; import com.android.launcher3.icons.IconCache; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.model.data.PackageItemInfo; import com.android.launcher3.testing.TestActivity; +import com.android.launcher3.widget.CachingWidgetPreviewLoader; +import com.android.launcher3.widget.DatabaseWidgetPreviewLoader; import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; import com.android.launcher3.widget.WidgetCell; import com.android.launcher3.widget.model.WidgetsListContentEntry; @@ -85,7 +86,7 @@ public final class WidgetsListTableViewHolderBinderTest { @Mock private IconCache mIconCache; @Mock - private WidgetPreviewLoader mWidgetPreviewLoader; + private DatabaseWidgetPreviewLoader mWidgetPreviewLoader; @Mock private DeviceProfile mDeviceProfile; @@ -113,11 +114,10 @@ public final class WidgetsListTableViewHolderBinderTest { /* iconClickListener= */ view -> {}, /* iconLongClickListener= */ view -> false); mViewHolderBinder = new WidgetsListTableViewHolderBinder( - mContext, LayoutInflater.from(mTestActivity), mOnIconClickListener, mOnLongClickListener, - mWidgetPreviewLoader, + new CachingWidgetPreviewLoader(mWidgetPreviewLoader), new WidgetsListDrawableFactory(mTestActivity), widgetsListAdapter); } diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index b3d096c4b8..3d6be696bb 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -48,6 +48,7 @@ import com.android.launcher3.util.SafeCloseable; import com.android.launcher3.util.SettingsCache; import com.android.launcher3.util.SimpleBroadcastReceiver; import com.android.launcher3.util.Themes; +import com.android.launcher3.widget.DatabaseWidgetPreviewLoader; import com.android.launcher3.widget.custom.CustomWidgetManager; public class LauncherAppState { @@ -63,7 +64,7 @@ public class LauncherAppState { private final LauncherModel mModel; private final IconProvider mIconProvider; private final IconCache mIconCache; - private final WidgetPreviewLoader mWidgetCache; + private final DatabaseWidgetPreviewLoader mWidgetCache; private final InvariantDeviceProfile mInvariantDeviceProfile; private final RunnableList mOnTerminateCallback = new RunnableList(); @@ -138,7 +139,7 @@ public class LauncherAppState { mIconProvider = new IconProvider(context, Themes.isThemedIconEnabled(context)); mIconCache = new IconCache(mContext, mInvariantDeviceProfile, iconCacheFileName, mIconProvider); - mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache); + mWidgetCache = new DatabaseWidgetPreviewLoader(mContext, mIconCache); mModel = new LauncherModel(context, this, mIconCache, new AppFilter(mContext)); mOnTerminateCallback.add(mIconCache::close); } @@ -180,7 +181,7 @@ public class LauncherAppState { return mModel; } - public WidgetPreviewLoader getWidgetCache() { + public DatabaseWidgetPreviewLoader getWidgetCache() { return mWidgetCache; } diff --git a/src/com/android/launcher3/model/WidgetItem.java b/src/com/android/launcher3/model/WidgetItem.java index 97071bbaad..7198d54611 100644 --- a/src/com/android/launcher3/model/WidgetItem.java +++ b/src/com/android/launcher3/model/WidgetItem.java @@ -1,7 +1,11 @@ package com.android.launcher3.model; +import static com.android.launcher3.Utilities.ATLEAST_S; + +import android.annotation.SuppressLint; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; +import android.content.res.Resources; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.Utilities; @@ -59,4 +63,15 @@ public class WidgetItem extends ComponentKey { } return false; } + + /** Returns whether this {@link WidgetItem} has a preview layout that can be used. */ + @SuppressLint("NewApi") // Already added API check. + public boolean hasPreviewLayout() { + return ATLEAST_S && widgetInfo != null && widgetInfo.previewLayout != Resources.ID_NULL; + } + + /** Returns whether this {@link WidgetItem} is for a shortcut rather than an app widget. */ + public boolean isShortcut() { + return activityInfo != null; + } } diff --git a/src/com/android/launcher3/widget/CachingWidgetPreviewLoader.java b/src/com/android/launcher3/widget/CachingWidgetPreviewLoader.java new file mode 100644 index 0000000000..afceadd9a3 --- /dev/null +++ b/src/com/android/launcher3/widget/CachingWidgetPreviewLoader.java @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.widget; + +import android.graphics.Bitmap; +import android.os.CancellationSignal; +import android.util.Size; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.UiThread; +import androidx.collection.ArrayMap; +import androidx.collection.ArraySet; + +import com.android.launcher3.BaseActivity; +import com.android.launcher3.model.WidgetItem; +import com.android.launcher3.util.ComponentKey; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** Wrapper around {@link DatabaseWidgetPreviewLoader} that contains caching logic. */ +public class CachingWidgetPreviewLoader implements WidgetPreviewLoader { + + @NonNull private final WidgetPreviewLoader mDelegate; + @NonNull private final Map> mCache = new ArrayMap<>(); + + public CachingWidgetPreviewLoader(@NonNull WidgetPreviewLoader delegate) { + mDelegate = delegate; + } + + /** Returns whether the preview is loaded for the item and size. */ + public boolean isPreviewLoaded(@NonNull WidgetItem item, @NonNull Size previewSize) { + return getPreview(item, previewSize) != null; + } + + /** Returns the cached preview for the item and size, or null if there is none. */ + @Nullable + public Bitmap getPreview(@NonNull WidgetItem item, @NonNull Size previewSize) { + CacheResult cacheResult = getCacheResult(item, previewSize); + if (cacheResult instanceof CacheResult.Loaded) { + return ((CacheResult.Loaded) cacheResult).mBitmap; + } else { + return null; + } + } + + @NonNull + private CacheResult getCacheResult(@NonNull WidgetItem item, @NonNull Size previewSize) { + synchronized (mCache) { + Map cacheResults = mCache.get(toComponentKey(item)); + if (cacheResults == null) { + return CacheResult.MISS; + } + + return cacheResults.getOrDefault(previewSize, CacheResult.MISS); + } + } + + /** + * Puts the result in the cache for the item and size. Returns the value previously in the + * cache, or null if there was none. + */ + @Nullable + private CacheResult putCacheResult( + @NonNull WidgetItem item, + @NonNull Size previewSize, + @Nullable CacheResult cacheResult) { + ComponentKey key = toComponentKey(item); + synchronized (mCache) { + Map cacheResults = mCache.getOrDefault(key, new ArrayMap<>()); + CacheResult previous; + if (cacheResult == null) { + previous = cacheResults.remove(previewSize); + if (cacheResults.isEmpty()) { + mCache.remove(key); + } else { + previous = cacheResults.put(previewSize, cacheResult); + mCache.put(key, cacheResults); + } + } else { + previous = cacheResults.put(previewSize, cacheResult); + mCache.put(key, cacheResults); + } + return previous; + } + } + + private void removeCacheResult(@NonNull WidgetItem item, @NonNull Size previewSize) { + ComponentKey key = toComponentKey(item); + synchronized (mCache) { + Map cacheResults = mCache.getOrDefault(key, new ArrayMap<>()); + cacheResults.remove(previewSize); + mCache.put(key, cacheResults); + } + } + + /** + * Gets the preview for the widget item and size, using the value in the cache if stored. + * + * @return a {@link CancellationSignal}, which can cancel the request before it loads + */ + @Override + @UiThread + @NonNull + public CancellationSignal loadPreview( + @NonNull BaseActivity activity, @NonNull WidgetItem item, @NonNull Size previewSize, + @NonNull WidgetPreviewLoadedCallback callback) { + CancellationSignal signal = new CancellationSignal(); + signal.setOnCancelListener(() -> { + synchronized (mCache) { + CacheResult cacheResult = getCacheResult(item, previewSize); + if (!(cacheResult instanceof CacheResult.Loading)) { + // If the key isn't actively loading, then this is a no-op. Cancelling loading + // shouldn't clear the cache if we've already loaded. + return; + } + + CacheResult.Loading prev = (CacheResult.Loading) cacheResult; + CacheResult.Loading updated = prev.withoutCallback(callback); + + if (updated.mCallbacks.isEmpty()) { + // If the last callback was removed, then cancel the underlying request in the + // delegate. + prev.mCancellationSignal.cancel(); + removeCacheResult(item, previewSize); + } else { + // If there are other callbacks still active, then don't cancel the delegate's + // request, just remove this callback from the set. + putCacheResult(item, previewSize, updated); + } + } + }); + + synchronized (mCache) { + CacheResult cacheResult = getCacheResult(item, previewSize); + if (cacheResult instanceof CacheResult.Loaded) { + // If the bitmap is already present in the cache, invoke the callback immediately. + callback.onPreviewLoaded(((CacheResult.Loaded) cacheResult).mBitmap); + return signal; + } + + if (cacheResult instanceof CacheResult.Loading) { + // If we're already loading the preview for this key, then just add the callback + // to the set we'll call after it loads. + CacheResult.Loading prev = (CacheResult.Loading) cacheResult; + putCacheResult(item, previewSize, prev.withCallback(callback)); + return signal; + } + + CancellationSignal delegateCancellationSignal = + mDelegate.loadPreview( + activity, + item, + previewSize, + preview -> { + CacheResult prev; + synchronized (mCache) { + prev = putCacheResult( + item, previewSize, new CacheResult.Loaded(preview)); + } + if (prev instanceof CacheResult.Loading) { + // Notify each stored callback that the preview has loaded. + ((CacheResult.Loading) prev).mCallbacks + .forEach(c -> c.onPreviewLoaded(preview)); + } else { + // If there isn't a loading object in the cache, then we were + // notified before adding this signal to the cache. Just + // call back to the provided callback, there can't be others. + callback.onPreviewLoaded(preview); + } + }); + ArraySet callbacks = new ArraySet<>(); + callbacks.add(callback); + putCacheResult( + item, + previewSize, + new CacheResult.Loading(delegateCancellationSignal, callbacks)); + } + + return signal; + } + + /** Clears all cached previews for {@code items}, cancelling any in-progress preview loading. */ + public void clearPreviews(Iterable items) { + List previousCacheResults = new ArrayList<>(); + synchronized (mCache) { + for (WidgetItem item : items) { + Map previousMap = mCache.remove(toComponentKey(item)); + if (previousMap != null) { + previousCacheResults.addAll(previousMap.values()); + } + } + } + + for (CacheResult previousCacheResult : previousCacheResults) { + if (previousCacheResult instanceof CacheResult.Loading) { + ((CacheResult.Loading) previousCacheResult).mCancellationSignal.cancel(); + } + } + } + + /** Clears all cached previews, cancelling any in-progress preview loading. */ + public void clearAll() { + List previousCacheResults; + synchronized (mCache) { + previousCacheResults = + mCache + .values() + .stream() + .flatMap(sizeToResult -> sizeToResult.values().stream()) + .collect(Collectors.toList()); + mCache.clear(); + } + + for (CacheResult previousCacheResult : previousCacheResults) { + if (previousCacheResult instanceof CacheResult.Loading) { + ((CacheResult.Loading) previousCacheResult).mCancellationSignal.cancel(); + } + } + } + + private abstract static class CacheResult { + static final CacheResult MISS = new CacheResult() {}; + + static final class Loading extends CacheResult { + @NonNull final CancellationSignal mCancellationSignal; + @NonNull final Set mCallbacks; + + Loading(@NonNull CancellationSignal cancellationSignal, + @NonNull Set callbacks) { + mCancellationSignal = cancellationSignal; + mCallbacks = callbacks; + } + + @NonNull + Loading withCallback(@NonNull WidgetPreviewLoadedCallback callback) { + if (mCallbacks.contains(callback)) return this; + Set newCallbacks = + new ArraySet<>(mCallbacks.size() + 1); + newCallbacks.addAll(mCallbacks); + newCallbacks.add(callback); + return new Loading(mCancellationSignal, newCallbacks); + } + + @NonNull + Loading withoutCallback(@NonNull WidgetPreviewLoadedCallback callback) { + if (!mCallbacks.contains(callback)) return this; + Set newCallbacks = + new ArraySet<>(mCallbacks.size() - 1); + for (WidgetPreviewLoadedCallback existingCallback : mCallbacks) { + if (!existingCallback.equals(callback)) { + newCallbacks.add(existingCallback); + } + } + return new Loading(mCancellationSignal, newCallbacks); + } + } + + static final class Loaded extends CacheResult { + @NonNull final Bitmap mBitmap; + + Loaded(@NonNull Bitmap bitmap) { + mBitmap = bitmap; + } + } + } + + @NonNull + private static ComponentKey toComponentKey(@NonNull WidgetItem item) { + return new ComponentKey(item.componentName, item.user); + } +} diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java similarity index 86% rename from src/com/android/launcher3/WidgetPreviewLoader.java rename to src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java index ff3584acd3..6de3e11ac8 100644 --- a/src/com/android/launcher3/WidgetPreviewLoader.java +++ b/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java @@ -1,4 +1,19 @@ -package com.android.launcher3; +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.widget; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; @@ -32,8 +47,14 @@ import android.util.LongSparseArray; import android.util.Pair; import android.util.Size; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.launcher3.BaseActivity; +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.LauncherFiles; +import com.android.launcher3.R; +import com.android.launcher3.Utilities; import com.android.launcher3.icons.GraphicsUtils; import com.android.launcher3.icons.IconCache; import com.android.launcher3.icons.LauncherIcons; @@ -47,9 +68,6 @@ import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.Preconditions; import com.android.launcher3.util.SQLiteCacheHelper; import com.android.launcher3.util.Thunk; -import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; -import com.android.launcher3.widget.WidgetCell; -import com.android.launcher3.widget.WidgetManagerHelper; import com.android.launcher3.widget.util.WidgetSizes; import java.util.ArrayList; @@ -60,7 +78,8 @@ import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.ExecutionException; -public class WidgetPreviewLoader { +/** {@link WidgetPreviewLoader} that loads preview images from a {@link CacheDb}. */ +public class DatabaseWidgetPreviewLoader implements WidgetPreviewLoader { private static final String TAG = "WidgetPreviewLoader"; private static final boolean DEBUG = false; @@ -80,7 +99,7 @@ public class WidgetPreviewLoader { private final UserCache mUserCache; private final CacheDb mDb; - public WidgetPreviewLoader(Context context, IconCache iconCache) { + public DatabaseWidgetPreviewLoader(Context context, IconCache iconCache) { mContext = context; mIconCache = iconCache; mUserCache = UserCache.INSTANCE.get(context); @@ -89,16 +108,24 @@ public class WidgetPreviewLoader { /** * Generates the widget preview on {@link AsyncTask#THREAD_POOL_EXECUTOR}. Must be - * called on UI thread + * called on UI thread. * * @return a request id which can be used to cancel the request. */ - public CancellationSignal getPreview(WidgetItem item, int previewWidth, - int previewHeight, WidgetCell caller) { + @Override + @NonNull + public CancellationSignal loadPreview( + @NonNull BaseActivity activity, + @NonNull WidgetItem item, + @NonNull Size previewSize, + @NonNull WidgetPreviewLoadedCallback callback) { + int previewWidth = previewSize.getWidth(); + int previewHeight = previewSize.getHeight(); String size = previewWidth + "x" + previewHeight; WidgetCacheKey key = new WidgetCacheKey(item.componentName, item.user, size); - PreviewLoadTask task = new PreviewLoadTask(key, item, previewWidth, previewHeight, caller); + PreviewLoadTask task = + new PreviewLoadTask(activity, key, item, previewWidth, previewHeight, callback); task.executeOnExecutor(Executors.THREAD_POOL_EXECUTOR); CancellationSignal signal = new CancellationSignal(); @@ -106,6 +133,7 @@ public class WidgetPreviewLoader { return signal; } + /** Clears the database storing previews. */ public void refresh() { mDb.clear(); } @@ -126,21 +154,37 @@ public class WidgetPreviewLoader { private static final String COLUMN_VERSION = "version"; private static final String COLUMN_PREVIEW_BITMAP = "preview_bitmap"; - public CacheDb(Context context) { + CacheDb(Context context) { super(context, LauncherFiles.WIDGET_PREVIEWS_DB, DB_VERSION, TABLE_NAME); } @Override public void onCreateTable(SQLiteDatabase database) { - database.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" + - COLUMN_COMPONENT + " TEXT NOT NULL, " + - COLUMN_USER + " INTEGER NOT NULL, " + - COLUMN_SIZE + " TEXT NOT NULL, " + - COLUMN_PACKAGE + " TEXT NOT NULL, " + - COLUMN_LAST_UPDATED + " INTEGER NOT NULL DEFAULT 0, " + - COLUMN_VERSION + " INTEGER NOT NULL DEFAULT 0, " + - COLUMN_PREVIEW_BITMAP + " BLOB, " + - "PRIMARY KEY (" + COLUMN_COMPONENT + ", " + COLUMN_USER + ", " + COLUMN_SIZE + ") " + + database.execSQL("CREATE TABLE IF NOT EXISTS " + + TABLE_NAME + + " (" + + COLUMN_COMPONENT + + " TEXT NOT NULL, " + + COLUMN_USER + + " INTEGER NOT NULL, " + + COLUMN_SIZE + + " TEXT NOT NULL, " + + COLUMN_PACKAGE + + " TEXT NOT NULL, " + + COLUMN_LAST_UPDATED + + " INTEGER NOT NULL DEFAULT 0, " + + COLUMN_VERSION + + " INTEGER NOT NULL DEFAULT 0, " + + COLUMN_PREVIEW_BITMAP + + " BLOB, " + + "PRIMARY KEY (" + + COLUMN_COMPONENT + + ", " + + COLUMN_USER + + ", " + + COLUMN_SIZE + + ") " + + ");"); } } @@ -149,7 +193,7 @@ public class WidgetPreviewLoader { ContentValues values = new ContentValues(); values.put(CacheDb.COLUMN_COMPONENT, key.componentName.flattenToShortString()); values.put(CacheDb.COLUMN_USER, mUserCache.getSerialNumberForUser(key.user)); - values.put(CacheDb.COLUMN_SIZE, key.size); + values.put(CacheDb.COLUMN_SIZE, key.mSize); values.put(CacheDb.COLUMN_PACKAGE, key.componentName.getPackageName()); values.put(CacheDb.COLUMN_VERSION, versions[0]); values.put(CacheDb.COLUMN_LAST_UPDATED, versions[1]); @@ -157,12 +201,14 @@ public class WidgetPreviewLoader { mDb.insertOrReplace(values); } + /** Removes the package from the preview database. */ public void removePackage(String packageName, UserHandle user) { removePackage(packageName, user, mUserCache.getSerialNumberForUser(user)); } - private void removePackage(String packageName, UserHandle user, long userSerial) { - synchronized(mPackageVersions) { + /** Removes the package from the preview database. */ + public void removePackage(String packageName, UserHandle user, long userSerial) { + synchronized (mPackageVersions) { mPackageVersions.remove(packageName); } @@ -264,7 +310,7 @@ public class WidgetPreviewLoader { new String[]{ key.componentName.flattenToShortString(), Long.toString(mUserCache.getSerialNumberForUser(key.user)), - key.size + key.mSize }); // If cancelled, skip getting the blob and decoding it into a bitmap if (loadTask.isCancelled()) { @@ -293,7 +339,7 @@ public class WidgetPreviewLoader { } /** - * Returns generatedPreview for a widget and if the preview should be saved in persistent + * Returns a generated preview for a widget and if the preview should be saved in persistent * storage. * @param launcher * @param item @@ -344,8 +390,10 @@ public class WidgetPreviewLoader { if (drawable != null) { drawable = mutateOnMainThread(drawable); } else { - Log.w(TAG, "Can't load widget preview drawable 0x" + - Integer.toHexString(info.previewImage) + " for provider: " + info.provider); + Log.w(TAG, "Can't load widget preview drawable 0x" + + Integer.toHexString(info.previewImage) + + " for provider: " + + info.provider); } } @@ -379,8 +427,8 @@ public class WidgetPreviewLoader { scale = maxPreviewWidth / (float) (previewWidth); } if (scale != 1f) { - previewWidth = Math.max((int)(scale * previewWidth), 1); - previewHeight = Math.max((int)(scale * previewHeight), 1); + previewWidth = Math.max((int) (scale * previewWidth), 1); + previewHeight = Math.max((int) (scale * previewHeight), 1); } final Canvas c = new Canvas(); @@ -554,13 +602,13 @@ public class WidgetPreviewLoader { } } - public class PreviewLoadTask extends AsyncTask + private class PreviewLoadTask extends AsyncTask implements CancellationSignal.OnCancelListener { @Thunk final WidgetCacheKey mKey; private final WidgetItem mInfo; private final int mPreviewHeight; private final int mPreviewWidth; - private final WidgetCell mCaller; + private final WidgetPreviewLoadedCallback mCallback; private final BaseActivity mActivity; @Thunk long[] mVersions; @Thunk Bitmap mBitmapToRecycle; @@ -568,14 +616,14 @@ public class WidgetPreviewLoader { @Nullable private Bitmap mUnusedPreviewBitmap; private boolean mSaveToDB = false; - PreviewLoadTask(WidgetCacheKey key, WidgetItem info, int previewWidth, - int previewHeight, WidgetCell caller) { + PreviewLoadTask(BaseActivity activity, WidgetCacheKey key, WidgetItem info, + int previewWidth, int previewHeight, WidgetPreviewLoadedCallback callback) { + mActivity = activity; mKey = key; mInfo = info; mPreviewHeight = previewHeight; mPreviewWidth = previewWidth; - mCaller = caller; - mActivity = BaseActivity.fromContext(mCaller.getContext()); + mCallback = callback; if (DEBUG) { Log.d(TAG, String.format("%s, %s, %d, %d", mKey, mInfo, mPreviewHeight, mPreviewWidth)); @@ -593,9 +641,9 @@ public class WidgetPreviewLoader { synchronized (mUnusedBitmaps) { // Check if we can re-use a bitmap for (Bitmap candidate : mUnusedBitmaps) { - if (candidate != null && candidate.isMutable() && - candidate.getWidth() == mPreviewWidth && - candidate.getHeight() == mPreviewHeight) { + if (candidate != null && candidate.isMutable() + && candidate.getWidth() == mPreviewWidth + && candidate.getHeight() == mPreviewHeight) { unusedBitmap = candidate; mUnusedBitmaps.remove(unusedBitmap); break; @@ -638,7 +686,7 @@ public class WidgetPreviewLoader { @Override protected void onPostExecute(final Bitmap preview) { - mCaller.applyPreview(preview); + mCallback.onPreviewLoaded(preview); // Write the generated preview to the DB in the worker thread if (mVersions != null) { @@ -716,21 +764,21 @@ public class WidgetPreviewLoader { private static final class WidgetCacheKey extends ComponentKey { - @Thunk final String size; + @Thunk final String mSize; - public WidgetCacheKey(ComponentName componentName, UserHandle user, String size) { + WidgetCacheKey(ComponentName componentName, UserHandle user, String size) { super(componentName, user); - this.size = size; + this.mSize = size; } @Override public int hashCode() { - return super.hashCode() ^ size.hashCode(); + return super.hashCode() ^ mSize.hashCode(); } @Override public boolean equals(Object o) { - return super.equals(o) && ((WidgetCacheKey) o).size.equals(size); + return super.equals(o) && ((WidgetCacheKey) o).mSize.equals(mSize); } } } diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java index b1ccfd9591..91529be30b 100644 --- a/src/com/android/launcher3/widget/WidgetCell.java +++ b/src/com/android/launcher3/widget/WidgetCell.java @@ -19,7 +19,6 @@ package com.android.launcher3.widget; import static com.android.launcher3.Utilities.ATLEAST_S; import android.content.Context; -import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Rect; import android.graphics.drawable.Drawable; @@ -44,7 +43,6 @@ import com.android.launcher3.BaseActivity; import com.android.launcher3.CheckLongPressHelper; import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; -import com.android.launcher3.WidgetPreviewLoader; import com.android.launcher3.icons.FastBitmapDrawable; import com.android.launcher3.icons.RoundDrawableWrapper; import com.android.launcher3.model.WidgetItem; @@ -222,21 +220,18 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { return; } - if (ATLEAST_S - && mRemoteViewsPreview == null - && item.widgetInfo != null - && item.widgetInfo.previewLayout != Resources.ID_NULL) { - mAppWidgetHostViewPreview = new LauncherAppWidgetHostView(getContext()); - LauncherAppWidgetProviderInfo launcherAppWidgetProviderInfo = - LauncherAppWidgetProviderInfo.fromProviderInfo(getContext(), - item.widgetInfo.clone()); - // A hack to force the initial layout to be the preview layout since there is no API for - // rendering a preview layout for work profile apps yet. For non-work profile layout, a - // proper solution is to use RemoteViews(PackageName, LayoutId). - launcherAppWidgetProviderInfo.initialLayout = item.widgetInfo.previewLayout; - setAppWidgetHostViewPreview(mAppWidgetHostViewPreview, - launcherAppWidgetProviderInfo, /* remoteViews= */ null); - } + if (!item.hasPreviewLayout()) return; + + mAppWidgetHostViewPreview = new LauncherAppWidgetHostView(getContext()); + LauncherAppWidgetProviderInfo launcherAppWidgetProviderInfo = + LauncherAppWidgetProviderInfo.fromProviderInfo(getContext(), + item.widgetInfo.clone()); + // A hack to force the initial layout to be the preview layout since there is no API for + // rendering a preview layout for work profile apps yet. For non-work profile layout, a + // proper solution is to use RemoteViews(PackageName, LayoutId). + launcherAppWidgetProviderInfo.initialLayout = item.widgetInfo.previewLayout; + setAppWidgetHostViewPreview(mAppWidgetHostViewPreview, + launcherAppWidgetProviderInfo, /* remoteViews= */ null); } private void setAppWidgetHostViewPreview( @@ -344,22 +339,25 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { if (mActiveRequest != null) { return; } - mActiveRequest = mWidgetPreviewLoader.getPreview(mItem, mPreviewWidth, mPreviewHeight, - this); + mActiveRequest = mWidgetPreviewLoader.loadPreview( + BaseActivity.fromContext(getContext()), mItem, + new Size(mPreviewWidth, mPreviewHeight), + this::applyPreview); } /** Sets the widget preview image size in number of cells. */ - public void setPreviewSize(int spanX, int spanY) { - setPreviewSize(spanX, spanY, 1f); + public Size setPreviewSize(int spanX, int spanY) { + return setPreviewSize(spanX, spanY, 1f); } /** Sets the widget preview image size, in number of cells, and preview scale. */ - public void setPreviewSize(int spanX, int spanY, float previewScale) { + public Size setPreviewSize(int spanX, int spanY, float previewScale) { DeviceProfile deviceProfile = mActivity.getDeviceProfile(); Size widgetSize = WidgetSizes.getWidgetSizePx(deviceProfile, spanX, spanY); mPreviewWidth = widgetSize.getWidth(); mPreviewHeight = widgetSize.getHeight(); mPreviewScale = previewScale; + return widgetSize; } @Override diff --git a/src/com/android/launcher3/widget/WidgetPreviewLoader.java b/src/com/android/launcher3/widget/WidgetPreviewLoader.java new file mode 100644 index 0000000000..ff5c82f872 --- /dev/null +++ b/src/com/android/launcher3/widget/WidgetPreviewLoader.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.widget; + +import android.graphics.Bitmap; +import android.os.CancellationSignal; +import android.util.Size; + +import androidx.annotation.NonNull; +import androidx.annotation.UiThread; + +import com.android.launcher3.BaseActivity; +import com.android.launcher3.model.WidgetItem; + +/** Asynchronous loader of preview bitmaps for {@link WidgetItem}s. */ +public interface WidgetPreviewLoader { + /** + * Loads a widget preview and calls back to {@code callback} when complete. + * + * @return a {@link CancellationSignal} which can be used to cancel the request. + */ + @NonNull + @UiThread + CancellationSignal loadPreview( + @NonNull BaseActivity activity, + @NonNull WidgetItem item, + @NonNull Size previewSize, + @NonNull WidgetPreviewLoadedCallback callback); + + /** Callback class for requests to {@link WidgetPreviewLoader}. */ + interface WidgetPreviewLoadedCallback { + void onPreviewLoaded(@NonNull Bitmap preview); + } +} diff --git a/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java b/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java index 73bae6f484..abc79ff966 100644 --- a/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java +++ b/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java @@ -59,6 +59,19 @@ public abstract class WidgetsListBaseEntry { @Rank public abstract int getRank(); + /** + * Marker interface for subclasses that are headers for widget list items. + * + * @param The type of this class. + */ + public interface Header> { + /** Returns whether the widget list is currently expanded. */ + boolean isWidgetListShown(); + + /** Returns a copy of the item with the widget list shown. */ + T withWidgetListShown(); + } + @Retention(SOURCE) @IntDef({RANK_WIDGETS_LIST_HEADER, RANK_WIDGETS_LIST_SEARCH_HEADER, RANK_WIDGETS_LIST_CONTENT}) public @interface Rank { diff --git a/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java b/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java index 1fdc39959a..5b3ea94a2d 100644 --- a/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java +++ b/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java @@ -21,41 +21,33 @@ import com.android.launcher3.model.data.PackageItemInfo; import java.util.List; /** An information holder for an app which has widgets or/and shortcuts. */ -public final class WidgetsListHeaderEntry extends WidgetsListBaseEntry { +public final class WidgetsListHeaderEntry extends WidgetsListBaseEntry + implements WidgetsListBaseEntry.Header { public final int widgetsCount; public final int shortcutsCount; - private boolean mIsWidgetListShown = false; - private boolean mHasEntryUpdated = false; + private final boolean mIsWidgetListShown; public WidgetsListHeaderEntry(PackageItemInfo pkgItem, String titleSectionName, List items) { + this(pkgItem, titleSectionName, items, /* isWidgetListShown= */ false); + } + + private WidgetsListHeaderEntry(PackageItemInfo pkgItem, String titleSectionName, + List items, boolean isWidgetListShown) { super(pkgItem, titleSectionName, items); widgetsCount = (int) items.stream().filter(item -> item.widgetInfo != null).count(); shortcutsCount = Math.max(0, items.size() - widgetsCount); - } - - /** Sets if the widgets list associated with this header is shown. */ - public void setIsWidgetListShown(boolean isWidgetListShown) { - if (mIsWidgetListShown != isWidgetListShown) { - this.mIsWidgetListShown = isWidgetListShown; - mHasEntryUpdated = true; - } else { - mHasEntryUpdated = false; - } + mIsWidgetListShown = isWidgetListShown; } /** Returns {@code true} if the widgets list associated with this header is shown. */ + @Override public boolean isWidgetListShown() { return mIsWidgetListShown; } - /** Returns {@code true} if this entry has been updated due to user interactions. */ - public boolean hasEntryUpdated() { - return mHasEntryUpdated; - } - @Override public String toString() { return "Header:" + mPkgItem.packageName + ":" + mWidgets.size(); @@ -72,6 +64,18 @@ public final class WidgetsListHeaderEntry extends WidgetsListBaseEntry { if (!(obj instanceof WidgetsListHeaderEntry)) return false; WidgetsListHeaderEntry otherEntry = (WidgetsListHeaderEntry) obj; return mWidgets.equals(otherEntry.mWidgets) && mPkgItem.equals(otherEntry.mPkgItem) - && mTitleSectionName.equals(otherEntry.mTitleSectionName); + && mTitleSectionName.equals(otherEntry.mTitleSectionName) + && mIsWidgetListShown == otherEntry.mIsWidgetListShown; + } + + /** Returns a copy of this {@link WidgetsListHeaderEntry} with the widget list shown. */ + @Override + public WidgetsListHeaderEntry withWidgetListShown() { + if (mIsWidgetListShown) return this; + return new WidgetsListHeaderEntry( + mPkgItem, + mTitleSectionName, + mWidgets, + /* isWidgetListShown= */ true); } } diff --git a/src/com/android/launcher3/widget/model/WidgetsListSearchHeaderEntry.java b/src/com/android/launcher3/widget/model/WidgetsListSearchHeaderEntry.java index 2aec3f8fd9..c0f89bcbcc 100644 --- a/src/com/android/launcher3/widget/model/WidgetsListSearchHeaderEntry.java +++ b/src/com/android/launcher3/widget/model/WidgetsListSearchHeaderEntry.java @@ -21,36 +21,28 @@ import com.android.launcher3.model.data.PackageItemInfo; import java.util.List; /** An information holder for an app which has widgets or/and shortcuts, to be shown in search. */ -public final class WidgetsListSearchHeaderEntry extends WidgetsListBaseEntry { +public final class WidgetsListSearchHeaderEntry extends WidgetsListBaseEntry + implements WidgetsListBaseEntry.Header { - private boolean mIsWidgetListShown = false; - private boolean mHasEntryUpdated = false; + private final boolean mIsWidgetListShown; public WidgetsListSearchHeaderEntry(PackageItemInfo pkgItem, String titleSectionName, List items) { - super(pkgItem, titleSectionName, items); + this(pkgItem, titleSectionName, items, /* isWidgetListShown= */ false); } - /** Sets if the widgets list associated with this header is shown. */ - public void setIsWidgetListShown(boolean isWidgetListShown) { - if (mIsWidgetListShown != isWidgetListShown) { - this.mIsWidgetListShown = isWidgetListShown; - mHasEntryUpdated = true; - } else { - mHasEntryUpdated = false; - } + private WidgetsListSearchHeaderEntry(PackageItemInfo pkgItem, String titleSectionName, + List items, boolean isWidgetListShown) { + super(pkgItem, titleSectionName, items); + mIsWidgetListShown = isWidgetListShown; } /** Returns {@code true} if the widgets list associated with this header is shown. */ + @Override public boolean isWidgetListShown() { return mIsWidgetListShown; } - /** Returns {@code true} if this entry has been updated due to user interactions. */ - public boolean hasEntryUpdated() { - return mHasEntryUpdated; - } - @Override public String toString() { return "SearchHeader:" + mPkgItem.packageName + ":" + mWidgets.size(); @@ -67,6 +59,18 @@ public final class WidgetsListSearchHeaderEntry extends WidgetsListBaseEntry { if (!(obj instanceof WidgetsListSearchHeaderEntry)) return false; WidgetsListSearchHeaderEntry otherEntry = (WidgetsListSearchHeaderEntry) obj; return mWidgets.equals(otherEntry.mWidgets) && mPkgItem.equals(otherEntry.mPkgItem) - && mTitleSectionName.equals(otherEntry.mTitleSectionName); + && mTitleSectionName.equals(otherEntry.mTitleSectionName) + && mIsWidgetListShown; + } + + /** Returns a copy of this {@link WidgetsListSearchHeaderEntry} with the widget list shown. */ + @Override + public WidgetsListSearchHeaderEntry withWidgetListShown() { + if (mIsWidgetListShown) return this; + return new WidgetsListSearchHeaderEntry( + mPkgItem, + mTitleSectionName, + mWidgets, + /* isWidgetListShown= */ true); } } diff --git a/src/com/android/launcher3/widget/picker/WidgetsDiffReporter.java b/src/com/android/launcher3/widget/picker/WidgetsDiffReporter.java index 42896ba350..dfe447a38e 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsDiffReporter.java +++ b/src/com/android/launcher3/widget/picker/WidgetsDiffReporter.java @@ -177,7 +177,7 @@ public class WidgetsDiffReporter { */ private boolean hasHeaderUpdated(WidgetsListBaseEntry curRow, WidgetsListBaseEntry newRow) { if (newRow instanceof WidgetsListHeaderEntry && curRow instanceof WidgetsListHeaderEntry) { - return ((WidgetsListHeaderEntry) newRow).hasEntryUpdated() || !curRow.equals(newRow); + return !curRow.equals(newRow); } if (newRow instanceof WidgetsListSearchHeaderEntry && curRow instanceof WidgetsListSearchHeaderEntry) { diff --git a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java index 6863c60a37..5d9adf0e71 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java +++ b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java @@ -20,6 +20,7 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH import android.content.Context; import android.os.Process; import android.util.Log; +import android.util.Size; import android.util.SparseArray; import android.view.LayoutInflater; import android.view.View; @@ -35,19 +36,25 @@ import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView.Adapter; import androidx.recyclerview.widget.RecyclerView.ViewHolder; +import com.android.launcher3.BaseActivity; +import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.R; -import com.android.launcher3.WidgetPreviewLoader; import com.android.launcher3.icons.IconCache; +import com.android.launcher3.model.WidgetItem; import com.android.launcher3.model.data.PackageItemInfo; import com.android.launcher3.recyclerview.ViewHolderBinder; import com.android.launcher3.util.LabelComparator; import com.android.launcher3.util.PackageUserKey; +import com.android.launcher3.widget.CachingWidgetPreviewLoader; +import com.android.launcher3.widget.DatabaseWidgetPreviewLoader; import com.android.launcher3.widget.WidgetCell; +import com.android.launcher3.widget.WidgetPreviewLoader.WidgetPreviewLoadedCallback; import com.android.launcher3.widget.model.WidgetsListBaseEntry; import com.android.launcher3.widget.model.WidgetsListContentEntry; import com.android.launcher3.widget.model.WidgetsListHeaderEntry; import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry; +import com.android.launcher3.widget.util.WidgetSizes; import java.util.ArrayList; import java.util.Arrays; @@ -79,7 +86,9 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC private static final int VIEW_TYPE_WIDGETS_HEADER = R.id.view_type_widgets_header; private static final int VIEW_TYPE_WIDGETS_SEARCH_HEADER = R.id.view_type_widgets_search_header; + private final Context mContext; private final Launcher mLauncher; + private final CachingWidgetPreviewLoader mCachingPreviewLoader; private final WidgetsDiffReporter mDiffReporter; private final SparseArray mViewHolderBinders = new SparseArray<>(); private final WidgetsListTableViewHolderBinder mWidgetsListTableViewHolderBinder; @@ -97,16 +106,23 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC .equals(mWidgetsContentVisiblePackageUserKey); @Nullable private Predicate mFilter = null; @Nullable private RecyclerView mRecyclerView; + @Nullable private PackageUserKey mPendingClickHeader; + private int mShortcutPreviewPadding; + + private final WidgetPreviewLoadedCallback mPreviewLoadedCallback = + ignored -> updateVisibleEntries(); public WidgetsListAdapter(Context context, LayoutInflater layoutInflater, - WidgetPreviewLoader widgetPreviewLoader, IconCache iconCache, + DatabaseWidgetPreviewLoader widgetPreviewLoader, IconCache iconCache, OnClickListener iconClickListener, OnLongClickListener iconLongClickListener) { + mContext = context; mLauncher = Launcher.getLauncher(context); + mCachingPreviewLoader = new CachingWidgetPreviewLoader(widgetPreviewLoader); mDiffReporter = new WidgetsDiffReporter(iconCache, this); WidgetsListDrawableFactory listDrawableFactory = new WidgetsListDrawableFactory(context); - mWidgetsListTableViewHolderBinder = new WidgetsListTableViewHolderBinder(context, + mWidgetsListTableViewHolderBinder = new WidgetsListTableViewHolderBinder( layoutInflater, iconClickListener, iconLongClickListener, - widgetPreviewLoader, listDrawableFactory, /* listAdapter= */ this); + mCachingPreviewLoader, listDrawableFactory, /* listAdapter= */ this); mViewHolderBinders.put(VIEW_TYPE_WIDGETS_LIST, mWidgetsListTableViewHolderBinder); mViewHolderBinders.put( VIEW_TYPE_WIDGETS_HEADER, @@ -122,6 +138,9 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC /* onHeaderClickListener= */ this, listDrawableFactory, /* listAdapter= */ this)); + mShortcutPreviewPadding = + 2 * context.getResources() + .getDimensionPixelSize(R.dimen.widget_preview_shortcut_padding); } @Override @@ -177,6 +196,7 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC /** Updates the widget list based on {@code tempEntries}. */ public void setWidgets(List tempEntries) { + mCachingPreviewLoader.clearAll(); mAllEntries = tempEntries.stream().sorted(mRowComparator) .collect(Collectors.toList()); if (shouldClearVisibleEntries()) { @@ -189,36 +209,110 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC public void setWidgetsOnSearch(List searchResults) { // Forget the expanded package every time widget list is refreshed in search mode. mWidgetsContentVisiblePackageUserKey = null; + cancelLoadingPreviews(); setWidgets(searchResults); } private void updateVisibleEntries() { - mAllEntries.forEach(entry -> { - if (entry instanceof WidgetsListHeaderEntry) { - ((WidgetsListHeaderEntry) entry).setIsWidgetListShown( - isHeaderForVisibleContent(entry)); - } else if (entry instanceof WidgetsListSearchHeaderEntry) { - ((WidgetsListSearchHeaderEntry) entry).setIsWidgetListShown( - isHeaderForVisibleContent(entry)); - } - }); + // If not all previews are ready, then defer this update and try again after the preview + // loads. + if (!ensureAllPreviewsReady()) return; + + // Get the current top of the header with the matching key before adjusting the visible + // entries. + OptionalInt previousPositionForPackageUserKey = + getPositionForPackageUserKey(mPendingClickHeader); + OptionalInt topForPackageUserKey = + getOffsetForPosition(previousPositionForPackageUserKey); + List newVisibleEntries = mAllEntries.stream() .filter(entry -> (mFilter == null || mFilter.test(entry)) && mHeaderAndSelectedContentFilter.test(entry)) + .map(entry -> { + // Adjust the original entries to expand headers for the selected content. + if (entry instanceof WidgetsListBaseEntry.Header + && matchesKey(entry, mWidgetsContentVisiblePackageUserKey)) { + return ((WidgetsListBaseEntry.Header) entry).withWidgetListShown(); + } + return entry; + }) .collect(Collectors.toList()); + mDiffReporter.process(mVisibleEntries, newVisibleEntries, mRowComparator); + + if (mPendingClickHeader != null) { + // Get the position for the clicked header after adjusting the visible entries. The + // position may have changed if another header had previously been expanded. + OptionalInt positionForPackageUserKey = + getPositionForPackageUserKey(mPendingClickHeader); + scrollToPositionAndMaintainOffset(positionForPackageUserKey, topForPackageUserKey); + mPendingClickHeader = null; + } } - /** Returns whether {@code entry} matches {@link #mWidgetsContentVisiblePackageUserKey}. */ - private boolean isHeaderForVisibleContent(WidgetsListBaseEntry entry) { - return isHeaderForPackageUserKey(entry, mWidgetsContentVisiblePackageUserKey); + /** + * Checks that all preview images are loaded and starts loading for those that aren't ready. + * + * @return true if all previews are ready and the data can be updated, false otherwise. + */ + private boolean ensureAllPreviewsReady() { + boolean allReady = true; + BaseActivity activity = BaseActivity.fromContext(mContext); + for (WidgetsListBaseEntry entry : mAllEntries) { + if (!(entry instanceof WidgetsListContentEntry)) continue; + + WidgetsListContentEntry contentEntry = (WidgetsListContentEntry) entry; + if (!matchesKey(entry, mWidgetsContentVisiblePackageUserKey)) { + // If the entry isn't visible, clear any loaded previews. + mCachingPreviewLoader.clearPreviews(contentEntry.mWidgets); + continue; + } + + for (int i = 0; i < entry.mWidgets.size(); i++) { + WidgetItem widgetItem = entry.mWidgets.get(i); + DeviceProfile deviceProfile = activity.getDeviceProfile(); + Size widgetSize = + WidgetSizes.getWidgetSizePx( + deviceProfile, + widgetItem.spanX, + widgetItem.spanY); + if (widgetItem.isShortcut()) { + widgetSize = + new Size( + widgetSize.getWidth() + mShortcutPreviewPadding, + widgetSize.getHeight() + mShortcutPreviewPadding); + } + + if (widgetItem.hasPreviewLayout() + || mCachingPreviewLoader.isPreviewLoaded(widgetItem, widgetSize)) { + // The widget is ready if it can be rendered with a preview layout or if its + // preview bitmap is in the cache. + continue; + } + + // If we've reached this point, we should load the preview for the widget. + allReady = false; + mCachingPreviewLoader.loadPreview( + activity, + widgetItem, + widgetSize, + mPreviewLoadedCallback); + } + } + return allReady; } /** Returns whether {@code entry} matches {@code key}. */ - private boolean isHeaderForPackageUserKey(WidgetsListBaseEntry entry, PackageUserKey key) { - return (entry instanceof WidgetsListHeaderEntry - || entry instanceof WidgetsListSearchHeaderEntry) - && new PackageUserKey(entry.mPkgItem.packageName, entry.mPkgItem.user).equals(key); + private static boolean isHeaderForPackageUserKey( + @NonNull WidgetsListBaseEntry entry, @Nullable PackageUserKey key) { + return entry instanceof WidgetsListBaseEntry.Header && matchesKey(entry, key); + } + + private static boolean matchesKey( + @NonNull WidgetsListBaseEntry entry, @Nullable PackageUserKey key) { + if (key == null) return false; + return entry.mPkgItem.packageName.equals(key.mPackageName) + && entry.mPkgItem.user.equals(key.mUser); } /** @@ -227,6 +321,7 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC public void resetExpandedHeader() { if (mWidgetsContentVisiblePackageUserKey != null) { mWidgetsContentVisiblePackageUserKey = null; + cancelLoadingPreviews(); updateVisibleEntries(); } } @@ -285,6 +380,8 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC // Ignore invalid clicks, such as collapsing a package that isn't currently expanded. if (!showWidgets && !packageUserKey.equals(mWidgetsContentVisiblePackageUserKey)) return; + cancelLoadingPreviews(); + if (showWidgets) { mWidgetsContentVisiblePackageUserKey = packageUserKey; mLauncher.getStatsLogManager().logger().log(LAUNCHER_WIDGETSTRAY_APP_EXPANDED); @@ -292,17 +389,15 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC mWidgetsContentVisiblePackageUserKey = null; } - // Get the current top of the header with the matching key before adjusting the visible - // entries. - OptionalInt topForPackageUserKey = - getOffsetForPosition(getPositionForPackageUserKey(packageUserKey)); + // Store the header that was clicked so that its position will be maintained the next time + // we update the entries. + mPendingClickHeader = packageUserKey; updateVisibleEntries(); + } - // Get the position for the clicked header after adjusting the visible entries. The - // position may have changed if another header had previously been expanded. - OptionalInt positionForPackageUserKey = getPositionForPackageUserKey(packageUserKey); - scrollToPositionAndMaintainOffset(positionForPackageUserKey, topForPackageUserKey); + private void cancelLoadingPreviews() { + mCachingPreviewLoader.clearAll(); } /** Returns the position of the currently expanded header, or empty if it's not present. */ @@ -315,7 +410,8 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC * Returns the position of {@code key} in {@link #mVisibleEntries}, or empty if it's not * present. */ - private OptionalInt getPositionForPackageUserKey(PackageUserKey key) { + @NonNull + private OptionalInt getPositionForPackageUserKey(@Nullable PackageUserKey key) { return IntStream.range(0, mVisibleEntries.size()) .filter(index -> isHeaderForPackageUserKey(mVisibleEntries.get(index), key)) .findFirst(); diff --git a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java index 7e8c55bfe6..7b526639f5 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java +++ b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java @@ -18,8 +18,9 @@ package com.android.launcher3.widget.picker; import static com.android.launcher3.widget.picker.WidgetsListDrawableState.LAST; import static com.android.launcher3.widget.picker.WidgetsListDrawableState.MIDDLE; -import android.content.Context; +import android.graphics.Bitmap; import android.util.Log; +import android.util.Size; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; @@ -30,9 +31,9 @@ import android.widget.TableLayout; import android.widget.TableRow; import com.android.launcher3.R; -import com.android.launcher3.WidgetPreviewLoader; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.recyclerview.ViewHolderBinder; +import com.android.launcher3.widget.CachingWidgetPreviewLoader; import com.android.launcher3.widget.WidgetCell; import com.android.launcher3.widget.model.WidgetsListContentEntry; import com.android.launcher3.widget.util.WidgetsTableUtils; @@ -52,17 +53,16 @@ public final class WidgetsListTableViewHolderBinder private final LayoutInflater mLayoutInflater; private final OnClickListener mIconClickListener; private final OnLongClickListener mIconLongClickListener; - private final WidgetPreviewLoader mWidgetPreviewLoader; private final WidgetsListDrawableFactory mListDrawableFactory; + private final CachingWidgetPreviewLoader mWidgetPreviewLoader; private final WidgetsListAdapter mWidgetsListAdapter; private boolean mApplyBitmapDeferred = false; public WidgetsListTableViewHolderBinder( - Context context, LayoutInflater layoutInflater, OnClickListener iconClickListener, OnLongClickListener iconLongClickListener, - WidgetPreviewLoader widgetPreviewLoader, + CachingWidgetPreviewLoader widgetPreviewLoader, WidgetsListDrawableFactory listDrawableFactory, WidgetsListAdapter listAdapter) { mLayoutInflater = layoutInflater; @@ -75,7 +75,7 @@ public final class WidgetsListTableViewHolderBinder /** * Defers applying bitmap on all the {@link WidgetCell} at - * {@link #bindViewHolder(WidgetsRowViewHolder, WidgetsListContentEntry)} if + * {@link #bindViewHolder(WidgetsRowViewHolder, WidgetsListContentEntry, int)} if * {@code applyBitmapDeferred} is {@code true}. */ public void setApplyBitmapDeferred(boolean applyBitmapDeferred) { @@ -124,10 +124,15 @@ public final class WidgetsListTableViewHolderBinder WidgetCell widget = (WidgetCell) row.getChildAt(j); widget.clear(); WidgetItem widgetItem = widgetItemsPerRow.get(j); - widget.setPreviewSize(widgetItem.spanX, widgetItem.spanY); + Size previewSize = widget.setPreviewSize(widgetItem.spanX, widgetItem.spanY); widget.applyFromCellItem(widgetItem, mWidgetPreviewLoader); widget.setApplyBitmapDeferred(mApplyBitmapDeferred); - widget.ensurePreview(); + Bitmap preview = mWidgetPreviewLoader.getPreview(widgetItem, previewSize); + if (preview == null) { + widget.ensurePreview(); + } else { + widget.applyPreview(preview); + } widget.setVisibility(View.VISIBLE); } } From c79d577f18cdae7445c1e5d8c10d5fb3bf0db78d Mon Sep 17 00:00:00 2001 From: Brian Isganitis Date: Thu, 17 Jun 2021 19:56:30 -0400 Subject: [PATCH 12/13] Load widgets in wallpaper app launcher preview Test: Widgets in wallpaper app launcher preview rendered Bug: 185306338 Change-Id: I38569d2ff0b64ba55eb188afa42fba4100aae7ff --- .../graphics/LauncherPreviewRenderer.java | 57 +++++++-- .../widget/BaseLauncherAppWidgetHostView.java | 119 ++++++++++++++++++ .../widget/LauncherAppWidgetHostView.java | 84 +------------ 3 files changed, 170 insertions(+), 90 deletions(-) create mode 100644 src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java index c6add3190e..a27d5c8e06 100644 --- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java +++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java @@ -26,6 +26,7 @@ import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially import android.annotation.TargetApi; import android.app.Fragment; +import android.appwidget.AppWidgetHost; import android.appwidget.AppWidgetHostView; import android.appwidget.AppWidgetProviderInfo; import android.content.Context; @@ -83,6 +84,8 @@ import com.android.launcher3.util.IntSet; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.views.ActivityContext; import com.android.launcher3.views.BaseDragLayer; +import com.android.launcher3.widget.BaseLauncherAppWidgetHostView; +import com.android.launcher3.widget.LauncherAppWidgetHost; import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; import com.android.launcher3.widget.NavigableAppWidgetHostView; import com.android.launcher3.widget.custom.CustomWidgetManager; @@ -202,6 +205,7 @@ public class LauncherPreviewRenderer extends ContextWrapper private final InsettableFrameLayout mRootView; private final Hotseat mHotseat; private final CellLayout mWorkspace; + private final AppWidgetHost mAppWidgetHost; public LauncherPreviewRenderer(Context context, InvariantDeviceProfile idp) { super(context); @@ -255,6 +259,10 @@ public class LauncherPreviewRenderer extends ContextWrapper mDp.workspacePadding.top, mDp.workspacePadding.right + mDp.cellLayoutPaddingLeftRightPx, mDp.workspacePadding.bottom); + + mAppWidgetHost = FeatureFlags.WIDGETS_IN_LAUNCHER_PREVIEW.get() + ? new LauncherPreviewAppWidgetHost(context) + : null; } /** Populate preview and render it. */ @@ -354,14 +362,20 @@ public class LauncherPreviewRenderer extends ContextWrapper private void inflateAndAddWidgets( LauncherAppWidgetInfo info, LauncherAppWidgetProviderInfo providerInfo) { - AppWidgetHostView view = new NavigableAppWidgetHostView(this) { - @Override - protected boolean shouldAllowDirectClick() { - return false; - } - }; - view.setAppWidget(-1, providerInfo); - view.updateAppWidget(null); + AppWidgetHostView view; + if (FeatureFlags.WIDGETS_IN_LAUNCHER_PREVIEW.get()) { + view = mAppWidgetHost.createView(mContext, info.appWidgetId, providerInfo); + } else { + view = new NavigableAppWidgetHostView(this) { + @Override + protected boolean shouldAllowDirectClick() { + return false; + } + }; + view.setAppWidget(-1, providerInfo); + view.updateAppWidget(null); + } + view.setTag(info); addInScreenFromBind(view, info); } @@ -477,4 +491,31 @@ public class LauncherPreviewRenderer extends ContextWrapper view.measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY)); view.layout(0, 0, width, height); } + + private class LauncherPreviewAppWidgetHost extends AppWidgetHost { + + private LauncherPreviewAppWidgetHost(Context context) { + super(context, LauncherAppWidgetHost.APPWIDGET_HOST_ID); + } + + @Override + protected AppWidgetHostView onCreateView( + Context context, + int appWidgetId, + AppWidgetProviderInfo appWidget) { + return new LauncherPreviewAppWidgetHostView(LauncherPreviewRenderer.this); + } + } + + private static class LauncherPreviewAppWidgetHostView extends BaseLauncherAppWidgetHostView { + + private LauncherPreviewAppWidgetHostView(Context context) { + super(context); + } + + @Override + protected boolean shouldAllowDirectClick() { + return false; + } + } } diff --git a/src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java new file mode 100644 index 0000000000..2742882b1f --- /dev/null +++ b/src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.widget; + +import android.content.Context; +import android.graphics.Outline; +import android.graphics.Rect; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewOutlineProvider; +import android.widget.RemoteViews; + +import androidx.annotation.UiThread; + +import com.android.launcher3.R; +import com.android.launcher3.util.Executors; + +/** + * Launcher AppWidgetHostView with support for rounded corners and a fallback View. + */ +public abstract class BaseLauncherAppWidgetHostView extends NavigableAppWidgetHostView { + + protected final LayoutInflater mInflater; + + private final Rect mEnforcedRectangle = new Rect(); + private final float mEnforcedCornerRadius; + private final ViewOutlineProvider mCornerRadiusEnforcementOutline = new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + if (mEnforcedRectangle.isEmpty() || mEnforcedCornerRadius <= 0) { + outline.setEmpty(); + } else { + outline.setRoundRect(mEnforcedRectangle, mEnforcedCornerRadius); + } + } + }; + + public BaseLauncherAppWidgetHostView(Context context) { + super(context); + + setExecutor(Executors.THREAD_POOL_EXECUTOR); + + mInflater = LayoutInflater.from(context); + mEnforcedCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(getContext()); + } + + @Override + protected View getErrorView() { + return mInflater.inflate(R.layout.appwidget_error, this, false); + } + + /** + * Fall back to error layout instead of showing widget. + */ + public void switchToErrorView() { + // Update the widget with 0 Layout id, to reset the view to error view. + updateAppWidget(new RemoteViews(getAppWidgetInfo().provider.getPackageName(), 0)); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + try { + super.onLayout(changed, left, top, right, bottom); + } catch (final RuntimeException e) { + post(this::switchToErrorView); + } + + enforceRoundedCorners(); + } + + @UiThread + private void resetRoundedCorners() { + setOutlineProvider(ViewOutlineProvider.BACKGROUND); + setClipToOutline(false); + } + + @UiThread + private void enforceRoundedCorners() { + if (mEnforcedCornerRadius <= 0 || !RoundedCornerEnforcement.isRoundedCornerEnabled()) { + resetRoundedCorners(); + return; + } + View background = RoundedCornerEnforcement.findBackground(this); + if (background == null + || RoundedCornerEnforcement.hasAppWidgetOptedOut(this, background)) { + resetRoundedCorners(); + return; + } + RoundedCornerEnforcement.computeRoundedRectangle(this, + background, + mEnforcedRectangle); + setOutlineProvider(mCornerRadiusEnforcementOutline); + setClipToOutline(true); + } + + /** Returns the corner radius currently enforced, in pixels. */ + public float getEnforcedCornerRadius() { + return mEnforcedCornerRadius; + } + + /** Returns true if the corner radius are enforced for this App Widget. */ + public boolean hasEnforcedCornerRadius() { + return getClipToOutline(); + } +} diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java index 70ed02f006..fa50dfb667 100644 --- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java +++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java @@ -20,19 +20,16 @@ import android.appwidget.AppWidgetProviderInfo; import android.content.Context; import android.content.res.Configuration; import android.graphics.Canvas; -import android.graphics.Outline; import android.graphics.Rect; import android.graphics.RectF; import android.os.Handler; import android.os.SystemClock; import android.util.SparseBooleanArray; import android.util.SparseIntArray; -import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; -import android.view.ViewOutlineProvider; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.AdapterView; import android.widget.Advanceable; @@ -40,7 +37,6 @@ import android.widget.RemoteViews; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.UiThread; import com.android.launcher3.CheckLongPressHelper; import com.android.launcher3.Launcher; @@ -51,7 +47,6 @@ import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.keyboard.ViewGroupFocusHelper; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.LauncherAppWidgetInfo; -import com.android.launcher3.util.Executors; import com.android.launcher3.util.Themes; import com.android.launcher3.views.BaseDragLayer.TouchCompleteListener; import com.android.launcher3.widget.dragndrop.AppWidgetHostViewDragListener; @@ -61,7 +56,7 @@ import java.util.List; /** * {@inheritDoc} */ -public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView +public class LauncherAppWidgetHostView extends BaseLauncherAppWidgetHostView implements TouchCompleteListener, View.OnLongClickListener, LocalColorExtractor.Listener { @@ -76,8 +71,6 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView // Maximum duration for which updates can be deferred. private static final long UPDATE_LOCK_TIMEOUT_MILLIS = 1000; - protected final LayoutInflater mInflater; - private final CheckLongPressHelper mLongPressHelper; protected final Launcher mLauncher; private final Workspace mWorkspace; @@ -101,18 +94,6 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView private final Rect mWidgetSizeAtDrag = new Rect(); private final RectF mTempRectF = new RectF(); - private final Rect mEnforcedRectangle = new Rect(); - private final float mEnforcedCornerRadius; - private final ViewOutlineProvider mCornerRadiusEnforcementOutline = new ViewOutlineProvider() { - @Override - public void getOutline(View view, Outline outline) { - if (mEnforcedRectangle.isEmpty() || mEnforcedCornerRadius <= 0) { - outline.setEmpty(); - } else { - outline.setRoundRect(mEnforcedRectangle, mEnforcedCornerRadius); - } - } - }; private final Object mUpdateLock = new Object(); private final ViewGroupFocusHelper mDragLayerRelativeCoordinateHelper; private long mDeferUpdatesUntilMillis = 0; @@ -123,18 +104,15 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView mLauncher = Launcher.getLauncher(context); mWorkspace = mLauncher.getWorkspace(); mLongPressHelper = new CheckLongPressHelper(this, this); - mInflater = LayoutInflater.from(context); setAccessibilityDelegate(mLauncher.getAccessibilityDelegate()); setBackgroundResource(R.drawable.widget_internal_focus_bg); - setExecutor(Executors.THREAD_POOL_EXECUTOR); if (Utilities.ATLEAST_Q && Themes.getAttrBoolean(mLauncher, R.attr.isWorkspaceDarkText)) { setOnLightBackground(true); } mColorExtractor = LocalColorExtractor.newInstance(getContext()); mColorExtractor.setListener(this); - mEnforcedCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(getContext()); mDragLayerRelativeCoordinateHelper = new ViewGroupFocusHelper(mLauncher.getDragLayer()); } @@ -165,11 +143,6 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView return true; } - @Override - protected View getErrorView() { - return mInflater.inflate(R.layout.appwidget_error, this, false); - } - @Override public void updateAppWidget(RemoteViews remoteViews) { synchronized (mUpdateLock) { @@ -304,34 +277,17 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView } } - public void switchToErrorView() { - // Update the widget with 0 Layout id, to reset the view to error view. - updateAppWidget(new RemoteViews(getAppWidgetInfo().provider.getPackageName(), 0)); - } - @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - try { - super.onLayout(changed, left, top, right, bottom); - } catch (final RuntimeException e) { - post(new Runnable() { - @Override - public void run() { - switchToErrorView(); - } - }); - } + super.onLayout(changed, left, top, right, bottom); mIsScrollable = checkScrollableRecursively(this); if (!mIsInDragMode && getTag() instanceof LauncherAppWidgetInfo) { - LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) getTag(); mDragLayerRelativeCoordinateHelper.viewToRect(this, mCurrentWidgetSize); updateColorExtraction(mCurrentWidgetSize, mWorkspace.getPageIndexForScreenId(info.screenId)); } - - enforceRoundedCorners(); } /** Starts the drag mode. */ @@ -502,40 +458,4 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView } return false; } - - @UiThread - private void resetRoundedCorners() { - setOutlineProvider(ViewOutlineProvider.BACKGROUND); - setClipToOutline(false); - } - - @UiThread - private void enforceRoundedCorners() { - if (mEnforcedCornerRadius <= 0 || !RoundedCornerEnforcement.isRoundedCornerEnabled()) { - resetRoundedCorners(); - return; - } - View background = RoundedCornerEnforcement.findBackground(this); - if (background == null - || RoundedCornerEnforcement.hasAppWidgetOptedOut(this, background)) { - resetRoundedCorners(); - return; - } - RoundedCornerEnforcement.computeRoundedRectangle(this, - background, - mEnforcedRectangle); - setOutlineProvider(mCornerRadiusEnforcementOutline); - setClipToOutline(true); - } - - /** Returns the corner radius currently enforced, in pixels. */ - public float getEnforcedCornerRadius() { - return mEnforcedCornerRadius; - } - - /** Returns true if the corner radius are enforced for this App Widget. */ - public boolean hasEnforcedCornerRadius() { - return getClipToOutline(); - } - } From f13ddbd333d7b2aabcd45e4e708b8479e99d3cc9 Mon Sep 17 00:00:00 2001 From: Jon Miranda Date: Thu, 17 Jun 2021 15:40:04 -0700 Subject: [PATCH 13/13] Fix bug where ripple extends out of bounds in pop up container. Bug: 186820908 Test: test apps with diff. pop up container arrangements test prefs in all apps Change-Id: I4d3e26871416835ab2a851fcc6fe2c4f70235b62 --- .../android/launcher3/popup/ArrowPopup.java | 4 +- .../launcher3/shortcuts/DeepShortcutView.java | 51 ++++++++++++++++++- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java index 2095a0de54..cb35f74135 100644 --- a/src/com/android/launcher3/popup/ArrowPopup.java +++ b/src/com/android/launcher3/popup/ArrowPopup.java @@ -252,9 +252,9 @@ public abstract class ArrowPopup> view.setBackgroundResource(R.drawable.single_item_primary); } else if (totalVisibleShortcuts > 1) { if (numVisibleShortcut == 0) { - view.setBackground(mRoundedTop); + view.setBackground(mRoundedTop.getConstantState().newDrawable()); } else if (numVisibleShortcut == (totalVisibleShortcuts - 1)) { - view.setBackground(mRoundedBottom); + view.setBackground(mRoundedBottom.getConstantState().newDrawable()); } else { view.setBackgroundResource(R.drawable.middle_item_primary); } diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutView.java b/src/com/android/launcher3/shortcuts/DeepShortcutView.java index cc658c9ba0..71d288c0ab 100644 --- a/src/com/android/launcher3/shortcuts/DeepShortcutView.java +++ b/src/com/android/launcher3/shortcuts/DeepShortcutView.java @@ -18,7 +18,13 @@ package com.android.launcher3.shortcuts; import android.content.Context; import android.content.pm.ShortcutInfo; +import android.content.res.ColorStateList; +import android.graphics.Color; import android.graphics.Point; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.RippleDrawable; import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; @@ -30,16 +36,20 @@ import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.popup.PopupContainerWithArrow; +import com.android.launcher3.util.Themes; import com.android.launcher3.views.BubbleTextHolder; /** - * A {@link android.widget.FrameLayout} that contains a {@link DeepShortcutView}. - * This lets us animate the DeepShortcutView (icon and text) separately from the background. + * A {@link android.widget.FrameLayout} that contains an icon and a {@link BubbleTextView} for text. + * This lets us animate the child BubbleTextView's background (transparent ripple) separately from + * the {@link DeepShortcutView} background color. */ public class DeepShortcutView extends FrameLayout implements BubbleTextHolder { private static final Point sTempPoint = new Point(); + private final Drawable mTransparentDrawable = new ColorDrawable(Color.TRANSPARENT); + private BubbleTextView mBubbleText; private View mIconView; @@ -63,6 +73,43 @@ public class DeepShortcutView extends FrameLayout implements BubbleTextHolder { super.onFinishInflate(); mBubbleText = findViewById(R.id.bubble_text); mIconView = findViewById(R.id.icon); + tryUpdateTextBackground(); + } + + @Override + public void setBackground(Drawable background) { + super.setBackground(background); + tryUpdateTextBackground(); + } + + @Override + public void setBackgroundResource(int resid) { + super.setBackgroundResource(resid); + tryUpdateTextBackground(); + } + + /** + * Updates the text background to match the shape of this background (when applicable). + */ + private void tryUpdateTextBackground() { + if (!(getBackground() instanceof GradientDrawable) || mBubbleText == null) { + return; + } + GradientDrawable background = (GradientDrawable) getBackground(); + + int color = Themes.getAttrColor(getContext(), android.R.attr.colorControlHighlight); + GradientDrawable backgroundMask = new GradientDrawable(); + backgroundMask.setColor(color); + backgroundMask.setShape(GradientDrawable.RECTANGLE); + if (background.getCornerRadii() != null) { + backgroundMask.setCornerRadii(background.getCornerRadii()); + } else { + backgroundMask.setCornerRadius(background.getCornerRadius()); + } + + RippleDrawable drawable = new RippleDrawable(ColorStateList.valueOf(color), + mTransparentDrawable, backgroundMask); + mBubbleText.setBackground(drawable); } @Override