Snap for 12361654 from f37570e46e to 24Q4-release

Change-Id: I8eb0b17ae5eea2c3bb31fc4f2c7a2cf16a31b2e6
This commit is contained in:
Android Build Coastguard Worker
2024-09-12 23:21:19 +00:00
33 changed files with 726 additions and 371 deletions

View File

@@ -783,9 +783,13 @@
<activity android:name="Settings$CombinedBiometricSettingsActivity" <activity android:name="Settings$CombinedBiometricSettingsActivity"
android:label="@string/security_settings_biometric_preference_title" android:label="@string/security_settings_biometric_preference_title"
android:exported="false" android:exported="true"
android:enableOnBackInvokedCallback="false" android:enableOnBackInvokedCallback="false"
android:taskAffinity="com.android.settings.root"> android:taskAffinity="com.android.settings.root">
<intent-filter>
<action android:name="android.settings.COMBINED_BIOMETRICS_SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data android:name="com.android.settings.FRAGMENT_CLASS" <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
android:value="com.android.settings.biometrics.combination.CombinedBiometricSettings" /> android:value="com.android.settings.biometrics.combination.CombinedBiometricSettings" />
<meta-data android:name="com.android.settings.HIGHLIGHT_MENU_KEY" <meta-data android:name="com.android.settings.HIGHLIGHT_MENU_KEY"

View File

@@ -0,0 +1,35 @@
<!--
Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<com.android.settingslib.widget.AdaptiveIconShapeDrawable
android:width="@dimen/accessibility_icon_size"
android:height="@dimen/accessibility_icon_size"
android:color="@color/accessibility_feature_background"/>
</item>
<item android:gravity="center">
<vector
android:height="32dp"
android:width="32dp"
android:viewportHeight="32"
android:viewportWidth="32">
<path
android:fillColor="#fff"
android:pathData="M19.15,22.35a2.6,2.6 0,0 0,1.91 -0.79,2.6 2.6,0 0,0 0.79,-1.91v-0.9h-5.4v0.9c0,0.75 0.26,1.39 0.79,1.91a2.6,2.6 0,0 0,1.91 0.79ZM16.49,17.4h1.98v-2.17a2.6,2.6 0,0 0,-1.98 2.17ZM19.82,17.4h2a2.6,2.6 0,0 0,-0.68 -1.39,2.62 2.62,0 0,0 -1.32,-0.78v2.17ZM19.15,23.7a3.96,3.96 0,0 1,-4.05 -4.05v-1.8c0,-1.14 0.39,-2.1 1.16,-2.87a3.93,3.93 0,0 1,2.89 -1.18c1.14,0 2.1,0.4 2.87,1.18a3.87,3.87 0,0 1,1.18 2.87v1.8c0,1.14 -0.4,2.1 -1.18,2.89a3.9,3.9 0,0 1,-2.87 1.16ZM10.15,20.55v-8.1,8.1ZM10.15,21.9c-0.38,0 -0.7,-0.13 -0.96,-0.4s-0.39,-0.59 -0.39,-0.95v-8.1c0,-0.36 0.13,-0.67 0.4,-0.94 0.26,-0.27 0.57,-0.41 0.95,-0.41h11.7c0.37,0 0.7,0.14 0.96,0.41 0.26,0.27 0.39,0.58 0.39,0.94L10.15,12.45v8.1h3.6v1.35h-3.6Z"/>
</vector>
</item>
</layer-list>

View File

@@ -19,21 +19,5 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
android:id="@+id/setup_wizard_layout"
android:icon="@drawable/ic_delete_accent" android:icon="@drawable/ic_delete_accent"
app:sucHeaderText="@string/main_clear_confirm_title"> app:sucHeaderText="@string/main_clear_confirm_title" />
<LinearLayout
style="@style/SudContentFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/sud_layout_subtitle"
style="@style/TextAppearance.PreferenceTitle.SettingsLib"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/main_clear_final_desc"/>
</LinearLayout>
</com.google.android.setupdesign.GlifLayout>

View File

@@ -4532,8 +4532,8 @@
<string name="trackpad_bottom_right_tap_summary">Click in the bottom right corner of the touchpad for more options</string> <string name="trackpad_bottom_right_tap_summary">Click in the bottom right corner of the touchpad for more options</string>
<!-- Title text for 'Pointer speed'. [CHAR LIMIT=35] --> <!-- Title text for 'Pointer speed'. [CHAR LIMIT=35] -->
<string name="trackpad_pointer_speed">Pointer speed</string> <string name="trackpad_pointer_speed">Pointer speed</string>
<!-- Title text for mouse pointer fill style. [CHAR LIMIT=35] --> <!-- Title text for mouse pointer color. [CHAR LIMIT=35] -->
<string name="pointer_fill_style">Pointer fill style</string> <string name="pointer_fill_style">Pointer color</string>
<!-- Content description for black pointer fill style. [CHAR LIMIT=60] --> <!-- Content description for black pointer fill style. [CHAR LIMIT=60] -->
<string name="pointer_fill_style_black_button">Change pointer fill style to black</string> <string name="pointer_fill_style_black_button">Change pointer fill style to black</string>
<!-- Content description for green pointer fill style. [CHAR LIMIT=60] --> <!-- Content description for green pointer fill style. [CHAR LIMIT=60] -->
@@ -4810,6 +4810,12 @@
<string name="display_category_title">Display</string> <string name="display_category_title">Display</string>
<!-- Title for the accessibility color and motion page. [CHAR LIMIT=50] --> <!-- Title for the accessibility color and motion page. [CHAR LIMIT=50] -->
<string name="accessibility_color_and_motion_title">Color and motion</string> <string name="accessibility_color_and_motion_title">Color and motion</string>
<!-- Title for the accessibility pointer and touchpad page. [CHAR LIMIT=50] -->
<string name="accessibility_pointer_and_touchpad_title">Pointer &amp; touchpad accessibility</string>
<!-- Summary for the accessibility pointer and touchpad page. [CHAR LIMIT=50] -->
<string name="accessibility_pointer_and_touchpad_summary">Pointer color, pointer size &amp; more</string>
<!-- Title for the accessibility pointer color customization page. [CHAR LIMIT=50] -->
<string name="accessibility_pointer_color_customization_title">Pointer color customization</string>
<!-- Title for the accessibility color contrast page. [CHAR LIMIT=50] --> <!-- Title for the accessibility color contrast page. [CHAR LIMIT=50] -->
<string name="accessibility_color_contrast_title">Color contrast</string> <string name="accessibility_color_contrast_title">Color contrast</string>
<!-- Intro for the accessibility color contrast page. [CHAR LIMIT=NONE] --> <!-- Intro for the accessibility color contrast page. [CHAR LIMIT=NONE] -->

View File

@@ -72,17 +72,6 @@
android:title="@string/accessibility_toggle_large_pointer_icon_title" android:title="@string/accessibility_toggle_large_pointer_icon_title"
settings:controller="com.android.settings.accessibility.LargePointerIconPreferenceController"/> settings:controller="com.android.settings.accessibility.LargePointerIconPreferenceController"/>
<com.android.settings.widget.LabeledSeekBarPreference
android:key="large_pointer_scale"
android:title="@string/accessibility_toggle_large_pointer_icon_title"
android:summary="@string/accessibility_toggle_large_pointer_icon_summary"
android:max="@integer/pointer_scale_seek_bar_end"
settings:iconStart="@drawable/ic_remove_24dp"
settings:iconStartContentDescription="@string/pointer_scale_decrease_content_description"
settings:iconEnd="@drawable/ic_add_24dp"
settings:iconEndContentDescription="@string/pointer_scale_increase_content_description"
settings:controller="com.android.settings.inputmethod.PointerScaleSeekBarController" />
<PreferenceCategory <PreferenceCategory
android:key="experimental_category" android:key="experimental_category"
android:persistent="false" android:persistent="false"

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:key="accessibility_pointer_and_touchpad"
android:persistent="false"
android:title="@string/accessibility_pointer_and_touchpad_title">
<com.android.settings.widget.LabeledSeekBarPreference
android:key="pointer_scale_preference"
android:title="@string/pointer_scale"
android:max="@integer/pointer_scale_seek_bar_end"
settings:iconStart="@drawable/ic_remove_24dp"
settings:iconStartContentDescription="@string/pointer_scale_decrease_content_description"
settings:iconEnd="@drawable/ic_add_24dp"
settings:iconEndContentDescription="@string/pointer_scale_increase_content_description"
settings:controller="com.android.settings.inputmethod.PointerScaleSeekBarController" />
<Preference
android:fragment="com.android.settings.inputmethod.PointerColorCustomizationFragment"
android:key="pointer_color_customization_preference"
android:persistent="false"
android:title="@string/accessibility_pointer_color_customization_title"/>
<Preference
android:fragment="com.android.settings.accessibility.ToggleAutoclickPreferenceFragment"
android:key="autoclick_preference"
android:persistent="false"
android:title="@string/accessibility_autoclick_preference_title"
settings:keywords="@string/keywords_auto_click"
settings:controller="com.android.settings.accessibility.AutoclickPreferenceController"/>
</PreferenceScreen>

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:key="accessibility_pointer_color_customization"
android:persistent="false"
android:title="@string/accessibility_pointer_color_customization_title">
<com.android.settings.inputmethod.PointerFillStylePreference
android:key="pointer_fill_style"
android:title="@string/pointer_fill_style"
settings:controller="com.android.settings.inputmethod.PointerFillStylePreferenceController"/>
<com.android.settings.inputmethod.PointerStrokeStylePreference
android:key="pointer_stroke_style"
android:title="@string/pointer_stroke_style"
settings:controller="com.android.settings.inputmethod.PointerStrokeStylePreferenceController"/>
</PreferenceScreen>

