Snap for 12110738 from bc0b3342bd to 24Q4-release

Change-Id: I93a53e69e981a20310c9760e9d46c9e9cbc45357
This commit is contained in:
Android Build Coastguard Worker
2024-07-18 23:21:27 +00:00
25 changed files with 892 additions and 108 deletions

View File

@@ -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",

View File

@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<!-- Based off preference_two_target.xml, with the added LinearLayout for the icons. -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeightSmall"
android:gravity="center_vertical"
android:background="?android:attr/selectableItemBackground"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:clipToPadding="false">
<include layout="@layout/settingslib_icon_frame"/>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingTop="16dp"
android:paddingBottom="16dp">
<TextView
android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
android:hyphenationFrequency="normalFast"
android:lineBreakWordStyle="phrase"
android:textAppearance="?android:attr/textAppearanceListItem"
android:ellipsize="marquee"/>
<TextView
android:id="@android:id/summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@android:id/title"
android:layout_alignStart="@android:id/title"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="?android:attr/textColorSecondary"
android:hyphenationFrequency="normalFast"
android:lineBreakWordStyle="phrase"
android:maxLines="10"/>
<!-- Circular icons (32dp) will be ImageViews under this LinearLayout -->
<LinearLayout
android:id="@+id/circles_container"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_below="@android:id/summary"
android:layout_alignStart="@android:id/title" />
</RelativeLayout>
<include layout="@layout/preference_two_target_divider" />
<!-- Preference should place its actual preference widget here. -->
<LinearLayout
android:id="@android:id/widget_frame"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:minWidth="@dimen/two_target_min_width"
android:gravity="center"
android:orientation="vertical" />
</LinearLayout>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="@dimen/zen_mode_circular_icon_size"
android:layout_height="@dimen/zen_mode_circular_icon_size"
android:layout_marginTop="@dimen/zen_mode_circular_icon_margin_vertical"
android:layout_marginBottom="@dimen/zen_mode_circular_icon_margin_vertical"
android:layout_marginEnd="@dimen/zen_mode_circular_icon_margin_between" />

View File

@@ -508,4 +508,8 @@
<dimen name="zen_mode_icon_list_item_size">96dp</dimen>
<dimen name="zen_mode_icon_list_item_circle_diameter">56dp</dimen>
<dimen name="zen_mode_icon_list_item_icon_size">32dp</dimen>
<!-- For the items in the CircularIconsPreference (contacts, apps, sound channels). -->
<dimen name="zen_mode_circular_icon_size">32dp</dimen>
<dimen name="zen_mode_circular_icon_margin_between">4dp</dimen>
<dimen name="zen_mode_circular_icon_margin_vertical">8dp</dimen>
</resources>

View File

@@ -36,17 +36,17 @@
android:key="allow_filtering"
android:title="@string/mode_notification_filter_title"/>
<Preference
android:key="zen_mode_people"
android:title="@string/zen_category_people"/>
<com.android.settings.notification.modes.CircularIconsPreference
android:key="zen_mode_people"
android:title="@string/zen_category_people" />
<Preference
<com.android.settings.notification.modes.CircularIconsPreference
android:key="zen_mode_apps"
android:title="@string/zen_category_apps"/>
android:title="@string/zen_category_apps" />
<Preference
android:key="zen_other_settings"
android:title="@string/zen_category_exceptions" />
<com.android.settings.notification.modes.CircularIconsPreference
android:key="zen_other_settings"
android:title="@string/zen_category_exceptions" />
</PreferenceCategory>
<!-- automatic trigger section; preference changes programmatically depending on type -->

View File

@@ -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));

View File

@@ -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<Integer, SubscriptionInfo> mSubscriptionInfoMap = new ArrayMap<>();
private Map<Integer, TelephonyManager> 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<UiccPortInfo> 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<UiccCardInfo> 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

View File

