diff --git a/Android.bp b/Android.bp
index 0fd4cb09389..4c472c8c411 100644
--- a/Android.bp
+++ b/Android.bp
@@ -68,6 +68,9 @@ android_library {
static_libs: [
// External dependencies
"androidx.navigation_navigation-fragment-ktx",
+ "androidx.slice_slice-builders",
+ "androidx.slice_slice-core",
+ "androidx.slice_slice-view",
"androidx.window_window-java",
"gson",
"guava",
diff --git a/res/layout/preference_circular_icons.xml b/res/layout/preference_circular_icons.xml
new file mode 100644
index 00000000000..ae981b2c562
--- /dev/null
+++ b/res/layout/preference_circular_icons.xml
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/preference_circular_icons_item.xml b/res/layout/preference_circular_icons_item.xml
new file mode 100644
index 00000000000..3e8d7fa1f2b
--- /dev/null
+++ b/res/layout/preference_circular_icons_item.xml
@@ -0,0 +1,24 @@
+
+
+
+
\ No newline at end of file
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 8a96727d0a2..c76fff5965e 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -508,4 +508,8 @@
96dp
56dp
32dp
+
+ 32dp
+ 4dp
+ 8dp
diff --git a/res/xml/modes_rule_settings.xml b/res/xml/modes_rule_settings.xml
index 0c687b2aa50..a8ba5530e8b 100644
--- a/res/xml/modes_rule_settings.xml
+++ b/res/xml/modes_rule_settings.xml
@@ -36,17 +36,17 @@
android:key="allow_filtering"
android:title="@string/mode_notification_filter_title"/>
-
+
-
+ android:title="@string/zen_category_apps" />
-
+
diff --git a/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java b/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java
index 1ee980e392a..72a0f08c30f 100644
--- a/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java
+++ b/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java
@@ -50,6 +50,7 @@ import com.android.settings.homepage.SettingsHomepageActivity;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.password.ChooseLockPattern;
import com.android.settings.privatespace.PrivateSpaceSetupActivity;
+import com.android.settings.privatespace.delete.PrivateSpaceDeleteActivity;
import com.android.settings.remoteauth.RemoteAuthActivity;
import com.android.settings.remoteauth.RemoteAuthActivityInternal;
@@ -263,6 +264,9 @@ public class ActivityEmbeddingRulesController {
addActivityFilter(activityFilters, RemoteAuthActivityInternal.class);
addActivityFilter(activityFilters, ChooseLockPattern.class);
addActivityFilter(activityFilters, PrivateSpaceSetupActivity.class);
+ if (android.multiuser.Flags.fixLargeDisplayPrivateSpaceSettings()) {
+ addActivityFilter(activityFilters, PrivateSpaceDeleteActivity.class);
+ }
String action = mContext.getString(R.string.config_avatar_picker_action);
addActivityFilter(activityFilters, new Intent(action));
diff --git a/src/com/android/settings/network/MobileNetworkRepository.java b/src/com/android/settings/network/MobileNetworkRepository.java
index 99019291fac..f5763eb4847 100644
--- a/src/com/android/settings/network/MobileNetworkRepository.java
+++ b/src/com/android/settings/network/MobileNetworkRepository.java
@@ -29,7 +29,6 @@ import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
-import android.telephony.UiccCardInfo;
import android.telephony.UiccPortInfo;
import android.telephony.UiccSlotInfo;
import android.util.ArrayMap;
@@ -86,12 +85,6 @@ public class MobileNetworkRepository extends SubscriptionManager.OnSubscriptions
private AirplaneModeObserver mAirplaneModeObserver;
private MetricsFeatureProvider mMetricsFeatureProvider;
private int mPhysicalSlotIndex = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
- private int mLogicalSlotIndex = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
- private int mCardState = UiccSlotInfo.CARD_STATE_INFO_ABSENT;
- private int mPortIndex = TelephonyManager.INVALID_PORT_INDEX;
- private int mCardId = TelephonyManager.UNINITIALIZED_CARD_ID;
- private boolean mIsEuicc = false;
- private boolean mIsRemovable = false;
private boolean mIsActive = false;
private Map mSubscriptionInfoMap = new ArrayMap<>();
private Map mTelephonyManagerMap = new HashMap<>();
@@ -301,19 +294,13 @@ public class MobileNetworkRepository extends SubscriptionManager.OnSubscriptions
UiccSlotInfo curSlotInfo = uiccSlotInfos[i];
if (curSlotInfo != null && curSlotInfo.getCardStateInfo() == CARD_STATE_INFO_PRESENT) {
final int index = i;
- mIsEuicc = curSlotInfo.getIsEuicc();
- mCardState = curSlotInfo.getCardStateInfo();
- mIsRemovable = curSlotInfo.isRemovable();
- mCardId = subInfo.getCardId();
Collection uiccPortInfos = curSlotInfo.getPorts();
uiccPortInfos.forEach(portInfo -> {
if (portInfo.getPortIndex() == subInfo.getPortIndex()
&& portInfo.getLogicalSlotIndex() == subInfo.getSimSlotIndex()) {
mPhysicalSlotIndex = index;
- mLogicalSlotIndex = portInfo.getLogicalSlotIndex();
mIsActive = portInfo.isActive();
- mPortIndex = portInfo.getPortIndex();
} else if (DEBUG) {
Log.d(TAG, "Can not get port index and physicalSlotIndex for subId "
+ subInfo.getSubscriptionId());
@@ -433,7 +420,7 @@ public class MobileNetworkRepository extends SubscriptionManager.OnSubscriptions
mMobileNetworkDatabase.insertSubsInfo(subInfoEntity);
mMetricsFeatureProvider.action(mContext,
SettingsEnums.ACTION_MOBILE_NETWORK_DB_INSERT_SUB_INFO, subId);
- insertUiccInfo(subId, telephonyManager);
+ insertUiccInfo(subId);
insertMobileNetworkInfo(subId, telephonyManager);
}
} else if (DEBUG) {
@@ -488,8 +475,8 @@ public class MobileNetworkRepository extends SubscriptionManager.OnSubscriptions
}
}
- private void insertUiccInfo(int subId, TelephonyManager telephonyManager) {
- UiccInfoEntity uiccInfoEntity = convertToUiccInfoEntity(subId, telephonyManager);
+ private void insertUiccInfo(int subId) {
+ UiccInfoEntity uiccInfoEntity = convertToUiccInfoEntity(subId);
if (DEBUG) {
Log.d(TAG, "uiccInfoEntity = " + uiccInfoEntity);
}
@@ -532,27 +519,8 @@ public class MobileNetworkRepository extends SubscriptionManager.OnSubscriptions
);
}
- private UiccInfoEntity convertToUiccInfoEntity(int subId, TelephonyManager telephonyManager) {
- return new UiccInfoEntity(String.valueOf(subId), String.valueOf(mPhysicalSlotIndex),
- mLogicalSlotIndex, mCardId, mIsEuicc,
- isMultipleEnabledProfilesSupported(telephonyManager), mCardState, mIsRemovable,
- mIsActive, mPortIndex
- );
- }
-
- private boolean isMultipleEnabledProfilesSupported(TelephonyManager telephonyManager) {
- if (telephonyManager == null) {
- Log.d(TAG, "TelephonyManager is null");
- return false;
- }
-
- List cardInfos = telephonyManager.getUiccCardsInfo();
- if (cardInfos == null) {
- Log.d(TAG, "UICC card info list is empty.");
- return false;
- }
- return cardInfos.stream().anyMatch(
- cardInfo -> cardInfo.isMultipleEnabledProfilesSupported());
+ private UiccInfoEntity convertToUiccInfoEntity(int subId) {
+ return new UiccInfoEntity(String.valueOf(subId), mIsActive);
}
@Override
diff --git a/src/com/android/settings/notification/modes/CircularIconSet.java b/src/com/android/settings/notification/modes/CircularIconSet.java
new file mode 100644
index 00000000000..55a92fd749c
--- /dev/null
+++ b/src/com/android/settings/notification/modes/CircularIconSet.java
@@ -0,0 +1,98 @@
+/*
+ * 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.notification.modes;
+
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.function.Function;
+
+/**
+ * A set of icons to be displayed in a {@link CircularIconsPreference}
+ *
+ * @param The type of the items in the set. Can be an arbitrary type, the only requirement
+ * being that the {@code drawableLoader} supplied to the constructor is able to produce
+ * a {@link Drawable} from it (for example a resource id, a Content Uri, etc).
+ */
+class CircularIconSet {
+
+ @VisibleForTesting // Can be set by tests, before creating instances.
+ static ExecutorService sExecutorService = Executors.newCachedThreadPool();
+
+ static final CircularIconSet> EMPTY = new CircularIconSet<>(ImmutableList.of(),
+ unused -> new ColorDrawable(Color.BLACK));
+
+ private final ImmutableList mItems;
+ private final Function mDrawableLoader;
+ private final ListeningExecutorService mBackgroundExecutor;
+
+ private final ConcurrentHashMap mCachedIcons;
+
+ CircularIconSet(List items, Function drawableLoader) {
+ mItems = ImmutableList.copyOf(items);
+ mDrawableLoader = drawableLoader;
+ mBackgroundExecutor = MoreExecutors.listeningDecorator(sExecutorService);
+ mCachedIcons = new ConcurrentHashMap<>();
+ }
+
+ int size() {
+ return mItems.size();
+ }
+
+ /**
+ * Loads all icons from the set, using the supplied {@code drawableLoader}, in a background
+ * thread.
+ */
+ List> getIcons() {
+ return getIcons(Integer.MAX_VALUE);
+ }
+
+ /**
+ * Loads up to {@code maxSize} icons from the set, using the supplied {@code drawableLoader}, in
+ * a background thread.
+ */
+ List> getIcons(int maxNumber) {
+ return mItems.stream().limit(maxNumber)
+ .map(this::loadIcon)
+ .toList();
+ }
+
+ private ListenableFuture loadIcon(T item) {
+ return mBackgroundExecutor.submit(() -> {
+ if (mCachedIcons.containsKey(item)) {
+ return mCachedIcons.get(item);
+ }
+ Drawable drawable = mDrawableLoader.apply(item);
+ if (drawable != null) {
+ mCachedIcons.put(item, drawable);
+ }
+ return drawable;
+ });
+ }
+}
diff --git a/src/com/android/settings/notification/modes/CircularIconsPreference.java b/src/com/android/settings/notification/modes/CircularIconsPreference.java
new file mode 100644
index 00000000000..1f6e0b0ef34
--- /dev/null
+++ b/src/com/android/settings/notification/modes/CircularIconsPreference.java
@@ -0,0 +1,208 @@
+/*
+ * 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.notification.modes;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.settings.R;
+import com.android.settingslib.RestrictedPreference;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+public class CircularIconsPreference extends RestrictedPreference {
+
+ private Executor mUiExecutor;
+ @Nullable private LinearLayout mIconContainer;
+
+ @Nullable private CircularIconSet> mPendingIconSet;
+ @Nullable private ListenableFuture> mPendingLoadIconsFuture;
+
+ public CircularIconsPreference(Context context) {
+ super(context);
+ init(context);
+ }
+
+ @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+ public CircularIconsPreference(Context context, Executor uiExecutor) {
+ this(context);
+ mUiExecutor = uiExecutor;
+ }
+
+ public CircularIconsPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context);
+ }
+
+ public CircularIconsPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init(context);
+ }
+
+ public CircularIconsPreference(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ init(context);
+ }
+
+ private void init(Context context) {
+ mUiExecutor = context.getMainExecutor();
+ setLayoutResource(R.layout.preference_circular_icons);
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+
+ mIconContainer = checkNotNull((LinearLayout) holder.findViewById(R.id.circles_container));
+ displayIconsIfPending();
+ }
+
+ private void displayIconsIfPending() {
+ CircularIconSet> pendingIconSet = mPendingIconSet;
+ if (pendingIconSet != null) {
+ mPendingIconSet = null;
+ displayIcons(pendingIconSet);
+ }
+ }
+
+ void displayIcons(CircularIconSet> iconSet) {
+ if (mIconContainer == null) {
+ // Too soon, wait for bind.
+ mPendingIconSet = iconSet;
+ return;
+ }
+ mIconContainer.setVisibility(iconSet.size() != 0 ? View.VISIBLE : View.GONE);
+ if (iconSet.size() == 0) {
+ return;
+ }
+ if (mIconContainer.getMeasuredWidth() == 0) {
+ // Too soon, wait for first measure to know width.
+ mPendingIconSet = iconSet;
+ ViewTreeObserver vto = mIconContainer.getViewTreeObserver();
+ vto.addOnGlobalLayoutListener(() ->
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ vto.removeOnGlobalLayoutListener(this);
+ displayIconsIfPending();
+ }
+ });
+ return;
+ }
+
+ mIconContainer.setVisibility(View.VISIBLE);
+ Resources res = getContext().getResources();
+ int availableSpace = mIconContainer.getMeasuredWidth();
+ int iconHorizontalSpace = res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_size)
+ + res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_margin_between);
+ int numIconsThatFit = availableSpace / iconHorizontalSpace;
+
+ List> iconFutures;
+ int extraItems = 0;
+ if (iconSet.size() > numIconsThatFit) {
+ // Reserve one space for the (+xx) circle.
+ int numIconsToShow = numIconsThatFit - 1;
+ if (numIconsToShow < 0) {
+ numIconsToShow = 0;
+ }
+ iconFutures = iconSet.getIcons(numIconsToShow);
+ extraItems = iconSet.size() - numIconsToShow;
+ } else {
+ // Fit exactly or with remaining space.
+ iconFutures = iconSet.getIcons();
+ }
+
+ displayIconsWhenReady(iconFutures, extraItems);
+ }
+
+ private void displayIconsWhenReady(List> iconFutures,
+ int extraItems) {
+ checkState(mIconContainer != null);
+ if (mPendingLoadIconsFuture != null) {
+ mPendingLoadIconsFuture.cancel(true);
+ }
+
+ int numCircles = iconFutures.size() + (extraItems > 0 ? 1 : 0);
+ if (mIconContainer.getChildCount() > numCircles) {
+ mIconContainer.removeViews(numCircles, mIconContainer.getChildCount() - numCircles);
+ }
+ for (int i = mIconContainer.getChildCount(); i < numCircles; i++) {
+ ImageView imageView = (ImageView) LayoutInflater.from(getContext()).inflate(
+ R.layout.preference_circular_icons_item, mIconContainer, false);
+ mIconContainer.addView(imageView);
+ }
+
+ // Set up placeholders and extra items indicator.
+ for (int i = 0; i < iconFutures.size(); i++) {
+ ImageView imageView = (ImageView) mIconContainer.getChildAt(i);
+ // TODO: b/346551087 - proper color and shape, should be a gray circle.
+ imageView.setImageDrawable(new ColorDrawable(Color.RED));
+ }
+ if (extraItems > 0) {
+ ImageView imageView = (ImageView) mIconContainer.getChildAt(
+ mIconContainer.getChildCount() - 1);
+ // TODO: b/346551087 - proper color and shape and number.
+ imageView.setImageDrawable(new ColorDrawable(Color.BLUE));
+ }
+
+ // Display icons when all are ready (more consistent than randomly loading).
+ mPendingLoadIconsFuture = Futures.allAsList(iconFutures);
+ FutureUtil.whenDone(
+ Futures.allAsList(iconFutures),
+ icons -> {
+ checkState(mIconContainer != null);
+ for (int i = 0; i < icons.size(); i++) {
+ ((ImageView) mIconContainer.getChildAt(i)).setImageDrawable(icons.get(i));
+ }
+ },
+ mUiExecutor);
+ }
+
+ @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+ ImmutableList getIconViews() {
+ if (mIconContainer == null) {
+ return ImmutableList.of();
+ }
+ ImmutableList.Builder imageViews = new ImmutableList.Builder<>();
+ for (int i = 0; i < mIconContainer.getChildCount(); i++) {
+ imageViews.add((ImageView) mIconContainer.getChildAt(i));
+ }
+ return imageViews.build();
+ }
+}
diff --git a/src/com/android/settings/notification/modes/FutureUtil.java b/src/com/android/settings/notification/modes/FutureUtil.java
index e7bf8b9a75f..b9a4300dd8c 100644
--- a/src/com/android/settings/notification/modes/FutureUtil.java
+++ b/src/com/android/settings/notification/modes/FutureUtil.java
@@ -18,10 +18,13 @@ package com.android.settings.notification.modes;
import android.util.Log;
+import androidx.annotation.NonNull;
+
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
+import java.util.concurrent.CancellationException;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -42,8 +45,10 @@ class FutureUtil {
}
@Override
- public void onFailure(Throwable throwable) {
- Log.e(TAG, String.format(errorLogMessage, errorLogMessageArgs), throwable);
+ public void onFailure(@NonNull Throwable throwable) {
+ if (!(throwable instanceof CancellationException)) {
+ Log.e(TAG, String.format(errorLogMessage, errorLogMessageArgs), throwable);
+ }
}
}, executor);
}
diff --git a/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java
index 9c3f267e188..9bff2bbb558 100644
--- a/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java
@@ -24,6 +24,7 @@ import android.content.Context;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
+import android.service.notification.ZenPolicy;
import android.text.TextUtils;
import androidx.annotation.NonNull;
@@ -32,6 +33,7 @@ import androidx.fragment.app.Fragment;
import androidx.preference.Preference;
import com.android.settings.R;
+import com.android.settings.Utils;
import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.applications.ApplicationsState.AppEntry;
@@ -59,7 +61,7 @@ class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceContr
private ApplicationsState.Session mAppSession;
private final ZenHelperBackend mHelperBackend;
private ZenMode mZenMode;
- private Preference mPreference;
+ private CircularIconsPreference mPreference;
private final Fragment mHost;
ZenModeAppsLinkPreferenceController(Context context, String key, Fragment host,
@@ -97,14 +99,21 @@ class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceContr
.setArguments(bundle)
.toIntent());
mZenMode = zenMode;
- mPreference = preference;
- if (TextUtils.isEmpty(mPreference.getSummary())) {
- mPreference.setSummary(R.string.zen_mode_apps_calculating);
+ mPreference = (CircularIconsPreference) preference;
+
+ if (zenMode.getPolicy().getAllowedChannels() == ZenPolicy.CHANNEL_POLICY_NONE) {
+ mPreference.setSummary(R.string.zen_mode_apps_none_apps);
+ mPreference.displayIcons(CircularIconSet.EMPTY);
+ } else {
+ if (TextUtils.isEmpty(mPreference.getSummary())) {
+ mPreference.setSummary(R.string.zen_mode_apps_calculating);
+ }
+ if (mApplicationsState != null && mHost != null) {
+ mAppSession = mApplicationsState.newSession(mAppSessionCallbacks,
+ mHost.getLifecycle());
+ }
+ triggerUpdateAppsBypassingDnd();
}
- if (mApplicationsState != null && mHost != null) {
- mAppSession = mApplicationsState.newSession(mAppSessionCallbacks, mHost.getLifecycle());
- }
- triggerUpdateAppsBypassingDnd();
}
private void triggerUpdateAppsBypassingDnd() {
@@ -126,6 +135,9 @@ class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceContr
ImmutableList apps = getAppsBypassingDndSortedByName(allApps);
mPreference.setSummary(mSummaryHelper.getAppsSummary(mZenMode, apps));
+
+ mPreference.displayIcons(new CircularIconSet<>(apps,
+ app -> Utils.getBadgedIcon(mContext, app.info)));
}
@VisibleForTesting
diff --git a/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java
index 248ef1dd585..452faed3f8d 100644
--- a/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java
@@ -17,15 +17,12 @@
package com.android.settings.notification.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
-import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
import android.content.Context;
-import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.preference.Preference;
-import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.notification.modes.ZenMode;
/**
@@ -48,13 +45,13 @@ class ZenModeOtherLinkPreferenceController extends AbstractZenModePreferenceCont
@Override
public void updateState(Preference preference, @NonNull ZenMode zenMode) {
- Bundle bundle = new Bundle();
- bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, zenMode.getId());
- preference.setIntent(new SubSettingLauncher(mContext)
- .setDestination(ZenModeOtherFragment.class.getName())
- .setSourceMetricsCategory(0)
- .setArguments(bundle)
- .toIntent());
+ // TODO: b/332937635 - Update metrics category
+ preference.setIntent(
+ ZenSubSettingLauncher.forModeFragment(mContext, ZenModeOtherFragment.class,
+ zenMode.getId(), 0).toIntent());
+
preference.setSummary(mSummaryHelper.getOtherSoundCategoriesSummary(zenMode));
+ // TODO: b/346551087 - Show media icons
+ ((CircularIconsPreference) preference).displayIcons(CircularIconSet.EMPTY);
}
}
diff --git a/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java
index 936cea6ce5c..2a614188801 100644
--- a/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java
@@ -17,15 +17,12 @@
package com.android.settings.notification.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
-import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
import android.content.Context;
-import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.preference.Preference;
-import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.notification.modes.ZenMode;
/**
@@ -48,14 +45,13 @@ class ZenModePeopleLinkPreferenceController extends AbstractZenModePreferenceCon
@Override
public void updateState(Preference preference, @NonNull ZenMode zenMode) {
- Bundle bundle = new Bundle();
- bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, zenMode.getId());
// TODO(b/332937635): Update metrics category
- preference.setIntent(new SubSettingLauncher(mContext)
- .setDestination(ZenModePeopleFragment.class.getName())
- .setSourceMetricsCategory(0)
- .setArguments(bundle)
- .toIntent());
+ preference.setIntent(
+ ZenSubSettingLauncher.forModeFragment(mContext, ZenModePeopleFragment.class,
+ zenMode.getId(), 0).toIntent());
+
preference.setSummary(mSummaryHelper.getPeopleSummary(zenMode));
+ // TODO: b/346551087 - Show people icons
+ ((CircularIconsPreference) preference).displayIcons(CircularIconSet.EMPTY);
}
}
diff --git a/src/com/android/settings/spa/core/instrumentation/SpaLogProvider.kt b/src/com/android/settings/spa/core/instrumentation/SpaLogProvider.kt
index 8659bf56884..4377ccc6734 100644
--- a/src/com/android/settings/spa/core/instrumentation/SpaLogProvider.kt
+++ b/src/com/android/settings/spa/core/instrumentation/SpaLogProvider.kt
@@ -28,7 +28,6 @@ import com.android.settingslib.spa.framework.common.SpaLogger
import com.android.settingslib.spa.framework.util.SESSION_BROWSE
import com.android.settingslib.spa.framework.util.SESSION_EXTERNAL
import com.android.settingslib.spa.framework.util.SESSION_SEARCH
-import com.android.settingslib.spa.framework.util.SESSION_SLICE
import com.android.settingslib.spa.framework.util.SESSION_UNKNOWN
/**
@@ -74,7 +73,6 @@ class SpaLogData(val id: String, val event: LogEvent,
return when(sessionSource) {
SESSION_BROWSE -> SettingsEnums.SESSION_BROWSE
SESSION_SEARCH -> SettingsEnums.SESSION_SEARCH
- SESSION_SLICE -> SettingsEnums.SESSION_SLICE_TYPE
SESSION_EXTERNAL -> SettingsEnums.SESSION_EXTERNAL
else -> SettingsEnums.SESSION_UNKNOWN
}
diff --git a/src/com/android/settings/wifi/WifiConfigInfo.java b/src/com/android/settings/wifi/WifiConfigInfo.java
index 0de306396a2..16a4446772a 100644
--- a/src/com/android/settings/wifi/WifiConfigInfo.java
+++ b/src/com/android/settings/wifi/WifiConfigInfo.java
@@ -37,6 +37,7 @@ public class WifiConfigInfo extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ WifiUtils.setupEdgeToEdge(this);
mWifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
setContentView(R.layout.wifi_config_info);
diff --git a/src/com/android/settings/wifi/WifiStatusTest.java b/src/com/android/settings/wifi/WifiStatusTest.java
index b4f3ab6ca4a..b9b1d70373e 100644
--- a/src/com/android/settings/wifi/WifiStatusTest.java
+++ b/src/com/android/settings/wifi/WifiStatusTest.java
@@ -115,6 +115,7 @@ public class WifiStatusTest extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ WifiUtils.setupEdgeToEdge(this);
mWifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
diff --git a/src/com/android/settings/wifi/WifiUtils.java b/src/com/android/settings/wifi/WifiUtils.java
index 68d8bebd582..e307bcf9e0b 100644
--- a/src/com/android/settings/wifi/WifiUtils.java
+++ b/src/com/android/settings/wifi/WifiUtils.java
@@ -16,6 +16,8 @@
package com.android.settings.wifi;
+import android.app.ActionBar;
+import android.app.Activity;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -32,8 +34,13 @@ import android.os.UserManager;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
+import android.util.TypedValue;
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
+import androidx.core.graphics.Insets;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.WindowInsetsCompat;
import com.android.settings.R;
import com.android.settings.Utils;
@@ -308,4 +315,34 @@ public class WifiUtils extends com.android.settingslib.wifi.WifiUtils {
public static void setCanShowWifiHotspotCached(Boolean cached) {
sCanShowWifiHotspotCached = cached;
}
+
+ /**
+ * Enable new edge to edge feature.
+ *
+ * @param activity the Activity need to setup the edge to edge feature.
+ */
+ public static void setupEdgeToEdge(@NonNull Activity activity) {
+ final ActionBar actionBar = activity.getActionBar();
+ if (actionBar == null) {
+ return;
+ }
+
+ final TypedValue typedValue = new TypedValue();
+ if (activity.getTheme().resolveAttribute(
+ com.android.internal.R.attr.actionBarSize, typedValue, true)) {
+ ViewCompat.setOnApplyWindowInsetsListener(activity.findViewById(android.R.id.content),
+ (v, windowInsets) -> {
+ Insets insets = windowInsets.getInsets(
+ WindowInsetsCompat.Type.systemBars() |
+ WindowInsetsCompat.Type.ime());
+
+ // Apply the insets paddings to the view.
+ v.setPadding(insets.left, insets.top, insets.right, insets.bottom);
+
+ // Return CONSUMED if you don't want the window insets to keep being
+ // passed down to descendant views.
+ return WindowInsetsCompat.CONSUMED;
+ });
+ }
+ }
}
diff --git a/tests/robotests/src/com/android/settings/notification/modes/CircularIconSetTest.java b/tests/robotests/src/com/android/settings/notification/modes/CircularIconSetTest.java
new file mode 100644
index 00000000000..22dc7547959
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/modes/CircularIconSetTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.notification.modes;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.List;
+import java.util.function.Function;
+
+@RunWith(RobolectricTestRunner.class)
+public class CircularIconSetTest {
+
+ @Mock private Function mDrawableLoader;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ CircularIconSet.sExecutorService = MoreExecutors.newDirectExecutorService();
+ when(mDrawableLoader.apply(anyInt())).thenReturn(new ColorDrawable(Color.BLACK));
+ }
+
+ @Test
+ public void getIcons_loadsAllIcons() {
+ CircularIconSet set = new CircularIconSet<>(ImmutableList.of(1, 2, 3),
+ mDrawableLoader);
+
+ List> iconFutures = set.getIcons();
+
+ assertThat(iconFutures).hasSize(3);
+ verify(mDrawableLoader).apply(1);
+ verify(mDrawableLoader).apply(2);
+ verify(mDrawableLoader).apply(3);
+ }
+
+ @Test
+ public void getIcons_loadsRequestedIcons() {
+ CircularIconSet set = new CircularIconSet<>(ImmutableList.of(1, 2, 3, 4, 5),
+ mDrawableLoader);
+
+ List> iconFutures = set.getIcons(2);
+
+ assertThat(iconFutures).hasSize(2);
+ verify(mDrawableLoader).apply(1);
+ verify(mDrawableLoader).apply(2);
+ verifyNoMoreInteractions(mDrawableLoader);
+ }
+
+ @Test
+ public void getIcons_cachesIcons() {
+ CircularIconSet set = new CircularIconSet<>(ImmutableList.of(1, 2, 3, 4, 5),
+ mDrawableLoader);
+
+ List> iconFutures = set.getIcons(2);
+ assertThat(iconFutures).hasSize(2);
+ verify(mDrawableLoader).apply(1);
+ verify(mDrawableLoader).apply(2);
+ verifyNoMoreInteractions(mDrawableLoader);
+
+ List> iconFuturesAgain = set.getIcons(3);
+ assertThat(iconFuturesAgain).hasSize(3);
+ verify(mDrawableLoader).apply(3);
+ verifyNoMoreInteractions(mDrawableLoader);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/modes/CircularIconsPreferenceTest.java b/tests/robotests/src/com/android/settings/notification/modes/CircularIconsPreferenceTest.java
new file mode 100644
index 00000000000..2ef62d0d29c
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/modes/CircularIconsPreferenceTest.java
@@ -0,0 +1,201 @@
+/*
+ * 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.notification.modes;
+
+import static android.view.View.MeasureSpec.makeMeasureSpec;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.settings.R;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.MoreExecutors;
+
+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;
+
+import java.util.List;
+import java.util.stream.IntStream;
+
+@RunWith(RobolectricTestRunner.class)
+public class CircularIconsPreferenceTest {
+
+ private static final int VIEW_WIDTH = 800;
+
+ private Context mContext;
+ private CircularIconsPreference mPreference;
+ private View mIconContainer;
+
+ private int mOneIconWidth;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+ CircularIconSet.sExecutorService = MoreExecutors.newDirectExecutorService();
+ mPreference = new CircularIconsPreference(mContext, MoreExecutors.directExecutor());
+ // Tests should call bindAndMeasureViewHolder() so that icons can be added.
+
+ Resources res = mContext.getResources();
+ mOneIconWidth = res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_size)
+ + res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_margin_between);
+ }
+
+ private void bindAndMeasureViewHolder(int viewWidth) {
+ View preferenceView = LayoutInflater.from(mContext).inflate(mPreference.getLayoutResource(),
+ null);
+ mIconContainer = checkNotNull(preferenceView.findViewById(R.id.circles_container));
+ mIconContainer.measure(makeMeasureSpec(viewWidth, View.MeasureSpec.EXACTLY),
+ makeMeasureSpec(1000, View.MeasureSpec.EXACTLY));
+ PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(preferenceView);
+ mPreference.onBindViewHolder(holder);
+ }
+
+ @Test
+ public void displayIcons_loadsIcons() {
+ CircularIconSet iconSet = new CircularIconSet<>(ImmutableList.of(1, 2),
+ ColorDrawable::new);
+
+ bindAndMeasureViewHolder(VIEW_WIDTH);
+ mPreference.displayIcons(iconSet);
+
+ assertThat(mPreference.getIconViews()).hasSize(2);
+ assertThat(mPreference.getIconViews().get(0).getDrawable())
+ .isInstanceOf(ColorDrawable.class);
+ assertThat(((ColorDrawable) mPreference.getIconViews().get(0).getDrawable()).getColor())
+ .isEqualTo(1);
+ assertThat(((ColorDrawable) mPreference.getIconViews().get(1).getDrawable()).getColor())
+ .isEqualTo(2);
+ assertThat(mIconContainer.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void displayIcons_noIcons_hidesRow() {
+ CircularIconSet iconSet = new CircularIconSet<>(ImmutableList.of(),
+ ColorDrawable::new);
+
+ bindAndMeasureViewHolder(VIEW_WIDTH);
+ mPreference.displayIcons(iconSet);
+
+ assertThat(mIconContainer.getVisibility()).isEqualTo(View.GONE);
+ }
+
+
+ @Test
+ public void displayIcons_exactlyMaxIcons_loadsAllIcons() throws Exception {
+ int width = 300;
+ int fittingIcons = width / mOneIconWidth;
+ CircularIconSet iconSet = new CircularIconSet<>(
+ IntStream.range(0, fittingIcons).boxed().toList(),
+ ColorDrawable::new);
+
+ bindAndMeasureViewHolder(width);
+ mPreference.displayIcons(iconSet);
+
+ List displayedDrawables = mPreference.getIconViews().stream()
+ .map(ImageView::getDrawable).toList();
+ assertThat(displayedDrawables).hasSize(fittingIcons);
+ assertThat(displayedDrawables).containsExactlyElementsIn(
+ Futures.allAsList(iconSet.getIcons()).get()).inOrder();
+ }
+
+ @Test
+ public void displayIcons_tooManyIcons_loadsFirstNAndPlusIcon() throws Exception {
+ int width = 300;
+ int fittingIcons = width / mOneIconWidth;
+ CircularIconSet iconSet = new CircularIconSet<>(
+ IntStream.range(0, fittingIcons + 5).boxed().toList(),
+ ColorDrawable::new);
+
+ bindAndMeasureViewHolder(width);
+ mPreference.displayIcons(iconSet);
+
+ List displayedDrawables = mPreference.getIconViews().stream()
+ .map(ImageView::getDrawable).toList();
+ assertThat(displayedDrawables).hasSize(fittingIcons);
+ // N-1 are actual icons, Nth icon is (+xx).
+ assertThat(displayedDrawables.stream().limit(fittingIcons - 1).toList())
+ .containsExactlyElementsIn(
+ Futures.allAsList(iconSet.getIcons(fittingIcons - 1)).get())
+ .inOrder();
+ // TODO: b/346551087 - Correctly verify the plus-6 icon, once we generate it properly.
+ assertThat(((ColorDrawable) displayedDrawables.get(
+ displayedDrawables.size() - 1)).getColor()).isEqualTo(Color.BLUE);
+ }
+
+ @Test
+ public void displayIcons_teenyTinySpace_showsPlusIcon_noCrash() {
+ int width = 1;
+ CircularIconSet iconSet = new CircularIconSet<>(ImmutableList.of(1, 2),
+ ColorDrawable::new);
+
+ bindAndMeasureViewHolder(width);
+ mPreference.displayIcons(iconSet);
+
+ assertThat(mPreference.getIconViews()).hasSize(1);
+ // TODO: b/346551087 - Correctly verify the plus-2 icon, once we generate it properly.
+ assertThat(((ColorDrawable) mPreference.getIconViews().get(0).getDrawable()).getColor())
+ .isEqualTo(Color.BLUE);
+ }
+
+ @Test
+ public void displayIcons_beforeBind_loadsIconsOnBind() {
+ CircularIconSet iconSet = new CircularIconSet<>(ImmutableList.of(1, 2, 3),
+ ColorDrawable::new);
+
+ mPreference.displayIcons(iconSet);
+ assertThat(mPreference.getIconViews()).isEmpty();
+
+ bindAndMeasureViewHolder(VIEW_WIDTH);
+ assertThat(mPreference.getIconViews()).hasSize(3);
+ }
+
+ @Test
+ public void displayIcons_calledAgain_reloadsIcons() {
+ CircularIconSet threeIcons = new CircularIconSet<>(ImmutableList.of(1, 2, 3),
+ ColorDrawable::new);
+ CircularIconSet twoIcons = new CircularIconSet<>(ImmutableList.of(1, 2),
+ ColorDrawable::new);
+ CircularIconSet fourIcons = new CircularIconSet<>(ImmutableList.of(1, 2, 3, 4),
+ ColorDrawable::new);
+ bindAndMeasureViewHolder(VIEW_WIDTH);
+
+ mPreference.displayIcons(threeIcons);
+ assertThat(mPreference.getIconViews()).hasSize(3);
+ mPreference.displayIcons(twoIcons);
+ assertThat(mPreference.getIconViews()).hasSize(2);
+ mPreference.displayIcons(fourIcons);
+ assertThat(mPreference.getIconViews()).hasSize(4);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java
index 6d12594d1c1..4a6c59627bd 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java
@@ -23,10 +23,13 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
@@ -41,8 +44,11 @@ import android.os.UserManager;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.ZenPolicy;
+import android.view.LayoutInflater;
+import android.view.View;
import androidx.fragment.app.Fragment;
+import androidx.preference.PreferenceViewHolder;
import com.android.settings.SettingsActivity;
import com.android.settingslib.applications.ApplicationsState;
@@ -51,7 +57,9 @@ import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.notification.modes.TestModeBuilder;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
-import com.android.settingslib.widget.SelectorWithWidgetPreference;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.MoreExecutors;
import org.junit.Before;
import org.junit.Rule;
@@ -71,6 +79,7 @@ import java.util.Random;
public final class ZenModeAppsLinkPreferenceControllerTest {
private ZenModeAppsLinkPreferenceController mController;
+ private CircularIconsPreference mPreference;
private Context mContext;
@Mock
@@ -91,10 +100,21 @@ public final class ZenModeAppsLinkPreferenceControllerTest {
public void setup() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
+ CircularIconSet.sExecutorService = MoreExecutors.newDirectExecutorService();
+ mPreference = new CircularIconsPreference(mContext, MoreExecutors.directExecutor());
+
when(mApplicationsState.newSession(any(), any())).thenReturn(mSession);
mController = new ZenModeAppsLinkPreferenceController(
mContext, "controller_key", mock(Fragment.class), mApplicationsState,
mZenModesBackend, mHelperBackend);
+
+ // Ensure the preference view is bound & measured (needed to add child ImageViews).
+ View preferenceView = LayoutInflater.from(mContext).inflate(mPreference.getLayoutResource(),
+ null);
+ preferenceView.measure(View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.EXACTLY),
+ View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.EXACTLY));
+ PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(preferenceView);
+ mPreference.onBindViewHolder(holder);
}
private AppEntry createAppEntry(String packageName, int userId) {
@@ -123,13 +143,11 @@ public final class ZenModeAppsLinkPreferenceControllerTest {
@Test
public void testUpdateSetsIntent() {
- // Creates the preference
- SelectorWithWidgetPreference preference = new SelectorWithWidgetPreference(mContext);
// Create a zen mode that allows priority channels to breakthrough.
ZenMode zenMode = createPriorityChannelsZenMode();
- mController.updateState(preference, zenMode);
- Intent launcherIntent = preference.getIntent();
+ mController.updateState(mPreference, zenMode);
+ Intent launcherIntent = mPreference.getIntent();
assertThat(launcherIntent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
.isEqualTo("com.android.settings.notification.modes.ZenModeAppsFragment");
@@ -193,9 +211,20 @@ public final class ZenModeAppsLinkPreferenceControllerTest {
}
@Test
- public void testUpdateTriggersRebuild() {
- // Creates the preference
- SelectorWithWidgetPreference preference = new SelectorWithWidgetPreference(mContext);
+ public void updateState_withPolicyAllowingNoChannels_doesNotLoadPriorityApps() {
+ ZenMode zenMode = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder().allowPriorityChannels(false).build())
+ .build();
+
+ mController.updateState(mPreference, zenMode);
+
+ verifyNoMoreInteractions(mSession);
+ verify(mHelperBackend, never()).getPackagesBypassingDnd(anyInt(), anyBoolean());
+ assertThat(String.valueOf(mPreference.getSummary())).isEqualTo("None");
+ }
+
+ @Test
+ public void updateState_withPolicyAllowingPriorityChannels_triggersRebuild() {
// Create a zen mode that allows priority channels to breakthrough.
ZenMode zenMode = createPriorityChannelsZenMode();
@@ -209,21 +238,35 @@ public final class ZenModeAppsLinkPreferenceControllerTest {
// Updates the preference with the zen mode. We expect that this causes the app session
// to trigger a rebuild (and display a temporary text in the meantime).
- mController.updateZenMode(preference, zenMode);
+ mController.updateZenMode(mPreference, zenMode);
verify(mSession).rebuild(any(), any(), eq(false));
- assertThat(String.valueOf(preference.getSummary())).isEqualTo("Calculating…");
+ assertThat(String.valueOf(mPreference.getSummary())).isEqualTo("Calculating…");
// Manually triggers the callback that will happen on rebuild.
mController.mAppSessionCallbacks.onRebuildComplete(appEntries);
- assertThat(String.valueOf(preference.getSummary())).isEqualTo("test can interrupt");
+ assertThat(String.valueOf(mPreference.getSummary())).isEqualTo("test can interrupt");
+ }
+
+ @Test
+ public void updateState_withPolicyAllowingPriorityChannels_loadsIcons() {
+ ZenMode zenMode = createPriorityChannelsZenMode();
+
+ mController.updateState(mPreference, zenMode);
+ when(mHelperBackend.getPackagesBypassingDnd(anyInt(), anyBoolean()))
+ .thenReturn(ImmutableList.of("test1", "test2"));
+ ArrayList appEntries = new ArrayList<>();
+ appEntries.add(createAppEntry("test1", mContext.getUserId()));
+ appEntries.add(createAppEntry("test2", mContext.getUserId()));
+ mController.mAppSessionCallbacks.onRebuildComplete(appEntries);
+
+ assertThat(mPreference.getIconViews()).hasSize(2);
}
@Test
public void testOnPackageListChangedTriggersRebuild() {
- SelectorWithWidgetPreference preference = new SelectorWithWidgetPreference(mContext);
// Create a zen mode that allows priority channels to breakthrough.
ZenMode zenMode = createPriorityChannelsZenMode();
- mController.updateState(preference, zenMode);
+ mController.updateState(mPreference, zenMode);
verify(mSession).rebuild(any(), any(), eq(false));
mController.mAppSessionCallbacks.onPackageListChanged();
@@ -232,10 +275,9 @@ public final class ZenModeAppsLinkPreferenceControllerTest {
@Test
public void testOnLoadEntriesCompletedTriggersRebuild() {
- SelectorWithWidgetPreference preference = new SelectorWithWidgetPreference(mContext);
// Create a zen mode that allows priority channels to breakthrough.
ZenMode zenMode = createPriorityChannelsZenMode();
- mController.updateState(preference, zenMode);
+ mController.updateState(mPreference, zenMode);
verify(mSession).rebuild(any(), any(), eq(false));
mController.mAppSessionCallbacks.onLoadEntriesCompleted();
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceControllerTest.java
index 772bd1d8fad..c9ea6d4ac69 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceControllerTest.java
@@ -17,6 +17,7 @@
package com.android.settings.notification.modes;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -25,8 +26,6 @@ import android.content.Context;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
-import androidx.preference.Preference;
-
import com.android.settingslib.notification.modes.TestModeBuilder;
import org.junit.Before;
@@ -63,8 +62,11 @@ public final class ZenModeOtherLinkPreferenceControllerTest {
@Test
@EnableFlags(Flags.FLAG_MODES_UI)
public void testHasSummary() {
- Preference pref = mock(Preference.class);
+ CircularIconsPreference pref = mock(CircularIconsPreference.class);
+
mController.updateZenMode(pref, TestModeBuilder.EXAMPLE);
+
verify(pref).setSummary(any());
+ verify(pref).displayIcons(eq(CircularIconSet.EMPTY));
}
}
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java
index dd97d6e690d..0db26c3cae4 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java
@@ -17,6 +17,7 @@
package com.android.settings.notification.modes;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -25,8 +26,6 @@ import android.content.Context;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
-import androidx.preference.Preference;
-
import com.android.settingslib.notification.modes.TestModeBuilder;
import org.junit.Before;
@@ -63,8 +62,11 @@ public final class ZenModePeopleLinkPreferenceControllerTest {
@Test
@EnableFlags(Flags.FLAG_MODES_UI)
public void testHasSummary() {
- Preference pref = mock(Preference.class);
+ CircularIconsPreference pref = mock(CircularIconsPreference.class);
+
mController.updateZenMode(pref, TestModeBuilder.EXAMPLE);
+
verify(pref).setSummary(any());
+ verify(pref).displayIcons(eq(CircularIconSet.EMPTY));
}
}
\ No newline at end of file
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreferenceTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreferenceTest.kt
index 96e47076778..6297c62a015 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreferenceTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreferenceTest.kt
@@ -68,7 +68,6 @@ class AppInstallerInfoPreferenceTest {
@Before
fun setUp() {
mockSession = mockitoSession()
- .initMocks(this)
.mockStatic(AppStoreUtil::class.java)
.mockStatic(Utils::class.java)
.mockStatic(AppUtils::class.java)
@@ -77,8 +76,7 @@ class AppInstallerInfoPreferenceTest {
whenever(AppStoreUtil.getInstallerPackageName(any(), eq(PACKAGE_NAME)))
.thenReturn(INSTALLER_PACKAGE_NAME)
whenever(AppStoreUtil.getInstallerPackageNameAndInstallSourceInfo(any(), eq(PACKAGE_NAME)))
- .thenReturn(
- Pair(INSTALLER_PACKAGE_NAME, INSTALL_SOURCE_INFO))
+ .thenReturn(Pair(INSTALLER_PACKAGE_NAME, INSTALL_SOURCE_INFO))
whenever(AppStoreUtil.getAppStoreLink(context, INSTALLER_PACKAGE_NAME, PACKAGE_NAME))
.thenReturn(STORE_LINK)
whenever(AppStoreUtil.isInitiatedFromDifferentPackage(eq(INSTALL_SOURCE_INFO)))
@@ -97,7 +95,8 @@ class AppInstallerInfoPreferenceTest {
@Test
fun whenNoInstaller_notDisplayed() {
- whenever(AppStoreUtil.getInstallerPackageName(any(), eq(PACKAGE_NAME))).thenReturn(null)
+ whenever(AppStoreUtil.getInstallerPackageNameAndInstallSourceInfo(any(), eq(PACKAGE_NAME)))
+ .thenReturn(Pair(null, INSTALL_SOURCE_INFO))
setContent()
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/backgroundinstall/BackgroundInstalledAppsPageProviderTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/backgroundinstall/BackgroundInstalledAppsPageProviderTest.kt
index 5c65da11a66..6a6b91b2922 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/backgroundinstall/BackgroundInstalledAppsPageProviderTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/backgroundinstall/BackgroundInstalledAppsPageProviderTest.kt
@@ -23,6 +23,7 @@ import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.pm.ParceledListSlice
import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
@@ -30,6 +31,7 @@ import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.R
import com.android.settingslib.spa.testutils.FakeNavControllerWrapper
+import com.android.settingslib.spa.testutils.waitUntilExists
import com.android.settingslib.spaprivileged.template.app.AppListItemModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.first
@@ -146,8 +148,9 @@ class BackgroundInstalledAppsPageProviderTest {
BackgroundInstalledAppList()
}
- composeTestRule.onNodeWithText(
- context.getString(R.string.background_install_title)).assertIsDisplayed()
+ composeTestRule.waitUntilExists(
+ hasText(context.getString(R.string.background_install_title))
+ )
}
@Test
diff --git a/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt b/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt
index dc15f0b976a..d7241b9419c 100644
--- a/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt
+++ b/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt
@@ -40,7 +40,6 @@ import com.android.settings.localepicker.LocaleFeatureProvider
import com.android.settings.notification.syncacrossdevices.SyncAcrossDevicesFeatureProvider
import com.android.settings.overlay.DockUpdaterFeatureProvider
import com.android.settings.overlay.FeatureFactory
-import com.android.settings.overlay.SurveyFeatureProvider
import com.android.settings.panel.PanelFeatureProvider
import com.android.settings.privatespace.PrivateSpaceLoginFeatureProvider
import com.android.settings.search.SearchFeatureProvider
@@ -98,9 +97,7 @@ class FakeFeatureFactory : FeatureFactory() {
override val searchFeatureProvider: SearchFeatureProvider
get() = TODO("Not yet implemented")
- override fun getSurveyFeatureProvider(context: Context): SurveyFeatureProvider? {
- TODO("Not yet implemented")
- }
+ override fun getSurveyFeatureProvider(context: Context) = null
override val securityFeatureProvider: SecurityFeatureProvider
get() = TODO("Not yet implemented")