Snap for 8590907 from 36693d8096 to tm-release

Change-Id: I6f4a4d6bcffef25a6b3371df9c093e3caae09a40
This commit is contained in:
Android Build Coastguard Worker
2022-05-14 01:09:06 +00:00
54 changed files with 1564 additions and 304 deletions

View File

@@ -4474,6 +4474,16 @@
</intent-filter>
</receiver>
<activity
android:name="com.android.settings.bluetooth.QrCodeScanModeActivity"
android:permission="android.permission.BLUETOOTH_CONNECT"
android:exported="true">
<intent-filter>
<action android:name="android.settings.BLUETOOTH_LE_AUDIO_QR_CODE_SCANNER"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<!-- This is the longest AndroidManifest.xml ever. -->
</application>
</manifest>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp"
android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path android:fillColor="@android:color/white"
android:pathData="M2,7V2H7V4H4V7ZM2,22V17H4V20H7V22ZM17,22V20H20V17H22V22ZM20,7V4H17V2H22V7ZM17.5,17.5H19V19H17.5ZM17.5,14.5H19V16H17.5ZM16,16H17.5V17.5H16ZM14.5,17.5H16V19H14.5ZM13,16H14.5V17.5H13ZM16,13H17.5V14.5H16ZM14.5,14.5H16V16H14.5ZM13,13H14.5V14.5H13ZM19,5V11H13V5ZM11,13V19H5V13ZM11,5V11H5V5ZM9.5,17.5V14.5H6.5V17.5ZM9.5,9.5V6.5H6.5V9.5ZM17.5,9.5V6.5H14.5V9.5Z"/>
</vector>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>

View File

@@ -0,0 +1,102 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="@+id/sud_layout_icon_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="3"
android:layout_marginBottom="35dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:id="@+id/sud_layout_icon"
android:src="@drawable/ic_qr_code_scanner"
android:tint="?androidprv:attr/colorAccentPrimaryVariant"
android:layout_width="@dimen/qrcode_icon_size"
android:layout_height="@dimen/qrcode_icon_size"
android:contentDescription="@null"/>
<TextView
android:id="@+id/sud_layout_title"
style="@style/QrCodeScanner"
android:textSize="24sp"
android:text="@string/bluetooth_find_broadcast_button_scan"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="19dp"/>
<TextView
android:id="@+id/sud_layout_subtitle"
style="@style/QrCodeScanner"
android:text="@string/bt_le_audio_scan_qr_code_scanner"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"/>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="7"
android:orientation="vertical">
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:gravity="center"
android:clipChildren="true">
<TextureView
android:id="@+id/preview_view"
android:layout_marginStart="@dimen/qrcode_preview_margin"
android:layout_marginEnd="@dimen/qrcode_preview_margin"
android:layout_width="match_parent"
android:layout_height="@dimen/qrcode_preview_size"/>
</FrameLayout>
<TextView
android:id="@+id/error_message"
style="@style/TextAppearance.ErrorText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginStart="?attr/sudMarginStart"
android:layout_marginEnd="?attr/sudMarginEnd"
android:gravity="center"
android:layout_gravity="center"
android:visibility="invisible"/>
</LinearLayout>
</LinearLayout>

View File

@@ -21,12 +21,6 @@
android:layout_height="match_parent"
android:orientation="vertical">
<com.android.settings.widget.SettingsMainSwitchBar
android:id="@+id/switch_bar"
android:title="@string/wifi_calling_main_switch_title"
android:layout_height="wrap_content"
android:layout_width="match_parent" />
<FrameLayout
android:id="@android:id/tabcontent"
android:layout_width="match_parent"

View File

@@ -485,4 +485,9 @@
<!-- Sims/Data mobile/Calls/SMS select dialog-->
<dimen name="sims_select_margin_bottom">24dp</dimen>
<dimen name="sims_select_margin_top">8dp</dimen>
<!-- QR code picture size -->
<dimen name="qrcode_preview_margin">40dp</dimen>
<dimen name="qrcode_preview_radius">30dp</dimen>
<dimen name="qrcode_icon_size">27dp</dimen>
</resources>