@@ -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 <T> 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<T> {
@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<T> mItems;
private final Function<T, Drawable> mDrawableLoader;
private final ListeningExecutorService mBackgroundExecutor;
private final ConcurrentHashMap<T, Drawable> mCachedIcons;
CircularIconSet(List<T> items, Function<T, Drawable> 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<ListenableFuture<Drawable>> 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<ListenableFuture<Drawable>> getIcons(int maxNumber) {
return mItems.stream().limit(maxNumber)
.map(this::loadIcon)
.toList();
}
private ListenableFuture<Drawable> 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;
});
}
}

View File

@@ -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<ListenableFuture<Drawable>> 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<ListenableFuture<Drawable>> 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<ImageView> getIconViews() {
if (mIconContainer == null) {
return ImmutableList.of();
}
ImmutableList.Builder<ImageView> imageViews = new ImmutableList.Builder<>();
for (int i = 0; i < mIconContainer.getChildCount(); i++) {
imageViews.add((ImageView) mIconContainer.getChildAt(i));
}
return imageViews.build();
}
}

View File

@@ -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);
}

View File

@@ -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<AppEntry> apps = getAppsBypassingDndSortedByName(allApps);
mPreference.setSummary(mSummaryHelper.getAppsSummary(mZenMode, apps));
mPreference.displayIcons(new CircularIconSet<>(apps,
app -> Utils.getBadgedIcon(mContext, app.info)));
}
@VisibleForTesting

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;
});
}
}
}

View File

@@ -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<Integer, Drawable> 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<Integer> set = new CircularIconSet<>(ImmutableList.of(1, 2, 3),
mDrawableLoader);
List<ListenableFuture<Drawable>> 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<Integer> set = new CircularIconSet<>(ImmutableList.of(1, 2, 3, 4, 5),
mDrawableLoader);
List<ListenableFuture<Drawable>> iconFutures = set.getIcons(2);
assertThat(iconFutures).hasSize(2);
verify(mDrawableLoader).apply(1);
verify(mDrawableLoader).apply(2);
verifyNoMoreInteractions(mDrawableLoader);
}
@Test
public void getIcons_cachesIcons() {
CircularIconSet<Integer> set = new CircularIconSet<>(ImmutableList.of(1, 2, 3, 4, 5),
mDrawableLoader);
List<ListenableFuture<Drawable>> iconFutures = set.getIcons(2);
assertThat(iconFutures).hasSize(2);
verify(mDrawableLoader).apply(1);
verify(mDrawableLoader).apply(2);
verifyNoMoreInteractions(mDrawableLoader);
List<ListenableFuture<Drawable>> iconFuturesAgain = set.getIcons(3);
assertThat(iconFuturesAgain).hasSize(3);
verify(mDrawableLoader).apply(3);
verifyNoMoreInteractions(mDrawableLoader);
}
}

View File

@@ -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<Integer> 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<Integer> 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<Integer> iconSet = new CircularIconSet<>(
IntStream.range(0, fittingIcons).boxed().toList(),
ColorDrawable::new);
bindAndMeasureViewHolder(width);
mPreference.displayIcons(iconSet);
List<Drawable> 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<Integer> iconSet = new CircularIconSet<>(
IntStream.range(0, fittingIcons + 5).boxed().toList(),
ColorDrawable::new);
bindAndMeasureViewHolder(width);
mPreference.displayIcons(iconSet);
List<Drawable> 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<Integer> 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<Integer> 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<Integer> threeIcons = new CircularIconSet<>(ImmutableList.of(1, 2, 3),
ColorDrawable::new);
CircularIconSet<Integer> twoIcons = new CircularIconSet<>(ImmutableList.of(1, 2),
ColorDrawable::new);
CircularIconSet<Integer> 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);
}
}

View File

@@ -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<ApplicationsState.AppEntry> 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();

View File

@@ -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));
}
}

View File

@@ -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));
}
}

View File

@@ -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<String, InstallSourceInfo>(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()

View File

@@ -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

View File

@@ -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")