diff --git a/res/color-night/accent_select_primary_text.xml b/res/color-night/accent_select_primary_text.xml new file mode 100644 index 00000000000..049ddf63678 --- /dev/null +++ b/res/color-night/accent_select_primary_text.xml @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/res/color-night/accent_select_secondary_text.xml b/res/color-night/accent_select_secondary_text.xml new file mode 100644 index 00000000000..6b7b4f27dcf --- /dev/null +++ b/res/color-night/accent_select_secondary_text.xml @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/res/color/accent_select_background.xml b/res/color/accent_select_background.xml new file mode 100644 index 00000000000..5dab7230532 --- /dev/null +++ b/res/color/accent_select_background.xml @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/res/color/accent_select_primary_text.xml b/res/color/accent_select_primary_text.xml new file mode 100644 index 00000000000..f790d425a1a --- /dev/null +++ b/res/color/accent_select_primary_text.xml @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/res/color/accent_select_secondary_text.xml b/res/color/accent_select_secondary_text.xml new file mode 100644 index 00000000000..25d24a8cdac --- /dev/null +++ b/res/color/accent_select_secondary_text.xml @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/res/drawable/homepage_highlighted_item_background.xml b/res/drawable/homepage_highlighted_item_background.xml index d54ff601643..7e0dea546f3 100644 --- a/res/drawable/homepage_highlighted_item_background.xml +++ b/res/drawable/homepage_highlighted_item_background.xml @@ -17,7 +17,7 @@ + android:color="@color/accent_select_background" /> \ No newline at end of file diff --git a/res/drawable/user_select_background.xml b/res/drawable/user_select_background.xml new file mode 100644 index 00000000000..777bff95db7 --- /dev/null +++ b/res/drawable/user_select_background.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + diff --git a/res/layout/user_select.xml b/res/layout/user_select.xml new file mode 100644 index 00000000000..8c8c37a64fe --- /dev/null +++ b/res/layout/user_select.xml @@ -0,0 +1,29 @@ + + + + + + diff --git a/res/layout/user_select_item.xml b/res/layout/user_select_item.xml new file mode 100644 index 00000000000..fa0c91a4ab5 --- /dev/null +++ b/res/layout/user_select_item.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + diff --git a/res/layout/user_select_title.xml b/res/layout/user_select_title.xml new file mode 100644 index 00000000000..e01ca22554f --- /dev/null +++ b/res/layout/user_select_title.xml @@ -0,0 +1,27 @@ + + + diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java index 2988ddcaedb..f996444b482 100644 --- a/src/com/android/settings/Utils.java +++ b/src/com/android/settings/Utils.java @@ -85,7 +85,6 @@ import android.text.TextUtils; import android.text.format.DateUtils; import android.text.style.TtsSpan; import android.util.ArraySet; -import android.util.FeatureFlagUtils; import android.util.IconDrawableFactory; import android.util.Log; import android.view.LayoutInflater; @@ -1220,7 +1219,7 @@ public final class Utils extends com.android.settingslib.Utils { */ @ColorInt public static int getHomepageIconColor(Context context) { - return getColorAttrDefaultColor(context, android.R.attr.textColorSecondary); + return getColorAttrDefaultColor(context, android.R.attr.textColorPrimary); } /** @@ -1228,6 +1227,6 @@ public final class Utils extends com.android.settingslib.Utils { */ @ColorInt public static int getHomepageIconColorHighlight(Context context) { - return getColorAttrDefaultColor(context, android.R.attr.textColorSecondaryInverse); + return context.getColor(R.color.accent_select_primary_text); } } diff --git a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java index d9a02c00a8a..a6aeb897357 100644 --- a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java @@ -470,9 +470,7 @@ public class ToggleScreenMagnificationPreferenceFragment extends mShortcutPreference.setKey(getShortcutPreferenceKey()); mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext())); mShortcutPreference.setOnClickCallback(this); - - final CharSequence title = getString(R.string.accessibility_shortcut_title, mPackageName); - mShortcutPreference.setTitle(title); + mShortcutPreference.setTitle(getShortcutTitle()); final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY); generalCategory.addPreference(mShortcutPreference); diff --git a/src/com/android/settings/dashboard/profileselector/ProfileSelectDialog.java b/src/com/android/settings/dashboard/profileselector/ProfileSelectDialog.java index d3234dd1151..f82694c5841 100644 --- a/src/com/android/settings/dashboard/profileselector/ProfileSelectDialog.java +++ b/src/com/android/settings/dashboard/profileselector/ProfileSelectDialog.java @@ -20,7 +20,6 @@ import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnCancelListener; -import android.content.DialogInterface.OnClickListener; import android.content.DialogInterface.OnDismissListener; import android.content.DialogInterface.OnShowListener; import android.content.Intent; @@ -28,17 +27,27 @@ import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.DialogFragment; import androidx.fragment.app.FragmentManager; +import com.android.internal.widget.DialogTitle; +import com.android.internal.widget.LinearLayoutManager; +import com.android.internal.widget.RecyclerView; +import com.android.settings.R; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.drawer.Tile; import java.util.List; -public class ProfileSelectDialog extends DialogFragment implements OnClickListener { +/** + * A {@link DialogFragment} that can select one of the different profiles. + */ +public class ProfileSelectDialog extends DialogFragment implements UserAdapter.OnClickListener { private static final String TAG = "ProfileSelectDialog"; private static final String ARG_SELECTED_TILE = "selectedTile"; @@ -53,12 +62,13 @@ public class ProfileSelectDialog extends DialogFragment implements OnClickListen /** * Display the profile select dialog, adding the fragment to the given FragmentManager. - * @param manager The FragmentManager this fragment will be added to. - * @param tile The tile for this fragment. + * + * @param manager The FragmentManager this fragment will be added to. + * @param tile The tile for this fragment. * @param sourceMetricCategory The source metric category. - * @param onShowListener The listener listens to the dialog showing event. - * @param onDismissListener The listener listens to the dialog dismissing event. - * @param onCancelListener The listener listens to the dialog cancelling event. + * @param onShowListener The listener listens to the dialog showing event. + * @param onDismissListener The listener listens to the dialog dismissing event. + * @param onCancelListener The listener listens to the dialog cancelling event. */ public static void show(FragmentManager manager, Tile tile, int sourceMetricCategory, OnShowListener onShowListener, OnDismissListener onDismissListener, @@ -77,32 +87,53 @@ public class ProfileSelectDialog extends DialogFragment implements OnClickListen @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mSelectedTile = getArguments().getParcelable(ARG_SELECTED_TILE); - mSourceMetricCategory = getArguments().getInt(ARG_SOURCE_METRIC_CATEGORY); + Bundle arguments = requireArguments(); + mSelectedTile = arguments.getParcelable(ARG_SELECTED_TILE, Tile.class); + mSourceMetricCategory = arguments.getInt(ARG_SOURCE_METRIC_CATEGORY); } + @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { - final Context context = getActivity(); - final AlertDialog.Builder builder = new AlertDialog.Builder(context); - final UserAdapter adapter = UserAdapter.createUserAdapter(UserManager.get(context), context, - mSelectedTile.userHandle); - builder.setTitle(com.android.settingslib.R.string.choose_profile) - .setAdapter(adapter, this); + return createDialog(getContext(), mSelectedTile.userHandle, this); + } - return builder.create(); + /** + * Creates the profile select dialog. + */ + public static Dialog createDialog(Context context, List userProfiles, + UserAdapter.OnClickListener onClickListener) { + LayoutInflater layoutInflater = context.getSystemService(LayoutInflater.class); + + DialogTitle titleView = + (DialogTitle) layoutInflater.inflate(R.layout.user_select_title, null); + titleView.setText(com.android.settingslib.R.string.choose_profile); + + View contentView = layoutInflater.inflate(R.layout.user_select, null); + + RecyclerView listView = contentView.findViewById(R.id.list); + listView.setAdapter( + UserAdapter.createUserRecycleViewAdapter(context, userProfiles, onClickListener)); + listView.setLayoutManager( + new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)); + + return new AlertDialog.Builder(context) + .setCustomTitle(titleView) + .setView(contentView) + .create(); } @Override - public void onClick(DialogInterface dialog, int which) { - final UserHandle user = mSelectedTile.userHandle.get(which); + public void onClick(int position) { + final UserHandle user = mSelectedTile.userHandle.get(position); // Show menu on top level items. final Intent intent = new Intent(mSelectedTile.getIntent()); FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider() .logStartedIntentWithProfile(intent, mSourceMetricCategory, - which == 1 /* isWorkProfile */); + position == 1 /* isWorkProfile */); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); getActivity().startActivityAsUser(intent, user); + dismiss(); } @Override diff --git a/src/com/android/settings/dashboard/profileselector/UserAdapter.java b/src/com/android/settings/dashboard/profileselector/UserAdapter.java index ae5351e1390..2573d11ce19 100644 --- a/src/com/android/settings/dashboard/profileselector/UserAdapter.java +++ b/src/com/android/settings/dashboard/profileselector/UserAdapter.java @@ -18,188 +18,156 @@ package com.android.settings.dashboard.profileselector; import static android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_CATEGORY_HEADER; import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_CATEGORY_HEADER; -import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_USER_LABEL; import android.app.ActivityManager; import android.app.admin.DevicePolicyManager; +import android.app.admin.DevicePolicyResourcesManager; import android.content.Context; import android.content.pm.UserInfo; -import android.database.DataSetObserver; -import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.UserHandle; import android.os.UserManager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.BaseAdapter; import android.widget.ImageView; -import android.widget.ListAdapter; -import android.widget.SpinnerAdapter; import android.widget.TextView; import com.android.internal.util.UserIcons; +import com.android.internal.widget.RecyclerView; import com.android.settingslib.R; -import com.android.settingslib.drawable.UserIconDrawable; +import com.android.settingslib.Utils; import java.util.ArrayList; import java.util.List; +import java.util.Objects; /** * Adapter for a spinner that shows a list of users. */ -public class UserAdapter implements SpinnerAdapter, ListAdapter { +public class UserAdapter extends BaseAdapter { + /** Holder for user details */ public static class UserDetails { private final UserHandle mUserHandle; - private final String mName; private final Drawable mIcon; + private final String mTitle; public UserDetails(UserHandle userHandle, UserManager um, Context context) { mUserHandle = userHandle; UserInfo userInfo = um.getUserInfo(mUserHandle.getIdentifier()); - Drawable icon; + int tintColor = Utils.getColorAttrDefaultColor(context, + com.android.internal.R.attr.colorAccentPrimaryVariant); if (userInfo.isManagedProfile()) { - mName = context.getSystemService(DevicePolicyManager.class).getResources() - .getString(WORK_PROFILE_USER_LABEL, - () -> context.getString(R.string.managed_user_title)); - icon = context.getPackageManager().getUserBadgeForDensityNoBackground( + mIcon = context.getPackageManager().getUserBadgeForDensityNoBackground( userHandle, /* density= */ 0); + mIcon.setTint(tintColor); } else { - mName = userInfo.name; - final int userId = userInfo.id; - if (um.getUserIcon(userId) != null) { - icon = new BitmapDrawable(context.getResources(), um.getUserIcon(userId)); - } else { - icon = UserIcons.getDefaultUserIcon( - context.getResources(), userId, /* light= */ false); - } + mIcon = UserIcons.getDefaultUserIconInColor(context.getResources(), tintColor); } - this.mIcon = encircle(context, icon); + mTitle = getTitle(context); } - private static Drawable encircle(Context context, Drawable icon) { - return new UserIconDrawable(UserIconDrawable.getDefaultSize(context)) - .setIconDrawable(icon).bake(); + private String getTitle(Context context) { + DevicePolicyManager devicePolicyManager = + Objects.requireNonNull(context.getSystemService(DevicePolicyManager.class)); + DevicePolicyResourcesManager resources = devicePolicyManager.getResources(); + int userHandle = mUserHandle.getIdentifier(); + if (userHandle == UserHandle.USER_CURRENT + || userHandle == ActivityManager.getCurrentUser()) { + return resources.getString(PERSONAL_CATEGORY_HEADER, + () -> context.getString(R.string.category_personal)); + } else { + return resources.getString(WORK_CATEGORY_HEADER, + () -> context.getString(R.string.category_work)); + } } } - private ArrayList data; - private final Context mContext; + private final ArrayList mUserDetails; private final LayoutInflater mInflater; - private final DevicePolicyManager mDevicePolicyManager; public UserAdapter(Context context, ArrayList users) { if (users == null) { throw new IllegalArgumentException("A list of user details must be provided"); } - mContext = context; - this.data = users; - mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class); + mUserDetails = users; + mInflater = context.getSystemService(LayoutInflater.class); } public UserHandle getUserHandle(int position) { - if (position < 0 || position >= data.size()) { + if (position < 0 || position >= mUserDetails.size()) { return null; } - return data.get(position).mUserHandle; - } - - @Override - public View getDropDownView(int position, View convertView, ViewGroup parent) { - final View row = convertView != null ? convertView : createUser(parent); - - UserDetails user = data.get(position); - ((ImageView) row.findViewById(android.R.id.icon)).setImageDrawable(user.mIcon); - ((TextView) row.findViewById(android.R.id.title)).setText(getTitle(user)); - return row; - } - - private String getTitle(UserDetails user) { - int userHandle = user.mUserHandle.getIdentifier(); - if (userHandle == UserHandle.USER_CURRENT - || userHandle == ActivityManager.getCurrentUser()) { - return mDevicePolicyManager.getResources().getString(PERSONAL_CATEGORY_HEADER, - () -> mContext.getString(R.string.category_personal)); - } else { - return mDevicePolicyManager.getResources().getString(WORK_CATEGORY_HEADER, - () -> mContext.getString(R.string.category_work)); - } - } - - private View createUser(ViewGroup parent) { - return mInflater.inflate(R.layout.user_preference, parent, false); - } - - @Override - public void registerDataSetObserver(DataSetObserver observer) { - // We don't support observers - } - - @Override - public void unregisterDataSetObserver(DataSetObserver observer) { - // We don't support observers - } - - @Override - public int getCount() { - return data.size(); - } - - @Override - public UserAdapter.UserDetails getItem(int position) { - return data.get(position); - } - - @Override - public long getItemId(int position) { - return data.get(position).mUserHandle.getIdentifier(); - } - - @Override - public boolean hasStableIds() { - return false; + return mUserDetails.get(position).mUserHandle; } @Override public View getView(int position, View convertView, ViewGroup parent) { - return getDropDownView(position, convertView, parent); + ViewHolder holder; + if (convertView != null) { + holder = (ViewHolder) convertView.getTag(); + } else { + convertView = mInflater.inflate(R.layout.user_preference, parent, false); + holder = new ViewHolder(convertView); + convertView.setTag(holder); + } + bindViewHolder(holder, position); + return convertView; + } + + private void bindViewHolder(ViewHolder holder, int position) { + UserDetails userDetails = getItem(position); + holder.getIconView().setImageDrawable(userDetails.mIcon); + holder.getTitleView().setText(userDetails.mTitle); } @Override - public int getItemViewType(int position) { - return 0; + public int getCount() { + return mUserDetails.size(); } @Override - public int getViewTypeCount() { - return 1; + public UserAdapter.UserDetails getItem(int position) { + return mUserDetails.get(position); } @Override - public boolean isEmpty() { - return data.isEmpty(); + public long getItemId(int position) { + return mUserDetails.get(position).mUserHandle.getIdentifier(); } - @Override - public boolean areAllItemsEnabled() { - return true; - } + private RecyclerView.Adapter createRecyclerViewAdapter( + OnClickListener onClickListener) { + return new RecyclerView.Adapter() { + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.user_select_item, parent, false); - @Override - public boolean isEnabled(int position) { - return true; + return new ViewHolder(view, onClickListener); + } + + @Override + public void onBindViewHolder(ViewHolder holder, int position) { + UserAdapter.this.bindViewHolder(holder, position); + } + + @Override + public int getItemCount() { + return getCount(); + } + }; } /** - * Creates a {@link UserAdapter} if there is more than one - * profile on the device. + * Creates a {@link UserAdapter} if there is more than one profile on the device. * - *

The adapter can be used to populate a spinner that switches between the Settings - * app on the different profiles. + *

The adapter can be used to populate a spinner that switches between the different + * profiles. * - * @return a {@link UserAdapter} or null if there is only one - * profile. + * @return a {@link UserAdapter} or null if there is only one profile. */ public static UserAdapter createUserSpinnerAdapter(UserManager userManager, Context context) { List userProfiles = userManager.getUserProfiles(); @@ -215,13 +183,60 @@ public class UserAdapter implements SpinnerAdapter, ListAdapter { return createUserAdapter(userManager, context, userProfiles); } - public static UserAdapter createUserAdapter( + /** + * Creates a {@link RecyclerView} adapter which be used to populate a {@link RecyclerView} that + * select one of the different profiles. + */ + public static RecyclerView.Adapter createUserRecycleViewAdapter( + Context context, List userProfiles, OnClickListener onClickListener) { + UserManager systemService = context.getSystemService(UserManager.class); + return createUserAdapter(systemService, context, userProfiles) + .createRecyclerViewAdapter(onClickListener); + } + + private static UserAdapter createUserAdapter( UserManager userManager, Context context, List userProfiles) { ArrayList userDetails = new ArrayList<>(userProfiles.size()); - final int count = userProfiles.size(); - for (int i = 0; i < count; i++) { - userDetails.add(new UserDetails(userProfiles.get(i), userManager, context)); + for (UserHandle userProfile : userProfiles) { + userDetails.add(new UserDetails(userProfile, userManager, context)); } return new UserAdapter(context, userDetails); } + + static class ViewHolder extends RecyclerView.ViewHolder { + private final ImageView mIconView; + private final TextView mTitleView; + + private ViewHolder(View view) { + super(view); + mIconView = view.findViewById(android.R.id.icon); + mTitleView = view.findViewById(android.R.id.title); + } + + private ViewHolder(View view, OnClickListener onClickListener) { + this(view); + View button = view.findViewById(R.id.button); + if (button != null) { + button.setOnClickListener(v -> onClickListener.onClick(getAdapterPosition())); + } + } + + private ImageView getIconView() { + return mIconView; + } + + private TextView getTitleView() { + return mTitleView; + } + } + + /** + * Interface definition for a callback to be invoked when a user is clicked. + */ + public interface OnClickListener { + /** + * Called when a user has been clicked. + */ + void onClick(int position); + } } diff --git a/src/com/android/settings/display/ScreenSaverPreferenceController.java b/src/com/android/settings/display/ScreenSaverPreferenceController.java index c1b0b4e9eb6..676a567f202 100644 --- a/src/com/android/settings/display/ScreenSaverPreferenceController.java +++ b/src/com/android/settings/display/ScreenSaverPreferenceController.java @@ -14,6 +14,7 @@ package com.android.settings.display; import android.content.Context; +import android.os.UserManager; import androidx.preference.Preference; @@ -32,8 +33,11 @@ public class ScreenSaverPreferenceController extends AbstractPreferenceControlle @Override public boolean isAvailable() { - return mContext.getResources().getBoolean( + final boolean dreamsSupported = mContext.getResources().getBoolean( com.android.internal.R.bool.config_dreamsSupported); + final boolean dreamsOnlyEnabledForSystemUser = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_dreamsOnlyEnabledForSystemUser); + return dreamsSupported && (!dreamsOnlyEnabledForSystemUser || isSystemUser()); } @Override @@ -45,4 +49,9 @@ public class ScreenSaverPreferenceController extends AbstractPreferenceControlle public void updateState(Preference preference) { preference.setSummary(DreamSettings.getSummaryTextWithDreamName(mContext)); } + + private boolean isSystemUser() { + final UserManager userManager = mContext.getSystemService(UserManager.class); + return userManager.isSystemUser(); + } } diff --git a/src/com/android/settings/network/helper/LifecycleCallbackIntentReceiver.java b/src/com/android/settings/network/helper/LifecycleCallbackIntentReceiver.java new file mode 100644 index 00000000000..8aaa53e0af8 --- /dev/null +++ b/src/com/android/settings/network/helper/LifecycleCallbackIntentReceiver.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2022 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.network.helper; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Handler; +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; +import androidx.lifecycle.Lifecycle; +import java.util.function.Consumer; + +/** + * A {@link BroadcastReceiver} for {@link Intent}. + * + * This is {@link BroadcastReceiver} supported by {@link LifecycleCallbackConverter}, + * and only register when state is either START or RESUME. + */ +@VisibleForTesting +public class LifecycleCallbackIntentReceiver extends LifecycleCallbackConverter { + private static final String TAG = "LifecycleCallbackIntentReceiver"; + + @VisibleForTesting + protected final BroadcastReceiver mReceiver; + + private final Runnable mRegisterCallback; + private final Runnable mUnRegisterCallback; + + /** + * Constructor + * @param lifecycle {@link Lifecycle} to monitor + * @param context for this BroadcastReceiver + * @param filter the IntentFilter for BroadcastReceiver + * @param broadcastPermission for permission when listening + * @param scheduler for running in background thread + * @param resultCallback for the Intent from BroadcastReceiver + */ + @VisibleForTesting + public LifecycleCallbackIntentReceiver(@NonNull Lifecycle lifecycle, + @NonNull Context context, @NonNull IntentFilter filter, + String broadcastPermission, Handler scheduler, + @NonNull Consumer resultCallback) { + super(lifecycle, resultCallback); + + // BroadcastReceiver + mReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + if (isInitialStickyBroadcast()) { + return; + } + final String action = intent.getAction(); + if ((action == null) || (action.length() <= 0)) { + return; + } + postResult(intent); + } + }; + + // Register operation + mRegisterCallback = () -> { + Intent initIntent = context.registerReceiver(mReceiver, + filter, broadcastPermission, scheduler); + if (initIntent != null) { + postResult(initIntent); + } + }; + + // Un-Register operation + mUnRegisterCallback = () -> { + context.unregisterReceiver(mReceiver); + }; + } + + @Override + public void setCallbackActive(boolean isActive) { + super.setCallbackActive(isActive); + Runnable op = (isActive) ? mRegisterCallback : mUnRegisterCallback; + op.run(); + } + + @Override + public void close() { + super.close(); + if (isCallbackActive()) { + setCallbackActive(false); + } + } +} diff --git a/src/com/android/settings/network/helper/LifecycleCallbackTelephonyAdapter.java b/src/com/android/settings/network/helper/LifecycleCallbackTelephonyAdapter.java new file mode 100644 index 00000000000..0fae4f27aaf --- /dev/null +++ b/src/com/android/settings/network/helper/LifecycleCallbackTelephonyAdapter.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2022 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.network.helper; + +import android.telephony.TelephonyCallback; +import android.telephony.TelephonyManager; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; +import androidx.lifecycle.Lifecycle; + +import java.util.concurrent.Executor; +import java.util.function.Consumer; + +/** + * A {@link LifecycleCallbackConverter} for supporting the register/unregister work for + * {@link TelephonyCallback}. + */ +@VisibleForTesting +public class LifecycleCallbackTelephonyAdapter extends LifecycleCallbackConverter { + private static final String TAG = "LifecycleCallbackTelephony"; + + private final Runnable mRegisterCallback; + private final Runnable mUnRegisterCallback; + + /** + * Constructor + * @param lifecycle {@link Lifecycle} to monitor + * @param telephonyManager {@link TelephonyManager} to interact with + * @param telephonyCallback {@link TelephonyCallback} + * @param executor {@link Executor} for receiving the notify from telephony framework. + * @param resultCallback for the result from {@link TelephonyCallback} + */ + @VisibleForTesting + public LifecycleCallbackTelephonyAdapter(@NonNull Lifecycle lifecycle, + @NonNull TelephonyManager telephonyManager, + @NonNull TelephonyCallback telephonyCallback, + Executor executor, @NonNull Consumer resultCallback) { + super(lifecycle, resultCallback); + + // Register operation + mRegisterCallback = () -> { + telephonyManager.registerTelephonyCallback(executor, telephonyCallback); + }; + + // Un-Register operation + mUnRegisterCallback = () -> { + telephonyManager.unregisterTelephonyCallback(telephonyCallback); + }; + } + + @Override + public void setCallbackActive(boolean isActive) { + super.setCallbackActive(isActive); + Runnable op = (isActive) ? mRegisterCallback : mUnRegisterCallback; + op.run(); + } +} diff --git a/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java b/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java index 2616a69ba1d..d348b24e58f 100644 --- a/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java +++ b/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java @@ -326,7 +326,7 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc /* Handles the enabling SIM action. */ private void showEnableSubDialog() { - Log.i(TAG, "Handle subscription enabling."); + Log.d(TAG, "Handle subscription enabling."); if (isDsdsConditionSatisfied()) { showEnableDsdsConfirmDialog(); return; @@ -452,7 +452,7 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc } private void showMepSwitchSimConfirmDialog() { - Log.i(TAG, "showMepSwitchSimConfirmDialog"); + Log.d(TAG, "showMepSwitchSimConfirmDialog"); final CharSequence displayName = SubscriptionUtil.getUniqueSubscriptionDisplayName( mSubInfo, this); String title = getString(R.string.sim_action_switch_sub_dialog_mep_title, displayName); @@ -556,27 +556,35 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc private boolean isDsdsConditionSatisfied() { if (mTelMgr.isMultiSimEnabled()) { - Log.i(TAG, "DSDS is already enabled. Condition not satisfied."); + Log.d(TAG, "DSDS is already enabled. Condition not satisfied."); return false; } if (mTelMgr.isMultiSimSupported() != TelephonyManager.MULTISIM_ALLOWED) { - Log.i(TAG, "Hardware does not support DSDS."); + Log.d(TAG, "Hardware does not support DSDS."); return false; } + boolean isActiveSim = SubscriptionUtil.getActiveSubscriptions( + mSubscriptionManager).size() > 0; + if (isMultipleEnabledProfilesSupported() && isActiveSim) { + Log.d(TAG, + "Device supports MEP and eSIM operation and eSIM profile is enabled." + + " DSDS condition satisfied."); + return true; + } boolean isRemovableSimEnabled = isRemovableSimEnabled(); if (mIsEsimOperation && isRemovableSimEnabled) { - Log.i(TAG, "eSIM operation and removable SIM is enabled. DSDS condition satisfied."); + Log.d(TAG, "eSIM operation and removable SIM is enabled. DSDS condition satisfied."); return true; } boolean isEsimProfileEnabled = SubscriptionUtil.getActiveSubscriptions(mSubscriptionManager).stream() .anyMatch(SubscriptionInfo::isEmbedded); if (!mIsEsimOperation && isEsimProfileEnabled) { - Log.i(TAG, "Removable SIM operation and eSIM profile is enabled. DSDS condition" + Log.d(TAG, "Removable SIM operation and eSIM profile is enabled. DSDS condition" + " satisfied."); return true; } - Log.i(TAG, "DSDS condition not satisfied."); + Log.d(TAG, "DSDS condition not satisfied."); return false; } diff --git a/src/com/android/settings/nfc/NfcForegroundPreferenceController.java b/src/com/android/settings/nfc/NfcForegroundPreferenceController.java index 246bdb9716e..611d3fcb56f 100644 --- a/src/com/android/settings/nfc/NfcForegroundPreferenceController.java +++ b/src/com/android/settings/nfc/NfcForegroundPreferenceController.java @@ -93,7 +93,7 @@ public class NfcForegroundPreferenceController extends BasePreferenceController return; } final ListPreference listPreference = (ListPreference) preference; - listPreference.setIconSpaceReserved(true); + listPreference.setIconSpaceReserved(false); listPreference.setValue(mListValues[mPaymentBackend.isForegroundMode() ? 1 : 0]); } diff --git a/src/com/android/settings/privacy/EnableContentCaptureWithServiceSettingsPreferenceController.java b/src/com/android/settings/privacy/EnableContentCaptureWithServiceSettingsPreferenceController.java index c3878d51b62..fcb2347f5b0 100644 --- a/src/com/android/settings/privacy/EnableContentCaptureWithServiceSettingsPreferenceController.java +++ b/src/com/android/settings/privacy/EnableContentCaptureWithServiceSettingsPreferenceController.java @@ -19,19 +19,18 @@ package com.android.settings.privacy; import android.annotation.NonNull; import android.content.ComponentName; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.content.pm.UserInfo; import android.os.UserHandle; import android.os.UserManager; +import android.text.TextUtils; import android.util.Log; -import androidx.appcompat.app.AlertDialog; import androidx.preference.Preference; import com.android.settings.R; import com.android.settings.core.TogglePreferenceController; -import com.android.settings.dashboard.profileselector.UserAdapter; +import com.android.settings.dashboard.profileselector.ProfileSelectDialog; import com.android.settings.utils.ContentCaptureUtils; import java.util.ArrayList; @@ -42,13 +41,9 @@ public final class EnableContentCaptureWithServiceSettingsPreferenceController private static final String TAG = "ContentCaptureController"; - private final UserManager mUserManager; - public EnableContentCaptureWithServiceSettingsPreferenceController(@NonNull Context context, @NonNull String key) { super(context, key); - - mUserManager = UserManager.get(context); } @Override @@ -74,11 +69,6 @@ public final class EnableContentCaptureWithServiceSettingsPreferenceController Log.w(TAG, "No component name for custom service settings"); preference.setSelectable(false); } - - preference.setOnPreferenceClickListener((pref) -> { - ProfileSelectDialog.show(mContext, pref); - return true; - }); } @Override @@ -93,32 +83,30 @@ public final class EnableContentCaptureWithServiceSettingsPreferenceController return R.string.menu_key_privacy; } - private static final class ProfileSelectDialog { - public static void show(Context context, Preference pref) { - final UserManager userManager = UserManager.get(context); - final List userInfos = userManager.getUsers(); - final ArrayList userHandles = new ArrayList<>(userInfos.size()); - for (UserInfo info: userInfos) { - userHandles.add(info.getUserHandle()); - } - if (userHandles.size() == 1) { - final Intent intent = pref.getIntent().addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); - context.startActivityAsUser(intent, userHandles.get(0)); - } else { - AlertDialog.Builder builder = new AlertDialog.Builder(context); - UserAdapter adapter = UserAdapter.createUserAdapter(userManager, context, - userHandles); - builder.setTitle(com.android.settingslib.R.string.choose_profile) - .setAdapter(adapter, (DialogInterface dialog, int which) -> { - final UserHandle user = userHandles.get(which); - // Show menu on top level items. - final Intent intent = pref.getIntent() - .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); - context.startActivityAsUser(intent, user); - }) - .show(); - } + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) { + return false; } + show(preference); + return true; } + private void show(Preference preference) { + final UserManager userManager = UserManager.get(mContext); + final List userInfos = userManager.getUsers(); + final ArrayList userHandles = new ArrayList<>(userInfos.size()); + for (UserInfo info : userInfos) { + userHandles.add(info.getUserHandle()); + } + final Intent intent = preference.getIntent().addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + if (userHandles.size() == 1) { + mContext.startActivityAsUser(intent, userHandles.get(0)); + return; + } + ProfileSelectDialog.createDialog(mContext, userHandles, (int position) -> { + // Show menu on top level items. + mContext.startActivityAsUser(intent, userHandles.get(position)); + }).show(); + } } diff --git a/src/com/android/settings/widget/HighlightableTopLevelPreferenceAdapter.java b/src/com/android/settings/widget/HighlightableTopLevelPreferenceAdapter.java index 338be483b6d..3cc7ef27f7e 100644 --- a/src/com/android/settings/widget/HighlightableTopLevelPreferenceAdapter.java +++ b/src/com/android/settings/widget/HighlightableTopLevelPreferenceAdapter.java @@ -78,12 +78,10 @@ public class HighlightableTopLevelPreferenceAdapter extends PreferenceGroupAdapt Context context = preferenceGroup.getContext(); mTitleColorNormal = Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary); - mTitleColorHighlight = Utils.getColorAttrDefaultColor(context, - android.R.attr.textColorPrimaryInverse); + mTitleColorHighlight = context.getColor(R.color.accent_select_primary_text); mSummaryColorNormal = Utils.getColorAttrDefaultColor(context, android.R.attr.textColorSecondary); - mSummaryColorHighlight = Utils.getColorAttrDefaultColor(context, - android.R.attr.textColorSecondaryInverse); + mSummaryColorHighlight = context.getColor(R.color.accent_select_secondary_text); mIconColorNormal = Utils.getHomepageIconColor(context); mIconColorHighlight = Utils.getHomepageIconColorHighlight(context); } diff --git a/tests/robotests/src/com/android/settings/dashboard/profileselector/ProfileSelectDialogTest.java b/tests/robotests/src/com/android/settings/dashboard/profileselector/ProfileSelectDialogTest.java index 53cddb1b056..e1cf52b995b 100644 --- a/tests/robotests/src/com/android/settings/dashboard/profileselector/ProfileSelectDialogTest.java +++ b/tests/robotests/src/com/android/settings/dashboard/profileselector/ProfileSelectDialogTest.java @@ -23,21 +23,29 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.Dialog; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.UserInfo; import android.os.UserHandle; import android.os.UserManager; +import android.widget.TextView; +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.R; import com.android.settingslib.drawer.ActivityTile; import com.android.settingslib.drawer.CategoryKey; import com.android.settingslib.drawer.Tile; +import com.google.android.collect.Lists; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.Spy; import org.robolectric.RobolectricTestRunner; @RunWith(RobolectricTestRunner.class) @@ -46,8 +54,9 @@ public class ProfileSelectDialogTest { private static final UserHandle NORMAL_USER = new UserHandle(1111); private static final UserHandle REMOVED_USER = new UserHandle(2222); - @Mock - private Context mContext; + @Spy + private Context mContext = ApplicationProvider.getApplicationContext(); + @Mock private UserManager mUserManager; @@ -91,4 +100,18 @@ public class ProfileSelectDialogTest { verify(mUserManager, times(1)).getUserInfo(NORMAL_USER.getIdentifier()); verify(mUserManager, times(2)).getUserInfo(REMOVED_USER.getIdentifier()); } + + @Test + public void createDialog_showsCorrectTitle() { + mContext.setTheme(R.style.Theme_AppCompat); + + Dialog dialog = ProfileSelectDialog.createDialog(mContext, Lists.newArrayList(NORMAL_USER), + (position) -> { + }); + dialog.show(); + + TextView titleView = dialog.findViewById(R.id.topPanel).findViewById(android.R.id.title); + assertThat(titleView.getText().toString()).isEqualTo( + mContext.getText(com.android.settingslib.R.string.choose_profile).toString()); + } } diff --git a/tests/robotests/src/com/android/settings/dashboard/profileselector/UserAdapterTest.java b/tests/robotests/src/com/android/settings/dashboard/profileselector/UserAdapterTest.java new file mode 100644 index 00000000000..aa7e30af809 --- /dev/null +++ b/tests/robotests/src/com/android/settings/dashboard/profileselector/UserAdapterTest.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2022 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.dashboard.profileselector; + +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.when; + +import android.content.Context; +import android.content.pm.UserInfo; +import android.os.UserHandle; +import android.os.UserManager; +import android.widget.FrameLayout; +import android.widget.TextView; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.internal.widget.RecyclerView; +import com.android.settingslib.R; + +import com.google.android.collect.Lists; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; + +import java.util.ArrayList; + +@RunWith(RobolectricTestRunner.class) +public class UserAdapterTest { + @Rule + public MockitoRule mRule = MockitoJUnit.rule(); + + private final int mPersonalUserId = UserHandle.myUserId(); + private static final int WORK_USER_ID = 1; + + @Mock + private UserManager mUserManager; + + @Mock + private UserInfo mPersonalUserInfo; + + @Mock + private UserInfo mWorkUserInfo; + + @Mock + private UserAdapter.OnClickListener mOnClickListener; + + @Spy + private Context mContext = ApplicationProvider.getApplicationContext(); + + @Before + public void setUp() { + when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager); + when(mUserManager.getUserInfo(mPersonalUserId)).thenReturn(mPersonalUserInfo); + when(mUserManager.getUserInfo(WORK_USER_ID)).thenReturn(mWorkUserInfo); + } + + @Test + public void createUserSpinnerAdapter_singleProfile_returnsNull() { + when(mUserManager.getUserProfiles()).thenReturn( + Lists.newArrayList(UserHandle.of(mPersonalUserId))); + + UserAdapter userSpinnerAdapter = + UserAdapter.createUserSpinnerAdapter(mUserManager, mContext); + + assertThat(userSpinnerAdapter).isNull(); + } + + @Test + public void createUserSpinnerAdapter_twoProfiles_succeed() { + when(mUserManager.getUserProfiles()).thenReturn( + Lists.newArrayList(UserHandle.of(mPersonalUserId), UserHandle.of(WORK_USER_ID))); + + UserAdapter userSpinnerAdapter = + UserAdapter.createUserSpinnerAdapter(mUserManager, mContext); + + assertThat(userSpinnerAdapter.getCount()).isEqualTo(2); + assertThat(userSpinnerAdapter.getUserHandle(0).getIdentifier()).isEqualTo(mPersonalUserId); + assertThat(userSpinnerAdapter.getUserHandle(1).getIdentifier()).isEqualTo(WORK_USER_ID); + } + + @Test + public void createUserRecycleViewAdapter_canBindViewHolderCorrectly() { + ArrayList userHandles = + Lists.newArrayList(UserHandle.of(mPersonalUserId), UserHandle.of(WORK_USER_ID)); + FrameLayout parent = new FrameLayout(mContext); + + RecyclerView.Adapter adapter = + UserAdapter.createUserRecycleViewAdapter(mContext, userHandles, mOnClickListener); + UserAdapter.ViewHolder holder = adapter.createViewHolder(parent, 0); + adapter.bindViewHolder(holder, 0); + holder.itemView.findViewById(R.id.button).performClick(); + + assertThat(adapter.getItemCount()).isEqualTo(2); + TextView textView = holder.itemView.findViewById(android.R.id.title); + assertThat(textView.getText().toString()).isEqualTo("Personal"); + verify(mOnClickListener).onClick(anyInt()); + } +} diff --git a/tests/unit/src/com/android/settings/display/ScreenSaverPreferenceControllerTest.java b/tests/unit/src/com/android/settings/display/ScreenSaverPreferenceControllerTest.java new file mode 100644 index 00000000000..3319e2a710d --- /dev/null +++ b/tests/unit/src/com/android/settings/display/ScreenSaverPreferenceControllerTest.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2022 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.display; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.res.Resources; +import android.os.UserManager; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; + +@RunWith(AndroidJUnit4.class) +public class ScreenSaverPreferenceControllerTest { + @Spy + private final Context mContext = ApplicationProvider.getApplicationContext(); + @Spy + private final Resources mResources = mContext.getResources(); + @Mock + private UserManager mUserManager; + + private ScreenSaverPreferenceController mController; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + + mController = new ScreenSaverPreferenceController(mContext); + + when(mContext.getResources()).thenReturn(mResources); + when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager); + } + + @Test + public void isAvailable_dreamsEnabledForAllUsers_shouldBeTrueForSystemUser() { + when(mResources.getBoolean( + com.android.internal.R.bool.config_dreamsSupported)).thenReturn(true); + when(mResources.getBoolean( + com.android.internal.R.bool.config_dreamsOnlyEnabledForSystemUser)) + .thenReturn(false); + when(mUserManager.isSystemUser()).thenReturn(true); + assertTrue(mController.isAvailable()); + } + + @Test + public void isAvailable_dreamsEnabledForAllUsers_shouldBeTrueForNonSystemUser() { + when(mResources.getBoolean( + com.android.internal.R.bool.config_dreamsSupported)).thenReturn(true); + when(mResources.getBoolean( + com.android.internal.R.bool.config_dreamsOnlyEnabledForSystemUser)) + .thenReturn(false); + when(mUserManager.isSystemUser()).thenReturn(false); + assertTrue(mController.isAvailable()); + } + + @Test + public void isAvailable_dreamsDisabled_shouldBeFalseForSystemUser() { + when(mResources.getBoolean( + com.android.internal.R.bool.config_dreamsSupported)).thenReturn(false); + when(mResources.getBoolean( + com.android.internal.R.bool.config_dreamsOnlyEnabledForSystemUser)) + .thenReturn(false); + when(mUserManager.isSystemUser()).thenReturn(true); + assertFalse(mController.isAvailable()); + } + + @Test + public void isAvailable_dreamsOnlyEnabledForSystemUser_shouldBeTrueForSystemUser() { + when(mResources.getBoolean( + com.android.internal.R.bool.config_dreamsSupported)).thenReturn(true); + when(mResources.getBoolean( + com.android.internal.R.bool.config_dreamsOnlyEnabledForSystemUser)) + .thenReturn(true); + when(mUserManager.isSystemUser()).thenReturn(true); + assertTrue(mController.isAvailable()); + } + + @Test + public void isAvailable_dreamsOnlyEnabledForSystemUser_shouldBeFalseForNonSystemUser() { + when(mResources.getBoolean( + com.android.internal.R.bool.config_dreamsSupported)).thenReturn(true); + when(mResources.getBoolean( + com.android.internal.R.bool.config_dreamsOnlyEnabledForSystemUser)) + .thenReturn(true); + when(mUserManager.isSystemUser()).thenReturn(false); + assertFalse(mController.isAvailable()); + } +} diff --git a/tests/unit/src/com/android/settings/network/helper/LifecycleCallbackIntentReceiverTest.java b/tests/unit/src/com/android/settings/network/helper/LifecycleCallbackIntentReceiverTest.java new file mode 100644 index 00000000000..c85937d3a34 --- /dev/null +++ b/tests/unit/src/com/android/settings/network/helper/LifecycleCallbackIntentReceiverTest.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2022 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.network.helper; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Handler; +import android.os.HandlerThread; + +import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.LifecycleRegistry; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.function.Consumer; + +@RunWith(AndroidJUnit4.class) +public class LifecycleCallbackIntentReceiverTest implements LifecycleOwner { + + private final LifecycleRegistry mRegistry = LifecycleRegistry.createUnsafe(this); + + private static final String TEST_SCHEDULER_HANDLER = "testScheduler"; + private static final String TEST_INTENT_ACTION = "testAction"; + private static final String TEST_INTENT_PERMISSION = "testPermission"; + + private Context mContext; + private Intent mIntent; + private IntentFilter mIntentFilter; + private Handler mHandler; + private TestConsumer mConsumer; + + private TestObj mTarget; + + @Before + public void setUp() { + mContext = ApplicationProvider.getApplicationContext(); + + mIntentFilter = new IntentFilter(TEST_INTENT_ACTION); + mIntent = new Intent(TEST_INTENT_ACTION); + + HandlerThread thread = new HandlerThread(TEST_SCHEDULER_HANDLER); + thread.start(); + + mHandler = new Handler(thread.getLooper()); + mConsumer = new TestConsumer(); + + mTarget = new TestObj(getLifecycle(), mContext, + mIntentFilter, TEST_INTENT_PERMISSION, + mHandler, mConsumer); + } + + public Lifecycle getLifecycle() { + return mRegistry; + } + + @Test + public void receiver_register_whenActive() { + mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE); + + assertThat(mTarget.getCallbackActiveCount(true) + + mTarget.getCallbackActiveCount(false)).isEqualTo(0); + + mTarget.mReceiver.onReceive(mContext, mIntent); + + assertThat(mConsumer.getCallbackCount()).isEqualTo(0); + + mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START); + + assertThat(mTarget.getCallbackActiveCount(true)).isEqualTo(1); + assertThat(mConsumer.getCallbackCount()).isEqualTo(0); + + mTarget.mReceiver.onReceive(mContext, mIntent); + + assertThat(mConsumer.getCallbackCount()).isEqualTo(1); + assertThat(mConsumer.getData()).isEqualTo(mIntent); + } + + @Test + public void receiver_unregister_whenInActive() { + mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE); + mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START); + mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP); + + assertThat(mTarget.getCallbackActiveCount(false)).isEqualTo(1); + + mTarget.mReceiver.onReceive(mContext, mIntent); + + assertThat(mConsumer.getCallbackCount()).isEqualTo(0); + } + + @Test + public void receiver_register_whenReActive() { + mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE); + mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START); + mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP); + mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START); + + assertThat(mTarget.getCallbackActiveCount(true)).isEqualTo(2); + + mTarget.mReceiver.onReceive(mContext, mIntent); + + assertThat(mConsumer.getCallbackCount()).isEqualTo(1); + assertThat(mConsumer.getData()).isEqualTo(mIntent); + } + + @Test + public void receiver_close_whenDestroy() { + mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE); + mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START); + mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP); + mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY); + + assertThat(mTarget.getCallbackActiveCount(false)).isEqualTo(1); + + mTarget.mReceiver.onReceive(mContext, mIntent); + + assertThat(mConsumer.getCallbackCount()).isEqualTo(0); + } + + public static class TestConsumer implements Consumer { + long mNumberOfCallback; + Intent mLatestData; + + public TestConsumer() {} + + public void accept(Intent data) { + mLatestData = data; + mNumberOfCallback ++; + } + + protected long getCallbackCount() { + return mNumberOfCallback; + } + + protected Intent getData() { + return mLatestData; + } + } + + public static class TestObj extends LifecycleCallbackIntentReceiver { + long mCallbackActiveCount; + long mCallbackInActiveCount; + + public TestObj(Lifecycle lifecycle, Context context, IntentFilter filter, + String broadcastPermission, Handler scheduler, Consumer resultCallback) { + super(lifecycle, context, filter, broadcastPermission, scheduler, resultCallback); + } + + @Override + public void setCallbackActive(boolean isActive) { + if (isActive) { + mCallbackActiveCount ++; + } else { + mCallbackInActiveCount ++; + } + super.setCallbackActive(isActive); + } + + protected long getCallbackActiveCount(boolean forActive) { + return forActive ? mCallbackActiveCount : mCallbackInActiveCount; + } + } +} diff --git a/tests/unit/src/com/android/settings/network/helper/LifecycleCallbackTelephonyAdapterTest.java b/tests/unit/src/com/android/settings/network/helper/LifecycleCallbackTelephonyAdapterTest.java new file mode 100644 index 00000000000..be940f2fa95 --- /dev/null +++ b/tests/unit/src/com/android/settings/network/helper/LifecycleCallbackTelephonyAdapterTest.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2022 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.network.helper; + +import static org.mockito.ArgumentMatchers.anyObject; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.telephony.TelephonyCallback; +import android.telephony.TelephonyManager; + +import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.LifecycleRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.concurrent.atomic.AtomicReference; + +@RunWith(AndroidJUnit4.class) +public class LifecycleCallbackTelephonyAdapterTest implements LifecycleOwner { + + private final LifecycleRegistry mRegistry = LifecycleRegistry.createUnsafe(this); + + @Mock + private TelephonyManager mTelMgr; + + private TestCallback mTestCallback; + private AtomicReference mResult; + private LifecycleCallbackTelephonyAdapter mAdapter; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mResult = new AtomicReference(); + mTestCallback = new TestCallback(); + + doNothing().when(mTelMgr).registerTelephonyCallback(null, mTestCallback); + doNothing().when(mTelMgr).unregisterTelephonyCallback(mTestCallback); + + mAdapter = new LifecycleCallbackTelephonyAdapter(getLifecycle(), mTelMgr, + mTestCallback, null, result -> mResult.set(result)); + } + + public Lifecycle getLifecycle() { + return mRegistry; + } + + @Test + public void telephonyCallback_register_whenActive() { + mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE); + + verify(mTelMgr, never()).registerTelephonyCallback(anyObject(), anyObject()); + + mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START); + + verify(mTelMgr).registerTelephonyCallback(anyObject(), anyObject()); + } + + @Test + public void telephonyCallback_unregister_whenInActive() { + mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE); + + verify(mTelMgr, never()).unregisterTelephonyCallback(anyObject()); + + mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START); + + verify(mTelMgr, never()).unregisterTelephonyCallback(anyObject()); + + mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP); + + verify(mTelMgr).unregisterTelephonyCallback(anyObject()); + + mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY); + + verify(mTelMgr, times(1)).unregisterTelephonyCallback(anyObject()); + } + + protected static class TestCallback extends TelephonyCallback + implements TelephonyCallback.CallStateListener { + @Override + public void onCallStateChanged(int state) {} + } +}