View File

@@ -518,6 +518,9 @@
<!-- Description for the disclaimer of per app language. [CHAR LIMIT=NONE]-->
<string name="desc_app_locale_disclaimer">Language may differ from languages available in the app. Some apps may not support this setting.</string>
<!-- Description for introduction of the locale selection supported of app list [CHAR LIMIT=NONE]-->
<string name="desc_app_locale_selection_supported">Only apps that support language selection are shown here.</string>
<!-- The title of the confirmation dialog shown when the user selects one / several languages and tries to remove them [CHAR LIMIT=60] -->
<plurals name="dlg_remove_locales_title">
<item quantity="one">Remove selected language?</item>
@@ -1297,7 +1300,10 @@
<string name="security_advanced_settings_no_work_profile_settings_summary">Encryption, credentials, and more</string>
<!-- Search keywords for the "More security settings" section in security settings. [CHAR_LIMIT=NONE] -->
<string name="security_advanced_settings_keywords">security, more security settings, more settings, advanced security settings</string>
<!-- Title for the section that has additional privacy settings. [CHAR LIMIT=60] -->
<string name="privacy_advanced_settings">More privacy settings</string>
<!-- Title for the section that has additional privacy settings. [CHAR LIMIT=60] -->
<string name="privacy_advanced_settings_summary">Autofill, activity controls, and more</string>
<!-- Text shown when "Add fingerprint" button is disabled -->
<string name="fingerprint_add_max">You can add up to <xliff:g id="count" example="5">%d</xliff:g> fingerprints</string>
@@ -8973,6 +8979,9 @@
<!-- Configure Notifications: Work profile section header [CHAR LIMIT=30] -->
<string name="profile_section_header">Work notifications</string>
<!-- Configure Notifications: Work profile section header [CHAR LIMIT=30] -->
<string name="profile_section_header_for_advanced_privacy">Work profile</string>
<!-- Configure Notifications: section header for prioritizer settings [CHAR LIMIT=80] -->
<string name="smart_notifications_title">Adaptive notifications</string>
@@ -13649,7 +13658,9 @@
<string name="default_active_sim_mobile_data">mobile data</string>
<!-- Provider Model: Message to describe "Wi-Fi scan always available feature" when Wi-Fi is off and Wi-Fi
scanning is on. To mark a link to bring the user to "scanning settings" screen. [CHAR LIMIT=NONE]-->
<string name="wifi_scan_notify_message">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. <annotation id="link">Change</annotation></string>
<string name="wifi_scan_notify_message">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.</string>
<!-- Provider Model: Link text to bring the user to "scanning settings" screen. [CHAR LIMIT=NONE]-->
<string name="wifi_scan_change">Change</string>
<!-- Summary text separator for preferences including a short description
(eg. "Connected / 5G"). [CHAR LIMIT=50] -->
@@ -14163,4 +14174,11 @@
<string name="find_broadcast_password_dialog_connection_error">Can\u2019t connect. Try again.</string>
<!-- The error message of enter password dialog in bluetooth find broadcast page [CHAR LIMIT=none] -->
<string name="find_broadcast_password_dialog_password_error">Wrong password</string>
<!-- [CHAR LIMIT=NONE] Le audio QR code scanner sub-title -->
<string name="bt_le_audio_scan_qr_code_scanner">To start listening, center the QR code below</string>
<!-- [CHAR LIMIT=NONE] Hint for QR code process failure -->
<string name="bt_le_audio_qr_code_is_not_valid_format">QR code isn\u0027t a valid format</string>
</resources>

View File

@@ -962,4 +962,11 @@
<item name="android:minWidth">0dp</item>
<item name="android:textAllCaps">false</item>
</style>
<style name="QrCodeScanner">
<item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:textSize">16sp</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:textDirection">locale</item>
</style>
</resources>

