diff --git a/AndroidManifest.xml b/AndroidManifest.xml index ae87edb5628..71c07a05148 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -4474,6 +4474,16 @@ + + + + + + + diff --git a/res/drawable/ic_qr_code_scanner.xml b/res/drawable/ic_qr_code_scanner.xml new file mode 100644 index 00000000000..f6f63c5ae7f --- /dev/null +++ b/res/drawable/ic_qr_code_scanner.xml @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/res/layout/qrcode_scan_mode_activity.xml b/res/layout/qrcode_scan_mode_activity.xml new file mode 100644 index 00000000000..f0a182b3d67 --- /dev/null +++ b/res/layout/qrcode_scan_mode_activity.xml @@ -0,0 +1,30 @@ + + + + + + + + diff --git a/res/layout/qrcode_scanner_fragment.xml b/res/layout/qrcode_scanner_fragment.xml new file mode 100644 index 00000000000..2c543f23abf --- /dev/null +++ b/res/layout/qrcode_scanner_fragment.xml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/layout/wifi_calling_settings_preferences.xml b/res/layout/wifi_calling_settings_preferences.xml index 9a6cbe6698b..bad90ad0434 100644 --- a/res/layout/wifi_calling_settings_preferences.xml +++ b/res/layout/wifi_calling_settings_preferences.xml @@ -21,12 +21,6 @@ android:layout_height="match_parent" android:orientation="vertical"> - - 24dp 8dp + + + 40dp + 30dp + 27dp diff --git a/res/values/strings.xml b/res/values/strings.xml index 9ab0cc2d301..1d01f14bb8e 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -518,6 +518,9 @@ Language may differ from languages available in the app. Some apps may not support this setting. + + Only apps that support language selection are shown here. + Remove selected language? @@ -1297,7 +1300,10 @@ Encryption, credentials, and more security, more security settings, more settings, advanced security settings - + + More privacy settings + + Autofill, activity controls, and more You can add up to %d fingerprints @@ -8973,6 +8979,9 @@ Work notifications + + Work profile + Adaptive notifications @@ -13649,7 +13658,9 @@ mobile data - To improve device experience, apps and services can still scan for Wi\u2011Fi networks at any time, even when Wi\u2011Fi is off. This can be used, for example, to improve location-based features and services. You can change this in Wi\u2011Fi scanning settings. Change + To improve device experience, apps and services can still scan for Wi\u2011Fi networks at any time, even when Wi\u2011Fi is off. This can be used, for example, to improve location-based features and services. You can change this in Wi\u2011Fi scanning settings. + + Change @@ -14163,4 +14174,11 @@ Can\u2019t connect. Try again. Wrong password + + + + To start listening, center the QR code below + + QR code isn\u0027t a valid format + diff --git a/res/values/styles.xml b/res/values/styles.xml index 7a879931a00..f147ce98903 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -962,4 +962,11 @@ 0dp false + + diff --git a/res/xml/bluetooth_device_details_fragment.xml b/res/xml/bluetooth_device_details_fragment.xml index f6c0af662aa..b44a93db62f 100644 --- a/res/xml/bluetooth_device_details_fragment.xml +++ b/res/xml/bluetooth_device_details_fragment.xml @@ -46,6 +46,11 @@ android:key="action_buttons" settings:allowDividerBelow="true"/> + + + android:key="usage_amount" + android:title="@string/summary_placeholder"> diff --git a/res/xml/privacy_advanced_settings.xml b/res/xml/privacy_advanced_settings.xml new file mode 100644 index 00000000000..9f465d45566 --- /dev/null +++ b/res/xml/privacy_advanced_settings.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/xml/wifi_calling_settings.xml b/res/xml/wifi_calling_settings.xml index 902ff1af2fa..c45f702ca13 100644 --- a/res/xml/wifi_calling_settings.xml +++ b/res/xml/wifi_calling_settings.xml @@ -19,6 +19,10 @@ android:key="wifi_calling_settings" android:title="@string/wifi_calling_settings_title"> + + 0: Personal tab. @@ -427,7 +430,14 @@ public class SettingsActivity extends SettingsBaseActivity } try { - startActivity(trampolineIntent); + final UserManager um = getSystemService(UserManager.class); + final UserInfo userInfo = um.getUserInfo(getUser().getIdentifier()); + if (userInfo.isManagedProfile()) { + trampolineIntent.putExtra(EXTRA_USER_HANDLE, getUser()); + startActivityAsUser(trampolineIntent, um.getPrimaryUser().getUserHandle()); + } else { + startActivity(trampolineIntent); + } } catch (ActivityNotFoundException e) { Log.e(LOG_TAG, "Deep link homepage is not available to show 2-pane UI"); return false; diff --git a/src/com/android/settings/accessibility/TextReadingPreferenceFragmentForSetupWizard.java b/src/com/android/settings/accessibility/TextReadingPreferenceFragmentForSetupWizard.java index 930fbe4c9c7..6ead3907a28 100644 --- a/src/com/android/settings/accessibility/TextReadingPreferenceFragmentForSetupWizard.java +++ b/src/com/android/settings/accessibility/TextReadingPreferenceFragmentForSetupWizard.java @@ -27,8 +27,11 @@ import androidx.recyclerview.widget.RecyclerView; import com.android.settings.R; import com.android.settingslib.Utils; +import com.android.settingslib.widget.LayoutPreference; import com.google.android.setupdesign.GlifPreferenceLayout; +import com.google.android.setupdesign.util.LayoutStyler; + /** * A {@link androidx.preference.PreferenceFragmentCompat} that displays the settings page related @@ -47,6 +50,8 @@ public class TextReadingPreferenceFragmentForSetupWizard extends TextReadingPref icon.setTintList(Utils.getColorAttr(getContext(), android.R.attr.colorPrimary)); AccessibilitySetupWizardUtils.updateGlifPreferenceLayout(getContext(), layout, title, /* description= */ null, icon); + + updateResetButtonPadding(); } @Override @@ -66,4 +71,14 @@ public class TextReadingPreferenceFragmentForSetupWizard extends TextReadingPref // Hides help center in action bar and footer bar in SuW return 0; } + + /** + * Updates the padding of the reset button to meet for SetupWizard style. + */ + private void updateResetButtonPadding() { + final LayoutPreference resetPreference = (LayoutPreference) findPreference(RESET_KEY); + final ViewGroup parentView = + (ViewGroup) resetPreference.findViewById(R.id.reset_button).getParent(); + LayoutStyler.applyPartnerCustomizationLayoutPaddingStyle(parentView); + } } diff --git a/src/com/android/settings/accessibility/ToggleScreenReaderPreferenceFragmentForSetupWizard.java b/src/com/android/settings/accessibility/ToggleScreenReaderPreferenceFragmentForSetupWizard.java index f9a1113c054..0af8aa17524 100644 --- a/src/com/android/settings/accessibility/ToggleScreenReaderPreferenceFragmentForSetupWizard.java +++ b/src/com/android/settings/accessibility/ToggleScreenReaderPreferenceFragmentForSetupWizard.java @@ -49,6 +49,8 @@ public class ToggleScreenReaderPreferenceFragmentForSetupWizard if (mTopIntroPreference != null) { mTopIntroPreference.setVisible(false); } + + mToggleServiceSwitchPreference.applyPartnerCustomizationPaddingStyle(); } @Override diff --git a/src/com/android/settings/applications/manageapplications/ApplicationViewHolder.java b/src/com/android/settings/applications/manageapplications/ApplicationViewHolder.java index ef5b029a9d0..b91057865e4 100644 --- a/src/com/android/settings/applications/manageapplications/ApplicationViewHolder.java +++ b/src/com/android/settings/applications/manageapplications/ApplicationViewHolder.java @@ -85,6 +85,14 @@ public class ApplicationViewHolder extends RecyclerView.ViewHolder { return view; } + static View newHeader(ViewGroup parent, int resText) { + ViewGroup view = (ViewGroup) LayoutInflater.from(parent.getContext()) + .inflate(R.layout.preference_app_header, parent, false); + TextView textView = view.findViewById(R.id.apps_top_intro_text); + textView.setText(resText); + return view; + } + void setSummary(CharSequence summary) { mSummary.setText(summary); } diff --git a/src/com/android/settings/applications/manageapplications/ManageApplications.java b/src/com/android/settings/applications/manageapplications/ManageApplications.java index a6abd10374f..24328a277e4 100644 --- a/src/com/android/settings/applications/manageapplications/ManageApplications.java +++ b/src/com/android/settings/applications/manageapplications/ManageApplications.java @@ -824,14 +824,16 @@ public class ManageApplications extends InstrumentedFragment if (mApplications == null) { return; } - final int position = mRecyclerView.getChildAdapterPosition(view); + final int applicationPosition = + ApplicationsAdapter.getApplicationPosition( + mListType, mRecyclerView.getChildAdapterPosition(view)); - if (position == RecyclerView.NO_POSITION) { + if (applicationPosition == RecyclerView.NO_POSITION) { Log.w(TAG, "Cannot find position for child, skipping onClick handling"); return; } - if (mApplications.getApplicationCount() > position) { - ApplicationsState.AppEntry entry = mApplications.getAppEntry(position); + if (mApplications.getApplicationCount() > applicationPosition) { + ApplicationsState.AppEntry entry = mApplications.getAppEntry(applicationPosition); mCurrentPkgName = entry.info.packageName; mCurrentUid = entry.info.uid; startApplicationDetailsActivity(); @@ -1058,6 +1060,7 @@ public class ManageApplications extends InstrumentedFragment private static final String STATE_LAST_SCROLL_INDEX = "state_last_scroll_index"; private static final int VIEW_TYPE_APP = 0; private static final int VIEW_TYPE_EXTRA_VIEW = 1; + private static final int VIEW_TYPE_APP_HEADER = 2; private final ApplicationsState mState; private final ApplicationsState.Session mSession; @@ -1229,7 +1232,11 @@ public class ManageApplications extends InstrumentedFragment @Override public ApplicationViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { final View view; - if (mManageApplications.mListType == LIST_TYPE_NOTIFICATION) { + if (mManageApplications.mListType == LIST_TYPE_APPS_LOCALE + && viewType == VIEW_TYPE_APP_HEADER) { + view = ApplicationViewHolder.newHeader(parent, + R.string.desc_app_locale_selection_supported); + } else if (mManageApplications.mListType == LIST_TYPE_NOTIFICATION) { view = ApplicationViewHolder.newView(parent, true /* twoTarget */); } else { view = ApplicationViewHolder.newView(parent, false /* twoTarget */); @@ -1239,6 +1246,9 @@ public class ManageApplications extends InstrumentedFragment @Override public int getItemViewType(int position) { + if (position == 0 && mManageApplications.mListType == LIST_TYPE_APPS_LOCALE) { + return VIEW_TYPE_APP_HEADER; + } return VIEW_TYPE_APP; } @@ -1472,10 +1482,11 @@ public class ManageApplications extends InstrumentedFragment @Override public int getItemCount() { - if (mEntries == null) { - return 0; + int count = getApplicationCount(); + if (count != 0 && mManageApplications.mListType == LIST_TYPE_APPS_LOCALE) { + count++; } - return mEntries.size(); + return count; } public int getApplicationCount() { @@ -1483,15 +1494,18 @@ public class ManageApplications extends InstrumentedFragment } public AppEntry getAppEntry(int position) { - return mEntries.get(position); + return mEntries.get( + getApplicationPosition(mManageApplications.mListType, position)); } @Override public long getItemId(int position) { - if (position == mEntries.size()) { + int applicationPosition = + getApplicationPosition(mManageApplications.mListType, position); + if (applicationPosition == mEntries.size()) { return -1; } - return mEntries.get(position).id; + return mEntries.get(applicationPosition).id; } public boolean isEnabled(int position) { @@ -1499,7 +1513,9 @@ public class ManageApplications extends InstrumentedFragment || mManageApplications.mListType != LIST_TYPE_HIGH_POWER) { return true; } - ApplicationsState.AppEntry entry = mEntries.get(position); + ApplicationsState.AppEntry entry = + mEntries.get( + getApplicationPosition(mManageApplications.mListType, position)); return !mBackend.isSysAllowlisted(entry.info.packageName) && !mBackend.isDefaultActiveApp(entry.info.packageName); @@ -1507,8 +1523,15 @@ public class ManageApplications extends InstrumentedFragment @Override public void onBindViewHolder(ApplicationViewHolder holder, int position) { + if (getItemViewType(position) == VIEW_TYPE_APP_HEADER) { + // It does not bind holder here, due to header view. + return; + } + // Bind the data efficiently with the holder - final ApplicationsState.AppEntry entry = mEntries.get(position); + final ApplicationsState.AppEntry entry = + mEntries.get( + getApplicationPosition(mManageApplications.mListType, position)); synchronized (entry) { mState.ensureLabelDescription(entry); holder.setTitle(entry.label, entry.labelDescription); @@ -1608,6 +1631,22 @@ public class ManageApplications extends InstrumentedFragment } } + /** + * Adjusts position if this list adds a header. + * TODO(b/232533002) Add a header view on adapter of RecyclerView may not a good idea since + * ManageApplication is a generic purpose. In the future, here shall look for + * a better way to add a header without using recyclerView or any other ways + * to achieve the goal. + */ + public static int getApplicationPosition(int listType, int position) { + int applicationPosition = position; + // Adjust position due to header added. + if (position > 0 && listType == LIST_TYPE_APPS_LOCALE) { + applicationPosition = position - 1; + } + return applicationPosition; + } + public static class OnScrollListener extends RecyclerView.OnScrollListener { private int mScrollState = SCROLL_STATE_IDLE; private boolean mDelayNotifyDataChange; diff --git a/src/com/android/settings/bluetooth/BluetoothBroadcastSourcePreference.java b/src/com/android/settings/bluetooth/BluetoothBroadcastSourcePreference.java index 17b604c58ff..733a4a96e49 100644 --- a/src/com/android/settings/bluetooth/BluetoothBroadcastSourcePreference.java +++ b/src/com/android/settings/bluetooth/BluetoothBroadcastSourcePreference.java @@ -16,7 +16,9 @@ package com.android.settings.bluetooth; +import android.bluetooth.BluetoothLeAudioContentMetadata; import android.bluetooth.BluetoothLeBroadcastMetadata; +import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.bluetooth.BluetoothLeBroadcastSubgroup; import android.content.Context; import android.graphics.drawable.Drawable; @@ -43,16 +45,15 @@ class BluetoothBroadcastSourcePreference extends Preference { private static final int RESOURCE_ID_ICON = R.drawable.settings_input_antenna; private BluetoothLeBroadcastMetadata mBluetoothLeBroadcastMetadata; + private BluetoothLeBroadcastReceiveState mBluetoothLeBroadcastReceiveState; private ImageView mFrictionImageView; private String mTitle; private boolean mStatus; private boolean mIsEncrypted; - BluetoothBroadcastSourcePreference(@NonNull Context context, - @NonNull BluetoothLeBroadcastMetadata source) { + BluetoothBroadcastSourcePreference(@NonNull Context context) { super(context); initUi(); - updateMetadataAndRefreshUi(source, false); } @Override @@ -68,7 +69,7 @@ class BluetoothBroadcastSourcePreference extends Preference { private void initUi() { setLayoutResource(R.layout.preference_access_point); setWidgetLayoutResource(R.layout.access_point_friction_widget); - + mTitle = getContext().getString(RESOURCE_ID_UNKNOWN_PROGRAM_INFO); mStatus = false; final Drawable drawable = getContext().getDrawable(RESOURCE_ID_ICON); if (drawable != null) { @@ -105,9 +106,20 @@ class BluetoothBroadcastSourcePreference extends Preference { */ public void updateMetadataAndRefreshUi(BluetoothLeBroadcastMetadata source, boolean status) { mBluetoothLeBroadcastMetadata = source; - mTitle = getBroadcastMetadataProgramInfo(); + mTitle = getProgramInfo(); mIsEncrypted = mBluetoothLeBroadcastMetadata.isEncrypted(); - mStatus = status; + mStatus = status || mBluetoothLeBroadcastReceiveState != null; + + refresh(); + } + + /** + * Updates the title and status from BluetoothLeBroadcastReceiveState. + */ + public void updateReceiveStateAndRefreshUi(BluetoothLeBroadcastReceiveState receiveState) { + mBluetoothLeBroadcastReceiveState = receiveState; + mTitle = getProgramInfo(); + mStatus = true; refresh(); } @@ -124,7 +136,17 @@ class BluetoothBroadcastSourcePreference extends Preference { updateStatusButton(); } - private String getBroadcastMetadataProgramInfo() { + private String getProgramInfo() { + if (mBluetoothLeBroadcastReceiveState != null) { + List bluetoothLeAudioContentMetadata = + mBluetoothLeBroadcastReceiveState.getSubgroupMetadata(); + if (!bluetoothLeAudioContentMetadata.isEmpty()) { + return bluetoothLeAudioContentMetadata.stream() + .map(i -> i.getProgramInfo()) + .findFirst().orElse( + getContext().getString(RESOURCE_ID_UNKNOWN_PROGRAM_INFO)); + } + } if (mBluetoothLeBroadcastMetadata == null) { return getContext().getString(RESOURCE_ID_UNKNOWN_PROGRAM_INFO); } @@ -138,4 +160,24 @@ class BluetoothBroadcastSourcePreference extends Preference { .filter(i -> !TextUtils.isEmpty(i)) .findFirst().orElse(getContext().getString(RESOURCE_ID_UNKNOWN_PROGRAM_INFO)); } + + /** + * Whether the broadcast source is encrypted or not. + * @return If true, the broadcast source needs the broadcast code. If false, the broadcast + * source does not need the broadcast code. + */ + public boolean isEncrypted() { + return mIsEncrypted; + } + + /** + * Clear the BluetoothLeBroadcastReceiveState and reset the state when the user clicks the + * "leave broadcast" button. + */ + public void clearReceiveState() { + mBluetoothLeBroadcastReceiveState = null; + mTitle = getProgramInfo(); + mStatus = false; + refresh(); + } } diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java index 6d443ee4044..fdf0f383dab 100644 --- a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java +++ b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java @@ -22,12 +22,18 @@ import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH; import android.app.settings.SettingsEnums; import android.bluetooth.BluetoothDevice; import android.content.Context; +import android.net.Uri; import android.os.Bundle; import android.provider.DeviceConfig; +import android.text.TextUtils; import android.util.Log; +import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; import androidx.annotation.VisibleForTesting; @@ -36,12 +42,14 @@ import com.android.settings.core.SettingsUIDeviceConfig; import com.android.settings.dashboard.RestrictedDashboardFragment; import com.android.settings.overlay.FeatureFactory; import com.android.settings.slices.BlockingSlicePrefController; +import com.android.settings.slices.SlicePreferenceController; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.lifecycle.Lifecycle; import java.util.ArrayList; +import java.util.IllegalFormatException; import java.util.List; public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment { @@ -61,6 +69,7 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment @VisibleForTesting interface TestDataFactory { CachedBluetoothDevice getDevice(String deviceAddress); + LocalBluetoothManager getManager(Context context); } @@ -127,6 +136,49 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment use(BlockingSlicePrefController.class).setSliceUri(sliceEnabled ? featureProvider.getBluetoothDeviceSettingsUri(mCachedDevice.getDevice()) : null); + updateExtraControlUri(/* viewWidth */ 0); + } + + private void updateExtraControlUri(int viewWidth) { + BluetoothFeatureProvider featureProvider = FeatureFactory.getFactory( + getContext()).getBluetoothFeatureProvider(getContext()); + boolean sliceEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI, + SettingsUIDeviceConfig.BT_SLICE_SETTINGS_ENABLED, true); + Uri controlUri = null; + String uri = featureProvider.getBluetoothDeviceControlUri(mCachedDevice.getDevice()); + if (!TextUtils.isEmpty(uri)) { + try { + controlUri = Uri.parse(String.format(uri, viewWidth)); + } catch (IllegalFormatException | NullPointerException exception) { + Log.d(TAG, "unable to parse uri"); + controlUri = null; + } + } + use(SlicePreferenceController.class).setSliceUri(sliceEnabled ? controlUri : null); + } + + private final ViewTreeObserver.OnGlobalLayoutListener mOnGlobalLayoutListener = + new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + View view = getView(); + if (view == null) { + return; + } + updateExtraControlUri(view.getWidth()); + view.getViewTreeObserver().removeOnGlobalLayoutListener( + mOnGlobalLayoutListener); + } + }; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View view = super.onCreateView(inflater, container, savedInstanceState); + if (view != null) { + view.getViewTreeObserver().addOnGlobalLayoutListener(mOnGlobalLayoutListener); + } + return view; } @Override diff --git a/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java b/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java index 582a26c182c..51ef8e2a649 100644 --- a/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java +++ b/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java @@ -26,8 +26,17 @@ public interface BluetoothFeatureProvider { /** * Get the {@link Uri} that represents extra settings for a specific bluetooth device + * * @param bluetoothDevice bluetooth device * @return {@link Uri} for extra settings */ Uri getBluetoothDeviceSettingsUri(BluetoothDevice bluetoothDevice); + + /** + * Get the {@link Uri} that represents extra control for a specific bluetooth device + * + * @param bluetoothDevice bluetooth device + * @return {@link String} uri string for extra control + */ + String getBluetoothDeviceControlUri(BluetoothDevice bluetoothDevice); } diff --git a/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.java b/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.java index cd75951a482..04d3ec4cc1f 100644 --- a/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.java +++ b/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.java @@ -20,6 +20,8 @@ import android.bluetooth.BluetoothDevice; import android.content.Context; import android.net.Uri; +import com.android.settingslib.bluetooth.BluetoothUtils; + /** * Impl of {@link BluetoothFeatureProvider} */ @@ -37,4 +39,9 @@ public class BluetoothFeatureProviderImpl implements BluetoothFeatureProvider { BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI); return uriByte == null ? null : Uri.parse(new String(uriByte)); } + + @Override + public String getBluetoothDeviceControlUri(BluetoothDevice bluetoothDevice) { + return BluetoothUtils.getControlUriMetaData(bluetoothDevice); + } } diff --git a/src/com/android/settings/bluetooth/BluetoothFindBroadcastsFragment.java b/src/com/android/settings/bluetooth/BluetoothFindBroadcastsFragment.java index 07a31560d24..13388b3ecd3 100644 --- a/src/com/android/settings/bluetooth/BluetoothFindBroadcastsFragment.java +++ b/src/com/android/settings/bluetooth/BluetoothFindBroadcastsFragment.java @@ -86,9 +86,7 @@ public class BluetoothFindBroadcastsFragment extends RestrictedDashboardFragment @Override public void onSearchStarted(int reason) { Log.d(TAG, "onSearchStarted: " + reason); - - getActivity().runOnUiThread( - () -> cacheRemoveAllPrefs(mBroadcastSourceListCategory)); + getActivity().runOnUiThread(() -> handleSearchStarted()); } @Override @@ -109,7 +107,8 @@ public class BluetoothFindBroadcastsFragment extends RestrictedDashboardFragment @Override public void onSourceFound(@NonNull BluetoothLeBroadcastMetadata source) { Log.d(TAG, "onSourceFound:"); - getActivity().runOnUiThread(() -> updateListCategory(source, false)); + getActivity().runOnUiThread( + () -> updateListCategoryFromBroadcastMetadata(source, false)); } @Override @@ -119,7 +118,7 @@ public class BluetoothFindBroadcastsFragment extends RestrictedDashboardFragment Log.w(TAG, "onSourceAdded: mSelectedPreference == null!"); return; } - getActivity().runOnUiThread(() -> updateListCategory( + getActivity().runOnUiThread(() -> updateListCategoryFromBroadcastMetadata( mSelectedPreference.getBluetoothLeBroadcastMetadata(), true)); } @@ -144,6 +143,7 @@ public class BluetoothFindBroadcastsFragment extends RestrictedDashboardFragment public void onSourceRemoved(@NonNull BluetoothDevice sink, int sourceId, int reason) { Log.d(TAG, "onSourceRemoved:"); + getActivity().runOnUiThread(() -> handleSourceRemoved()); } @Override @@ -215,6 +215,8 @@ public class BluetoothFindBroadcastsFragment extends RestrictedDashboardFragment //check assistant status. Start searching... if (mLeBroadcastAssistant != null && !mLeBroadcastAssistant.isSearchInProgress()) { mLeBroadcastAssistant.startSearchingForSources(getScanFilter()); + } else { + addConnectedSourcePreference(); } } @@ -310,11 +312,13 @@ public class BluetoothFindBroadcastsFragment extends RestrictedDashboardFragment return Collections.emptyList(); } - private void updateListCategory(BluetoothLeBroadcastMetadata source, boolean isConnected) { + private void updateListCategoryFromBroadcastMetadata(BluetoothLeBroadcastMetadata source, + boolean isConnected) { BluetoothBroadcastSourcePreference item = mBroadcastSourceListCategory.findPreference( Integer.toString(source.getBroadcastId())); if (item == null) { - item = createBluetoothBroadcastSourcePreference(source); + item = createBluetoothBroadcastSourcePreference(); + item.setKey(Integer.toString(source.getBroadcastId())); mBroadcastSourceListCategory.addPreference(item); } item.updateMetadataAndRefreshUi(source, isConnected); @@ -326,13 +330,36 @@ public class BluetoothFindBroadcastsFragment extends RestrictedDashboardFragment } } - private BluetoothBroadcastSourcePreference createBluetoothBroadcastSourcePreference( - BluetoothLeBroadcastMetadata source) { + private void updateListCategoryFromBroadcastReceiveState( + BluetoothLeBroadcastReceiveState receiveState) { + BluetoothBroadcastSourcePreference item = mBroadcastSourceListCategory.findPreference( + Integer.toString(receiveState.getBroadcastId())); + if (item == null) { + item = createBluetoothBroadcastSourcePreference(); + item.setKey(Integer.toString(receiveState.getBroadcastId())); + mBroadcastSourceListCategory.addPreference(item); + } + item.updateReceiveStateAndRefreshUi(receiveState); + item.setOrder(0); + + setSourceId(receiveState.getSourceId()); + mSelectedPreference = item; + + //refresh the header + if (mBluetoothFindBroadcastsHeaderController != null) { + mBluetoothFindBroadcastsHeaderController.refreshUi(); + } + } + + private BluetoothBroadcastSourcePreference createBluetoothBroadcastSourcePreference() { BluetoothBroadcastSourcePreference pref = new BluetoothBroadcastSourcePreference( - getContext(), source); - pref.setKey(Integer.toString(source.getBroadcastId())); + getContext()); pref.setOnPreferenceClickListener(preference -> { - if (source.isEncrypted()) { + if (pref.getBluetoothLeBroadcastMetadata() == null) { + Log.d(TAG, "BluetoothLeBroadcastMetadata is null, do nothing."); + return false; + } + if (pref.isEncrypted()) { launchBroadcastCodeDialog(pref); } else { addSource(pref); @@ -383,6 +410,10 @@ public class BluetoothFindBroadcastsFragment extends RestrictedDashboardFragment .setPositiveButton(R.string.bluetooth_connect_access_dialog_positive, (d, w) -> { Log.d(TAG, "setPositiveButton: clicked"); + if (pref.getBluetoothLeBroadcastMetadata() == null) { + Log.d(TAG, "BluetoothLeBroadcastMetadata is null, do nothing."); + return; + } addBroadcastCodeIntoPreference(pref, editText.getText().toString()); addSource(pref); }) @@ -392,6 +423,30 @@ public class BluetoothFindBroadcastsFragment extends RestrictedDashboardFragment alertDialog.show(); } + private void handleSearchStarted() { + cacheRemoveAllPrefs(mBroadcastSourceListCategory); + addConnectedSourcePreference(); + } + + private void handleSourceRemoved() { + if (mSelectedPreference != null) { + if (mSelectedPreference.getBluetoothLeBroadcastMetadata() == null) { + mBroadcastSourceListCategory.removePreference(mSelectedPreference); + } else { + mSelectedPreference.clearReceiveState(); + } + } + mSelectedPreference = null; + } + + private void addConnectedSourcePreference() { + List receiveStateList = + mLeBroadcastAssistant.getAllSources(mCachedDevice.getDevice()); + if (!receiveStateList.isEmpty()) { + updateListCategoryFromBroadcastReceiveState(receiveStateList.get(0)); + } + } + public int getSourceId() { return mSourceId; } diff --git a/src/com/android/settings/bluetooth/BluetoothFindBroadcastsHeaderController.java b/src/com/android/settings/bluetooth/BluetoothFindBroadcastsHeaderController.java index 1527f2145fe..1282abda18e 100644 --- a/src/com/android/settings/bluetooth/BluetoothFindBroadcastsHeaderController.java +++ b/src/com/android/settings/bluetooth/BluetoothFindBroadcastsHeaderController.java @@ -33,7 +33,6 @@ import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.core.lifecycle.Lifecycle; -import com.android.settingslib.qrcode.QrCodeScanModeActivity; import com.android.settingslib.widget.LayoutPreference; /** @@ -135,7 +134,7 @@ public class BluetoothFindBroadcastsHeaderController extends BluetoothDetailsCon private void launchQrCodeScanner() { final Intent intent = new Intent(mContext, QrCodeScanModeActivity.class); intent.setAction(BluetoothBroadcastUtils.ACTION_BLUETOOTH_LE_AUDIO_QR_CODE_SCANNER) - .putExtra(BluetoothBroadcastUtils.EXTRA_BLUETOOTH_SINK_IS_GROUP, false) + .putExtra(BluetoothBroadcastUtils.EXTRA_BLUETOOTH_SINK_IS_GROUP, true) .putExtra(BluetoothBroadcastUtils.EXTRA_BLUETOOTH_DEVICE_SINK, mCachedDevice.getDevice()); mContext.startActivity(intent); diff --git a/src/com/android/settings/bluetooth/QrCodeScanModeActivity.java b/src/com/android/settings/bluetooth/QrCodeScanModeActivity.java new file mode 100644 index 00000000000..5c5b61f091c --- /dev/null +++ b/src/com/android/settings/bluetooth/QrCodeScanModeActivity.java @@ -0,0 +1,111 @@ +/** + * 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.bluetooth; + +import static com.android.settingslib.bluetooth.BluetoothBroadcastUtils.EXTRA_BLUETOOTH_DEVICE_SINK; +import static com.android.settingslib.bluetooth.BluetoothBroadcastUtils.EXTRA_BLUETOOTH_SINK_IS_GROUP; + +import android.bluetooth.BluetoothDevice; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; + +import androidx.fragment.app.FragmentTransaction; + +import com.android.settingslib.R; +import com.android.settingslib.bluetooth.BluetoothBroadcastUtils; +import com.android.settingslib.bluetooth.BluetoothUtils; + +//TODO (b/232365943): Add test case for tthe QrCode UI. +public class QrCodeScanModeActivity extends QrCodeScanModeBaseActivity { + private static final boolean DEBUG = BluetoothUtils.D; + private static final String TAG = "QrCodeScanModeActivity"; + + private boolean mIsGroupOp; + private BluetoothDevice mSink; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + protected void handleIntent(Intent intent) { + String action = intent != null ? intent.getAction() : null; + if (DEBUG) { + Log.d(TAG, "handleIntent(), action = " + action); + } + + if (action == null) { + finish(); + return; + } + + switch (action) { + case BluetoothBroadcastUtils.ACTION_BLUETOOTH_LE_AUDIO_QR_CODE_SCANNER: + showQrCodeScannerFragment(intent); + break; + default: + if (DEBUG) { + Log.e(TAG, "Launch with an invalid action"); + } + finish(); + } + } + + protected void showQrCodeScannerFragment(Intent intent) { + if (intent == null) { + if (DEBUG) { + Log.d(TAG, "intent is null, can not get bluetooth information from intent."); + } + return; + } + + if (DEBUG) { + Log.d(TAG, "showQrCodeScannerFragment"); + } + + mSink = intent.getParcelableExtra(EXTRA_BLUETOOTH_DEVICE_SINK); + mIsGroupOp = intent.getBooleanExtra(EXTRA_BLUETOOTH_SINK_IS_GROUP, false); + if (DEBUG) { + Log.d(TAG, "get extra from intent"); + } + + QrCodeScanModeFragment fragment = + (QrCodeScanModeFragment) mFragmentManager.findFragmentByTag( + BluetoothBroadcastUtils.TAG_FRAGMENT_QR_CODE_SCANNER); + + if (fragment == null) { + fragment = new QrCodeScanModeFragment(mIsGroupOp, mSink); + } else { + if (fragment.isVisible()) { + return; + } + + // When the fragment in back stack but not on top of the stack, we can simply pop + // stack because current fragment transactions are arranged in an order + mFragmentManager.popBackStackImmediate(); + return; + } + final FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction(); + + fragmentTransaction.replace(R.id.fragment_container, fragment, + BluetoothBroadcastUtils.TAG_FRAGMENT_QR_CODE_SCANNER); + fragmentTransaction.commit(); + } +} + diff --git a/src/com/android/settings/bluetooth/QrCodeScanModeBaseActivity.java b/src/com/android/settings/bluetooth/QrCodeScanModeBaseActivity.java new file mode 100644 index 00000000000..af8a6e9d97f --- /dev/null +++ b/src/com/android/settings/bluetooth/QrCodeScanModeBaseActivity.java @@ -0,0 +1,46 @@ +/** + * 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.bluetooth; + +import android.content.Intent; +import android.os.Bundle; + +import androidx.fragment.app.FragmentManager; + +import com.android.settingslib.R; +import com.android.settingslib.core.lifecycle.ObservableActivity; + +public abstract class QrCodeScanModeBaseActivity extends ObservableActivity { + + protected FragmentManager mFragmentManager; + + protected abstract void handleIntent(Intent intent); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setTheme(R.style.SudThemeGlifV3_DayNight); + + setContentView(R.layout.qrcode_scan_mode_activity); + mFragmentManager = getSupportFragmentManager(); + + if (savedInstanceState == null) { + handleIntent(getIntent()); + } + } +} diff --git a/src/com/android/settings/bluetooth/QrCodeScanModeController.java b/src/com/android/settings/bluetooth/QrCodeScanModeController.java new file mode 100644 index 00000000000..4504b4b71aa --- /dev/null +++ b/src/com/android/settings/bluetooth/QrCodeScanModeController.java @@ -0,0 +1,63 @@ +/** + * 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.bluetooth; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothLeBroadcastMetadata; +import android.content.Context; +import android.util.Log; + +import com.android.settingslib.bluetooth.BluetoothUtils; +import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; +import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; +import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastMetadata; +import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; + +public class QrCodeScanModeController { + + private static final boolean DEBUG = BluetoothUtils.D; + private static final String TAG = "QrCodeScanModeController"; + + private LocalBluetoothLeBroadcastMetadata mLocalBroadcastMetadata; + private LocalBluetoothLeBroadcastAssistant mLocalBroadcastAssistant; + private LocalBluetoothManager mLocalBluetoothManager; + private LocalBluetoothProfileManager mProfileManager; + + public QrCodeScanModeController(Context context) { + if (DEBUG) { + Log.d(TAG, "QrCodeScanModeController constructor."); + } + mLocalBluetoothManager = Utils.getLocalBtManager(context); + mProfileManager = mLocalBluetoothManager.getProfileManager(); + mLocalBroadcastMetadata = new LocalBluetoothLeBroadcastMetadata(); + CachedBluetoothDeviceManager cachedDeviceManager = new CachedBluetoothDeviceManager(context, + mLocalBluetoothManager); + mLocalBroadcastAssistant = new LocalBluetoothLeBroadcastAssistant(context, + cachedDeviceManager, mProfileManager); + } + + private BluetoothLeBroadcastMetadata convertToBroadcastMetadata(String qrCodeString) { + return mLocalBroadcastMetadata.convertToBroadcastMetadata(qrCodeString); + } + + public void addSource(BluetoothDevice sink, String sourceMetadata, + boolean isGroupOp) { + mLocalBroadcastAssistant.addSource(sink, + convertToBroadcastMetadata(sourceMetadata), isGroupOp); + } +} diff --git a/src/com/android/settings/bluetooth/QrCodeScanModeFragment.java b/src/com/android/settings/bluetooth/QrCodeScanModeFragment.java new file mode 100644 index 00000000000..dcf89ca9d0e --- /dev/null +++ b/src/com/android/settings/bluetooth/QrCodeScanModeFragment.java @@ -0,0 +1,243 @@ +/** + * 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.bluetooth; + +import android.app.settings.SettingsEnums; +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.graphics.Matrix; +import android.graphics.Outline; +import android.graphics.Rect; +import android.graphics.SurfaceTexture; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.util.Log; +import android.util.Size; +import android.view.LayoutInflater; +import android.view.TextureView; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewOutlineProvider; +import android.view.accessibility.AccessibilityEvent; +import android.widget.TextView; + +import com.android.settings.core.InstrumentedFragment; +import com.android.settingslib.R; +import com.android.settingslib.bluetooth.BluetoothBroadcastUtils; +import com.android.settingslib.bluetooth.BluetoothUtils; +import com.android.settingslib.core.lifecycle.ObservableFragment; +import com.android.settingslib.qrcode.QrCamera; + +import androidx.annotation.NonNull; +import androidx.annotation.StringRes; + +public class QrCodeScanModeFragment extends InstrumentedFragment implements + TextureView.SurfaceTextureListener, + QrCamera.ScannerCallback { + private static final boolean DEBUG = BluetoothUtils.D; + private static final String TAG = "QrCodeScanModeFragment"; + + /** Message sent to hide error message */ + private static final int MESSAGE_HIDE_ERROR_MESSAGE = 1; + /** Message sent to show error message */ + private static final int MESSAGE_SHOW_ERROR_MESSAGE = 2; + /** Message sent to broadcast QR code */ + private static final int MESSAGE_SCAN_BROADCAST_SUCCESS = 3; + + private static final long SHOW_ERROR_MESSAGE_INTERVAL = 10000; + private static final long SHOW_SUCCESS_SQUARE_INTERVAL = 1000; + + private boolean mIsGroupOp; + private int mCornerRadius; + private BluetoothDevice mSink; + private String mBroadcastMetadata; + private Context mContext; + private QrCamera mCamera; + private QrCodeScanModeController mController; + private TextureView mTextureView; + private TextView mSummary; + private TextView mErrorMessage; + + public QrCodeScanModeFragment(boolean isGroupOp, BluetoothDevice sink) { + mIsGroupOp = isGroupOp; + mSink = sink; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mContext = getContext(); + mController = new QrCodeScanModeController(mContext); + } + + @Override + public final View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.qrcode_scanner_fragment, container, + /* attachToRoot */ false); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + mTextureView = view.findViewById(R.id.preview_view); + mCornerRadius = mContext.getResources().getDimensionPixelSize( + R.dimen.qrcode_preview_radius); + mTextureView.setSurfaceTextureListener(this); + mTextureView.setOutlineProvider(new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + outline.setRoundRect(0,0, view.getWidth(), view.getHeight(), mCornerRadius); + } + }); + mTextureView.setClipToOutline(true); + mErrorMessage = view.findViewById(R.id.error_message); + } + + private void initCamera(SurfaceTexture surface) { + // Check if the camera has already created. + if (mCamera == null) { + mCamera = new QrCamera(mContext, this); + mCamera.start(surface); + } + } + + private void destroyCamera() { + if (mCamera != null) { + mCamera.stop(); + mCamera = null; + } + } + + @Override + public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) { + initCamera(surface); + } + + @Override + public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, + int height) { + } + + @Override + public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) { + destroyCamera(); + return true; + } + + @Override + public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) { + } + + @Override + public void handleSuccessfulResult(String qrCode) { + if (DEBUG) { + Log.d(TAG, "handleSuccessfulResult(), get the qr code string."); + } + mBroadcastMetadata = qrCode; + handleBtLeAudioScanner(); + } + + @Override + public void handleCameraFailure() { + destroyCamera(); + } + + @Override + public Size getViewSize() { + return new Size(mTextureView.getWidth(), mTextureView.getHeight()); + } + + @Override + public Rect getFramePosition(Size previewSize, int cameraOrientation) { + return new Rect(0, 0, previewSize.getHeight(), previewSize.getHeight()); + } + + @Override + public void setTransform(Matrix transform) { + mTextureView.setTransform(transform); + } + + @Override + public boolean isValid(String qrCode) { + if (qrCode.startsWith(BluetoothBroadcastUtils.SCHEME_BT_BROADCAST_METADATA)) { + return true; + } else { + showErrorMessage(R.string.bt_le_audio_qr_code_is_not_valid_format); + return false; + } + } + + protected boolean isDecodeTaskAlive() { + return mCamera != null && mCamera.isDecodeTaskAlive(); + } + + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MESSAGE_HIDE_ERROR_MESSAGE: + mErrorMessage.setVisibility(View.INVISIBLE); + break; + + case MESSAGE_SHOW_ERROR_MESSAGE: + final String errorMessage = (String) msg.obj; + + mErrorMessage.setVisibility(View.VISIBLE); + mErrorMessage.setText(errorMessage); + mErrorMessage.sendAccessibilityEvent( + AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); + + // Cancel any pending messages to hide error view and requeue the message so + // user has time to see error + removeMessages(MESSAGE_HIDE_ERROR_MESSAGE); + sendEmptyMessageDelayed(MESSAGE_HIDE_ERROR_MESSAGE, + SHOW_ERROR_MESSAGE_INTERVAL); + break; + + case MESSAGE_SCAN_BROADCAST_SUCCESS: + mController.addSource(mSink, mBroadcastMetadata, mIsGroupOp); + updateSummary(); + mSummary.sendAccessibilityEvent( + AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); + break; + default: + } + } + }; + + private void showErrorMessage(@StringRes int messageResId) { + final Message message = mHandler.obtainMessage(MESSAGE_SHOW_ERROR_MESSAGE, + getString(messageResId)); + message.sendToTarget(); + } + + private void handleBtLeAudioScanner() { + Message message = mHandler.obtainMessage(MESSAGE_SCAN_BROADCAST_SUCCESS); + mHandler.sendMessageDelayed(message, SHOW_SUCCESS_SQUARE_INTERVAL); + } + + private void updateSummary() { + mSummary.setText(getString(R.string.bt_le_audio_scan_qr_code_scanner, + null /* broadcast_name*/));; + } + + @Override + public int getMetricsCategory() { + return SettingsEnums.LE_AUDIO_BROADCAST_SCAN_QR_CODE; + } +} diff --git a/src/com/android/settings/dashboard/profileselector/ProfileSelectStorageFragment.java b/src/com/android/settings/dashboard/profileselector/ProfileSelectStorageFragment.java index 57988c5d0dc..1473dd1c04c 100644 --- a/src/com/android/settings/dashboard/profileselector/ProfileSelectStorageFragment.java +++ b/src/com/android/settings/dashboard/profileselector/ProfileSelectStorageFragment.java @@ -84,10 +84,19 @@ public class ProfileSelectStorageFragment extends ProfileSelectFragment { } final StorageEntry changedStorageEntry = new StorageEntry(getContext(), volumeInfo); - switch (volumeInfo.getState()) { + final int volumeState = volumeInfo.getState(); + switch (volumeState) { + case VolumeInfo.STATE_REMOVED: + case VolumeInfo.STATE_BAD_REMOVAL: + // Remove removed storage from list and don't show it on spinner. + if (!mStorageEntries.remove(changedStorageEntry)) { + break; + } case VolumeInfo.STATE_MOUNTED: case VolumeInfo.STATE_MOUNTED_READ_ONLY: case VolumeInfo.STATE_UNMOUNTABLE: + case VolumeInfo.STATE_UNMOUNTED: + case VolumeInfo.STATE_EJECTING: // Add mounted or unmountable storage in the list and show it on spinner. // Unmountable storages are the storages which has a problem format and android // is not able to mount it automatically. @@ -95,25 +104,15 @@ public class ProfileSelectStorageFragment extends ProfileSelectFragment { mStorageEntries.removeIf(storageEntry -> { return storageEntry.equals(changedStorageEntry); }); - mStorageEntries.add(changedStorageEntry); + if (volumeState != VolumeInfo.STATE_REMOVED + && volumeState != VolumeInfo.STATE_BAD_REMOVAL) { + mStorageEntries.add(changedStorageEntry); + } if (changedStorageEntry.equals(mSelectedStorageEntry)) { mSelectedStorageEntry = changedStorageEntry; } refreshUi(); break; - case VolumeInfo.STATE_REMOVED: - case VolumeInfo.STATE_UNMOUNTED: - case VolumeInfo.STATE_BAD_REMOVAL: - case VolumeInfo.STATE_EJECTING: - // Remove removed storage from list and don't show it on spinner. - if (mStorageEntries.remove(changedStorageEntry)) { - if (changedStorageEntry.equals(mSelectedStorageEntry)) { - mSelectedStorageEntry = - StorageEntry.getDefaultInternalStorageEntry(getContext()); - } - refreshUi(); - } - break; default: // Do nothing. } diff --git a/src/com/android/settings/datausage/CycleAdapter.java b/src/com/android/settings/datausage/CycleAdapter.java index b41b6aad91b..2af40120864 100644 --- a/src/com/android/settings/datausage/CycleAdapter.java +++ b/src/com/android/settings/datausage/CycleAdapter.java @@ -21,7 +21,6 @@ import com.android.settingslib.net.NetworkCycleData; import com.android.settingslib.widget.SettingsSpinnerAdapter; import java.util.List; -import java.util.Objects; public class CycleAdapter extends SettingsSpinnerAdapter { @@ -67,7 +66,7 @@ public class CycleAdapter extends SettingsSpinnerAdapter * Rebuild list based on network data. Always selects the newest item, * updating the inspection range on chartData. */ - public boolean updateCycleList(List cycleData) { + public void updateCycleList(List cycleData) { mSpinner.setOnItemSelectedListener(mListener); // stash away currently selected cycle to try restoring below final CycleAdapter.CycleItem previousItem = (CycleAdapter.CycleItem) @@ -83,16 +82,7 @@ public class CycleAdapter extends SettingsSpinnerAdapter if (getCount() > 0) { final int position = findNearestPosition(previousItem); mSpinner.setSelection(position); - - // only force-update cycle when changed; skipping preserves any - // user-defined inspection region. - final CycleAdapter.CycleItem selectedItem = getItem(position); - if (!Objects.equals(selectedItem, previousItem)) { - mListener.onItemSelected(null, null, position, 0); - return false; - } } - return true; } /** diff --git a/src/com/android/settings/datausage/DataUsageList.java b/src/com/android/settings/datausage/DataUsageList.java index 4a44a1674e0..9d8fbb14162 100644 --- a/src/com/android/settings/datausage/DataUsageList.java +++ b/src/com/android/settings/datausage/DataUsageList.java @@ -69,6 +69,7 @@ import com.android.settingslib.net.UidDetailProvider; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; /** * Panel showing data usage history across various networks, including options @@ -111,7 +112,11 @@ public class DataUsageList extends DataUsageBaseFragment private ChartDataUsagePreference mChart; private List mCycleData; + // Caches the cycles for startAppDataUsage usage, which need be cleared when resumed. private ArrayList mCycles; + // Spinner will keep the selected cycle even after paused, this only keeps the displayed cycle, + // which need be cleared when resumed. + private CycleAdapter.CycleItem mLastDisplayedCycle; private UidDetailProvider mUidDetailProvider; private CycleAdapter mCycleAdapter; private Preference mUsageAmount; @@ -199,13 +204,15 @@ public class DataUsageList extends DataUsageBaseFragment mLoadingViewController = new LoadingViewController( getView().findViewById(R.id.loading_container), getListView()); - mLoadingViewController.showLoadingViewDelayed(); } @Override public void onResume() { super.onResume(); + mLoadingViewController.showLoadingViewDelayed(); mDataStateListener.start(mSubId); + mCycles = null; + mLastDisplayedCycle = null; // kick off loader for network history // TODO: consider chaining two loaders together instead of reloading @@ -319,9 +326,46 @@ public class DataUsageList extends DataUsageBaseFragment } // generate cycle list based on policy and available history - if (mCycleAdapter.updateCycleList(mCycleData)) { - updateDetailData(); + mCycleAdapter.updateCycleList(mCycleData); + updateSelectedCycle(); + } + + /** + * Updates the chart and detail data when initial loaded or selected cycle changed. + */ + private void updateSelectedCycle() { + // Avoid from updating UI after #onStop. + if (!getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) { + return; } + + // Avoid from updating UI when async query still on-going. + // This could happen when a request from #onMobileDataEnabledChange. + if (mCycleData == null) { + return; + } + + final int position = mCycleSpinner.getSelectedItemPosition(); + if (mCycleAdapter.getCount() == 0 || position < 0) { + return; + } + final CycleAdapter.CycleItem cycle = mCycleAdapter.getItem(position); + if (Objects.equals(cycle, mLastDisplayedCycle)) { + // Avoid duplicate update to avoid page flash. + return; + } + mLastDisplayedCycle = cycle; + + if (LOGD) { + Log.d(TAG, "showing cycle " + cycle + ", [start=" + cycle.start + ", end=" + + cycle.end + "]"); + } + + // update chart to show selected cycle, and update detail data + // to match updated sweep bounds. + mChart.setNetworkCycleData(mCycleData.get(position)); + + updateDetailData(); } /** @@ -495,33 +539,10 @@ public class DataUsageList extends DataUsageBaseFragment return Math.max(largest, item.total); } - private OnItemSelectedListener mCycleListener = new OnItemSelectedListener() { + private final OnItemSelectedListener mCycleListener = new OnItemSelectedListener() { @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { - final CycleAdapter.CycleItem cycle = (CycleAdapter.CycleItem) - mCycleSpinner.getSelectedItem(); - - if (LOGD) { - Log.d(TAG, "showing cycle " + cycle + ", start=" + cycle.start + ", end=" - + cycle.end + "]"); - } - - // Avoid from updating UI after #onStop. - if (!getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) { - return; - } - - // Avoid from updating UI when async query still on-going. - // This could happen when a request from #onMobileDataEnabledChange. - if (mCycleData == null) { - return; - } - - // update chart to show selected cycle, and update detail data - // to match updated sweep bounds. - mChart.setNetworkCycleData(mCycleData.get(position)); - - updateDetailData(); + updateSelectedCycle(); } @Override diff --git a/src/com/android/settings/datausage/SpinnerPreference.java b/src/com/android/settings/datausage/SpinnerPreference.java index c4b7a4e18da..c6b5f9f8ecc 100644 --- a/src/com/android/settings/datausage/SpinnerPreference.java +++ b/src/com/android/settings/datausage/SpinnerPreference.java @@ -14,6 +14,7 @@ package com.android.settings.datausage; +import android.annotation.Nullable; import android.content.Context; import android.util.AttributeSet; import android.view.View; @@ -28,6 +29,7 @@ import com.android.settings.R; public class SpinnerPreference extends Preference implements CycleAdapter.SpinnerInterface { private CycleAdapter mAdapter; + @Nullable private AdapterView.OnItemSelectedListener mListener; private Object mCurrentObject; private int mPosition; @@ -88,19 +90,24 @@ public class SpinnerPreference extends Preference implements CycleAdapter.Spinne view.findViewById(R.id.cycles_spinner).performClick(); } - private final AdapterView.OnItemSelectedListener mOnSelectedListener - = new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - if (mPosition == position) return; - mPosition = position; - mCurrentObject = mAdapter.getItem(position); - mListener.onItemSelected(parent, view, position, id); - } + private final AdapterView.OnItemSelectedListener mOnSelectedListener = + new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected( + AdapterView parent, View view, int position, long id) { + if (mPosition == position) return; + mPosition = position; + mCurrentObject = mAdapter.getItem(position); + if (mListener != null) { + mListener.onItemSelected(parent, view, position, id); + } + } - @Override - public void onNothingSelected(AdapterView parent) { - mListener.onNothingSelected(parent); - } - }; + @Override + public void onNothingSelected(AdapterView parent) { + if (mListener != null) { + mListener.onNothingSelected(parent); + } + } + }; } diff --git a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java index 77d4072956d..a4809c929fd 100644 --- a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java +++ b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java @@ -115,10 +115,19 @@ public class StorageDashboardFragment extends DashboardFragment } final StorageEntry changedStorageEntry = new StorageEntry(getContext(), volumeInfo); - switch (volumeInfo.getState()) { + final int volumeState = volumeInfo.getState(); + switch (volumeState) { + case VolumeInfo.STATE_REMOVED: + case VolumeInfo.STATE_BAD_REMOVAL: + // Remove removed storage from list and don't show it on spinner. + if (!mStorageEntries.remove(changedStorageEntry)) { + break; + } case VolumeInfo.STATE_MOUNTED: case VolumeInfo.STATE_MOUNTED_READ_ONLY: case VolumeInfo.STATE_UNMOUNTABLE: + case VolumeInfo.STATE_UNMOUNTED: + case VolumeInfo.STATE_EJECTING: // Add mounted or unmountable storage in the list and show it on spinner. // Unmountable storages are the storages which has a problem format and android // is not able to mount it automatically. @@ -126,25 +135,15 @@ public class StorageDashboardFragment extends DashboardFragment mStorageEntries.removeIf(storageEntry -> { return storageEntry.equals(changedStorageEntry); }); - mStorageEntries.add(changedStorageEntry); + if (volumeState != VolumeInfo.STATE_REMOVED + && volumeState != VolumeInfo.STATE_BAD_REMOVAL) { + mStorageEntries.add(changedStorageEntry); + } if (changedStorageEntry.equals(mSelectedStorageEntry)) { mSelectedStorageEntry = changedStorageEntry; } refreshUi(); break; - case VolumeInfo.STATE_REMOVED: - case VolumeInfo.STATE_UNMOUNTED: - case VolumeInfo.STATE_BAD_REMOVAL: - case VolumeInfo.STATE_EJECTING: - // Remove removed storage from list and don't show it on spinner. - if (mStorageEntries.remove(changedStorageEntry)) { - if (changedStorageEntry.equals(mSelectedStorageEntry)) { - mSelectedStorageEntry = - StorageEntry.getDefaultInternalStorageEntry(getContext()); - } - refreshUi(); - } - break; default: // Do nothing. } diff --git a/src/com/android/settings/deviceinfo/VolumeOptionMenuController.java b/src/com/android/settings/deviceinfo/VolumeOptionMenuController.java index 42a3a16da3e..4b87e422c1b 100644 --- a/src/com/android/settings/deviceinfo/VolumeOptionMenuController.java +++ b/src/com/android/settings/deviceinfo/VolumeOptionMenuController.java @@ -26,6 +26,7 @@ import android.os.UserManager; import android.os.storage.DiskInfo; import android.os.storage.StorageManager; import android.os.storage.VolumeInfo; +import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -53,6 +54,8 @@ import java.util.Objects; public class VolumeOptionMenuController implements LifecycleObserver, OnCreateOptionsMenu, OnPrepareOptionsMenu, OnOptionsItemSelected { + private static final String TAG = "VolumeOptionMenuController"; + @VisibleForTesting MenuItem mRename; @VisibleForTesting @@ -103,6 +106,17 @@ public class VolumeOptionMenuController implements LifecycleObserver, OnCreateOp mFree = menu.findItem(R.id.storage_free); mForget = menu.findItem(R.id.storage_forget); + updateOptionsMenu(); + } + + private void updateOptionsMenu() { + if (mRename == null || mMount == null || mUnmount == null || mFormat == null + || mFormatAsPortable == null || mFormatAsInternal == null || mMigrate == null + || mFree == null || mForget == null) { + Log.d(TAG, "Menu items are not available"); + return; + } + mRename.setVisible(false); mMount.setVisible(false); mUnmount.setVisible(false); @@ -252,5 +266,7 @@ public class VolumeOptionMenuController implements LifecycleObserver, OnCreateOp public void setSelectedStorageEntry(StorageEntry storageEntry) { mStorageEntry = storageEntry; + + updateOptionsMenu(); } } diff --git a/src/com/android/settings/homepage/SettingsHomepageActivity.java b/src/com/android/settings/homepage/SettingsHomepageActivity.java index 025168778bb..86b123be49b 100644 --- a/src/com/android/settings/homepage/SettingsHomepageActivity.java +++ b/src/com/android/settings/homepage/SettingsHomepageActivity.java @@ -20,6 +20,8 @@ import static android.provider.Settings.ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY import static android.provider.Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY; import static android.provider.Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI; +import static com.android.settings.SettingsActivity.EXTRA_USER_HANDLE; + import android.animation.LayoutTransition; import android.app.ActivityManager; import android.app.settings.SettingsEnums; @@ -27,6 +29,7 @@ import android.content.ComponentName; import android.content.Intent; import android.content.res.Configuration; import android.os.Bundle; +import android.os.UserHandle; import android.text.TextUtils; import android.util.ArraySet; import android.util.FeatureFlagUtils; @@ -449,7 +452,13 @@ public class SettingsHomepageActivity extends FragmentActivity implements SplitRule.FINISH_ALWAYS, SplitRule.FINISH_ALWAYS, true /* clearTop */); - startActivity(targetIntent); + + final UserHandle user = intent.getParcelableExtra(EXTRA_USER_HANDLE, UserHandle.class); + if (user != null) { + startActivityAsUser(targetIntent, user); + } else { + startActivity(targetIntent); + } } private String getHighlightMenuKey() { diff --git a/src/com/android/settings/localepicker/AppLocalePickerActivity.java b/src/com/android/settings/localepicker/AppLocalePickerActivity.java index 791a4e8e785..377a076ec63 100644 --- a/src/com/android/settings/localepicker/AppLocalePickerActivity.java +++ b/src/com/android/settings/localepicker/AppLocalePickerActivity.java @@ -118,6 +118,7 @@ public class AppLocalePickerActivity extends SettingsBaseActivity /** Sets the app's locale to the supplied language tag */ private void setAppDefaultLocale(String languageTag) { + Log.d(TAG, "setAppDefaultLocale: " + languageTag); LocaleManager localeManager = mContextAsUser.getSystemService(LocaleManager.class); if (localeManager == null) { Log.w(TAG, "LocaleManager is null, cannot set default app locale"); diff --git a/src/com/android/settings/network/NetworkProviderSettings.java b/src/com/android/settings/network/NetworkProviderSettings.java index de4d127a84e..ec17dd371b1 100644 --- a/src/com/android/settings/network/NetworkProviderSettings.java +++ b/src/com/android/settings/network/NetworkProviderSettings.java @@ -67,7 +67,6 @@ import com.android.settings.datausage.DataUsagePreference; import com.android.settings.datausage.DataUsageUtils; import com.android.settings.location.WifiScanningFragment; import com.android.settings.search.BaseSearchIndexProvider; -import com.android.settings.utils.AnnotationSpan; import com.android.settings.wifi.AddNetworkFragment; import com.android.settings.wifi.AddWifiNetworkPreference; import com.android.settings.wifi.ConfigureWifiEntryFragment; @@ -829,12 +828,10 @@ public class NetworkProviderSettings extends RestrictedSettingsFragment return; } if (TextUtils.isEmpty(mWifiStatusMessagePreference.getTitle())) { - AnnotationSpan.LinkInfo info = new AnnotationSpan.LinkInfo( - AnnotationSpan.LinkInfo.DEFAULT_ANNOTATION, - v -> launchWifiScanningFragment()); - CharSequence text = AnnotationSpan.linkify( - context.getText(R.string.wifi_scan_notify_message), info); - mWifiStatusMessagePreference.setTitle(text); + mWifiStatusMessagePreference.setTitle(R.string.wifi_scan_notify_message); + mWifiStatusMessagePreference.setLearnMoreText( + context.getString(R.string.wifi_scan_change)); + mWifiStatusMessagePreference.setLearnMoreAction(v -> launchWifiScanningFragment()); } mWifiStatusMessagePreference.setVisible(true); } diff --git a/src/com/android/settings/privacy/PrivacyDashboardFragment.java b/src/com/android/settings/privacy/PrivacyDashboardFragment.java index df59bd5f119..75ed225e3a6 100644 --- a/src/com/android/settings/privacy/PrivacyDashboardFragment.java +++ b/src/com/android/settings/privacy/PrivacyDashboardFragment.java @@ -25,6 +25,7 @@ import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROF import android.app.settings.SettingsEnums; import android.content.Context; import android.os.Bundle; +import android.provider.SearchIndexableResource; import com.android.settings.R; import com.android.settings.dashboard.DashboardFragment; @@ -36,6 +37,7 @@ import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.search.SearchIndexable; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; @SearchIndexable @@ -72,12 +74,6 @@ public class PrivacyDashboardFragment extends DashboardFragment { replaceEnterpriseStringSummary("work_policy_info", WORK_PROFILE_PRIVACY_POLICY_INFO_SUMMARY, R.string.work_policy_privacy_settings_summary); - - } - - @Override - protected int getPreferenceScreenResId() { - return R.xml.privacy_dashboard_settings; } @Override @@ -90,6 +86,19 @@ public class PrivacyDashboardFragment extends DashboardFragment { return buildPreferenceControllers(context, getSettingsLifecycle()); } + @Override + protected int getPreferenceScreenResId() { + return getPreferenceScreenResId(getContext()); + } + + private static int getPreferenceScreenResId(Context context) { + if (SafetyCenterManagerWrapper.get().isEnabled(context)) { + return R.xml.privacy_advanced_settings; + } else { + return R.xml.privacy_dashboard_settings; + } + } + private static List buildPreferenceControllers( Context context, Lifecycle lifecycle) { final List controllers = new ArrayList<>(); @@ -108,17 +117,19 @@ public class PrivacyDashboardFragment extends DashboardFragment { } public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = - new BaseSearchIndexProvider(R.xml.privacy_dashboard_settings) { + new BaseSearchIndexProvider() { + @Override + public List getXmlResourcesToIndex( + Context context, boolean enabled) { + final SearchIndexableResource sir = new SearchIndexableResource(context); + sir.xmlResId = getPreferenceScreenResId(context); + return Arrays.asList(sir); + } @Override public List createPreferenceControllers( Context context) { return buildPreferenceControllers(context, null); } - - @Override - protected boolean isPageSearchEnabled(Context context) { - return !SafetyCenterManagerWrapper.get().isEnabled(context); - } }; } diff --git a/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiver.java b/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiver.java index 3ea23f3a897..0b556e74a37 100644 --- a/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiver.java +++ b/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiver.java @@ -49,9 +49,9 @@ public class SafetySourceBroadcastReceiver extends BroadcastReceiver { if (ACTION_REFRESH_SAFETY_SOURCES.equals(intent.getAction())) { String[] sourceIdsExtra = intent.getStringArrayExtra(EXTRA_REFRESH_SAFETY_SOURCE_IDS); - if (sourceIdsExtra != null && sourceIdsExtra.length > 0) { - final String refreshBroadcastId = intent.getStringExtra( - SafetyCenterManager.EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID); + final String refreshBroadcastId = intent.getStringExtra( + SafetyCenterManager.EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID); + if (sourceIdsExtra != null && sourceIdsExtra.length > 0 && refreshBroadcastId != null) { final SafetyEvent safetyEvent = new SafetyEvent.Builder( SAFETY_EVENT_TYPE_REFRESH_REQUESTED) .setRefreshBroadcastId(refreshBroadcastId).build(); diff --git a/src/com/android/settings/widget/SettingsMainSwitchPreference.java b/src/com/android/settings/widget/SettingsMainSwitchPreference.java index b7c69017404..92649113d82 100644 --- a/src/com/android/settings/widget/SettingsMainSwitchPreference.java +++ b/src/com/android/settings/widget/SettingsMainSwitchPreference.java @@ -22,6 +22,7 @@ import android.content.Context; import android.content.res.TypedArray; import android.text.TextUtils; import android.util.AttributeSet; +import android.view.ViewGroup; import android.widget.Switch; import androidx.core.content.res.TypedArrayUtils; @@ -33,6 +34,8 @@ import com.android.settings.widget.SettingsMainSwitchBar.OnBeforeCheckedChangeLi import com.android.settingslib.RestrictedPreferenceHelper; import com.android.settingslib.widget.OnMainSwitchChangeListener; +import com.google.android.setupdesign.util.LayoutStyler; + import java.util.ArrayList; import java.util.List; @@ -48,6 +51,7 @@ public class SettingsMainSwitchPreference extends TwoStatePreference implements new ArrayList<>(); private final List mSwitchChangeListeners = new ArrayList<>(); + private boolean mApplyPartnerCustomizationPaddingStyle; private SettingsMainSwitchBar mMainSwitchBar; private CharSequence mTitle; private EnforcedAdmin mEnforcedAdmin; @@ -95,6 +99,12 @@ public class SettingsMainSwitchPreference extends TwoStatePreference implements } else { mMainSwitchBar.hide(); } + + if (mApplyPartnerCustomizationPaddingStyle) { + // TODO(b/232494666): Replace all margins of the root view with the padding + final ViewGroup parentView = (ViewGroup) mMainSwitchBar.getParent(); + LayoutStyler.applyPartnerCustomizationLayoutPaddingStyle(parentView); + } } private void init(Context context, AttributeSet attrs) { @@ -241,6 +251,14 @@ public class SettingsMainSwitchPreference extends TwoStatePreference implements } } + /** + * Apples the padding style of the partner's customization. It's used in the SetupWizard. + */ + public void applyPartnerCustomizationPaddingStyle() { + mApplyPartnerCustomizationPaddingStyle = true; + notifyChanged(); + } + private void initMainSwitchBar() { if (mMainSwitchBar != null) { mMainSwitchBar.setTitle(mTitle); diff --git a/src/com/android/settings/wifi/WifiScanModeActivity.java b/src/com/android/settings/wifi/WifiScanModeActivity.java index 9d502810d5a..d37213522aa 100644 --- a/src/com/android/settings/wifi/WifiScanModeActivity.java +++ b/src/com/android/settings/wifi/WifiScanModeActivity.java @@ -20,26 +20,30 @@ import android.app.Dialog; import android.app.settings.SettingsEnums; import android.content.DialogInterface; import android.content.Intent; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; import android.net.wifi.WifiManager; import android.os.Bundle; import android.text.TextUtils; import android.view.WindowManager; +import androidx.annotation.VisibleForTesting; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.DialogFragment; import androidx.fragment.app.FragmentActivity; import com.android.settings.R; +import com.android.settings.Utils; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; +import com.android.settingslib.wifi.WifiPermissionChecker; /** * This activity requests users permission to allow scanning even when Wi-Fi is turned off */ public class WifiScanModeActivity extends FragmentActivity { private DialogFragment mDialog; - private String mApp; + @VisibleForTesting + String mApp; + @VisibleForTesting + WifiPermissionChecker mWifiPermissionChecker; @Override protected void onCreate(Bundle savedInstanceState) { @@ -50,13 +54,7 @@ public class WifiScanModeActivity extends FragmentActivity { if (savedInstanceState == null) { if (intent != null && WifiManager.ACTION_REQUEST_SCAN_ALWAYS_AVAILABLE .equals(intent.getAction())) { - ApplicationInfo ai; - mApp = getCallingPackage(); - try { - PackageManager pm = getPackageManager(); - ai = pm.getApplicationInfo(mApp, 0); - mApp = (String)pm.getApplicationLabel(ai); - } catch (PackageManager.NameNotFoundException e) { } + refreshAppLabel(); } else { finish(); return; @@ -67,6 +65,19 @@ public class WifiScanModeActivity extends FragmentActivity { createDialog(); } + @VisibleForTesting + void refreshAppLabel() { + if (mWifiPermissionChecker == null) { + mWifiPermissionChecker = new WifiPermissionChecker(this); + } + String packageName = mWifiPermissionChecker.getLaunchedPackage(); + if (TextUtils.isEmpty(packageName)) { + mApp = null; + return; + } + mApp = Utils.getApplicationLabel(getApplicationContext(), packageName).toString(); + } + private void createDialog() { if (mDialog == null) { mDialog = AlertDialogFragment.newInstance(mApp); diff --git a/src/com/android/settings/wifi/calling/WifiCallingSettings.java b/src/com/android/settings/wifi/calling/WifiCallingSettings.java index 25508f73caf..b4951bf5c59 100644 --- a/src/com/android/settings/wifi/calling/WifiCallingSettings.java +++ b/src/com/android/settings/wifi/calling/WifiCallingSettings.java @@ -30,13 +30,12 @@ import android.view.ViewGroup; import androidx.annotation.VisibleForTesting; import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentPagerAdapter; import com.android.internal.util.CollectionUtils; import com.android.settings.R; -import com.android.settings.core.InstrumentedFragment; +import com.android.settings.SettingsPreferenceFragment; import com.android.settings.network.ActiveSubscriptionsListener; import com.android.settings.network.SubscriptionUtil; import com.android.settings.network.ims.WifiCallingQueryImsState; @@ -54,7 +53,8 @@ import java.util.List; * "Wi-Fi Calling settings" screen. This is the container fragment which holds * {@link WifiCallingSettingsForSub} fragments. */ -public class WifiCallingSettings extends InstrumentedFragment implements HelpResourceProvider { +public class WifiCallingSettings extends SettingsPreferenceFragment + implements HelpResourceProvider { private static final String TAG = "WifiCallingSettings"; private int mConstructionSubId; private List mSil; @@ -317,17 +317,7 @@ public class WifiCallingSettings extends InstrumentedFragment implements HelpRes } // close this fragment - finish(); - } - - protected void finish() { - FragmentActivity activity = getActivity(); - if (activity == null) return; - if (getFragmentManager().getBackStackEntryCount() > 0) { - getFragmentManager().popBackStack(); - } else { - activity.finish(); - } + finishFragment(); } protected int [] subscriptionIdList(List subInfoList) { diff --git a/src/com/android/settings/wifi/calling/WifiCallingSettingsForSub.java b/src/com/android/settings/wifi/calling/WifiCallingSettingsForSub.java index 749af3ecbfc..492a2284edb 100644 --- a/src/com/android/settings/wifi/calling/WifiCallingSettingsForSub.java +++ b/src/com/android/settings/wifi/calling/WifiCallingSettingsForSub.java @@ -27,7 +27,6 @@ import android.content.res.Resources; import android.os.Bundle; import android.os.PersistableBundle; import android.telephony.CarrierConfigManager; -import android.telephony.PhoneStateListener; import android.telephony.SubscriptionManager; import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; @@ -40,7 +39,6 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Switch; -import android.widget.TextView; import androidx.appcompat.app.AlertDialog; import androidx.preference.Preference; @@ -56,8 +54,7 @@ import com.android.settings.SettingsPreferenceFragment; import com.android.settings.Utils; import com.android.settings.core.SubSettingLauncher; import com.android.settings.network.ims.WifiCallingQueryImsState; -import com.android.settings.widget.SettingsMainSwitchBar; -import com.android.settings.wifi.calling.LinkifyDescriptionPreference; +import com.android.settings.widget.SettingsMainSwitchPreference; import com.android.settingslib.widget.OnMainSwitchChangeListener; import java.util.List; @@ -72,6 +69,7 @@ public class WifiCallingSettingsForSub extends SettingsPreferenceFragment private static final String TAG = "WifiCallingForSub"; //String keys for preference lookup + private static final String SWITCH_BAR = "wifi_calling_switch_bar"; private static final String BUTTON_WFC_MODE = "wifi_calling_mode"; private static final String BUTTON_WFC_ROAMING_MODE = "wifi_calling_roaming_mode"; private static final String PREFERENCE_EMERGENCY_ADDRESS = "emergency_address_key"; @@ -91,7 +89,7 @@ public class WifiCallingSettingsForSub extends SettingsPreferenceFragment public static final int LAUCH_APP_UPDATE = 1; //UI objects - private SettingsMainSwitchBar mSwitchBar; + private SettingsMainSwitchPreference mSwitchBar; private ListWithEntrySummaryPreference mButtonWfcMode; private ListWithEntrySummaryPreference mButtonWfcRoamingMode; private Preference mUpdateAddress; @@ -119,41 +117,57 @@ public class WifiCallingSettingsForSub extends SettingsPreferenceFragment @Override public void onCallStateChanged(int state) { final SettingsActivity activity = (SettingsActivity) getActivity(); - final boolean isNonTtyOrTtyOnVolteEnabled = - queryImsState(WifiCallingSettingsForSub.this.mSubId).isAllowUserControl(); - final boolean isWfcEnabled = mSwitchBar.isChecked() - && isNonTtyOrTtyOnVolteEnabled; - boolean isCallStateIdle = getTelephonyManagerForSub( - WifiCallingSettingsForSub.this.mSubId).getCallState() - == TelephonyManager.CALL_STATE_IDLE; - mSwitchBar.setEnabled(isCallStateIdle - && isNonTtyOrTtyOnVolteEnabled); + + boolean isWfcEnabled = false; + boolean isCallStateIdle = false; + + final SettingsMainSwitchPreference prefSwitch = (SettingsMainSwitchPreference) + getPreferenceScreen().findPreference(SWITCH_BAR); + if (prefSwitch != null) { + isWfcEnabled = prefSwitch.isChecked(); + isCallStateIdle = getTelephonyManagerForSub( + WifiCallingSettingsForSub.this.mSubId).getCallState() + == TelephonyManager.CALL_STATE_IDLE; + + boolean isNonTtyOrTtyOnVolteEnabled = true; + if (isWfcEnabled || isCallStateIdle) { + isNonTtyOrTtyOnVolteEnabled = + queryImsState(WifiCallingSettingsForSub.this.mSubId) + .isAllowUserControl(); + } + + isWfcEnabled = isWfcEnabled && isNonTtyOrTtyOnVolteEnabled; + prefSwitch.setEnabled(isCallStateIdle && isNonTtyOrTtyOnVolteEnabled); + } boolean isWfcModeEditable = true; boolean isWfcRoamingModeEditable = false; - final CarrierConfigManager configManager = (CarrierConfigManager) - activity.getSystemService(Context.CARRIER_CONFIG_SERVICE); - if (configManager != null) { - PersistableBundle b = - configManager.getConfigForSubId(WifiCallingSettingsForSub.this.mSubId); - if (b != null) { - isWfcModeEditable = b.getBoolean( - CarrierConfigManager.KEY_EDITABLE_WFC_MODE_BOOL); - isWfcRoamingModeEditable = b.getBoolean( - CarrierConfigManager.KEY_EDITABLE_WFC_ROAMING_MODE_BOOL); + if (isWfcEnabled && isCallStateIdle) { + final CarrierConfigManager configManager = (CarrierConfigManager) + activity.getSystemService(Context.CARRIER_CONFIG_SERVICE); + if (configManager != null) { + PersistableBundle b = configManager.getConfigForSubId( + WifiCallingSettingsForSub.this.mSubId); + if (b != null) { + isWfcModeEditable = b.getBoolean( + CarrierConfigManager.KEY_EDITABLE_WFC_MODE_BOOL); + isWfcRoamingModeEditable = b.getBoolean( + CarrierConfigManager.KEY_EDITABLE_WFC_ROAMING_MODE_BOOL); + } } + } else { + isWfcModeEditable = false; + isWfcRoamingModeEditable = false; } final Preference pref = getPreferenceScreen().findPreference(BUTTON_WFC_MODE); if (pref != null) { - pref.setEnabled(isWfcEnabled && isWfcModeEditable - && isCallStateIdle); + pref.setEnabled(isWfcModeEditable); } final Preference pref_roam = getPreferenceScreen().findPreference(BUTTON_WFC_ROAMING_MODE); if (pref_roam != null) { - pref_roam.setEnabled(isWfcEnabled && isWfcRoamingModeEditable - && isCallStateIdle); + pref_roam.setEnabled(isWfcRoamingModeEditable); } } } @@ -184,20 +198,6 @@ public class WifiCallingSettingsForSub extends SettingsPreferenceFragment } }; - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - mSwitchBar = getView().findViewById(R.id.switch_bar); - mSwitchBar.show(); - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - mSwitchBar.hide(); - } - @VisibleForTesting void showAlert(Intent intent) { final Context context = getActivity(); @@ -292,6 +292,8 @@ public class WifiCallingSettingsForSub extends SettingsPreferenceFragment mProvisioningManager = getImsProvisioningManager(); mImsMmTelManager = getImsMmTelManager(); + mSwitchBar = (SettingsMainSwitchPreference) findPreference(SWITCH_BAR); + mButtonWfcMode = findPreference(BUTTON_WFC_MODE); mButtonWfcMode.setOnPreferenceChangeListener(this); diff --git a/src/com/android/settings/wifi/p2p/WifiP2pSettings.java b/src/com/android/settings/wifi/p2p/WifiP2pSettings.java index 5d28bccfa01..10ee24191e4 100644 --- a/src/com/android/settings/wifi/p2p/WifiP2pSettings.java +++ b/src/com/android/settings/wifi/p2p/WifiP2pSettings.java @@ -522,7 +522,7 @@ public class WifiP2pSettings extends DashboardFragment final LayoutInflater layoutInflater = LayoutInflater.from(getPrefContext()); final View root = layoutInflater.inflate(R.layout.dialog_edittext, null /* root */); mDeviceNameText = root.findViewById(R.id.edittext); - mDeviceNameText.setFilters(new InputFilter[] {new InputFilter.LengthFilter(30)}); + mDeviceNameText.setFilters(new InputFilter[] {new InputFilter.LengthFilter(22)}); if (mSavedDeviceName != null) { mDeviceNameText.setText(mSavedDeviceName); mDeviceNameText.setSelection(mSavedDeviceName.length()); diff --git a/src/com/android/settings/wifi/tether/TetherService.java b/src/com/android/settings/wifi/tether/TetherService.java index 8a557ba0c3e..b0e8fd8338a 100644 --- a/src/com/android/settings/wifi/tether/TetherService.java +++ b/src/com/android/settings/wifi/tether/TetherService.java @@ -256,6 +256,7 @@ public class TetherService extends Service { } private void disableTethering(final int tetheringType) { + Log.w(TAG, "Disable tethering, type:" + tetheringType); final TetheringManager tm = (TetheringManager) getSystemService(Context.TETHERING_SERVICE); tm.stopTethering(tetheringType); } diff --git a/src/com/android/settings/wifi/tether/WifiTetherSwitchBarController.java b/src/com/android/settings/wifi/tether/WifiTetherSwitchBarController.java index 580bc39937e..e88931cceff 100644 --- a/src/com/android/settings/wifi/tether/WifiTetherSwitchBarController.java +++ b/src/com/android/settings/wifi/tether/WifiTetherSwitchBarController.java @@ -31,6 +31,7 @@ import android.net.ConnectivityManager; import android.net.wifi.WifiManager; import android.os.Handler; import android.os.Looper; +import android.util.Log; import android.widget.Switch; import androidx.annotation.VisibleForTesting; @@ -47,6 +48,8 @@ import com.android.settingslib.widget.OnMainSwitchChangeListener; */ public class WifiTetherSwitchBarController implements LifecycleObserver, OnStart, OnStop, DataSaverBackend.Listener, OnMainSwitchChangeListener { + + private static final String TAG = "WifiTetherSBC"; private static final IntentFilter WIFI_INTENT_FILTER; private final Context mContext; @@ -63,8 +66,8 @@ public class WifiTetherSwitchBarController implements @Override public void onTetheringFailed() { super.onTetheringFailed(); - mSwitchBar.setChecked(false); - updateWifiSwitch(); + Log.e(TAG, "Failed to start Wi-Fi Tethering."); + handleWifiApStateChanged(mWifiManager.getWifiApState()); } }; @@ -111,16 +114,28 @@ public class WifiTetherSwitchBarController implements } void stopTether() { + if (!isWifiApActivated()) return; + mSwitchBar.setEnabled(false); mConnectivityManager.stopTethering(TETHERING_WIFI); } void startTether() { + if (isWifiApActivated()) return; + mSwitchBar.setEnabled(false); mConnectivityManager.startTethering(TETHERING_WIFI, true /* showProvisioningUi */, mOnStartTetheringCallback, new Handler(Looper.getMainLooper())); } + private boolean isWifiApActivated() { + final int wifiApState = mWifiManager.getWifiApState(); + if (wifiApState == WIFI_AP_STATE_ENABLED || wifiApState == WIFI_AP_STATE_ENABLING) { + return true; + } + return false; + } + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { diff --git a/tests/robotests/src/com/android/settings/accessibility/TextReadingPreferenceFragmentForSetupWizardTest.java b/tests/robotests/src/com/android/settings/accessibility/TextReadingPreferenceFragmentForSetupWizardTest.java index 0418906aeaa..bddaed5aa9a 100644 --- a/tests/robotests/src/com/android/settings/accessibility/TextReadingPreferenceFragmentForSetupWizardTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/TextReadingPreferenceFragmentForSetupWizardTest.java @@ -16,6 +16,8 @@ package com.android.settings.accessibility; +import static com.android.settings.accessibility.TextReadingPreferenceFragment.RESET_KEY; + import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -25,6 +27,7 @@ import android.content.Context; import androidx.test.core.app.ApplicationProvider; import com.android.settings.R; +import com.android.settingslib.widget.LayoutPreference; import com.google.android.setupdesign.GlifPreferenceLayout; @@ -51,6 +54,9 @@ public class TextReadingPreferenceFragmentForSetupWizardTest { MockitoAnnotations.initMocks(this); mFragment = spy(new TextReadingPreferenceFragmentForSetupWizard()); + final LayoutPreference resetPreference = + new LayoutPreference(mContext, R.layout.accessibility_text_reading_reset_button); + doReturn(resetPreference).when(mFragment).findPreference(RESET_KEY); } @Test diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothFeatureProviderImplTest.java index 9ab1d875183..3d40bfca597 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothFeatureProviderImplTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothFeatureProviderImplTest.java @@ -33,6 +33,11 @@ import org.robolectric.RuntimeEnvironment; @RunWith(RobolectricTestRunner.class) public class BluetoothFeatureProviderImplTest { private static final String SETTINGS_URI = "content://test.provider/settings_uri"; + private static final String CONTROL_METADATA = + "" + SETTINGS_URI + + ""; + private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25; + private BluetoothFeatureProvider mBluetoothFeatureProvider; @Mock @@ -54,4 +59,13 @@ public class BluetoothFeatureProviderImplTest { final Uri uri = mBluetoothFeatureProvider.getBluetoothDeviceSettingsUri(mBluetoothDevice); assertThat(uri.toString()).isEqualTo(SETTINGS_URI); } + + @Test + public void getBluetoothDeviceControlUri_returnsCorrectUri() { + when(mBluetoothDevice.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS)).thenReturn( + CONTROL_METADATA.getBytes()); + assertThat( + mBluetoothFeatureProvider.getBluetoothDeviceControlUri(mBluetoothDevice)).isEqualTo( + SETTINGS_URI); + } } diff --git a/tests/robotests/src/com/android/settings/datausage/DataUsageListTest.java b/tests/robotests/src/com/android/settings/datausage/DataUsageListTest.java index e4f5b1fc9e9..a13cf6c7c8f 100644 --- a/tests/robotests/src/com/android/settings/datausage/DataUsageListTest.java +++ b/tests/robotests/src/com/android/settings/datausage/DataUsageListTest.java @@ -94,6 +94,7 @@ public class DataUsageListTest { mMobileDataEnabledListener); ReflectionHelpers.setField(mDataUsageList, "services", mNetworkServices); doReturn(mLoaderManager).when(mDataUsageList).getLoaderManager(); + mDataUsageList.mLoadingViewController = mock(LoadingViewController.class); } @Test @@ -207,8 +208,6 @@ public class DataUsageListTest { @Test public void onLoadFinished_networkCycleDataCallback_shouldShowCycleSpinner() { - final LoadingViewController loadingViewController = mock(LoadingViewController.class); - mDataUsageList.mLoadingViewController = loadingViewController; final Spinner spinner = getSpinner(getHeader()); spinner.setVisibility(View.INVISIBLE); mDataUsageList.mCycleSpinner = spinner; diff --git a/tests/robotests/src/com/android/settings/wifi/WifiScanModeActivityTest.java b/tests/robotests/src/com/android/settings/wifi/WifiScanModeActivityTest.java index 3e6ba00eff5..1e3afdbf32f 100644 --- a/tests/robotests/src/com/android/settings/wifi/WifiScanModeActivityTest.java +++ b/tests/robotests/src/com/android/settings/wifi/WifiScanModeActivityTest.java @@ -16,16 +16,75 @@ package com.android.settings.wifi; +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.text.TextUtils; + +import com.android.settings.testutils.shadow.ShadowUtils; +import com.android.settingslib.wifi.WifiPermissionChecker; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowUtils.class}) public class WifiScanModeActivityTest { + + static final String LAUNCHED_PACKAGE = "launched_package"; + static final String APP_LABEL = "app_label"; + + @Rule + public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Mock + WifiPermissionChecker mWifiPermissionChecker; + + WifiScanModeActivity mActivity; + + @Before + public void setUp() { + mActivity = spy(Robolectric.setupActivity(WifiScanModeActivity.class)); + mActivity.mWifiPermissionChecker = mWifiPermissionChecker; + } + + @After + public void tearDown() { + ShadowUtils.reset(); + } + @Test public void launchActivity_noIntentAction_shouldNotFatalException() { WifiScanModeActivity wifiScanModeActivity = Robolectric.setupActivity(WifiScanModeActivity.class); } + + @Test + public void refreshAppLabel_noPackageName_shouldNotFatalException() { + when(mWifiPermissionChecker.getLaunchedPackage()).thenReturn(null); + + mActivity.refreshAppLabel(); + + assertThat(TextUtils.isEmpty(mActivity.mApp)).isTrue(); + } + + @Test + public void refreshAppLabel_hasPackageName_shouldHasAppLabel() { + ShadowUtils.setApplicationLabel(LAUNCHED_PACKAGE, APP_LABEL); + when(mWifiPermissionChecker.getLaunchedPackage()).thenReturn(LAUNCHED_PACKAGE); + + mActivity.refreshAppLabel(); + + assertThat(mActivity.mApp).isEqualTo(APP_LABEL); + } } diff --git a/tests/robotests/src/com/android/settings/wifi/calling/WifiCallingSettingsForSubTest.java b/tests/robotests/src/com/android/settings/wifi/calling/WifiCallingSettingsForSubTest.java index 74bdddabbe5..e2c5ca33b4c 100644 --- a/tests/robotests/src/com/android/settings/wifi/calling/WifiCallingSettingsForSubTest.java +++ b/tests/robotests/src/com/android/settings/wifi/calling/WifiCallingSettingsForSubTest.java @@ -55,6 +55,7 @@ import com.android.settings.network.ims.MockWifiCallingQueryImsState; import com.android.settings.network.ims.WifiCallingQueryImsState; import com.android.settings.testutils.shadow.ShadowFragment; import com.android.settings.widget.SettingsMainSwitchBar; +import com.android.settings.widget.SettingsMainSwitchPreference; import org.junit.Before; import org.junit.Test; @@ -72,6 +73,7 @@ import org.robolectric.util.ReflectionHelpers; public class WifiCallingSettingsForSubTest { private static final int SUB_ID = 2; + private static final String SWITCH_BAR = "wifi_calling_switch_bar"; private static final String BUTTON_WFC_MODE = "wifi_calling_mode"; private static final String BUTTON_WFC_ROAMING_MODE = "wifi_calling_roaming_mode"; private static final String PREFERENCE_NO_OPTIONS_DESC = "no_options_description"; @@ -100,6 +102,8 @@ public class WifiCallingSettingsForSubTest { @Mock private View mView; @Mock + private SettingsMainSwitchPreference mSwitchBarPreference; + @Mock private LinkifyDescriptionPreference mDescriptionView; @Mock private ListWithEntrySummaryPreference mButtonWfcMode; @@ -116,6 +120,7 @@ public class WifiCallingSettingsForSubTest { doReturn(mContext.getTheme()).when(mActivity).getTheme(); mFragment = spy(new TestFragment()); + mFragment.setSwitchBar(mSwitchBarPreference); doReturn(mActivity).when(mFragment).getActivity(); doReturn(mContext).when(mFragment).getContext(); doReturn(mock(Intent.class)).when(mActivity).getIntent(); @@ -125,10 +130,6 @@ public class WifiCallingSettingsForSubTest { final Bundle bundle = new Bundle(); when(mFragment.getArguments()).thenReturn(bundle); doNothing().when(mFragment).addPreferencesFromResource(anyInt()); - doReturn(mock(ListWithEntrySummaryPreference.class)).when(mFragment).findPreference(any()); - doReturn(mButtonWfcMode).when(mFragment).findPreference(BUTTON_WFC_MODE); - doReturn(mButtonWfcRoamingMode).when(mFragment).findPreference(BUTTON_WFC_ROAMING_MODE); - doReturn(mDescriptionView).when(mFragment).findPreference(PREFERENCE_NO_OPTIONS_DESC); doNothing().when(mFragment).finish(); doReturn(mView).when(mFragment).getView(); @@ -344,6 +345,29 @@ public class WifiCallingSettingsForSubTest { } protected class TestFragment extends WifiCallingSettingsForSub { + private SettingsMainSwitchPreference mSwitchPref; + + protected void setSwitchBar(SettingsMainSwitchPreference switchPref) { + mSwitchPref = switchPref; + } + + @Override + public T findPreference(CharSequence key) { + if (SWITCH_BAR.equals(key)) { + return (T) mSwitchPref; + } + if (BUTTON_WFC_MODE.equals(key)) { + return (T) mButtonWfcMode; + } + if (BUTTON_WFC_ROAMING_MODE.equals(key)) { + return (T) mButtonWfcRoamingMode; + } + if (PREFERENCE_NO_OPTIONS_DESC.equals(key)) { + return (T) mDescriptionView; + } + return (T) mock(ListWithEntrySummaryPreference.class); + } + @Override protected Object getSystemService(final String name) { switch (name) { diff --git a/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSwitchBarControllerTest.java b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSwitchBarControllerTest.java index 7c17c5fa9bd..ca0247fdd05 100644 --- a/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSwitchBarControllerTest.java +++ b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSwitchBarControllerTest.java @@ -83,8 +83,45 @@ public class WifiTetherSwitchBarControllerTest { mController.mDataSaverBackend = mDataSaverBackend; } + @Test + public void startTether_wifiApIsActivated_doNothing() { + when(mWifiManager.getWifiApState()).thenReturn(WIFI_AP_STATE_ENABLED); + + mController.startTether(); + + verify(mConnectivityManager, never()).startTethering(anyInt(), anyBoolean(), any(), any()); + } + + @Test + public void startTether_wifiApNotActivated_startTethering() { + when(mWifiManager.getWifiApState()).thenReturn(WIFI_AP_STATE_DISABLED); + + mController.startTether(); + + verify(mConnectivityManager).startTethering(anyInt(), anyBoolean(), any(), any()); + } + + @Test + public void stopTether_wifiApIsActivated_stopTethering() { + when(mWifiManager.getWifiApState()).thenReturn(WIFI_AP_STATE_ENABLED); + + mController.stopTether(); + + verify(mConnectivityManager).stopTethering(anyInt()); + } + + @Test + public void stopTether_wifiApNotActivated_doNothing() { + when(mWifiManager.getWifiApState()).thenReturn(WIFI_AP_STATE_DISABLED); + + mController.stopTether(); + + verify(mConnectivityManager, never()).stopTethering(anyInt()); + } + @Test public void startTether_fail_resetSwitchBar() { + when(mWifiManager.getWifiApState()).thenReturn(WIFI_AP_STATE_DISABLED); when(mDataSaverBackend.isDataSaverEnabled()).thenReturn(false); mController.startTether(); @@ -130,6 +167,7 @@ public class WifiTetherSwitchBarControllerTest { @Test public void onSwitchChanged_isNotChecked_stopTethering() { + when(mWifiManager.getWifiApState()).thenReturn(WIFI_AP_STATE_ENABLED); when(mSwitch.isChecked()).thenReturn(false); mController.onSwitchChanged(mSwitch, mSwitch.isChecked()); diff --git a/tests/unit/src/com/android/settings/privacy/PrivacyDashboardActivityTest.java b/tests/unit/src/com/android/settings/privacy/PrivacyDashboardActivityTest.java index 1cfee0f377a..ae42c849837 100644 --- a/tests/unit/src/com/android/settings/privacy/PrivacyDashboardActivityTest.java +++ b/tests/unit/src/com/android/settings/privacy/PrivacyDashboardActivityTest.java @@ -44,54 +44,79 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidJUnit4.class) public class PrivacyDashboardActivityTest { - private static final String DEFAULT_FRAGMENT_CLASSNAME = "DefaultFragmentClassname"; - @Mock private SafetyCenterManagerWrapper mSafetyCenterManagerWrapper; private Settings.PrivacyDashboardActivity mActivity; + private static final String ACTION_PRIVACY_ADVANCED_SETTINGS = + "android.settings.PRIVACY_ADVANCED_SETTINGS"; @Before - public void setUp() throws Exception { + public void setUp() { MockitoAnnotations.initMocks(this); + SafetyCenterManagerWrapper.sInstance = mSafetyCenterManagerWrapper; + } + @Test + public void onCreate_whenSafetyCenterEnabled_redirectsToSafetyCenter() throws Exception { + startActivityUsingIntent(android.provider.Settings.ACTION_PRIVACY_SETTINGS); + when(mSafetyCenterManagerWrapper.isEnabled(any(Context.class))).thenReturn(true); + final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); + mActivity.handleSafetyCenterRedirection(); + verify(mActivity).startActivity(intentCaptor.capture()); + assertThat(intentCaptor.getValue().getAction()).isEqualTo(Intent.ACTION_SAFETY_CENTER); + } + + @Test + public void onCreateWithAdvancedIntent_whenSafetyCenterEnabled_doesntRedirectToSafetyCenter() + throws Exception { + startActivityUsingIntent(ACTION_PRIVACY_ADVANCED_SETTINGS); + when(mSafetyCenterManagerWrapper.isEnabled(any(Context.class))).thenReturn(true); + final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); + mActivity.handleSafetyCenterRedirection(); + verify(mActivity, times(0)).startActivity(any()); + } + + @Test + public void onCreate_whenSafetyCenterDisabled_doesntRedirectToSafetyCenter() throws Exception { + startActivityUsingIntent(android.provider.Settings.ACTION_PRIVACY_SETTINGS); + when(mSafetyCenterManagerWrapper.isEnabled(any(Context.class))).thenReturn(false); + mActivity.handleSafetyCenterRedirection(); + verify(mActivity, times(0)).startActivity(any()); + } + + @Test + public void onCreateWithAdvancedIntent_whenSafetyCenterDisabled_doesntRedirectToSafetyCenter() + throws Exception { + startActivityUsingIntent(ACTION_PRIVACY_ADVANCED_SETTINGS); + when(mSafetyCenterManagerWrapper.isEnabled(any(Context.class))).thenReturn(true); + final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); + mActivity.handleSafetyCenterRedirection(); + verify(mActivity, times(0)).startActivity(any()); + } + + private void startActivityUsingIntent(String intentAction) throws Exception { + MockitoAnnotations.initMocks(this); SafetyCenterManagerWrapper.sInstance = mSafetyCenterManagerWrapper; final Intent intent = new Intent(); - intent.setAction(android.provider.Settings.ACTION_PRIVACY_SETTINGS); + intent.setAction(intentAction); intent.setClass(InstrumentationRegistry.getInstrumentation().getTargetContext(), Settings.PrivacyDashboardActivity.class); intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT, DEFAULT_FRAGMENT_CLASSNAME); InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { try { - mActivity = - spy((Settings.PrivacyDashboardActivity) InstrumentationRegistry + Settings.PrivacyDashboardActivity activity = + (Settings.PrivacyDashboardActivity) InstrumentationRegistry .getInstrumentation().newActivity( getClass().getClassLoader(), Settings.PrivacyDashboardActivity.class.getName(), - intent)); + intent); + activity.setIntent(intent); + mActivity = spy(activity); } catch (Exception e) { throw new RuntimeException(e); // nothing to do } }); doNothing().when(mActivity).startActivity(any(Intent.class)); } - - @Test - public void onCreate_whenSafetyCenterEnabled_redirectsToSafetyCenter() { - when(mSafetyCenterManagerWrapper.isEnabled(any(Context.class))).thenReturn(true); - final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); - - mActivity.handleSafetyCenterRedirection(); - - verify(mActivity).startActivity(intentCaptor.capture()); - assertThat(intentCaptor.getValue().getAction()).isEqualTo(Intent.ACTION_SAFETY_CENTER); - } - - @Test - public void onCreate_whenSafetyCenterDisabled_doesntRedirectToSafetyCenter() { - when(mSafetyCenterManagerWrapper.isEnabled(any(Context.class))).thenReturn(false); - mActivity.handleSafetyCenterRedirection(); - - verify(mActivity, times(0)).startActivity(any()); - } } diff --git a/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java b/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java index 8004d600e9e..3ad1874995b 100644 --- a/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java +++ b/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java @@ -54,13 +54,13 @@ import java.util.List; @RunWith(AndroidJUnit4.class) public class SafetySourceBroadcastReceiverTest { + private static final String REFRESH_BROADCAST_ID = "REFRESH_BROADCAST_ID"; + private Context mApplicationContext; - @Mock - private SafetyCenterManagerWrapper mSafetyCenterManagerWrapper; + @Mock private SafetyCenterManagerWrapper mSafetyCenterManagerWrapper; - @Mock - private LockPatternUtils mLockPatternUtils; + @Mock private LockPatternUtils mLockPatternUtils; @Before public void setUp() { @@ -77,17 +77,6 @@ public class SafetySourceBroadcastReceiverTest { SafetyCenterManagerWrapper.sInstance = null; } - @Test - public void onReceive_onRefresh_whenSafetyCenterIsEnabled_withNoIntentAction_doesNotSetData() { - when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); - Intent intent = new Intent().putExtra(EXTRA_REFRESH_SAFETY_SOURCE_IDS, new String[]{}); - - new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent); - - verify(mSafetyCenterManagerWrapper, never()).setSafetySourceData( - any(), any(), any(), any()); - } - @Test public void onReceive_onRefresh_whenSafetyCenterIsDisabled_doesNotSetData() { when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(false); @@ -96,23 +85,43 @@ public class SafetySourceBroadcastReceiverTest { .setAction(ACTION_REFRESH_SAFETY_SOURCES) .putExtra( EXTRA_REFRESH_SAFETY_SOURCE_IDS, - new String[]{ LockScreenSafetySource.SAFETY_SOURCE_ID }); + new String[] {LockScreenSafetySource.SAFETY_SOURCE_ID}) + .putExtra(EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID, REFRESH_BROADCAST_ID); new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent); - verify(mSafetyCenterManagerWrapper, never()).setSafetySourceData( - any(), any(), any(), any()); + verify(mSafetyCenterManagerWrapper, never()) + .setSafetySourceData(any(), any(), any(), any()); + } + + @Test + public void onReceive_onRefresh_whenSafetyCenterIsEnabled_withNoIntentAction_doesNotSetData() { + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + Intent intent = + new Intent() + .putExtra( + EXTRA_REFRESH_SAFETY_SOURCE_IDS, + new String[] {LockScreenSafetySource.SAFETY_SOURCE_ID}) + .putExtra(EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID, REFRESH_BROADCAST_ID); + + new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent); + + verify(mSafetyCenterManagerWrapper, never()) + .setSafetySourceData(any(), any(), any(), any()); } @Test public void onReceive_onRefresh_whenSafetyCenterIsEnabled_withNullSourceIds_doesNotSetData() { when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); - Intent intent = new Intent().setAction(ACTION_REFRESH_SAFETY_SOURCES); + Intent intent = + new Intent() + .setAction(ACTION_REFRESH_SAFETY_SOURCES) + .putExtra(EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID, REFRESH_BROADCAST_ID); new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent); - verify(mSafetyCenterManagerWrapper, never()).setSafetySourceData( - any(), any(), any(), any()); + verify(mSafetyCenterManagerWrapper, never()) + .setSafetySourceData(any(), any(), any(), any()); } @Test @@ -121,12 +130,29 @@ public class SafetySourceBroadcastReceiverTest { Intent intent = new Intent() .setAction(ACTION_REFRESH_SAFETY_SOURCES) - .putExtra(EXTRA_REFRESH_SAFETY_SOURCE_IDS, new String[]{}); + .putExtra(EXTRA_REFRESH_SAFETY_SOURCE_IDS, new String[] {}) + .putExtra(EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID, REFRESH_BROADCAST_ID); new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent); - verify(mSafetyCenterManagerWrapper, never()).setSafetySourceData( - any(), any(), any(), any()); + verify(mSafetyCenterManagerWrapper, never()) + .setSafetySourceData(any(), any(), any(), any()); + } + + @Test + public void onReceive_onRefresh_whenSafetyCenterIsEnabled_withNoBroadcastId_doesNotSetData() { + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + Intent intent = + new Intent() + .setAction(ACTION_REFRESH_SAFETY_SOURCES) + .putExtra( + EXTRA_REFRESH_SAFETY_SOURCE_IDS, + new String[] {LockScreenSafetySource.SAFETY_SOURCE_ID}); + + new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent); + + verify(mSafetyCenterManagerWrapper, never()) + .setSafetySourceData(any(), any(), any(), any()); } @Test @@ -137,38 +163,19 @@ public class SafetySourceBroadcastReceiverTest { .setAction(ACTION_REFRESH_SAFETY_SOURCES) .putExtra( EXTRA_REFRESH_SAFETY_SOURCE_IDS, - new String[]{ LockScreenSafetySource.SAFETY_SOURCE_ID }); + new String[] {LockScreenSafetySource.SAFETY_SOURCE_ID}) + .putExtra(EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID, REFRESH_BROADCAST_ID); new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent); ArgumentCaptor captor = ArgumentCaptor.forClass(SafetyEvent.class); verify(mSafetyCenterManagerWrapper, times(1)) .setSafetySourceData(any(), any(), any(), captor.capture()); - assertThat(captor.getValue()).isEqualTo( - new SafetyEvent.Builder(SAFETY_EVENT_TYPE_REFRESH_REQUESTED).build()); - } - - @Test - public void onReceive_onRefreshWithBroadcastId_setsRefreshEventWithBroadcastId() { - final String refreshBroadcastId = "REFRESH_BROADCAST_ID"; - when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); - Intent intent = - new Intent() - .setAction(ACTION_REFRESH_SAFETY_SOURCES) - .putExtra( - EXTRA_REFRESH_SAFETY_SOURCE_IDS, - new String[]{ LockScreenSafetySource.SAFETY_SOURCE_ID }) - .putExtra(EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID, refreshBroadcastId); - - new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent); - ArgumentCaptor captor = ArgumentCaptor.forClass(SafetyEvent.class); - verify(mSafetyCenterManagerWrapper, times(1)) - .setSafetySourceData(any(), any(), any(), captor.capture()); - - assertThat(captor.getValue().getRefreshBroadcastId()).isEqualTo(refreshBroadcastId); - assertThat(captor.getValue()).isEqualTo( - new SafetyEvent.Builder(SAFETY_EVENT_TYPE_REFRESH_REQUESTED) - .setRefreshBroadcastId(refreshBroadcastId).build()); + assertThat(captor.getValue()) + .isEqualTo( + new SafetyEvent.Builder(SAFETY_EVENT_TYPE_REFRESH_REQUESTED) + .setRefreshBroadcastId(REFRESH_BROADCAST_ID) + .build()); } @Test @@ -179,7 +186,8 @@ public class SafetySourceBroadcastReceiverTest { .setAction(ACTION_REFRESH_SAFETY_SOURCES) .putExtra( EXTRA_REFRESH_SAFETY_SOURCE_IDS, - new String[]{ LockScreenSafetySource.SAFETY_SOURCE_ID }); + new String[] {LockScreenSafetySource.SAFETY_SOURCE_ID}) + .putExtra(EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID, REFRESH_BROADCAST_ID); new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent); ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); @@ -197,7 +205,8 @@ public class SafetySourceBroadcastReceiverTest { .setAction(ACTION_REFRESH_SAFETY_SOURCES) .putExtra( EXTRA_REFRESH_SAFETY_SOURCE_IDS, - new String[]{ BiometricsSafetySource.SAFETY_SOURCE_ID }); + new String[] {BiometricsSafetySource.SAFETY_SOURCE_ID}) + .putExtra(EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID, REFRESH_BROADCAST_ID); new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent); ArgumentCaptor captor = ArgumentCaptor.forClass(String.class);