View File

@@ -110,6 +110,16 @@
settings:keywords="@string/keywords_vibration" settings:keywords="@string/keywords_vibration"
android:summary="@string/accessibility_vibration_settings_summary"/> android:summary="@string/accessibility_vibration_settings_summary"/>
<Preference
android:fragment="com.android.settings.inputmethod.PointerTouchpadFragment"
android:key="pointer_and_touchpad"
android:icon="@drawable/ic_pointer_and_touchpad"
android:persistent="false"
android:title="@string/accessibility_pointer_and_touchpad_title"
android:summary="@string/accessibility_pointer_and_touchpad_summary"
settings:controller="com.android.settings.inputmethod.PointerTouchpadPreferenceController"
settings:searchable="true"/>
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory

View File

@@ -37,12 +37,4 @@
android:title="@string/accessibility_setting_item_control_timeout_title" android:title="@string/accessibility_setting_item_control_timeout_title"
settings:controller="com.android.settings.accessibility.AccessibilityTimeoutPreferenceController" settings:controller="com.android.settings.accessibility.AccessibilityTimeoutPreferenceController"
settings:keywords="@string/keywords_accessibility_timeout"/> settings:keywords="@string/keywords_accessibility_timeout"/>
<Preference
android:fragment="com.android.settings.accessibility.ToggleAutoclickPreferenceFragment"
android:key="autoclick_preference"
android:persistent="false"
android:title="@string/accessibility_autoclick_preference_title"
settings:keywords="@string/keywords_auto_click"
settings:controller="com.android.settings.accessibility.AutoclickPreferenceController"/>
</PreferenceScreen> </PreferenceScreen>

View File

@@ -62,29 +62,14 @@
android:selectable="false" android:selectable="false"
settings:controller="com.android.settings.inputmethod.TrackpadPointerSpeedPreferenceController"/> settings:controller="com.android.settings.inputmethod.TrackpadPointerSpeedPreferenceController"/>
<com.android.settings.inputmethod.PointerFillStylePreference <Preference
android:key="pointer_fill_style" android:fragment="com.android.settings.inputmethod.PointerTouchpadFragment"
android:title="@string/pointer_fill_style" android:key="pointer_and_touchpad"
android:order="50" android:order="50"
settings:controller="com.android.settings.inputmethod.PointerFillStylePreferenceController"/> android:persistent="false"
android:title="@string/accessibility_pointer_and_touchpad_title"
<com.android.settings.inputmethod.PointerStrokeStylePreference android:summary="@string/accessibility_pointer_and_touchpad_summary"
android:key="pointer_stroke_style" settings:searchable="true"/>
android:title="@string/pointer_stroke_style"
android:order="60"
settings:controller="com.android.settings.inputmethod.PointerStrokeStylePreferenceController"/>
<com.android.settings.widget.LabeledSeekBarPreference
android:key="pointer_scale"
android:title="@string/pointer_scale"
android:order="70"
android:max="@integer/pointer_scale_seek_bar_end"
settings:iconStart="@drawable/ic_remove_24dp"
settings:searchable="false"
settings:iconStartContentDescription="@string/pointer_scale_decrease_content_description"
settings:iconEnd="@drawable/ic_add_24dp"
settings:iconEndContentDescription="@string/pointer_scale_increase_content_description"
settings:controller="com.android.settings.inputmethod.PointerScaleSeekBarController" />
<com.android.settingslib.widget.ButtonPreference <com.android.settingslib.widget.ButtonPreference
android:key="trackpad_touch_gesture" android:key="trackpad_touch_gesture"

View File

@@ -179,6 +179,8 @@ public class FallbackHome extends Activity {
SystemClock.uptimeMillis(), false); SystemClock.uptimeMillis(), false);
finish(); finish();
} }
} else {
Log.d(TAG, "User not yet unlocked");
} }
} }

View File

@@ -19,8 +19,6 @@ package com.android.settings;
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import android.app.ActionBar;
import android.app.Activity;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager;
import android.app.admin.FactoryResetProtectionPolicy; import android.app.admin.FactoryResetProtectionPolicy;
@@ -28,7 +26,6 @@ import android.app.settings.SettingsEnums;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
import android.graphics.Color;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.os.SystemProperties; import android.os.SystemProperties;
@@ -36,12 +33,10 @@ import android.os.UserHandle;
import android.os.UserManager; import android.os.UserManager;
import android.service.oemlock.OemLockManager; import android.service.oemlock.OemLockManager;
import android.service.persistentdata.PersistentDataBlockManager; import android.service.persistentdata.PersistentDataBlockManager;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
import android.widget.TextView;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
@@ -62,7 +57,7 @@ import com.google.android.setupdesign.GlifLayout;
* has defined one, followed by a final strongly-worded "THIS WILL ERASE EVERYTHING * has defined one, followed by a final strongly-worded "THIS WILL ERASE EVERYTHING
* ON THE PHONE" prompt. If at any time the phone is allowed to go to sleep, is * ON THE PHONE" prompt. If at any time the phone is allowed to go to sleep, is
* locked, et cetera, then the confirmation sequence is abandoned. * locked, et cetera, then the confirmation sequence is abandoned.
* * <p>
* This is the confirmation screen. * This is the confirmation screen.
*/ */
public class MainClearConfirm extends InstrumentedFragment { public class MainClearConfirm extends InstrumentedFragment {
@@ -70,9 +65,11 @@ public class MainClearConfirm extends InstrumentedFragment {
private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst"; private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
@VisibleForTesting View mContentView; @VisibleForTesting
GlifLayout mContentView;
private boolean mEraseSdCard; private boolean mEraseSdCard;
@VisibleForTesting boolean mEraseEsims; @VisibleForTesting
boolean mEraseEsims;
/** /**
* The user has gone through the multiple confirmation, so now we go ahead * The user has gone through the multiple confirmation, so now we go ahead
@@ -215,9 +212,7 @@ public class MainClearConfirm extends InstrumentedFragment {
* Configure the UI for the final confirmation interaction * Configure the UI for the final confirmation interaction
*/ */
private void establishFinalConfirmationState() { private void establishFinalConfirmationState() {
final GlifLayout layout = mContentView.findViewById(R.id.setup_wizard_layout); final FooterBarMixin mixin = mContentView.getMixin(FooterBarMixin.class);
final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class);
mixin.setPrimaryButton( mixin.setPrimaryButton(
new FooterButton.Builder(getActivity()) new FooterButton.Builder(getActivity())
.setText(R.string.main_clear_button_text) .setText(R.string.main_clear_button_text)
@@ -228,21 +223,6 @@ public class MainClearConfirm extends InstrumentedFragment {
); );
} }
private void setUpActionBarAndTitle() {
final Activity activity = getActivity();
if (activity == null) {
Log.e(TAG, "No activity attached, skipping setUpActionBarAndTitle");
return;
}
final ActionBar actionBar = activity.getActionBar();
if (actionBar == null) {
Log.e(TAG, "No actionbar, skipping setUpActionBarAndTitle");
return;
}
actionBar.hide();
activity.getWindow().setStatusBarColor(Color.TRANSPARENT);
}
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
@@ -258,32 +238,26 @@ public class MainClearConfirm extends InstrumentedFragment {
.show(); .show();
return new View(getActivity()); return new View(getActivity());
} }
mContentView = inflater.inflate(R.layout.main_clear_confirm, null); mContentView = (GlifLayout) inflater.inflate(R.layout.main_clear_confirm, null);
setUpActionBarAndTitle();
establishFinalConfirmationState(); establishFinalConfirmationState();
setAccessibilityTitle();
setSubtitle(); setSubtitle();
setAccessibilityTitle();
return mContentView; return mContentView;
} }
private void setAccessibilityTitle() { private void setAccessibilityTitle() {
CharSequence currentTitle = getActivity().getTitle(); CharSequence currentTitle = getActivity().getTitle();
TextView confirmationMessage = mContentView.findViewById(R.id.sud_layout_description); CharSequence confirmationMessage = mContentView.getDescriptionText();
if (confirmationMessage != null) { if (confirmationMessage != null) {
String accessibleText = new StringBuilder(currentTitle).append(",").append( String accessibleText = currentTitle + "," + confirmationMessage;
confirmationMessage.getText()).toString();
getActivity().setTitle(Utils.createAccessibleSequence(currentTitle, accessibleText)); getActivity().setTitle(Utils.createAccessibleSequence(currentTitle, accessibleText));
} }
} }
@VisibleForTesting @VisibleForTesting
void setSubtitle() { void setSubtitle() {
if (mEraseEsims) { mContentView.setDescriptionText(
TextView confirmationMessage = mContentView.findViewById(R.id.sud_layout_description); mEraseEsims ? R.string.main_clear_final_desc_esim : R.string.main_clear_final_desc);
if (confirmationMessage != null) {
confirmationMessage.setText(R.string.main_clear_final_desc_esim);
}
}
} }
@Override @Override