View File

@@ -46,6 +46,11 @@
android:key="action_buttons"
settings:allowDividerBelow="true"/>
<com.android.settings.slices.SlicePreference
android:key="bt_extra_control"
settings:controller="com.android.settings.slices.SlicePreferenceController"
settings:allowDividerAbove="true"/>
<com.android.settings.slices.SlicePreference
android:key="bt_device_slice"
settings:controller="com.android.settings.slices.BlockingSlicePrefController"

View File

@@ -17,7 +17,8 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:key="usage_amount">
android:key="usage_amount"
android:title="@string/summary_placeholder">
<com.android.settings.datausage.ChartDataUsagePreference
android:key="chart_data" />

View File

@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:key="privacy_dashboard_page"
android:title="@string/privacy_advanced_settings">
<!-- Work Policy info -->
<Preference
android:key="work_policy_info"
android:title="@string/work_policy_privacy_settings"
android:summary="@string/work_policy_privacy_settings_summary"
settings:controller="com.android.settings.privacy.WorkPolicyInfoPreferenceController"/>
<!-- Connected work and personal apps -->
<Preference
android:key="interact_across_profiles_privacy"
android:title="@string/interact_across_profiles_title"
android:fragment="com.android.settings.applications.specialaccess.interactacrossprofiles.InteractAcrossProfilesSettings"
settings:searchable="false"
settings:controller="com.android.settings.applications.specialaccess.interactacrossprofiles.InteractAcrossProfilesController" />
<!-- Accessibility usage -->
<Preference
android:key="privacy_accessibility_usage"
android:title="@string/accessibility_usage_title"
settings:controller="com.android.settings.privacy.AccessibilityUsagePreferenceController">
<intent android:action="android.intent.action.REVIEW_ACCESSIBILITY_SERVICES"/>
</Preference>
<!-- On lock screen notifications -->
<com.android.settings.RestrictedListPreference
android:key="privacy_lock_screen_notifications"
android:title="@string/lock_screen_notifs_title"
android:summary="@string/summary_placeholder"
settings:searchable="false"/>
<!-- Privacy Service -->
<PreferenceCategory
android:key="privacy_services"
android:layout="@layout/preference_category_no_label"/>
<PreferenceCategory
android:key="dashboard_tile_placeholder"/>
<!-- Work profile settings are at the bottom with high order value to avoid users thinking that
any of the above settings (including dynamic) are specific to the work profile. -->
<PreferenceCategory
android:key="privacy_work_profile_notifications_category"
android:title="@string/profile_section_header_for_advanced_privacy"
android:order="998">
<com.android.settings.RestrictedListPreference
android:key="privacy_lock_screen_work_profile_notifications"
android:title="@string/locked_work_profile_notification_title"
android:summary="@string/summary_placeholder"
android:order="999"
settings:searchable="false"/>
</PreferenceCategory>
<!-- Content Capture -->
<!-- NOTE: content capture has a different preference, depending whether or not the
ContentCaptureService implementations defines a custom settings activitiy on its manifest.
Hence, we show both here, but the controller itself will decide if it's available or not.
-->
<SwitchPreference
android:key="content_capture"
android:title="@string/content_capture"
android:summary="@string/content_capture_summary"
settings:controller="com.android.settings.privacy.EnableContentCapturePreferenceController"/>
<com.android.settingslib.PrimarySwitchPreference
android:key="content_capture_custom_settings"
android:title="@string/content_capture"
android:summary="@string/content_capture_summary"
settings:controller="com.android.settings.privacy.EnableContentCaptureWithServiceSettingsPreferenceController"/>
</PreferenceScreen>

View File

@@ -19,6 +19,10 @@
android:key="wifi_calling_settings"
android:title="@string/wifi_calling_settings_title">
<com.android.settings.widget.SettingsMainSwitchPreference
android:key="wifi_calling_switch_bar"
android:title="@string/wifi_calling_main_switch_title" />
<com.android.settings.wifi.calling.ListWithEntrySummaryPreference
android:key="wifi_calling_mode"
isPreferenceVisible="false"

