diff --git a/AndroidManifest.xml b/AndroidManifest.xml index fd40e905fda..cc4d898403a 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -5362,6 +5362,14 @@ + + + + + + diff --git a/src/com/android/settings/backup/SettingsBackupHelper.java b/src/com/android/settings/backup/SettingsBackupHelper.java index 73760a42a44..5fdbb006b51 100644 --- a/src/com/android/settings/backup/SettingsBackupHelper.java +++ b/src/com/android/settings/backup/SettingsBackupHelper.java @@ -16,17 +16,19 @@ package com.android.settings.backup; - import android.app.backup.BackupAgentHelper; +import android.util.Log; import com.android.settings.flags.Flags; import com.android.settings.onboarding.OnboardingFeatureProvider; import com.android.settings.overlay.FeatureFactory; -import com.android.settings.shortcut.CreateShortcutPreferenceController; +import com.android.settings.shortcut.ShortcutsUpdater; import com.android.settingslib.datastore.BackupRestoreStorageManager; /** Backup agent for Settings APK */ public class SettingsBackupHelper extends BackupAgentHelper { + private static final String TAG = "SettingsBackupHelper"; + public static final String SOUND_BACKUP_HELPER = "SoundSettingsBackup"; public static final String ACCESSIBILITY_APPEARANCE_BACKUP_HELPER = "AccessibilityAppearanceSettingsBackup"; @@ -58,6 +60,10 @@ public class SettingsBackupHelper extends BackupAgentHelper { public void onRestoreFinished() { super.onRestoreFinished(); BackupRestoreStorageManager.getInstance(this).onRestoreFinished(); - CreateShortcutPreferenceController.updateRestoredShortcuts(this); + try { + ShortcutsUpdater.updatePinnedShortcuts(this); + } catch (Exception e) { + Log.e(TAG, "Error updating shortcuts after restoring backup", e); + } } } diff --git a/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java b/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java index ef129ff3d24..907fe7bd722 100644 --- a/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java +++ b/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java @@ -16,6 +16,8 @@ package com.android.settings.localepicker; +import static com.google.common.base.Preconditions.checkNotNull; + import android.app.settings.SettingsEnums; import android.content.Context; import android.graphics.Canvas; @@ -41,7 +43,8 @@ import com.android.internal.app.LocalePicker; import com.android.internal.app.LocaleStore; import com.android.settings.R; import com.android.settings.overlay.FeatureFactory; -import com.android.settings.shortcut.ShortcutsUpdateTask; +import com.android.settings.shortcut.ShortcutsUpdater; +import com.android.settingslib.utils.ThreadUtils; import java.text.NumberFormat; import java.util.ArrayList; @@ -96,7 +99,7 @@ class LocaleDragAndDropAdapter LocaleDragAndDropAdapter(LocaleListEditor parent, List feedItemList) { mFeedItemList = feedItemList; mCacheItemList = new ArrayList<>(feedItemList); - mContext = parent.getContext(); + mContext = checkNotNull(parent.getContext()); final float dragElevation = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, mContext.getResources().getDisplayMetrics()); @@ -350,7 +353,8 @@ class LocaleDragAndDropAdapter LocalePicker.updateLocales(mLocalesToSetNext); mLocalesSetLast = mLocalesToSetNext; - new ShortcutsUpdateTask(mContext).execute(); + ThreadUtils.postOnBackgroundThread( + () -> ShortcutsUpdater.updatePinnedShortcuts(mContext)); mLocalesToSetNext = null; diff --git a/src/com/android/settings/shortcut/CreateShortcutPreferenceController.java b/src/com/android/settings/shortcut/CreateShortcutPreferenceController.java index 8f74bd9e05a..0e2e6bcb44c 100644 --- a/src/com/android/settings/shortcut/CreateShortcutPreferenceController.java +++ b/src/com/android/settings/shortcut/CreateShortcutPreferenceController.java @@ -16,27 +16,19 @@ package com.android.settings.shortcut; +import static com.android.settings.shortcut.Shortcuts.SHORTCUT_PROBE; + import android.app.Activity; import android.app.settings.SettingsEnums; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.Icon; -import android.graphics.drawable.LayerDrawable; import android.net.ConnectivityManager; import android.util.Log; -import android.view.ContextThemeWrapper; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.ImageView; import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; @@ -48,7 +40,6 @@ import com.android.settings.Settings; import com.android.settings.Settings.DataUsageSummaryActivity; import com.android.settings.Settings.TetherSettingsActivity; import com.android.settings.Settings.WifiTetherSettingsActivity; -import com.android.settings.activityembedding.ActivityEmbeddingUtils; import com.android.settings.core.BasePreferenceController; import com.android.settings.gestures.OneHandedSettingsUtils; import com.android.settings.network.SubscriptionUtil; @@ -69,11 +60,6 @@ public class CreateShortcutPreferenceController extends BasePreferenceController private static final String TAG = "CreateShortcutPrefCtrl"; - static final String SHORTCUT_ID_PREFIX = "component-shortcut-"; - static final Intent SHORTCUT_PROBE = new Intent(Intent.ACTION_MAIN) - .addCategory("com.android.settings.SHORTCUT") - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - private final ShortcutManager mShortcutManager; private final PackageManager mPackageManager; private final ConnectivityManager mConnectivityManager; @@ -132,9 +118,7 @@ public class CreateShortcutPreferenceController extends BasePreferenceController if (mHost == null) { return false; } - final Intent shortcutIntent = createResultIntent( - buildShortcutIntent(uiContext, info), - info, clickTarget.getTitle()); + final Intent shortcutIntent = createResultIntent(info); mHost.setResult(Activity.RESULT_OK, shortcutIntent); logCreateShortcut(info); mHost.finish(); @@ -149,21 +133,20 @@ public class CreateShortcutPreferenceController extends BasePreferenceController * launcher widget using this intent. */ @VisibleForTesting - Intent createResultIntent(Intent shortcutIntent, ResolveInfo resolveInfo, - CharSequence label) { - ShortcutInfo info = createShortcutInfo(mContext, shortcutIntent, resolveInfo, label); + Intent createResultIntent(ResolveInfo resolveInfo) { + ShortcutInfo info = Shortcuts.createShortcutInfo(mContext, resolveInfo); Intent intent = mShortcutManager.createShortcutResultIntent(info); if (intent == null) { intent = new Intent(); } intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(mContext, R.mipmap.ic_launcher_settings)) - .putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent) - .putExtra(Intent.EXTRA_SHORTCUT_NAME, label); + .putExtra(Intent.EXTRA_SHORTCUT_INTENT, info.getIntent()) + .putExtra(Intent.EXTRA_SHORTCUT_NAME, info.getShortLabel()); final ActivityInfo activityInfo = resolveInfo.activityInfo; if (activityInfo.icon != 0) { - intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, createIcon( + intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, Shortcuts.createIcon( mContext, activityInfo.applicationInfo, activityInfo.icon, @@ -239,87 +222,6 @@ public class CreateShortcutPreferenceController extends BasePreferenceController info.activityInfo.name); } - private static Intent buildShortcutIntent(Context context, ResolveInfo info) { - Intent intent = new Intent(SHORTCUT_PROBE) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP) - .setClassName(info.activityInfo.packageName, info.activityInfo.name); - if (ActivityEmbeddingUtils.isEmbeddingActivityEnabled(context)) { - intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); - } - return intent; - } - - private static ShortcutInfo createShortcutInfo(Context context, Intent shortcutIntent, - ResolveInfo resolveInfo, CharSequence label) { - final ActivityInfo activityInfo = resolveInfo.activityInfo; - - final Icon maskableIcon; - if (activityInfo.icon != 0 && activityInfo.applicationInfo != null) { - maskableIcon = Icon.createWithAdaptiveBitmap(createIcon( - context, - activityInfo.applicationInfo, activityInfo.icon, - R.layout.shortcut_badge_maskable, - context.getResources().getDimensionPixelSize(R.dimen.shortcut_size_maskable))); - } else { - maskableIcon = Icon.createWithResource(context, R.drawable.ic_launcher_settings); - } - final String shortcutId = SHORTCUT_ID_PREFIX + - shortcutIntent.getComponent().flattenToShortString(); - return new ShortcutInfo.Builder(context, shortcutId) - .setShortLabel(label) - .setIntent(shortcutIntent) - .setIcon(maskableIcon) - .build(); - } - - private static Bitmap createIcon(Context context, ApplicationInfo app, int resource, - int layoutRes, int size) { - final Context themedContext = new ContextThemeWrapper(context, - android.R.style.Theme_Material); - final View view = LayoutInflater.from(themedContext).inflate(layoutRes, null); - final int spec = View.MeasureSpec.makeMeasureSpec(size, View.MeasureSpec.EXACTLY); - view.measure(spec, spec); - final Bitmap bitmap = Bitmap.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight(), - Bitmap.Config.ARGB_8888); - final Canvas canvas = new Canvas(bitmap); - - Drawable iconDrawable; - try { - iconDrawable = context.getPackageManager().getResourcesForApplication(app) - .getDrawable(resource, themedContext.getTheme()); - if (iconDrawable instanceof LayerDrawable) { - iconDrawable = ((LayerDrawable) iconDrawable).getDrawable(1); - } - ((ImageView) view.findViewById(android.R.id.icon)).setImageDrawable(iconDrawable); - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "Cannot load icon from app " + app + ", returning a default icon"); - Icon icon = Icon.createWithResource(context, R.drawable.ic_launcher_settings); - ((ImageView) view.findViewById(android.R.id.icon)).setImageIcon(icon); - } - - view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); - view.draw(canvas); - return bitmap; - } - - public static void updateRestoredShortcuts(Context context) { - ShortcutManager sm = context.getSystemService(ShortcutManager.class); - List updatedShortcuts = new ArrayList<>(); - for (ShortcutInfo si : sm.getPinnedShortcuts()) { - if (si.getId().startsWith(SHORTCUT_ID_PREFIX)) { - ResolveInfo ri = context.getPackageManager().resolveActivity(si.getIntent(), 0); - - if (ri != null) { - updatedShortcuts.add(createShortcutInfo(context, - buildShortcutIntent(context, ri), ri, si.getShortLabel())); - } - } - } - if (!updatedShortcuts.isEmpty()) { - sm.updateShortcuts(updatedShortcuts); - } - } - private static final Comparator SHORTCUT_COMPARATOR = (i1, i2) -> i1.priority - i2.priority; } diff --git a/src/com/android/settings/shortcut/Shortcuts.java b/src/com/android/settings/shortcut/Shortcuts.java new file mode 100644 index 00000000000..53544ebe120 --- /dev/null +++ b/src/com/android/settings/shortcut/Shortcuts.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2024 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.settings.shortcut; + +import static com.google.common.base.Preconditions.checkArgument; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ShortcutInfo; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; +import android.graphics.drawable.LayerDrawable; +import android.util.Log; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; + +import com.android.settings.R; +import com.android.settings.activityembedding.ActivityEmbeddingUtils; + +class Shortcuts { + + private static final String TAG = "Shortcuts"; + + static final String SHORTCUT_ID_PREFIX = "component-shortcut-"; + static final Intent SHORTCUT_PROBE = new Intent(Intent.ACTION_MAIN) + .addCategory("com.android.settings.SHORTCUT") + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + static ShortcutInfo createShortcutInfo(Context context, ResolveInfo target) { + checkArgument(target.activityInfo != null); + String shortcutId = SHORTCUT_ID_PREFIX + + target.activityInfo.getComponentName().flattenToShortString(); + + return createShortcutInfo(context, shortcutId, target); + } + + static ShortcutInfo createShortcutInfo(Context context, String id, ResolveInfo target) { + Intent intent = new Intent(SHORTCUT_PROBE) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP) + .setClassName(target.activityInfo.packageName, target.activityInfo.name); + if (ActivityEmbeddingUtils.isEmbeddingActivityEnabled(context)) { + intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + } + + CharSequence label = target.loadLabel(context.getPackageManager()); + Icon maskableIcon = getMaskableIcon(context, target.activityInfo); + + return new ShortcutInfo.Builder(context, id) + .setIntent(intent) + .setShortLabel(label) + .setIcon(maskableIcon) + .build(); + } + + private static Icon getMaskableIcon(Context context, ActivityInfo activityInfo) { + if (activityInfo.icon != 0 && activityInfo.applicationInfo != null) { + return Icon.createWithAdaptiveBitmap(createIcon( + context, + activityInfo.applicationInfo, activityInfo.icon, + R.layout.shortcut_badge_maskable, + context.getResources().getDimensionPixelSize(R.dimen.shortcut_size_maskable))); + } else { + return Icon.createWithResource(context, R.drawable.ic_launcher_settings); + } + } + + static Bitmap createIcon(Context context, ApplicationInfo app, int resource, int layoutRes, + int size) { + final Context themedContext = new ContextThemeWrapper(context, + android.R.style.Theme_Material); + final View view = LayoutInflater.from(themedContext).inflate(layoutRes, null); + final int spec = View.MeasureSpec.makeMeasureSpec(size, View.MeasureSpec.EXACTLY); + view.measure(spec, spec); + final Bitmap bitmap = Bitmap.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight(), + Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(bitmap); + + Drawable iconDrawable; + try { + iconDrawable = context.getPackageManager().getResourcesForApplication(app) + .getDrawable(resource, themedContext.getTheme()); + if (iconDrawable instanceof LayerDrawable) { + iconDrawable = ((LayerDrawable) iconDrawable).getDrawable(1); + } + ((ImageView) view.findViewById(android.R.id.icon)).setImageDrawable(iconDrawable); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Cannot load icon from app " + app + ", returning a default icon"); + Icon icon = Icon.createWithResource(context, R.drawable.ic_launcher_settings); + ((ImageView) view.findViewById(android.R.id.icon)).setImageIcon(icon); + } + + view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); + view.draw(canvas); + return bitmap; + } +} diff --git a/src/com/android/settings/shortcut/ShortcutsUpdateReceiver.java b/src/com/android/settings/shortcut/ShortcutsUpdateReceiver.java new file mode 100644 index 00000000000..657af5ba027 --- /dev/null +++ b/src/com/android/settings/shortcut/ShortcutsUpdateReceiver.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2024 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.settings.shortcut; + +import android.app.Flags; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +import androidx.annotation.NonNull; + +import com.android.settingslib.utils.ThreadUtils; + +public class ShortcutsUpdateReceiver extends BroadcastReceiver { + + private static final String TAG = "ShortcutsUpdateReceiver"; + + @Override + public void onReceive(@NonNull Context context, @NonNull Intent intent) { + if (!Flags.modesApi() || !Flags.modesUi()) { + return; + } + + if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) { + PendingResult pendingResult = goAsync(); + + ThreadUtils.getBackgroundExecutor().execute(() -> { + try { + ShortcutsUpdater.updatePinnedShortcuts(context); + } catch (Exception e) { + Log.e(TAG, "Error trying to update Settings shortcuts", e); + } finally { + pendingResult.finish(); + } + }); + } + } +} diff --git a/src/com/android/settings/shortcut/ShortcutsUpdateTask.java b/src/com/android/settings/shortcut/ShortcutsUpdateTask.java deleted file mode 100644 index 54f7d1ceafe..00000000000 --- a/src/com/android/settings/shortcut/ShortcutsUpdateTask.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settings.shortcut; - -import static com.android.settings.shortcut.CreateShortcutPreferenceController.SHORTCUT_ID_PREFIX; -import static com.android.settings.shortcut.CreateShortcutPreferenceController.SHORTCUT_PROBE; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.content.pm.ShortcutInfo; -import android.content.pm.ShortcutManager; -import android.os.AsyncTask; - -import java.util.ArrayList; -import java.util.List; - -public class ShortcutsUpdateTask extends AsyncTask { - - private final Context mContext; - - public ShortcutsUpdateTask(Context context) { - mContext = context; - } - - @Override - public Void doInBackground(Void... params) { - ShortcutManager sm = mContext.getSystemService(ShortcutManager.class); - PackageManager pm = mContext.getPackageManager(); - - List updates = new ArrayList<>(); - for (ShortcutInfo info : sm.getPinnedShortcuts()) { - if (!info.getId().startsWith(SHORTCUT_ID_PREFIX)) { - continue; - } - ComponentName cn = ComponentName.unflattenFromString( - info.getId().substring(SHORTCUT_ID_PREFIX.length())); - ResolveInfo ri = pm.resolveActivity(new Intent(SHORTCUT_PROBE).setComponent(cn), 0); - if (ri == null) { - continue; - } - updates.add(new ShortcutInfo.Builder(mContext, info.getId()) - .setShortLabel(ri.loadLabel(pm)).build()); - } - if (!updates.isEmpty()) { - sm.updateShortcuts(updates); - } - return null; - } -} diff --git a/src/com/android/settings/shortcut/ShortcutsUpdater.java b/src/com/android/settings/shortcut/ShortcutsUpdater.java new file mode 100644 index 00000000000..90a60fda379 --- /dev/null +++ b/src/com/android/settings/shortcut/ShortcutsUpdater.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.shortcut; + +import static com.android.settings.shortcut.Shortcuts.SHORTCUT_ID_PREFIX; +import static com.android.settings.shortcut.Shortcuts.SHORTCUT_PROBE; + +import static com.google.common.base.Preconditions.checkNotNull; + +import android.app.Flags; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.settings.Settings; + +import java.util.ArrayList; +import java.util.List; + +public class ShortcutsUpdater { + + /** + * Update label, icon, and intent of pinned shortcuts to Settings subpages. + * + *

Should be called whenever any of those could have changed, such as after changing locale, + * restoring a backup from a different device, or when flags controlling available features + * may have flipped. + */ + public static void updatePinnedShortcuts(Context context) { + ShortcutManager sm = checkNotNull(context.getSystemService(ShortcutManager.class)); + + List updates = new ArrayList<>(); + for (ShortcutInfo info : sm.getPinnedShortcuts()) { + ResolveInfo resolvedActivity = resolveActivity(context, info); + if (resolvedActivity != null) { + // Id is preserved to update an existing shortcut, but the activity it opens might + // be different, according to maybeGetReplacingComponent. + updates.add(Shortcuts.createShortcutInfo(context, info.getId(), resolvedActivity)); + } + } + if (!updates.isEmpty()) { + sm.updateShortcuts(updates); + } + } + + @Nullable + private static ResolveInfo resolveActivity(Context context, ShortcutInfo shortcut) { + if (!shortcut.getId().startsWith(SHORTCUT_ID_PREFIX)) { + return null; + } + + ComponentName cn = ComponentName.unflattenFromString( + shortcut.getId().substring(SHORTCUT_ID_PREFIX.length())); + if (cn == null) { + return null; + } + + // Check if the componentName is obsolete and has been replaced by a different one. + cn = maybeGetReplacingComponent(context, cn); + PackageManager pm = context.getPackageManager(); + return pm.resolveActivity(new Intent(SHORTCUT_PROBE).setComponent(cn), 0); + } + + @NonNull + private static ComponentName maybeGetReplacingComponent(Context context, ComponentName cn) { + // ZenModeSettingsActivity is replaced by ModesSettingsActivity and will be deleted + // soon (so we shouldn't use ZenModeSettingsActivity.class). + if (Flags.modesApi() && Flags.modesUi() + && cn.getClassName().endsWith("Settings$ZenModeSettingsActivity")) { + return new ComponentName(context, Settings.ModesSettingsActivity.class); + } + + return cn; + } +} diff --git a/tests/robotests/src/com/android/settings/shortcut/CreateShortcutPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/shortcut/CreateShortcutPreferenceControllerTest.java index 9727dd13848..8442a37873b 100644 --- a/tests/robotests/src/com/android/settings/shortcut/CreateShortcutPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/shortcut/CreateShortcutPreferenceControllerTest.java @@ -16,7 +16,7 @@ package com.android.settings.shortcut; -import static com.android.settings.shortcut.CreateShortcutPreferenceController.SHORTCUT_ID_PREFIX; +import static com.android.settings.shortcut.Shortcuts.SHORTCUT_ID_PREFIX; import static com.google.common.truth.Truth.assertThat; @@ -101,10 +101,10 @@ public class CreateShortcutPreferenceControllerTest { when(mShortcutManager.createShortcutResultIntent(any(ShortcutInfo.class))) .thenReturn(new Intent().putExtra("d1", "d2")); - final Intent intent = new Intent(CreateShortcutPreferenceController.SHORTCUT_PROBE) + final Intent intent = new Intent(Shortcuts.SHORTCUT_PROBE) .setClass(mContext, Settings.ManageApplicationsActivity.class); final ResolveInfo ri = mContext.getPackageManager().resolveActivity(intent, 0); - final Intent result = mController.createResultIntent(intent, ri, "mock"); + final Intent result = mController.createResultIntent(ri); assertThat(result.getStringExtra("d1")).isEqualTo("d2"); assertThat((Object) result.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT)).isNotNull(); @@ -131,7 +131,7 @@ public class CreateShortcutPreferenceControllerTest { ri2.activityInfo.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM; mPackageManager.setResolveInfosForIntent( - new Intent(CreateShortcutPreferenceController.SHORTCUT_PROBE), + new Intent(Shortcuts.SHORTCUT_PROBE), Arrays.asList(ri1, ri2)); doReturn(false).when(mController).canShowWifiHotspot(); @@ -158,7 +158,7 @@ public class CreateShortcutPreferenceControllerTest { ri2.activityInfo.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM; mPackageManager.setResolveInfosForIntent( - new Intent(CreateShortcutPreferenceController.SHORTCUT_PROBE), + new Intent(Shortcuts.SHORTCUT_PROBE), Arrays.asList(ri1, ri2)); doReturn(false).when(mController).canShowWifiHotspot(); @@ -276,7 +276,7 @@ public class CreateShortcutPreferenceControllerTest { ri.activityInfo.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM; mPackageManager.setResolveInfosForIntent( - new Intent(CreateShortcutPreferenceController.SHORTCUT_PROBE), + new Intent(Shortcuts.SHORTCUT_PROBE), Arrays.asList(ri)); } } diff --git a/tests/robotests/src/com/android/settings/shortcut/ShortcutsTest.java b/tests/robotests/src/com/android/settings/shortcut/ShortcutsTest.java new file mode 100644 index 00000000000..a347ff9e0db --- /dev/null +++ b/tests/robotests/src/com/android/settings/shortcut/ShortcutsTest.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2024 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.settings.shortcut; + +import static com.android.settings.shortcut.Shortcuts.SHORTCUT_PROBE; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.content.pm.ShortcutInfo; + +import com.android.settings.Settings; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class ShortcutsTest { + + private Context mContext; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.getApplication(); + } + + @Test + public void shortcutsUpdateTask() { + final Intent intent = new Intent(SHORTCUT_PROBE) + .setClass(mContext, Settings.ManageApplicationsActivity.class); + final ResolveInfo ri = mContext.getPackageManager().resolveActivity(intent, 0); + assertThat(ri).isNotNull(); + + ShortcutInfo shortcut = Shortcuts.createShortcutInfo(mContext, ri); + + assertThat(shortcut.getLabel()).isNotNull(); + assertThat(shortcut.getLabel().toString()).isEqualTo("App info"); + + assertThat(shortcut.getIntent()).isNotNull(); + assertThat(shortcut.getIntent().getAction()).isEqualTo(Intent.ACTION_MAIN); + assertThat(shortcut.getIntent().getCategories()).contains("com.android.settings.SHORTCUT"); + assertThat(shortcut.getIntent().getComponent()).isEqualTo( + new ComponentName(mContext, Settings.ManageApplicationsActivity.class)); + assertThat(shortcut.getIcon()).isNotNull(); + } +} diff --git a/tests/robotests/src/com/android/settings/shortcut/ShortcutsUpdateTaskTest.java b/tests/robotests/src/com/android/settings/shortcut/ShortcutsUpdaterTest.java similarity index 52% rename from tests/robotests/src/com/android/settings/shortcut/ShortcutsUpdateTaskTest.java rename to tests/robotests/src/com/android/settings/shortcut/ShortcutsUpdaterTest.java index 8352e7a9634..5324ff50f43 100644 --- a/tests/robotests/src/com/android/settings/shortcut/ShortcutsUpdateTaskTest.java +++ b/tests/robotests/src/com/android/settings/shortcut/ShortcutsUpdaterTest.java @@ -16,30 +16,30 @@ package com.android.settings.shortcut; -import static com.android.settings.shortcut.CreateShortcutPreferenceController.SHORTCUT_ID_PREFIX; +import static com.android.settings.shortcut.Shortcuts.SHORTCUT_ID_PREFIX; 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.doReturn; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.Flags; import android.content.ComponentName; import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import com.android.settings.Settings; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -48,17 +48,17 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; -import org.robolectric.shadow.api.Shadow; -import org.robolectric.shadows.ShadowPackageManager; import java.util.Arrays; import java.util.List; @RunWith(RobolectricTestRunner.class) -public class ShortcutsUpdateTaskTest { +public class ShortcutsUpdaterTest { private Context mContext; - private ShadowPackageManager mPackageManager; + + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Mock private ShortcutManager mShortcutManager; @@ -68,29 +68,12 @@ public class ShortcutsUpdateTaskTest { @Before public void setup() { MockitoAnnotations.initMocks(this); - mContext = RuntimeEnvironment.application; - mPackageManager = Shadow.extract(mContext.getPackageManager()); + mContext = spy(RuntimeEnvironment.application); + doReturn(mShortcutManager).when(mContext).getSystemService(eq(Context.SHORTCUT_SERVICE)); } @Test - public void shortcutsUpdateTask() { - mContext = spy(RuntimeEnvironment.application); - doReturn(mShortcutManager).when(mContext).getSystemService(eq(Context.SHORTCUT_SERVICE)); - final Intent shortcut1 = new Intent(CreateShortcutPreferenceController.SHORTCUT_PROBE) - .setComponent(new ComponentName( - mContext, Settings.ManageApplicationsActivity.class)); - final ResolveInfo ri1 = mock(ResolveInfo.class); - ri1.nonLocalizedLabel = "label1"; - - final Intent shortcut2 = new Intent(CreateShortcutPreferenceController.SHORTCUT_PROBE) - .setComponent(new ComponentName( - mContext, Settings.SoundSettingsActivity.class)); - final ResolveInfo ri2 = mock(ResolveInfo.class); - ri2.nonLocalizedLabel = "label2"; - - mPackageManager.addResolveInfoForIntent(shortcut1, ri1); - mPackageManager.addResolveInfoForIntent(shortcut2, ri2); - + public void updatePinnedShortcuts_updatesAllShortcuts() { final List pinnedShortcuts = Arrays.asList( makeShortcut("d1"), makeShortcut("d2"), @@ -99,7 +82,7 @@ public class ShortcutsUpdateTaskTest { makeShortcut(Settings.SoundSettingsActivity.class)); when(mShortcutManager.getPinnedShortcuts()).thenReturn(pinnedShortcuts); - new ShortcutsUpdateTask(mContext).doInBackground(); + ShortcutsUpdater.updatePinnedShortcuts(mContext); verify(mShortcutManager, times(1)).updateShortcuts(mListCaptor.capture()); @@ -108,6 +91,52 @@ public class ShortcutsUpdateTaskTest { assertThat(updates).hasSize(2); assertThat(pinnedShortcuts.get(2).getId()).isEqualTo(updates.get(0).getId()); assertThat(pinnedShortcuts.get(4).getId()).isEqualTo(updates.get(1).getId()); + assertThat(updates.get(0).getShortLabel().toString()).isEqualTo("App info"); + assertThat(updates.get(1).getShortLabel().toString()).isEqualTo("Sound & vibration"); + } + + @Test + @EnableFlags(Flags.FLAG_MODES_UI) + public void updatePinnedShortcuts_withModesFlag_replacesDndByModes() { + List shortcuts = List.of( + makeShortcut(Settings.ZenModeSettingsActivity.class)); + when(mShortcutManager.getPinnedShortcuts()).thenReturn(shortcuts); + + ShortcutsUpdater.updatePinnedShortcuts(mContext); + + verify(mShortcutManager, times(1)).updateShortcuts(mListCaptor.capture()); + final List updates = mListCaptor.getValue(); + assertThat(updates).hasSize(1); + + // Id hasn't changed, but intent and label has. + ComponentName zenCn = new ComponentName(mContext, Settings.ZenModeSettingsActivity.class); + ComponentName modesCn = new ComponentName(mContext, Settings.ModesSettingsActivity.class); + assertThat(updates.get(0).getId()).isEqualTo( + SHORTCUT_ID_PREFIX + zenCn.flattenToShortString()); + assertThat(updates.get(0).getIntent().getComponent()).isEqualTo(modesCn); + assertThat(updates.get(0).getShortLabel().toString()).isEqualTo("Modes"); + } + + @Test + @DisableFlags(Flags.FLAG_MODES_UI) + public void updatePinnedShortcuts_withoutModesFlag_leavesDndAlone() { + List shortcuts = List.of( + makeShortcut(Settings.ZenModeSettingsActivity.class)); + when(mShortcutManager.getPinnedShortcuts()).thenReturn(shortcuts); + + ShortcutsUpdater.updatePinnedShortcuts(mContext); + + verify(mShortcutManager, times(1)).updateShortcuts(mListCaptor.capture()); + final List updates = mListCaptor.getValue(); + assertThat(updates).hasSize(1); + + // Nothing has changed. + ComponentName zenCn = new ComponentName(mContext, Settings.ZenModeSettingsActivity.class); + assertThat(updates.get(0).getId()).isEqualTo( + SHORTCUT_ID_PREFIX + zenCn.flattenToShortString()); + assertThat(updates.get(0).getIntent().getComponent()).isEqualTo(zenCn); + assertThat(updates.get(0).getShortLabel().toString()).isEqualTo("Do Not Disturb"); + } private ShortcutInfo makeShortcut(Class className) {