View File

@@ -67,14 +67,11 @@ public class FloatingMenuFadePreferenceController extends BasePreferenceControll
@Override @Override
public CharSequence getSummary() { public CharSequence getSummary() {
if (mPreference != null) { int rId = R.string.accessibility_button_fade_summary;
return mPreference.isEnabled() if (mPreference != null && !mPreference.isEnabled()) {
? "%s" rId = R.string.accessibility_button_disabled_button_mode_summary;
: mContext.getString(
R.string.accessibility_button_disabled_button_mode_summary);
} else {
return "%s";
} }
return mContext.getString(rId);
} }
@Override @Override

View File

@@ -44,7 +44,7 @@ public class AudioSharingLoadingStateDialogFragment extends InstrumentedDialogFr
private static final String TAG = "AudioSharingLoadingDlg"; private static final String TAG = "AudioSharingLoadingDlg";
private static final String BUNDLE_KEY_MESSAGE = "bundle_key_message"; private static final String BUNDLE_KEY_MESSAGE = "bundle_key_message";
private static final long AUTO_DISMISS_TIME_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(10); private static final long AUTO_DISMISS_TIME_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(15);
private static final int AUTO_DISMISS_MESSAGE_ID = R.id.message; private static final int AUTO_DISMISS_MESSAGE_ID = R.id.message;
private static String sMessage = ""; private static String sMessage = "";
@@ -74,13 +74,15 @@ public class AudioSharingLoadingStateDialogFragment extends InstrumentedDialogFr
} }
AlertDialog dialog = AudioSharingDialogHelper.getDialogIfShowing(manager, TAG); AlertDialog dialog = AudioSharingDialogHelper.getDialogIfShowing(manager, TAG);
if (dialog != null) { if (dialog != null) {
if (sMessage.equals(message)) { if (!sMessage.equals(message)) {
Log.d(TAG, "Dialog is showing with same message, return."); Log.d(TAG, "Update dialog message.");
return; TextView messageView = dialog.findViewById(R.id.message);
} else { if (messageView != null) {
Log.d(TAG, "Dialog is showing with different message, dismiss and reshow."); messageView.setText(message);
dialog.dismiss(); }
} }
Log.d(TAG, "Dialog is showing, return.");
return;
} }
sMessage = message; sMessage = message;
Log.d(TAG, "Show up the loading dialog."); Log.d(TAG, "Show up the loading dialog.");
@@ -113,8 +115,10 @@ public class AudioSharingLoadingStateDialogFragment extends InstrumentedDialogFr
@NonNull @NonNull
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
mHandler = new Handler(Looper.getMainLooper()); mHandler = new Handler(Looper.getMainLooper());
mHandler.postDelayed(() -> dismiss(), AUTO_DISMISS_MESSAGE_ID, mHandler.postDelayed(() -> {
AUTO_DISMISS_TIME_THRESHOLD_MS); Log.d(TAG, "Auto dismiss dialog after timeout");
dismiss();
}, AUTO_DISMISS_MESSAGE_ID, AUTO_DISMISS_TIME_THRESHOLD_MS);
Bundle args = requireArguments(); Bundle args = requireArguments();
String message = args.getString(BUNDLE_KEY_MESSAGE, ""); String message = args.getString(BUNDLE_KEY_MESSAGE, "");
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
@@ -132,6 +136,7 @@ public class AudioSharingLoadingStateDialogFragment extends InstrumentedDialogFr
public void onDismiss(@NonNull DialogInterface dialog) { public void onDismiss(@NonNull DialogInterface dialog) {
super.onDismiss(dialog); super.onDismiss(dialog);
if (mHandler != null) { if (mHandler != null) {
Log.d(TAG, "Dialog dismissed, remove auto dismiss task");
mHandler.removeMessages(AUTO_DISMISS_MESSAGE_ID); mHandler.removeMessages(AUTO_DISMISS_MESSAGE_ID);
} }
} }

View File