View File

@@ -16,6 +16,8 @@
package com.android.settings;
import static android.provider.Settings.ACTION_PRIVACY_SETTINGS;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
@@ -214,7 +216,8 @@ public class Settings extends SettingsActivity {
/** Redirects to SafetyCenter if enabled. */
@VisibleForTesting
public void handleSafetyCenterRedirection() {
if (SafetyCenterManagerWrapper.get().isEnabled(this)) {
if (ACTION_PRIVACY_SETTINGS.equals(getIntent().getAction())
&& SafetyCenterManagerWrapper.get().isEnabled(this)) {
try {
startActivity(new Intent(Intent.ACTION_SAFETY_CENTER));
finish();

View File

@@ -34,6 +34,7 @@ import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.graphics.drawable.Icon;
@@ -152,6 +153,8 @@ public class SettingsActivity extends SettingsBaseActivity
*/
public static final String EXTRA_IS_FROM_SLICE = "is_from_slice";
public static final String EXTRA_USER_HANDLE = "user_handle";
/**
* Personal or Work profile tab of {@link ProfileSelectFragment}
* <p>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;

View File

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

View File

@@ -49,6 +49,8 @@ public class ToggleScreenReaderPreferenceFragmentForSetupWizard
if (mTopIntroPreference != null) {
mTopIntroPreference.setVisible(false);
}
mToggleServiceSwitchPreference.applyPartnerCustomizationPaddingStyle();
}
@Override

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<BluetoothLeBroadcastReceiveState> receiveStateList =
mLeBroadcastAssistant.getAllSources(mCachedDevice.getDevice());
if (!receiveStateList.isEmpty()) {
updateListCategoryFromBroadcastReceiveState(receiveStateList.get(0));
}
}
public int getSourceId() {
return mSourceId;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<CycleAdapter.CycleItem> {
@@ -67,7 +66,7 @@ public class CycleAdapter extends SettingsSpinnerAdapter<CycleAdapter.CycleItem>
* Rebuild list based on network data. Always selects the newest item,
* updating the inspection range on chartData.
*/
public boolean updateCycleList(List<? extends NetworkCycleData> cycleData) {
public void updateCycleList(List<? extends NetworkCycleData> 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<CycleAdapter.CycleItem>
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;
}
/**

View File

@@ -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<NetworkCycleChartData> mCycleData;
// Caches the cycles for startAppDataUsage usage, which need be cleared when resumed.
private ArrayList<Long> 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

View File

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

View File

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

View File

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

View File

@@ -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() {

View File

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

View File

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

View File

@@ -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<AbstractPreferenceController> buildPreferenceControllers(
Context context, Lifecycle lifecycle) {
final List<AbstractPreferenceController> 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<SearchIndexableResource> getXmlResourcesToIndex(
Context context, boolean enabled) {
final SearchIndexableResource sir = new SearchIndexableResource(context);
sir.xmlResId = getPreferenceScreenResId(context);
return Arrays.asList(sir);
}
@Override
public List<AbstractPreferenceController> createPreferenceControllers(
Context context) {
return buildPreferenceControllers(context, null);
}
@Override
protected boolean isPageSearchEnabled(Context context) {
return !SafetyCenterManagerWrapper.get().isEnabled(context);
}
};
}

View File

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

View File

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

View File

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

View File

@@ -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<SubscriptionInfo> 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<SubscriptionInfo> subInfoList) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 =
"<HEARABLE_CONTROL_SLICE_WITH_WIDTH>" + SETTINGS_URI
+ "</HEARABLE_CONTROL_SLICE_WITH_WIDTH>";
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);
}
}

View File

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

View File

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

View File

@@ -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 extends Preference> 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) {

View File

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

View File

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

View File

@@ -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<SafetyEvent> 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<SafetyEvent> 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<String> 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<String> captor = ArgumentCaptor.forClass(String.class);