@@ -70,6 +70,7 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@@ -112,12 +113,13 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
private final MetricsFeatureProvider mMetricsFeatureProvider; private final MetricsFeatureProvider mMetricsFeatureProvider;
private final OnAudioSharingStateChangedListener mListener; private final OnAudioSharingStateChangedListener mListener;
private Map<Integer, List<BluetoothDevice>> mGroupedConnectedDevices = new HashMap<>(); private Map<Integer, List<BluetoothDevice>> mGroupedConnectedDevices = new HashMap<>();
private List<BluetoothDevice> mTargetActiveSinks = new ArrayList<>(); @Nullable private AudioSharingDeviceItem mTargetActiveItem;
private List<AudioSharingDeviceItem> mDeviceItemsForSharing = new ArrayList<>(); private List<AudioSharingDeviceItem> mDeviceItemsForSharing = new ArrayList<>();
@VisibleForTesting IntentFilter mIntentFilter; @VisibleForTesting IntentFilter mIntentFilter;
private final AtomicBoolean mCallbacksRegistered = new AtomicBoolean(false); private final AtomicBoolean mCallbacksRegistered = new AtomicBoolean(false);
private AtomicInteger mIntentHandleStage = private AtomicInteger mIntentHandleStage =
new AtomicInteger(StartIntentHandleStage.TO_HANDLE.ordinal()); new AtomicInteger(StartIntentHandleStage.TO_HANDLE.ordinal());
private CopyOnWriteArrayList<BluetoothDevice> mSinksInAdding = new CopyOnWriteArrayList<>();
@VisibleForTesting @VisibleForTesting
BroadcastReceiver mReceiver = BroadcastReceiver mReceiver =
@@ -294,7 +296,16 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
public void onReceiveStateChanged( public void onReceiveStateChanged(
@NonNull BluetoothDevice sink, @NonNull BluetoothDevice sink,
int sourceId, int sourceId,
@NonNull BluetoothLeBroadcastReceiveState state) {} @NonNull BluetoothLeBroadcastReceiveState state) {
if (BluetoothUtils.isConnected(state)) {
if (mSinksInAdding.contains(sink)) {
mSinksInAdding.remove(sink);
}
dismissLoadingStateDialogIfNeeded();
Log.d(TAG, "onReceiveStateChanged() connected, sink = " + sink
+ ", remaining sinks = " + mSinksInAdding);
}
}
}; };
AudioSharingSwitchBarController( AudioSharingSwitchBarController(
@@ -506,17 +517,20 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
mBtManager, mGroupedConnectedDevices, /* filterByInSharing= */ false); mBtManager, mGroupedConnectedDevices, /* filterByInSharing= */ false);
// deviceItems is ordered. The active device is the first place if exits. // deviceItems is ordered. The active device is the first place if exits.
mDeviceItemsForSharing = new ArrayList<>(deviceItems); mDeviceItemsForSharing = new ArrayList<>(deviceItems);
mTargetActiveSinks = new ArrayList<>(); mTargetActiveItem = null;
if (!deviceItems.isEmpty() && deviceItems.get(0).isActive()) { if (!deviceItems.isEmpty() && deviceItems.get(0).isActive()) {
// If active device exists for audio sharing, share to it // If active device exists for audio sharing, share to it
// automatically once the broadcast is started. // automatically once the broadcast is started.
mTargetActiveSinks = mTargetActiveItem = deviceItems.get(0);
mGroupedConnectedDevices.getOrDefault(
deviceItems.get(0).getGroupId(), ImmutableList.of());
mDeviceItemsForSharing.remove(0); mDeviceItemsForSharing.remove(0);
} }
if (mBroadcast != null) { if (mBroadcast != null) {
mBroadcast.startPrivateBroadcast(); mBroadcast.startPrivateBroadcast();
mSinksInAdding.clear();
// TODO: use string res once finalized.
AudioSharingUtils.postOnMainThread(mContext,
() -> AudioSharingLoadingStateDialogFragment.show(mFragment,
"Starting audio stream..."));
mMetricsFeatureProvider.action( mMetricsFeatureProvider.action(
mContext, mContext,
SettingsEnums.ACTION_AUDIO_SHARING_MAIN_SWITCH_ON, SettingsEnums.ACTION_AUDIO_SHARING_MAIN_SWITCH_ON,
@@ -580,27 +594,30 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
} }
private void handleOnBroadcastReady() { private void handleOnBroadcastReady() {
List<BluetoothDevice> targetActiveSinks = mTargetActiveItem == null ? ImmutableList.of()
: mGroupedConnectedDevices.getOrDefault(
mTargetActiveItem.getGroupId(), ImmutableList.of());
Pair<Integer, Object>[] eventData = Pair<Integer, Object>[] eventData =
AudioSharingUtils.buildAudioSharingDialogEventData( AudioSharingUtils.buildAudioSharingDialogEventData(
SettingsEnums.AUDIO_SHARING_SETTINGS, SettingsEnums.AUDIO_SHARING_SETTINGS,
SettingsEnums.DIALOG_AUDIO_SHARING_ADD_DEVICE, SettingsEnums.DIALOG_AUDIO_SHARING_ADD_DEVICE,
/* userTriggered= */ false, /* userTriggered= */ false,
/* deviceCountInSharing= */ mTargetActiveSinks.isEmpty() ? 0 : 1, /* deviceCountInSharing= */ targetActiveSinks.isEmpty() ? 0 : 1,
/* candidateDeviceCount= */ mDeviceItemsForSharing.size()); /* candidateDeviceCount= */ mDeviceItemsForSharing.size());
if (!mTargetActiveSinks.isEmpty()) { if (!targetActiveSinks.isEmpty() && mTargetActiveItem != null) {
Log.d(TAG, "handleOnBroadcastReady: automatically add source to active sinks."); Log.d(TAG, "handleOnBroadcastReady: automatically add source to active sinks.");
AudioSharingUtils.addSourceToTargetSinks(mTargetActiveSinks, mBtManager); addSourceToTargetSinks(targetActiveSinks, mTargetActiveItem.getName());
mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING); mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING);
mTargetActiveSinks.clear(); mTargetActiveItem = null;
if (mIntentHandleStage.compareAndSet( if (mIntentHandleStage.compareAndSet(
StartIntentHandleStage.HANDLE_AUTO_ADD.ordinal(), StartIntentHandleStage.HANDLE_AUTO_ADD.ordinal(),
StartIntentHandleStage.HANDLED.ordinal()) StartIntentHandleStage.HANDLED.ordinal())
&& mDeviceItemsForSharing.size() == 1) { && mDeviceItemsForSharing.size() == 1) {
Log.d(TAG, "handleOnBroadcastReady: auto add source to the second device"); Log.d(TAG, "handleOnBroadcastReady: auto add source to the second device");
AudioSharingUtils.addSourceToTargetSinks( AudioSharingDeviceItem target = mDeviceItemsForSharing.get(0);
mGroupedConnectedDevices.getOrDefault( List<BluetoothDevice> targetSinks = mGroupedConnectedDevices.getOrDefault(
mDeviceItemsForSharing.get(0).getGroupId(), ImmutableList.of()), target.getGroupId(), ImmutableList.of());
mBtManager); addSourceToTargetSinks(targetSinks, target.getName());
cleanUp(); cleanUp();
// TODO: Add metric for auto add by intent // TODO: Add metric for auto add by intent
return; return;
@@ -611,6 +628,7 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
StartIntentHandleStage.HANDLED.ordinal()); StartIntentHandleStage.HANDLED.ordinal());
if (mFragment == null) { if (mFragment == null) {
Log.d(TAG, "handleOnBroadcastReady: dialog fail to show due to null fragment."); Log.d(TAG, "handleOnBroadcastReady: dialog fail to show due to null fragment.");
dismissLoadingStateDialogIfNeeded();
cleanUp(); cleanUp();
return; return;
} }
@@ -622,15 +640,15 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
new AudioSharingDialogFragment.DialogEventListener() { new AudioSharingDialogFragment.DialogEventListener() {
@Override @Override
public void onItemClick(@NonNull AudioSharingDeviceItem item) { public void onItemClick(@NonNull AudioSharingDeviceItem item) {
AudioSharingUtils.addSourceToTargetSinks( List<BluetoothDevice> targetSinks = mGroupedConnectedDevices.getOrDefault(
mGroupedConnectedDevices.getOrDefault( item.getGroupId(), ImmutableList.of());
item.getGroupId(), ImmutableList.of()), addSourceToTargetSinks(targetSinks, item.getName());
mBtManager);
cleanUp(); cleanUp();
} }
@Override @Override
public void onCancelClick() { public void onCancelClick() {
dismissLoadingStateDialogIfNeeded();
cleanUp(); cleanUp();
} }
}; };
@@ -700,6 +718,27 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
}); });
} }
private void addSourceToTargetSinks(List<BluetoothDevice> targetActiveSinks,
@NonNull String sinkName) {
mSinksInAdding.addAll(targetActiveSinks);
AudioSharingUtils.addSourceToTargetSinks(targetActiveSinks, mBtManager);
// TODO: move to res once finalized
String loadingMessage = "Sharing with " + sinkName + "...";
showLoadingStateDialog(loadingMessage);
}
private void showLoadingStateDialog(@NonNull String loadingMessage) {
AudioSharingUtils.postOnMainThread(mContext,
() -> AudioSharingLoadingStateDialogFragment.show(mFragment, loadingMessage));
}
private void dismissLoadingStateDialogIfNeeded() {
if (mSinksInAdding.isEmpty()) {
AudioSharingUtils.postOnMainThread(mContext,
() -> AudioSharingLoadingStateDialogFragment.dismiss(mFragment));
}
}
private void cleanUp() { private void cleanUp() {
mGroupedConnectedDevices.clear(); mGroupedConnectedDevices.clear();
mDeviceItemsForSharing.clear(); mDeviceItemsForSharing.clear();

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.inputmethod;
import static com.android.settings.inputmethod.NewKeyboardSettingsUtils.isMouse;
import static com.android.settings.inputmethod.NewKeyboardSettingsUtils.isTouchpad;
import android.app.settings.SettingsEnums;
import android.content.Context;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.search.SearchIndexable;
/** Settings for pointer and touchpad. */
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
public class PointerColorCustomizationFragment extends DashboardFragment {
private static final String TAG = "PointerColorCustomizationFragment";
@Override
public int getMetricsCategory() {
return SettingsEnums.ACCESSIBILITY_POINTER_COLOR_CUSTOMIZATION;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.accessibility_pointer_color_customization;
}
@Override
protected String getLogTag() {
return TAG;
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.accessibility_pointer_color_customization) {
@Override
protected boolean isPageSearchEnabled(Context context) {
return isTouchpad() || isMouse();
}
};
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.inputmethod;
import static com.android.settings.inputmethod.NewKeyboardSettingsUtils.isMouse;
import static com.android.settings.inputmethod.NewKeyboardSettingsUtils.isTouchpad;
import android.app.settings.SettingsEnums;
import android.content.Context;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.search.SearchIndexable;
/** Accessibility settings for pointer and touchpad. */
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
public class PointerTouchpadFragment extends DashboardFragment {
private static final String TAG = "PointerTouchpadFragment";
@Override
public int getMetricsCategory() {
return SettingsEnums.ACCESSIBILITY_POINTER_TOUCHPAD;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.accessibility_pointer_and_touchpad;
}
@Override
protected String getLogTag() {
return TAG;
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.accessibility_pointer_and_touchpad) {
@Override
protected boolean isPageSearchEnabled(Context context) {
return isTouchpad() || isMouse();
}
};
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.inputmethod;
import android.content.Context;
import androidx.annotation.NonNull;
import com.android.settings.core.BasePreferenceController;
/** Controller that shows and updates the pointer touchpad preference. */
public class PointerTouchpadPreferenceController extends BasePreferenceController {
public PointerTouchpadPreferenceController(@NonNull Context context,
@NonNull String preferenceKey) {
super(context, preferenceKey);
}
@Override
public int getAvailabilityStatus() {
boolean isTouchpad = NewKeyboardSettingsUtils.isTouchpad();
boolean isMouse = NewKeyboardSettingsUtils.isMouse();
return (isTouchpad || isMouse) ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
}
}

View File

@@ -20,7 +20,7 @@ import android.app.Application
import android.telephony.SubscriptionManager import android.telephony.SubscriptionManager
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.android.settings.network.telephony.getSelectableSubscriptionInfoList import com.android.settings.network.telephony.SubscriptionRepository
import com.android.settings.network.telephony.subscriptionsChangedFlow import com.android.settings.network.telephony.subscriptionsChangedFlow
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
@@ -45,7 +45,8 @@ class SubscriptionInfoListViewModel(application: Application) : AndroidViewModel
* Getting the Selectable SubscriptionInfo List from the SubscriptionRepository's * Getting the Selectable SubscriptionInfo List from the SubscriptionRepository's
* getAvailableSubscriptionInfoList * getAvailableSubscriptionInfoList
*/ */
val selectableSubscriptionInfoListFlow = application.subscriptionsChangedFlow().map { val selectableSubscriptionInfoListFlow =
application.getSelectableSubscriptionInfoList() SubscriptionRepository(application)
}.stateIn(scope, SharingStarted.Eagerly, initialValue = emptyList()) .selectableSubscriptionInfoListFlow()
.stateIn(scope, SharingStarted.Eagerly, initialValue = emptyList())
} }

View File

@@ -52,7 +52,7 @@ import com.android.settings.network.helper.SelectableSubscriptions;
import com.android.settings.network.helper.SubscriptionAnnotation; import com.android.settings.network.helper.SubscriptionAnnotation;
import com.android.settings.network.telephony.DeleteEuiccSubscriptionDialogActivity; import com.android.settings.network.telephony.DeleteEuiccSubscriptionDialogActivity;
import com.android.settings.network.telephony.EuiccRacConnectivityDialogActivity; import com.android.settings.network.telephony.EuiccRacConnectivityDialogActivity;
import com.android.settings.network.telephony.SubscriptionRepositoryKt; import com.android.settings.network.telephony.SubscriptionRepository;
import com.android.settings.network.telephony.ToggleSubscriptionDialogActivity; import com.android.settings.network.telephony.ToggleSubscriptionDialogActivity;
import java.util.ArrayList; import java.util.ArrayList;
@@ -508,7 +508,7 @@ public class SubscriptionUtil {
* @return list of user selectable subscriptions. * @return list of user selectable subscriptions.
*/ */
public static List<SubscriptionInfo> getSelectableSubscriptionInfoList(Context context) { public static List<SubscriptionInfo> getSelectableSubscriptionInfoList(Context context) {
return SubscriptionRepositoryKt.getSelectableSubscriptionInfoList(context); return new SubscriptionRepository(context).getSelectableSubscriptionInfoList();
} }
/** /**

View File

@@ -42,13 +42,48 @@ private const val TAG = "SubscriptionRepository"
class SubscriptionRepository(private val context: Context) { class SubscriptionRepository(private val context: Context) {
private val subscriptionManager = context.requireSubscriptionManager() private val subscriptionManager = context.requireSubscriptionManager()
/** A cold flow of a list of subscriptions that are available and visible to the user. */
fun selectableSubscriptionInfoListFlow(): Flow<List<SubscriptionInfo>> =
context
.subscriptionsChangedFlow()
.map { getSelectableSubscriptionInfoList() }
.conflate()
.flowOn(Dispatchers.Default)
/** /**
* Return a list of subscriptions that are available and visible to the user. * Return a list of subscriptions that are available and visible to the user.
* *
* @return list of user selectable subscriptions. * @return list of user selectable subscriptions.
*/ */
fun getSelectableSubscriptionInfoList(): List<SubscriptionInfo> = fun getSelectableSubscriptionInfoList(): List<SubscriptionInfo> {
context.getSelectableSubscriptionInfoList() val availableList =
subscriptionManager.getAvailableSubscriptionInfoList() ?: return emptyList()
val visibleList =
availableList.filter { subInfo ->
// Opportunistic subscriptions are considered invisible to users so they should
// never be returned.
SubscriptionUtil.isSubscriptionVisible(subscriptionManager, context, subInfo)
}
return visibleList
.groupBy { it.groupUuid }
.flatMap { (groupUuid, subInfos) ->
if (groupUuid == null) {
subInfos
} else {
// Multiple subscriptions in a group should only have one representative.
// It should be the current active primary subscription if any, or the primary
// subscription with minimum subscription id.
subInfos
.filter { it.simSlotIndex != SubscriptionManager.INVALID_SIM_SLOT_INDEX }
.ifEmpty { subInfos.sortedBy { it.subscriptionId } }
.take(1)
}
}
// Matching the sorting order in
// SubscriptionManagerService.getAvailableSubscriptionInfoList
.sortedWith(compareBy({ it.sortableSimSlotIndex }, { it.subscriptionId }))
.also { Log.d(TAG, "getSelectableSubscriptionInfoList: $it") }
}
/** Flow of whether the subscription visible for the given [subId]. */ /** Flow of whether the subscription visible for the given [subId]. */
fun isSubscriptionVisibleFlow(subId: Int): Flow<Boolean> { fun isSubscriptionVisibleFlow(subId: Int): Flow<Boolean> {
@@ -154,38 +189,6 @@ fun Context.phoneNumberFlow(subscriptionInfo: SubscriptionInfo): Flow<String?> =
fun Context.subscriptionsChangedFlow(): Flow<Unit> = fun Context.subscriptionsChangedFlow(): Flow<Unit> =
SubscriptionRepository(this).subscriptionsChangedFlow() SubscriptionRepository(this).subscriptionsChangedFlow()
/**
* Return a list of subscriptions that are available and visible to the user.
*
* @return list of user selectable subscriptions.
*/
fun Context.getSelectableSubscriptionInfoList(): List<SubscriptionInfo> {
val subscriptionManager = requireSubscriptionManager()
val availableList = subscriptionManager.getAvailableSubscriptionInfoList() ?: return emptyList()
val visibleList = availableList.filter { subInfo ->
// Opportunistic subscriptions are considered invisible
// to users so they should never be returned.
SubscriptionUtil.isSubscriptionVisible(subscriptionManager, this, subInfo)
}
return visibleList
.groupBy { it.groupUuid }
.flatMap { (groupUuid, subInfos) ->
if (groupUuid == null) {
subInfos
} else {
// Multiple subscriptions in a group should only have one representative.
// It should be the current active primary subscription if any, or the primary
// subscription with minimum subscription id.
subInfos.filter { it.simSlotIndex != SubscriptionManager.INVALID_SIM_SLOT_INDEX }
.ifEmpty { subInfos.sortedBy { it.subscriptionId } }
.take(1)
}
}
// Matching the sorting order in SubscriptionManagerService.getAvailableSubscriptionInfoList
.sortedWith(compareBy({ it.sortableSimSlotIndex }, { it.subscriptionId }))
.also { Log.d(TAG, "getSelectableSubscriptionInfoList: $it") }
}
/** Subscription with invalid sim slot index has lowest sort order. */ /** Subscription with invalid sim slot index has lowest sort order. */
private val SubscriptionInfo.sortableSimSlotIndex: Int private val SubscriptionInfo.sortableSimSlotIndex: Int
get() = if (simSlotIndex != SubscriptionManager.INVALID_SIM_SLOT_INDEX) { get() = if (simSlotIndex != SubscriptionManager.INVALID_SIM_SLOT_INDEX) {

View File

@@ -58,6 +58,5 @@ class ZenModeTriggerAddPreferenceController extends AbstractZenModePreferenceCon
conditionId -> saveMode(mode -> { conditionId -> saveMode(mode -> {
mode.setCustomModeConditionId(mContext, conditionId); mode.setCustomModeConditionId(mContext, conditionId);
return mode; return mode;
// TODO: b/342156843 - Maybe jump to the corresponding schedule editing screen?
}); });
} }

View File

@@ -142,9 +142,6 @@ public class ZenModesListFragment extends ZenModesFragmentBase {
@Override @Override
public List<String> getNonIndexableKeys(Context context) { public List<String> getNonIndexableKeys(Context context) {
final List<String> keys = super.getNonIndexableKeys(context); final List<String> keys = super.getNonIndexableKeys(context);
// TODO: b/332937523 - determine if this should be removed once the preference
// controller adds dynamic data to index
keys.add(ZenModesListPreferenceController.KEY);
return keys; return keys;
} }

View File

@@ -20,6 +20,7 @@ import static com.android.internal.widget.LockPatternUtils.MIN_AUTO_PIN_REQUIREM
import android.content.Context; import android.content.Context;
import androidx.fragment.app.Fragment;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.TwoStatePreference; import androidx.preference.TwoStatePreference;
@@ -28,7 +29,6 @@ import com.android.settings.R;
import com.android.settings.core.PreferenceControllerMixin; import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.password.ChooseLockSettingsHelper; import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.ObservablePreferenceFragment;
/** /**
* Preference controller for the pin_auto_confirm setting. * Preference controller for the pin_auto_confirm setting.
@@ -40,11 +40,10 @@ public class AutoPinConfirmPreferenceController extends AbstractPreferenceContro
private final int mUserId; private final int mUserId;
private final LockPatternUtils mLockPatternUtils; private final LockPatternUtils mLockPatternUtils;
private final ObservablePreferenceFragment mParentFragment; private final Fragment mParentFragment;
public AutoPinConfirmPreferenceController(Context context, int userId, public AutoPinConfirmPreferenceController(Context context, int userId,
LockPatternUtils lockPatternUtils, LockPatternUtils lockPatternUtils, Fragment parentFragment) {
ObservablePreferenceFragment parentFragment) {
super(context); super(context);
mUserId = userId; mUserId = userId;
mLockPatternUtils = lockPatternUtils; mLockPatternUtils = lockPatternUtils;

View File

@@ -1,73 +0,0 @@
/*
* Copyright (C) 2017 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.support.actionbar;
import static com.android.settings.support.actionbar.HelpResourceProvider.HELP_URI_RESOURCE_KEY;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import com.android.settingslib.HelpUtils;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.ObservableFragment;
import com.android.settingslib.core.lifecycle.ObservablePreferenceFragment;
import com.android.settingslib.core.lifecycle.events.OnCreateOptionsMenu;
/**
* A controller that adds help menu to any Settings page.
*/
public class HelpMenuController implements LifecycleObserver, OnCreateOptionsMenu {
private final Fragment mHost;
public static void init(@NonNull ObservablePreferenceFragment host) {
host.getSettingsLifecycle().addObserver(new HelpMenuController(host));
}
public static void init(@NonNull ObservableFragment host) {
host.getSettingsLifecycle().addObserver(new HelpMenuController(host));
}
private HelpMenuController(@NonNull Fragment host) {
mHost = host;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
final Bundle arguments = mHost.getArguments();
int helpResourceId = 0;
if (arguments != null && arguments.containsKey(HELP_URI_RESOURCE_KEY)) {
helpResourceId = arguments.getInt(HELP_URI_RESOURCE_KEY);
} else if (mHost instanceof HelpResourceProvider) {
helpResourceId = ((HelpResourceProvider) mHost).getHelpResource();
}
String helpUri = null;
if (helpResourceId != 0) {
helpUri = mHost.getContext().getString(helpResourceId);
}
final Activity activity = mHost.getActivity();
if (helpUri != null && activity != null) {
HelpUtils.prepareHelpMenuItem(activity, menu, helpUri, mHost.getClass().getName());
}
}
}

View File

@@ -31,10 +31,11 @@ import android.platform.test.flag.junit.SetFlagsRule;
import android.security.Flags; import android.security.Flags;
import android.service.persistentdata.PersistentDataBlockManager; import android.service.persistentdata.PersistentDataBlockManager;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.widget.TextView;
import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentActivity;
import com.google.android.setupdesign.GlifLayout;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
@@ -85,12 +86,12 @@ public class MainClearConfirmTest {
MainClearConfirm mainClearConfirm = new MainClearConfirm(); MainClearConfirm mainClearConfirm = new MainClearConfirm();
mainClearConfirm.mEraseEsims = true; mainClearConfirm.mEraseEsims = true;
mainClearConfirm.mContentView = mainClearConfirm.mContentView =
LayoutInflater.from(mActivity).inflate(R.layout.main_clear_confirm, null); (GlifLayout) LayoutInflater.from(mActivity)
.inflate(R.layout.main_clear_confirm, null);
mainClearConfirm.setSubtitle(); mainClearConfirm.setSubtitle();
assertThat(((TextView) mainClearConfirm.mContentView assertThat(mainClearConfirm.mContentView.getDescriptionText())
.findViewById(R.id.sud_layout_description)).getText())
.isEqualTo(mActivity.getString(R.string.main_clear_final_desc_esim)); .isEqualTo(mActivity.getString(R.string.main_clear_final_desc_esim));
} }
@@ -99,12 +100,12 @@ public class MainClearConfirmTest {
MainClearConfirm mainClearConfirm = new MainClearConfirm(); MainClearConfirm mainClearConfirm = new MainClearConfirm();
mainClearConfirm.mEraseEsims = false; mainClearConfirm.mEraseEsims = false;
mainClearConfirm.mContentView = mainClearConfirm.mContentView =
LayoutInflater.from(mActivity).inflate(R.layout.main_clear_confirm, null); (GlifLayout) LayoutInflater.from(mActivity)
.inflate(R.layout.main_clear_confirm, null);
mainClearConfirm.setSubtitle(); mainClearConfirm.setSubtitle();
assertThat(((TextView) mainClearConfirm.mContentView assertThat(mainClearConfirm.mContentView.getDescriptionText())
.findViewById(R.id.sud_layout_description)).getText())
.isEqualTo(mActivity.getString(R.string.main_clear_final_desc)); .isEqualTo(mActivity.getString(R.string.main_clear_final_desc));
} }

View File

@@ -150,7 +150,7 @@ public class AudioSharingLoadingStateDialogFragmentTest {
} }
@Test @Test
public void showDialog_newMessage_dismissAndShowNewDialog() { public void showDialog_newMessage_keepAndUpdateDialog() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
AudioSharingLoadingStateDialogFragment.show(mParent, TEST_MESSAGE1); AudioSharingLoadingStateDialogFragment.show(mParent, TEST_MESSAGE1);
shadowMainLooper().idle(); shadowMainLooper().idle();
@@ -163,12 +163,7 @@ public class AudioSharingLoadingStateDialogFragmentTest {
AudioSharingLoadingStateDialogFragment.show(mParent, TEST_MESSAGE2); AudioSharingLoadingStateDialogFragment.show(mParent, TEST_MESSAGE2);
shadowMainLooper().idle(); shadowMainLooper().idle();
assertThat(dialog.isShowing()).isFalse(); assertThat(dialog.isShowing()).isTrue();
AlertDialog newDialog = ShadowAlertDialogCompat.getLatestAlertDialog();
assertThat(newDialog).isNotNull();
assertThat(newDialog.isShowing()).isTrue();
view = newDialog.findViewById(R.id.message);
assertThat(view).isNotNull();
assertThat(view.getText().toString()).isEqualTo(TEST_MESSAGE2); assertThat(view.getText().toString()).isEqualTo(TEST_MESSAGE2);
} }
} }

View File

@@ -57,7 +57,9 @@ import android.util.Pair;
import android.view.View; import android.view.View;
import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityEvent;
import android.widget.CompoundButton; import android.widget.CompoundButton;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment; import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
@@ -235,6 +237,7 @@ public class AudioSharingSwitchBarControllerTest {
@After @After
public void tearDown() { public void tearDown() {
ShadowAlertDialogCompat.reset();
ShadowBluetoothUtils.reset(); ShadowBluetoothUtils.reset();
ShadowThreadUtils.reset(); ShadowThreadUtils.reset();
} }
@@ -426,6 +429,8 @@ public class AudioSharingSwitchBarControllerTest {
assertThat(childFragments) assertThat(childFragments)
.comparingElementsUsing(CLAZZNAME_EQUALS) .comparingElementsUsing(CLAZZNAME_EQUALS)
.containsExactly(AudioSharingConfirmDialogFragment.class.getName()); .containsExactly(AudioSharingConfirmDialogFragment.class.getName());
childFragments.forEach(fragment -> ((DialogFragment) fragment).dismiss());
} }
@Test @Test
@@ -490,14 +495,21 @@ public class AudioSharingSwitchBarControllerTest {
public void onAudioSharingProfilesConnected() {} public void onAudioSharingProfilesConnected() {}
}); });
mController.onCheckedChanged(mBtnView, /* isChecked= */ true); mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
shadowOf(Looper.getMainLooper()).idle();
verify(mBroadcast).startPrivateBroadcast(); verify(mBroadcast).startPrivateBroadcast();
List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
// No loading state dialog.
assertThat(childFragments).isEmpty();
mController.mBroadcastCallback.onPlaybackStarted(0, 0); mController.mBroadcastCallback.onPlaybackStarted(0, 0);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
verify(mFeatureFactory.metricsFeatureProvider) verify(mFeatureFactory.metricsFeatureProvider)
.action(any(Context.class), eq(SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING)); .action(any(Context.class), eq(SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING));
List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments(); childFragments = mParentFragment.getChildFragmentManager().getFragments();
// No audio sharing dialog.
assertThat(childFragments).isEmpty(); assertThat(childFragments).isEmpty();
} }
@@ -514,7 +526,13 @@ public class AudioSharingSwitchBarControllerTest {
when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata); when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata);
doNothing().when(mBroadcast).startPrivateBroadcast(); doNothing().when(mBroadcast).startPrivateBroadcast();
mController.onCheckedChanged(mBtnView, /* isChecked= */ true); mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
shadowOf(Looper.getMainLooper()).idle();
verify(mBroadcast).startPrivateBroadcast(); verify(mBroadcast).startPrivateBroadcast();
List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).containsExactly(
AudioSharingLoadingStateDialogFragment.class.getName());
mController.mBroadcastCallback.onPlaybackStarted(0, 0); mController.mBroadcastCallback.onPlaybackStarted(0, 0);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
@@ -522,8 +540,12 @@ public class AudioSharingSwitchBarControllerTest {
verify(mFeatureFactory.metricsFeatureProvider, never()) verify(mFeatureFactory.metricsFeatureProvider, never())
.action(any(Context.class), eq(SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING)); .action(any(Context.class), eq(SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING));
List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments(); childFragments = mParentFragment.getChildFragmentManager().getFragments();
assertThat(childFragments).isEmpty(); // No audio sharing dialog.
assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).doesNotContain(
AudioSharingDialogFragment.class.getName());
childFragments.forEach(fragment -> ((DialogFragment) fragment).dismiss());
} }
@Test @Test
@@ -534,23 +556,42 @@ public class AudioSharingSwitchBarControllerTest {
when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice2, mDevice1)); when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice2, mDevice1));
when(mAssistant.getAllSources(any(BluetoothDevice.class))).thenReturn(ImmutableList.of()); when(mAssistant.getAllSources(any(BluetoothDevice.class))).thenReturn(ImmutableList.of());
doNothing().when(mBroadcast).startPrivateBroadcast(); doNothing().when(mBroadcast).startPrivateBroadcast();
mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata); when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata);
mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
shadowOf(Looper.getMainLooper()).idle();
verify(mBroadcast).startPrivateBroadcast(); verify(mBroadcast).startPrivateBroadcast();
List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).containsExactly(
AudioSharingLoadingStateDialogFragment.class.getName());
AudioSharingLoadingStateDialogFragment loadingFragment =
(AudioSharingLoadingStateDialogFragment) Iterables.getOnlyElement(childFragments);
// TODO: use string res once finalized
String expectedMessage = "Starting audio stream...";
checkLoadingStateDialogMessage(loadingFragment, expectedMessage);
mController.mBroadcastCallback.onPlaybackStarted(0, 0); mController.mBroadcastCallback.onPlaybackStarted(0, 0);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
verify(mFeatureFactory.metricsFeatureProvider) verify(mFeatureFactory.metricsFeatureProvider)
.action(any(Context.class), eq(SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING)); .action(any(Context.class), eq(SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING));
// TODO: use string res once finalized
expectedMessage = "Sharing with " + TEST_DEVICE_NAME2 + "...";
checkLoadingStateDialogMessage(loadingFragment, expectedMessage);
List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments(); childFragments = mParentFragment.getChildFragmentManager().getFragments();
assertThat(childFragments) assertThat(childFragments)
.comparingElementsUsing(CLAZZNAME_EQUALS) .comparingElementsUsing(CLAZZNAME_EQUALS)
.containsExactly(AudioSharingDialogFragment.class.getName()); .containsExactly(AudioSharingDialogFragment.class.getName(),
AudioSharingLoadingStateDialogFragment.class.getName());
AudioSharingDialogFragment fragment = Pair<Integer, Object>[] eventData = new Pair[0];
(AudioSharingDialogFragment) Iterables.getOnlyElement(childFragments); for (Fragment fragment : childFragments) {
Pair<Integer, Object>[] eventData = fragment.getEventData(); if (fragment instanceof AudioSharingDialogFragment) {
eventData = ((AudioSharingDialogFragment) fragment).getEventData();
break;
}
}
assertThat(eventData) assertThat(eventData)
.asList() .asList()
.containsExactly( .containsExactly(
@@ -570,6 +611,8 @@ public class AudioSharingSwitchBarControllerTest {
AudioSharingUtils.MetricKey.METRIC_KEY_CANDIDATE_DEVICE_COUNT AudioSharingUtils.MetricKey.METRIC_KEY_CANDIDATE_DEVICE_COUNT
.ordinal(), .ordinal(),
1)); 1));
childFragments.forEach(fragment -> ((DialogFragment) fragment).dismiss());
} }
@Test @Test
@@ -582,6 +625,8 @@ public class AudioSharingSwitchBarControllerTest {
when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata); when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata);
doNothing().when(mBroadcast).startPrivateBroadcast(); doNothing().when(mBroadcast).startPrivateBroadcast();
mController.onCheckedChanged(mBtnView, /* isChecked= */ true); mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
shadowOf(Looper.getMainLooper()).idle();
verify(mBroadcast).startPrivateBroadcast(); verify(mBroadcast).startPrivateBroadcast();
mController.mBroadcastCallback.onPlaybackStarted(0, 0); mController.mBroadcastCallback.onPlaybackStarted(0, 0);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
@@ -597,6 +642,17 @@ public class AudioSharingSwitchBarControllerTest {
verify(mAssistant).addSource(mDevice1, mMetadata, /* isGroupOp= */ false); verify(mAssistant).addSource(mDevice1, mMetadata, /* isGroupOp= */ false);
assertThat(dialog.isShowing()).isFalse(); assertThat(dialog.isShowing()).isFalse();
// Loading state dialog shows sharing state for the user chosen sink.
List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).containsExactly(
AudioSharingLoadingStateDialogFragment.class.getName());
AudioSharingLoadingStateDialogFragment loadingFragment =
(AudioSharingLoadingStateDialogFragment) Iterables.getOnlyElement(childFragments);
// TODO: use string res once finalized
String expectedMessage = "Sharing with " + TEST_DEVICE_NAME1 + "...";
checkLoadingStateDialogMessage(loadingFragment, expectedMessage);
childFragments.forEach(fragment -> ((DialogFragment) fragment).dismiss());
} }
@Test @Test
@@ -609,6 +665,8 @@ public class AudioSharingSwitchBarControllerTest {
when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata); when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata);
doNothing().when(mBroadcast).startPrivateBroadcast(); doNothing().when(mBroadcast).startPrivateBroadcast();
mController.onCheckedChanged(mBtnView, /* isChecked= */ true); mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
shadowOf(Looper.getMainLooper()).idle();
verify(mBroadcast).startPrivateBroadcast(); verify(mBroadcast).startPrivateBroadcast();
mController.mBroadcastCallback.onPlaybackStarted(0, 0); mController.mBroadcastCallback.onPlaybackStarted(0, 0);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
@@ -624,10 +682,21 @@ public class AudioSharingSwitchBarControllerTest {
verify(mAssistant, never()).addSource(mDevice1, mMetadata, /* isGroupOp= */ false); verify(mAssistant, never()).addSource(mDevice1, mMetadata, /* isGroupOp= */ false);
assertThat(dialog.isShowing()).isFalse(); assertThat(dialog.isShowing()).isFalse();
// Loading state dialog shows sharing state for the auto add active sink.
List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).containsExactly(
AudioSharingLoadingStateDialogFragment.class.getName());
AudioSharingLoadingStateDialogFragment loadingFragment =
(AudioSharingLoadingStateDialogFragment) Iterables.getOnlyElement(childFragments);
// TODO: use string res once finalized
String expectedMessage = "Sharing with " + TEST_DEVICE_NAME2 + "...";
checkLoadingStateDialogMessage(loadingFragment, expectedMessage);
childFragments.forEach(fragment -> ((DialogFragment) fragment).dismiss());
} }
@Test @Test
public void testBluetoothLeBroadcastCallbacks_updateSwitch() { public void testBroadcastCallbacks_updateSwitch() {
mOnAudioSharingStateChanged = false; mOnAudioSharingStateChanged = false;
mSwitchBar.setChecked(false); mSwitchBar.setChecked(false);
when(mBroadcast.isEnabled(any())).thenReturn(false); when(mBroadcast.isEnabled(any())).thenReturn(false);
@@ -673,7 +742,7 @@ public class AudioSharingSwitchBarControllerTest {
} }
@Test @Test
public void testBluetoothLeBroadcastCallbacks_doNothing() { public void testBroadcastCallbacks_doNothing() {
mController.mBroadcastCallback.onBroadcastMetadataChanged(/* reason= */ 1, mMetadata); mController.mBroadcastCallback.onBroadcastMetadataChanged(/* reason= */ 1, mMetadata);
mController.mBroadcastCallback.onBroadcastUpdated(/* reason= */ 1, /* broadcastId= */ 1); mController.mBroadcastCallback.onBroadcastUpdated(/* reason= */ 1, /* broadcastId= */ 1);
mController.mBroadcastCallback.onPlaybackStarted(/* reason= */ 1, /* broadcastId= */ 1); mController.mBroadcastCallback.onPlaybackStarted(/* reason= */ 1, /* broadcastId= */ 1);
@@ -685,7 +754,7 @@ public class AudioSharingSwitchBarControllerTest {
} }
@Test @Test
public void testBluetoothLeBroadcastAssistantCallbacks_logAction() { public void testAssistantCallbacks_onSourceAddFailed_logAction() {
mController.mBroadcastAssistantCallback.onSourceAddFailed( mController.mBroadcastAssistantCallback.onSourceAddFailed(
mDevice1, mMetadata, /* reason= */ 1); mDevice1, mMetadata, /* reason= */ 1);
verify(mFeatureFactory.metricsFeatureProvider) verify(mFeatureFactory.metricsFeatureProvider)
@@ -696,7 +765,24 @@ public class AudioSharingSwitchBarControllerTest {
} }
@Test @Test
public void testBluetoothLeBroadcastAssistantCallbacks_doNothing() { public void testAssistantCallbacks_onReceiveStateChanged_dismissLoadingDialog() {
AudioSharingLoadingStateDialogFragment.show(mParentFragment, TEST_DEVICE_NAME1);
shadowOf(Looper.getMainLooper()).idle();
List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).containsExactly(
AudioSharingLoadingStateDialogFragment.class.getName());
BluetoothLeBroadcastReceiveState state = mock(BluetoothLeBroadcastReceiveState.class);
when(state.getBisSyncState()).thenReturn(ImmutableList.of(1L));
mController.mBroadcastAssistantCallback.onReceiveStateChanged(mDevice1, /* sourceId= */ 1,
state);
shadowOf(Looper.getMainLooper()).idle();
childFragments = mParentFragment.getChildFragmentManager().getFragments();
assertThat(childFragments).isEmpty();
}
@Test
public void testAssistantCallbacks_doNothing() {
BluetoothLeBroadcastReceiveState state = mock(BluetoothLeBroadcastReceiveState.class); BluetoothLeBroadcastReceiveState state = mock(BluetoothLeBroadcastReceiveState.class);
// Do nothing // Do nothing
@@ -784,7 +870,7 @@ public class AudioSharingSwitchBarControllerTest {
@Test @Test
public void handleStartAudioSharingFromIntent_flagOff_doNothing() { public void handleStartAudioSharingFromIntent_flagOff_doNothing() {
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
setUpStartSharingIntent(); var unused = setUpFragmentWithStartSharingIntent();
mController.onStart(mLifecycleOwner); mController.onStart(mLifecycleOwner);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
@@ -795,7 +881,7 @@ public class AudioSharingSwitchBarControllerTest {
public void handleStartAudioSharingFromIntent_profileNotReady_doNothing() { public void handleStartAudioSharingFromIntent_profileNotReady_doNothing() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
when(mAssistant.isProfileReady()).thenReturn(false); when(mAssistant.isProfileReady()).thenReturn(false);
setUpStartSharingIntent(); var unused = setUpFragmentWithStartSharingIntent();
mController.onServiceConnected(); mController.onServiceConnected();
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
@@ -817,13 +903,16 @@ public class AudioSharingSwitchBarControllerTest {
when(mBtnView.isEnabled()).thenReturn(true); when(mBtnView.isEnabled()).thenReturn(true);
when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice2, mDevice1)); when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice2, mDevice1));
when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata); when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata);
setUpStartSharingIntent(); Fragment parentFragment = setUpFragmentWithStartSharingIntent();
mController.onServiceConnected(); mController.onServiceConnected();
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
verify(mSwitchBar).setChecked(true); verify(mSwitchBar).setChecked(true);
doNothing().when(mBroadcast).startPrivateBroadcast(); doNothing().when(mBroadcast).startPrivateBroadcast();
mController.onCheckedChanged(mBtnView, /* isChecked= */ true); mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
shadowOf(Looper.getMainLooper()).idle();
verify(mBroadcast).startPrivateBroadcast();
mController.mBroadcastCallback.onPlaybackStarted(0, 0); mController.mBroadcastCallback.onPlaybackStarted(0, 0);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
@@ -831,11 +920,21 @@ public class AudioSharingSwitchBarControllerTest {
.action(any(Context.class), eq(SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING)); .action(any(Context.class), eq(SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING));
verify(mAssistant).addSource(mDevice1, mMetadata, /* isGroupOp= */ false); verify(mAssistant).addSource(mDevice1, mMetadata, /* isGroupOp= */ false);
verify(mAssistant).addSource(mDevice2, mMetadata, /* isGroupOp= */ false); verify(mAssistant).addSource(mDevice2, mMetadata, /* isGroupOp= */ false);
List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments(); List<Fragment> childFragments = parentFragment.getChildFragmentManager().getFragments();
assertThat(childFragments).isEmpty(); // Skip audio sharing dialog.
assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).containsExactly(
AudioSharingLoadingStateDialogFragment.class.getName());
// The loading state dialog shows sharing state for the auto add second sink.
AudioSharingLoadingStateDialogFragment loadingFragment =
(AudioSharingLoadingStateDialogFragment) Iterables.getOnlyElement(childFragments);
// TODO: use string res once finalized
String expectedMessage = "Sharing with " + TEST_DEVICE_NAME1 + "...";
checkLoadingStateDialogMessage(loadingFragment, expectedMessage);
childFragments.forEach(fragment -> ((DialogFragment) fragment).dismiss());
} }
private void setUpStartSharingIntent() { private Fragment setUpFragmentWithStartSharingIntent() {
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putBoolean(EXTRA_START_LE_AUDIO_SHARING, true); args.putBoolean(EXTRA_START_LE_AUDIO_SHARING, true);
Intent intent = new Intent(); Intent intent = new Intent();
@@ -849,5 +948,15 @@ public class AudioSharingSwitchBarControllerTest {
.get(); .get();
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
mController.init(fragment); mController.init(fragment);
return fragment;
}
private void checkLoadingStateDialogMessage(
@NonNull AudioSharingLoadingStateDialogFragment fragment,
@NonNull String expectedMessage) {
TextView loadingMessage = fragment.getDialog() == null ? null
: fragment.getDialog().findViewById(R.id.message);
assertThat(loadingMessage).isNotNull();
assertThat(loadingMessage.getText().toString()).isEqualTo(expectedMessage);
} }
} }

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.inputmethod;
import static com.google.common.truth.Truth.assertThat;
import android.app.settings.SettingsEnums;
import com.android.settings.R;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
@RunWith(RobolectricTestRunner.class)
public class PointerTouchpadFragmentTest {
private PointerTouchpadFragment mFragment;
@Before
public void setUp() {
mFragment = new PointerTouchpadFragment();
}
@Test
public void getPreferenceScreenResId_isPointerTouchpad() {
assertThat(mFragment.getPreferenceScreenResId())
.isEqualTo(R.xml.accessibility_pointer_and_touchpad);
}
@Test
public void getMetricsCategory_returnsCorrectCategory() {
assertThat(mFragment.getMetricsCategory()).isEqualTo(
SettingsEnums.ACCESSIBILITY_POINTER_TOUCHPAD);
}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.inputmethod;
import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.view.InputDevice;
import androidx.test.core.app.ApplicationProvider;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.testutils.shadow.ShadowInputDevice;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
/** Tests for {@link PointerTouchpadPreferenceController}. */
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {
ShadowInputDevice.class,
})
public final class PointerTouchpadPreferenceControllerTest {
@Rule public MockitoRule rule = MockitoJUnit.rule();
private PointerTouchpadPreferenceController mController;
@Before
public void initObjects() {
Context context = ApplicationProvider.getApplicationContext();
mController = new PointerTouchpadPreferenceController(context, "pointer_touchpad");
ShadowInputDevice.reset();
}
@Test
public void getAvailableStatus_noTouchpadOrMouseConditionallyUnavailable() {
int deviceId = 1;
ShadowInputDevice.sDeviceIds = new int[]{deviceId};
InputDevice device = ShadowInputDevice.makeInputDevicebyIdWithSources(deviceId,
InputDevice.SOURCE_BLUETOOTH_STYLUS);
ShadowInputDevice.addDevice(deviceId, device);
assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
}
@Test
public void getAvailabilityStatus_isTouchpadAvailable() {
int deviceId = 1;
ShadowInputDevice.sDeviceIds = new int[]{deviceId};
InputDevice device = ShadowInputDevice.makeInputDevicebyIdWithSources(deviceId,
InputDevice.SOURCE_TOUCHPAD);
ShadowInputDevice.addDevice(deviceId, device);
assertThat(mController.getAvailabilityStatus())
.isEqualTo(BasePreferenceController.AVAILABLE);
}
@Test
public void getAvailabilityStatus_isMouseAvailable() {
int deviceId = 1;
ShadowInputDevice.sDeviceIds = new int[]{deviceId};
InputDevice device = ShadowInputDevice.makeInputDevicebyIdWithSources(deviceId,
InputDevice.SOURCE_MOUSE);
ShadowInputDevice.addDevice(deviceId, device);
assertThat(mController.getAvailabilityStatus())
.isEqualTo(BasePreferenceController.AVAILABLE);
}
}

View File

@@ -22,11 +22,11 @@ import static org.mockito.Mockito.when;
import android.content.Context; import android.content.Context;
import androidx.fragment.app.Fragment;
import androidx.preference.SwitchPreference; import androidx.preference.SwitchPreference;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockPatternUtils;
import com.android.settingslib.core.lifecycle.ObservablePreferenceFragment;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -41,7 +41,7 @@ public class AutoPinConfirmPreferenceControllerTest {
@Mock @Mock
private LockPatternUtils mLockPatternUtils; private LockPatternUtils mLockPatternUtils;
@Mock @Mock
private ObservablePreferenceFragment mParentFragment; private Fragment mParentFragment;
private AutoPinConfirmPreferenceController mController; private AutoPinConfirmPreferenceController mController;
private SwitchPreference mPreference; private SwitchPreference mPreference;

View File

@@ -1,78 +0,0 @@
/*
* Copyright (C) 2017 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.support.actionbar;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.os.Bundle;
import com.android.settingslib.core.lifecycle.ObservablePreferenceFragment;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
@RunWith(RobolectricTestRunner.class)
public class HelpMenuControllerTest {
@Mock
private Context mContext;
private TestFragment mHost;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mHost = spy(new TestFragment());
doReturn(mContext).when(mHost).getContext();
}
@Test
public void onCreateOptionsMenu_withArgumentOverride_shouldPrepareHelpUsingOverride() {
final Bundle bundle = new Bundle();
bundle.putInt(HelpResourceProvider.HELP_URI_RESOURCE_KEY, 123);
mHost.setArguments(bundle);
HelpMenuController.init(mHost);
mHost.getSettingsLifecycle().onCreateOptionsMenu(null /* menu */, null /* inflater */);
verify(mContext).getString(123);
}
@Test
public void onCreateOptionsMenu_noArgumentOverride_shouldPrepareHelpUsingProvider() {
HelpMenuController.init(mHost);
mHost.getSettingsLifecycle().onCreateOptionsMenu(null /* menu */, null /* inflater */);
verify(mContext).getString(mHost.getHelpResource());
}
private static class TestFragment extends ObservablePreferenceFragment
implements HelpResourceProvider {
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
}
}
}

View File

@@ -120,7 +120,7 @@ class SubscriptionRepositoryTest {
) )
} }
val subInfos = context.getSelectableSubscriptionInfoList() val subInfos = repository.getSelectableSubscriptionInfoList()
assertThat(subInfos.map { it.simSlotIndex }) assertThat(subInfos.map { it.simSlotIndex })
.containsExactly(SIM_SLOT_INDEX_0, SIM_SLOT_INDEX_1).inOrder() .containsExactly(SIM_SLOT_INDEX_0, SIM_SLOT_INDEX_1).inOrder()
@@ -141,7 +141,7 @@ class SubscriptionRepositoryTest {
) )
} }
val subInfos = context.getSelectableSubscriptionInfoList() val subInfos = repository.getSelectableSubscriptionInfoList()
assertThat(subInfos.map { it.simSlotIndex }) assertThat(subInfos.map { it.simSlotIndex })
.containsExactly(SIM_SLOT_INDEX_1, SubscriptionManager.INVALID_SIM_SLOT_INDEX).inOrder() .containsExactly(SIM_SLOT_INDEX_1, SubscriptionManager.INVALID_SIM_SLOT_INDEX).inOrder()
@@ -164,7 +164,7 @@ class SubscriptionRepositoryTest {
) )
} }
val subInfos = context.getSelectableSubscriptionInfoList() val subInfos = repository.getSelectableSubscriptionInfoList()
assertThat(subInfos.map { it.subscriptionId }).containsExactly(SUB_ID_IN_SLOT_0) assertThat(subInfos.map { it.subscriptionId }).containsExactly(SUB_ID_IN_SLOT_0)
} }
@@ -184,7 +184,7 @@ class SubscriptionRepositoryTest {
) )
} }
val subInfos = context.getSelectableSubscriptionInfoList() val subInfos = repository.getSelectableSubscriptionInfoList()
assertThat(subInfos.map { it.subscriptionId }).containsExactly(SUB_ID_3_NOT_IN_SLOT) assertThat(subInfos.map { it.subscriptionId }).containsExactly(SUB_ID_3_NOT_IN_SLOT)
} }