Snap for 13174046 from 305a8e0ea7 to 25Q2-release
Change-Id: Ib56f1d75610e715634a3d0e9dc0697182123e995
This commit is contained in:
@@ -21,9 +21,9 @@
|
|||||||
android:padding="?android:attr/dialogPreferredPadding">
|
android:padding="?android:attr/dialogPreferredPadding">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/accessibility_dialog_header_text_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/accessibility_magnification_area_settings_message"
|
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
style="?android:attr/textAppearanceMedium"
|
style="?android:attr/textAppearanceMedium"
|
||||||
android:textColor="?android:attr/textColorAlertDialogListItem"/>
|
android:textColor="?android:attr/textColorAlertDialogListItem"/>
|
||||||
@@ -53,6 +53,8 @@
|
|||||||
android:id="@+id/name_layout"
|
android:id="@+id/name_layout"
|
||||||
android:hint="@string/vpn_name"
|
android:hint="@string/vpn_name"
|
||||||
app:endIconMode="clear_text"
|
app:endIconMode="clear_text"
|
||||||
|
app:helperTextEnabled="true"
|
||||||
|
app:helperText="@string/vpn_required"
|
||||||
app:errorEnabled="true">
|
app:errorEnabled="true">
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
style="@style/vpn_value"
|
style="@style/vpn_value"
|
||||||
@@ -73,6 +75,8 @@
|
|||||||
android:id="@+id/server_layout"
|
android:id="@+id/server_layout"
|
||||||
android:hint="@string/vpn_server"
|
android:hint="@string/vpn_server"
|
||||||
app:endIconMode="clear_text"
|
app:endIconMode="clear_text"
|
||||||
|
app:helperTextEnabled="true"
|
||||||
|
app:helperText="@string/vpn_required"
|
||||||
app:errorEnabled="true">
|
app:errorEnabled="true">
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
style="@style/vpn_value"
|
style="@style/vpn_value"
|
||||||
@@ -90,7 +94,7 @@
|
|||||||
android:hint="@string/vpn_ipsec_identifier"
|
android:hint="@string/vpn_ipsec_identifier"
|
||||||
app:endIconMode="clear_text"
|
app:endIconMode="clear_text"
|
||||||
app:helperTextEnabled="true"
|
app:helperTextEnabled="true"
|
||||||
app:helperText="@string/vpn_not_used"
|
app:helperText="@string/vpn_required"
|
||||||
app:errorEnabled="true">
|
app:errorEnabled="true">
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
style="@style/vpn_value"
|
style="@style/vpn_value"
|
||||||
@@ -108,6 +112,8 @@
|
|||||||
android:id="@+id/ipsec_secret_layout"
|
android:id="@+id/ipsec_secret_layout"
|
||||||
android:hint="@string/vpn_ipsec_secret"
|
android:hint="@string/vpn_ipsec_secret"
|
||||||
app:endIconMode="password_toggle"
|
app:endIconMode="password_toggle"
|
||||||
|
app:helperTextEnabled="true"
|
||||||
|
app:helperText="@string/vpn_required"
|
||||||
app:errorEnabled="true">
|
app:errorEnabled="true">
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
style="@style/vpn_value"
|
style="@style/vpn_value"
|
||||||
@@ -183,7 +189,7 @@
|
|||||||
android:hint="@string/proxy_hostname_label"
|
android:hint="@string/proxy_hostname_label"
|
||||||
app:endIconMode="clear_text"
|
app:endIconMode="clear_text"
|
||||||
app:helperTextEnabled="true"
|
app:helperTextEnabled="true"
|
||||||
app:helperText="@string/proxy_hostname_hint"
|
app:helperText="@string/vpn_optional"
|
||||||
app:errorEnabled="true">
|
app:errorEnabled="true">
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
style="@style/vpn_value"
|
style="@style/vpn_value"
|
||||||
@@ -197,7 +203,7 @@
|
|||||||
android:hint="@string/proxy_port_label"
|
android:hint="@string/proxy_port_label"
|
||||||
app:endIconMode="clear_text"
|
app:endIconMode="clear_text"
|
||||||
app:helperTextEnabled="true"
|
app:helperTextEnabled="true"
|
||||||
app:helperText="@string/proxy_port_hint"
|
app:helperText="@string/vpn_optional"
|
||||||
app:errorEnabled="true">
|
app:errorEnabled="true">
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
style="@style/vpn_value"
|
style="@style/vpn_value"
|
||||||
@@ -217,6 +223,8 @@
|
|||||||
android:id="@+id/username_layout"
|
android:id="@+id/username_layout"
|
||||||
android:hint="@string/vpn_username"
|
android:hint="@string/vpn_username"
|
||||||
app:endIconMode="clear_text"
|
app:endIconMode="clear_text"
|
||||||
|
app:helperTextEnabled="true"
|
||||||
|
app:helperText="@string/vpn_optional"
|
||||||
app:errorEnabled="true">
|
app:errorEnabled="true">
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
style="@style/vpn_value"
|
style="@style/vpn_value"
|
||||||
@@ -228,6 +236,8 @@
|
|||||||
android:id="@+id/password_layout"
|
android:id="@+id/password_layout"
|
||||||
android:hint="@string/vpn_password"
|
android:hint="@string/vpn_password"
|
||||||
app:endIconMode="password_toggle"
|
app:endIconMode="password_toggle"
|
||||||
|
app:helperTextEnabled="true"
|
||||||
|
app:helperText="@string/vpn_optional"
|
||||||
app:errorEnabled="true">
|
app:errorEnabled="true">
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
style="@style/vpn_value"
|
style="@style/vpn_value"
|
||||||
|
|||||||
@@ -5220,6 +5220,16 @@
|
|||||||
<string name="accessibility_screen_magnification_title">Magnification</string>
|
<string name="accessibility_screen_magnification_title">Magnification</string>
|
||||||
<!-- Title for accessibility shortcut preference for magnification. [CHAR LIMIT=60] -->
|
<!-- Title for accessibility shortcut preference for magnification. [CHAR LIMIT=60] -->
|
||||||
<string name="accessibility_screen_magnification_shortcut_title">Magnification shortcut</string>
|
<string name="accessibility_screen_magnification_shortcut_title">Magnification shortcut</string>
|
||||||
|
<!-- Title of cursor following mode preference for magnification. [CHAR LIMIT=60] -->
|
||||||
|
<string name="accessibility_magnification_cursor_following_title">Cursor following</string>
|
||||||
|
<!-- Header message of cursor following mode dialog for magnification. [CHAR LIMIT=none] -->
|
||||||
|
<string name="accessibility_magnification_cursor_following_header">Choose how Magnification follows your cursor.</string>
|
||||||
|
<!-- Option title of cursor following continuous mode in the mode selection dialog. [CHAR LIMIT=none] -->
|
||||||
|
<string name="accessibility_magnification_cursor_following_continuous">Move screen continuously as mouse moves</string>
|
||||||
|
<!-- Option title of cursor following center mode in the mode selection dialog. [CHAR LIMIT=none] -->
|
||||||
|
<string name="accessibility_magnification_cursor_following_center">Move screen keeping mouse at center of screen</string>
|
||||||
|
<!-- Option title of cursor following edge mode in the mode selection dialog. [CHAR LIMIT=none] -->
|
||||||
|
<string name="accessibility_magnification_cursor_following_edge">Move screen when mouse touches edges of screen</string>
|
||||||
<!-- Title for accessibility follow typing preference for magnification. [CHAR LIMIT=35] -->
|
<!-- Title for accessibility follow typing preference for magnification. [CHAR LIMIT=35] -->
|
||||||
<string name="accessibility_screen_magnification_follow_typing_title">Magnify typing</string>
|
<string name="accessibility_screen_magnification_follow_typing_title">Magnify typing</string>
|
||||||
<!-- Summary for accessibility follow typing preference for magnification. [CHAR LIMIT=none] -->
|
<!-- Summary for accessibility follow typing preference for magnification. [CHAR LIMIT=none] -->
|
||||||
@@ -6601,8 +6611,12 @@
|
|||||||
<string name="battery_usage_timestamps_content_description"><xliff:g id="from_timestamp">%1$s</xliff:g> to <xliff:g id="to_timestamp">%2$s</xliff:g></string>
|
<string name="battery_usage_timestamps_content_description"><xliff:g id="from_timestamp">%1$s</xliff:g> to <xliff:g id="to_timestamp">%2$s</xliff:g></string>
|
||||||
<!-- [CHAR_LIMIT=NONE] The first slot is a week day (e.g. "Monday"); the second slot is a hourly time span (e.g. "6 AM - 8 AM"). -->
|
<!-- [CHAR_LIMIT=NONE] The first slot is a week day (e.g. "Monday"); the second slot is a hourly time span (e.g. "6 AM - 8 AM"). -->
|
||||||
<string name="battery_usage_day_and_hour"><xliff:g id="day">%1$s</xliff:g> <xliff:g id="hour">%2$s</xliff:g></string>
|
<string name="battery_usage_day_and_hour"><xliff:g id="day">%1$s</xliff:g> <xliff:g id="hour">%2$s</xliff:g></string>
|
||||||
<!-- [CHAR_LIMIT=NONE] Accessibility content description for each slot in battery chart view. -->
|
<!-- [CHAR_LIMIT=NONE] Accessibility content description for each slot in battery chart view. Please reuse the words in tc/6732629268310936155 -->
|
||||||
<string name="battery_usage_time_info_and_battery_level"><xliff:g id="time_info" example="Battery usage for Monday 6 AM - 8 AM">%1$s</xliff:g> <xliff:g id="battery_level" example="Battery level percentage from 83% to 59%">%2$s</xliff:g></string>
|
<string name="battery_usage_status_time_info_and_battery_level"><xliff:g id="selected_status" example="Selected">%1$s</xliff:g>, <xliff:g id="time_info" example="Battery usage for Monday 6 AM - 8 AM">%2$s</xliff:g> <xliff:g id="battery_level" example="Battery level percentage from 83% to 59%">%3$s</xliff:g></string>
|
||||||
|
<!-- [CHAR_LIMIT=NONE] Accessibility content description for the battery usage slot is selected. -->
|
||||||
|
<string name="battery_chart_slot_status_selected">Selected</string>
|
||||||
|
<!-- [CHAR_LIMIT=NONE] Accessibility content description for the battery usage slot is not selected -->
|
||||||
|
<string name="battery_chart_slot_status_unselected">Unselected</string>
|
||||||
<!-- [CHAR_LIMIT=NONE] Accessibility content description for battery chart view. -->
|
<!-- [CHAR_LIMIT=NONE] Accessibility content description for battery chart view. -->
|
||||||
<string name="battery_usage_chart">Battery usage chart</string>
|
<string name="battery_usage_chart">Battery usage chart</string>
|
||||||
<!-- [CHAR_LIMIT=NONE] Accessibility content description for daily battery chart view. -->
|
<!-- [CHAR_LIMIT=NONE] Accessibility content description for daily battery chart view. -->
|
||||||
@@ -7276,6 +7290,12 @@ Data usage charges may apply.</string>
|
|||||||
generic error. [CHAR LIMIT=120] -->
|
generic error. [CHAR LIMIT=120] -->
|
||||||
<string name="vpn_always_on_invalid_reason_other">The information entered doesn\'t support
|
<string name="vpn_always_on_invalid_reason_other">The information entered doesn\'t support
|
||||||
always-on VPN</string>
|
always-on VPN</string>
|
||||||
|
<!-- Hint for an optional field in a VPN profile. [CHAR LIMIT=40] -->
|
||||||
|
<string name="vpn_optional">(optional)</string>
|
||||||
|
<!-- Hint for a required field in a VPN profile. [CHAR LIMIT=40] -->
|
||||||
|
<string name="vpn_required">(required)</string>
|
||||||
|
<!-- Error message displayed below the VPN EditText when the filed is required. [CHAR LIMIT=NONE] -->
|
||||||
|
<string name="vpn_field_required">The field is required</string>
|
||||||
|
|
||||||
<!-- Button label to cancel changing a VPN profile. [CHAR LIMIT=40] -->
|
<!-- Button label to cancel changing a VPN profile. [CHAR LIMIT=40] -->
|
||||||
<string name="vpn_cancel">Cancel</string>
|
<string name="vpn_cancel">Cancel</string>
|
||||||
@@ -12924,12 +12944,12 @@ Data usage charges may apply.</string>
|
|||||||
<!-- Title for a toggle that enables freeform windows. Freeform windows enables users to freely arrange and resize overlapping apps. [CHAR LIMIT=50] -->
|
<!-- Title for a toggle that enables freeform windows. Freeform windows enables users to freely arrange and resize overlapping apps. [CHAR LIMIT=50] -->
|
||||||
<string name="enable_desktop_mode">Enable freeform windows</string>
|
<string name="enable_desktop_mode">Enable freeform windows</string>
|
||||||
|
|
||||||
<!-- Title for a toggle that enables desktop experience features. This includes desktop view and connected displays. [CHAR LIMIT=50] -->
|
<!-- Title for a toggle that enables desktop experience features. This includes desktop windowing and connected displays. [CHAR LIMIT=50] -->
|
||||||
<string name="enable_desktop_experience_features">Enable desktop experience features</string>
|
<string name="enable_desktop_experience_features">Enable desktop experience features</string>
|
||||||
<!-- Summary for a toggle that enables desktop experience features when the device itself can show the desktop (but it is not available without the developer option). [CHAR LIMIT=NONE] -->
|
<!-- Summary for a toggle that enables desktop experience features when the device itself can show the desktop (but it is not available without the developer option). [CHAR LIMIT=NONE] -->
|
||||||
<string name="enable_desktop_experience_features_summary_with_desktop">Enable Desktop View on the device and on secondary displays.</string>
|
<string name="enable_desktop_experience_features_summary_with_desktop">Enable desktop windowing on the device and on secondary displays.</string>
|
||||||
<!-- Summary for a toggle that enables desktop experience features when desktop views don't need to be enable. [CHAR LIMIT=NONE] -->
|
<!-- Summary for a toggle that enables desktop experience features when desktop windowing doesn't need to be enabled. [CHAR LIMIT=NONE] -->
|
||||||
<string name="enable_desktop_experience_features_summary_without_desktop">Enable Desktop View on secondary displays.</string>
|
<string name="enable_desktop_experience_features_summary_without_desktop">Enable desktop windowing on secondary displays.</string>
|
||||||
|
|
||||||
<!-- Title for a toggle that enables freeform windows on secondary display. Freeform windows enables users to freely arrange and resize overlapping apps. [CHAR LIMIT=50] -->
|
<!-- Title for a toggle that enables freeform windows on secondary display. Freeform windows enables users to freely arrange and resize overlapping apps. [CHAR LIMIT=50] -->
|
||||||
<string name="enable_desktop_mode_on_secondary_display">Enable freeform windows on secondary display</string>
|
<string name="enable_desktop_mode_on_secondary_display">Enable freeform windows on secondary display</string>
|
||||||
@@ -14283,4 +14303,6 @@ Data usage charges may apply.</string>
|
|||||||
<string name="supervision_add_forgot_pin_preference_title">Forgot PIN</string>
|
<string name="supervision_add_forgot_pin_preference_title">Forgot PIN</string>
|
||||||
<!-- Title for web content filters entry [CHAR LIMIT=60] -->
|
<!-- Title for web content filters entry [CHAR LIMIT=60] -->
|
||||||
<string name="supervision_web_content_filters_title">Web content filters</string>
|
<string name="supervision_web_content_filters_title">Web content filters</string>
|
||||||
|
<!-- Generic content description that is attached to the preview illustration at the top of an Accessibility feature toggle page. [CHAR LIMIT=NONE] -->
|
||||||
|
<string name="accessibility_illustration_content_description"><xliff:g id="feature" example="Select to Speak">%1$s</xliff:g> animation</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -104,6 +104,11 @@ public class AccessibilityDialogUtils {
|
|||||||
* screen / Switch between full and partial screen > Save.
|
* screen / Switch between full and partial screen > Save.
|
||||||
*/
|
*/
|
||||||
int DIALOG_MAGNIFICATION_TRIPLE_TAP_WARNING = 1011;
|
int DIALOG_MAGNIFICATION_TRIPLE_TAP_WARNING = 1011;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OPEN: Settings > Accessibility > Magnification > Cursor following.
|
||||||
|
*/
|
||||||
|
int DIALOG_MAGNIFICATION_CURSOR_FOLLOWING_MODE = 1012;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,221 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2025 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.accessibility;
|
||||||
|
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.app.settings.SettingsEnums;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.provider.Settings;
|
||||||
|
import android.provider.Settings.Secure.AccessibilityMagnificationCursorFollowingMode;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.ListView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
import androidx.core.util.Preconditions;
|
||||||
|
import androidx.preference.Preference;
|
||||||
|
import androidx.preference.PreferenceScreen;
|
||||||
|
|
||||||
|
import com.android.settings.DialogCreatable;
|
||||||
|
import com.android.settings.R;
|
||||||
|
import com.android.settings.accessibility.AccessibilityDialogUtils.DialogEnums;
|
||||||
|
import com.android.settings.core.BasePreferenceController;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller that shows the magnification cursor following mode and the preference click behavior.
|
||||||
|
*/
|
||||||
|
public class MagnificationCursorFollowingModePreferenceController extends
|
||||||
|
BasePreferenceController implements DialogCreatable {
|
||||||
|
static final String PREF_KEY =
|
||||||
|
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE;
|
||||||
|
|
||||||
|
private static final String TAG =
|
||||||
|
MagnificationCursorFollowingModePreferenceController.class.getSimpleName();
|
||||||
|
|
||||||
|
private final List<ModeInfo> mModeList = new ArrayList<>();
|
||||||
|
@Nullable
|
||||||
|
private DialogHelper mDialogHelper;
|
||||||
|
@VisibleForTesting
|
||||||
|
@Nullable
|
||||||
|
ListView mModeListView;
|
||||||
|
@Nullable
|
||||||
|
private Preference mModePreference;
|
||||||
|
|
||||||
|
public MagnificationCursorFollowingModePreferenceController(@NonNull Context context,
|
||||||
|
@NonNull String preferenceKey) {
|
||||||
|
super(context, preferenceKey);
|
||||||
|
initModeList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDialogHelper(@NonNull DialogHelper dialogHelper) {
|
||||||
|
mDialogHelper = dialogHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initModeList() {
|
||||||
|
mModeList.add(new ModeInfo(mContext.getString(
|
||||||
|
R.string.accessibility_magnification_cursor_following_continuous),
|
||||||
|
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CONTINUOUS));
|
||||||
|
mModeList.add(new ModeInfo(
|
||||||
|
mContext.getString(R.string.accessibility_magnification_cursor_following_center),
|
||||||
|
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CENTER));
|
||||||
|
mModeList.add(new ModeInfo(
|
||||||
|
mContext.getString(R.string.accessibility_magnification_cursor_following_edge),
|
||||||
|
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_EDGE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getAvailabilityStatus() {
|
||||||
|
return AVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public CharSequence getSummary() {
|
||||||
|
return getCursorFollowingModeSummary(getCurrentMagnificationCursorFollowingMode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void displayPreference(@NonNull PreferenceScreen screen) {
|
||||||
|
super.displayPreference(screen);
|
||||||
|
mModePreference = screen.findPreference(getPreferenceKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handlePreferenceTreeClick(@NonNull Preference preference) {
|
||||||
|
if (!TextUtils.equals(preference.getKey(), getPreferenceKey()) || mModePreference == null) {
|
||||||
|
return super.handlePreferenceTreeClick(preference);
|
||||||
|
}
|
||||||
|
|
||||||
|
Preconditions.checkNotNull(mDialogHelper).showDialog(
|
||||||
|
DialogEnums.DIALOG_MAGNIFICATION_CURSOR_FOLLOWING_MODE);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Dialog onCreateDialog(int dialogId) {
|
||||||
|
Preconditions.checkArgument(
|
||||||
|
dialogId == DialogEnums.DIALOG_MAGNIFICATION_CURSOR_FOLLOWING_MODE,
|
||||||
|
"This only handles cursor following mode dialog");
|
||||||
|
return createMagnificationCursorFollowingModeDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDialogMetricsCategory(int dialogId) {
|
||||||
|
Preconditions.checkArgument(
|
||||||
|
dialogId == DialogEnums.DIALOG_MAGNIFICATION_CURSOR_FOLLOWING_MODE,
|
||||||
|
"This only handles cursor following mode dialog");
|
||||||
|
return SettingsEnums.DIALOG_MAGNIFICATION_CURSOR_FOLLOWING;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private Dialog createMagnificationCursorFollowingModeDialog() {
|
||||||
|
mModeListView = AccessibilityDialogUtils.createSingleChoiceListView(mContext, mModeList,
|
||||||
|
/* itemListener= */null);
|
||||||
|
final View headerView = LayoutInflater.from(mContext).inflate(
|
||||||
|
R.layout.accessibility_dialog_header, mModeListView,
|
||||||
|
/* attachToRoot= */false);
|
||||||
|
final TextView textView = Preconditions.checkNotNull(headerView.findViewById(
|
||||||
|
R.id.accessibility_dialog_header_text_view));
|
||||||
|
textView.setText(
|
||||||
|
mContext.getString(R.string.accessibility_magnification_cursor_following_header));
|
||||||
|
textView.setVisibility(View.VISIBLE);
|
||||||
|
mModeListView.addHeaderView(headerView, /* data= */null, /* isSelectable= */false);
|
||||||
|
final int selectionIndex = computeSelectionIndex();
|
||||||
|
if (selectionIndex != AdapterView.INVALID_POSITION) {
|
||||||
|
mModeListView.setItemChecked(selectionIndex, /* value= */true);
|
||||||
|
}
|
||||||
|
final CharSequence title = mContext.getString(
|
||||||
|
R.string.accessibility_magnification_cursor_following_title);
|
||||||
|
final CharSequence positiveBtnText = mContext.getString(R.string.save);
|
||||||
|
final CharSequence negativeBtnText = mContext.getString(R.string.cancel);
|
||||||
|
return AccessibilityDialogUtils.createCustomDialog(mContext, title, mModeListView,
|
||||||
|
positiveBtnText,
|
||||||
|
this::onMagnificationCursorFollowingModeDialogPositiveButtonClicked,
|
||||||
|
negativeBtnText, /* negativeListener= */null);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onMagnificationCursorFollowingModeDialogPositiveButtonClicked(
|
||||||
|
DialogInterface dialogInterface, int which) {
|
||||||
|
ListView listView = Preconditions.checkNotNull(mModeListView);
|
||||||
|
final int selectionIndex = listView.getCheckedItemPosition();
|
||||||
|
if (selectionIndex == AdapterView.INVALID_POSITION) {
|
||||||
|
Log.w(TAG, "Selected positive button with INVALID_POSITION index");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ModeInfo cursorFollowingMode = (ModeInfo) listView.getItemAtPosition(selectionIndex);
|
||||||
|
if (cursorFollowingMode != null) {
|
||||||
|
Preconditions.checkNotNull(mModePreference).setSummary(
|
||||||
|
getCursorFollowingModeSummary(cursorFollowingMode.mMode));
|
||||||
|
Settings.Secure.putInt(mContext.getContentResolver(), PREF_KEY,
|
||||||
|
cursorFollowingMode.mMode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int computeSelectionIndex() {
|
||||||
|
ListView listView = Preconditions.checkNotNull(mModeListView);
|
||||||
|
@AccessibilityMagnificationCursorFollowingMode
|
||||||
|
final int currentMode = getCurrentMagnificationCursorFollowingMode();
|
||||||
|
for (int i = 0; i < listView.getCount(); i++) {
|
||||||
|
final ModeInfo mode = (ModeInfo) listView.getItemAtPosition(i);
|
||||||
|
if (mode != null && mode.mMode == currentMode) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return AdapterView.INVALID_POSITION;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private CharSequence getCursorFollowingModeSummary(
|
||||||
|
@AccessibilityMagnificationCursorFollowingMode int cursorFollowingMode) {
|
||||||
|
int stringId = switch (cursorFollowingMode) {
|
||||||
|
case Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CENTER ->
|
||||||
|
R.string.accessibility_magnification_cursor_following_center;
|
||||||
|
case Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_EDGE ->
|
||||||
|
R.string.accessibility_magnification_cursor_following_edge;
|
||||||
|
default ->
|
||||||
|
R.string.accessibility_magnification_cursor_following_continuous;
|
||||||
|
};
|
||||||
|
return mContext.getString(stringId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private @AccessibilityMagnificationCursorFollowingMode int
|
||||||
|
getCurrentMagnificationCursorFollowingMode() {
|
||||||
|
return Settings.Secure.getInt(mContext.getContentResolver(), PREF_KEY,
|
||||||
|
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CONTINUOUS);
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ModeInfo extends ItemInfoArrayAdapter.ItemInfo {
|
||||||
|
@AccessibilityMagnificationCursorFollowingMode
|
||||||
|
public final int mMode;
|
||||||
|
|
||||||
|
ModeInfo(@NonNull CharSequence title,
|
||||||
|
@AccessibilityMagnificationCursorFollowingMode int mode) {
|
||||||
|
super(title, /* summary= */null, /* drawableId= */null);
|
||||||
|
mMode = mode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -176,8 +176,12 @@ public class MagnificationModePreferenceController extends BasePreferenceControl
|
|||||||
mContext, mModeInfos, this::onMagnificationModeSelected);
|
mContext, mModeInfos, this::onMagnificationModeSelected);
|
||||||
|
|
||||||
final View headerView = LayoutInflater.from(mContext).inflate(
|
final View headerView = LayoutInflater.from(mContext).inflate(
|
||||||
R.layout.accessibility_magnification_mode_header,
|
R.layout.accessibility_dialog_header, getMagnificationModesListView(),
|
||||||
getMagnificationModesListView(), /* attachToRoot= */false);
|
/* attachToRoot= */false);
|
||||||
|
final TextView textView = Preconditions.checkNotNull(headerView.findViewById(
|
||||||
|
R.id.accessibility_dialog_header_text_view));
|
||||||
|
textView.setText(
|
||||||
|
mContext.getString(R.string.accessibility_magnification_area_settings_message));
|
||||||
getMagnificationModesListView().addHeaderView(headerView, /* data= */null,
|
getMagnificationModesListView().addHeaderView(headerView, /* data= */null,
|
||||||
/* isSelectable= */false);
|
/* isSelectable= */false);
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ import com.android.settings.dashboard.DashboardFragment;
|
|||||||
import com.android.settings.flags.Flags;
|
import com.android.settings.flags.Flags;
|
||||||
import com.android.settings.widget.SettingsMainSwitchBar;
|
import com.android.settings.widget.SettingsMainSwitchBar;
|
||||||
import com.android.settings.widget.SettingsMainSwitchPreference;
|
import com.android.settings.widget.SettingsMainSwitchPreference;
|
||||||
|
import com.android.settingslib.utils.ThreadUtils;
|
||||||
import com.android.settingslib.widget.IllustrationPreference;
|
import com.android.settingslib.widget.IllustrationPreference;
|
||||||
import com.android.settingslib.widget.TopIntroPreference;
|
import com.android.settingslib.widget.TopIntroPreference;
|
||||||
|
|
||||||
@@ -311,6 +312,11 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment
|
|||||||
return getString(R.string.accessibility_shortcut_title, mFeatureName);
|
return getString(R.string.accessibility_shortcut_title, mFeatureName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
CharSequence getContentDescriptionForAnimatedIllustration() {
|
||||||
|
return getString(R.string.accessibility_illustration_content_description, mFeatureName);
|
||||||
|
}
|
||||||
|
|
||||||
protected void onPreferenceToggled(String preferenceKey, boolean enabled) {
|
protected void onPreferenceToggled(String preferenceKey, boolean enabled) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -427,22 +433,38 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment
|
|||||||
|
|
||||||
return drawable;
|
return drawable;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initAnimatedImagePreference() {
|
private void initAnimatedImagePreference() {
|
||||||
if (mImageUri == null) {
|
initAnimatedImagePreference(mImageUri, new IllustrationPreference(getPrefContext()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
void initAnimatedImagePreference(
|
||||||
|
@Nullable Uri imageUri,
|
||||||
|
@NonNull IllustrationPreference preference) {
|
||||||
|
if (imageUri == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final int displayHalfHeight =
|
final int displayHalfHeight =
|
||||||
AccessibilityUtil.getDisplayBounds(getPrefContext()).height() / 2;
|
AccessibilityUtil.getDisplayBounds(getPrefContext()).height() / 2;
|
||||||
final IllustrationPreference illustrationPreference =
|
preference.setImageUri(imageUri);
|
||||||
new IllustrationPreference(getPrefContext());
|
preference.setSelectable(false);
|
||||||
illustrationPreference.setImageUri(mImageUri);
|
preference.setMaxHeight(displayHalfHeight);
|
||||||
illustrationPreference.setSelectable(false);
|
preference.setKey(KEY_ANIMATED_IMAGE);
|
||||||
illustrationPreference.setMaxHeight(displayHalfHeight);
|
preference.setOnBindListener(view -> {
|
||||||
illustrationPreference.setKey(KEY_ANIMATED_IMAGE);
|
// isAnimatable is decided in
|
||||||
|
// {@link IllustrationPreference#onBindViewHolder(PreferenceViewHolder)}. Therefore, we
|
||||||
getPreferenceScreen().addPreference(illustrationPreference);
|
// wait until the view is bond to set the content description for it.
|
||||||
|
// The content description is added for an animation illustration only. Since the static
|
||||||
|
// images are decorative.
|
||||||
|
ThreadUtils.getUiThreadHandler().post(() -> {
|
||||||
|
if (preference.isAnimatable()) {
|
||||||
|
preference.setContentDescription(
|
||||||
|
getContentDescriptionForAnimatedIllustration());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
getPreferenceScreen().addPreference(preference);
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
|
|||||||
@@ -93,6 +93,8 @@ public class ToggleScreenMagnificationPreferenceFragment extends
|
|||||||
private TouchExplorationStateChangeListener mTouchExplorationStateChangeListener;
|
private TouchExplorationStateChangeListener mTouchExplorationStateChangeListener;
|
||||||
@Nullable
|
@Nullable
|
||||||
private DialogCreatable mMagnificationModeDialogDelegate;
|
private DialogCreatable mMagnificationModeDialogDelegate;
|
||||||
|
@Nullable
|
||||||
|
private DialogCreatable mMagnificationCursorFollowingModeDialogDelegate;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
MagnificationOneFingerPanningPreferenceController mOneFingerPanningPreferenceController;
|
MagnificationOneFingerPanningPreferenceController mOneFingerPanningPreferenceController;
|
||||||
@@ -104,6 +106,12 @@ public class ToggleScreenMagnificationPreferenceFragment extends
|
|||||||
mMagnificationModeDialogDelegate = delegate;
|
mMagnificationModeDialogDelegate = delegate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public void setMagnificationCursorFollowingModeDialogDelegate(
|
||||||
|
@NonNull DialogCreatable delegate) {
|
||||||
|
mMagnificationCursorFollowingModeDialogDelegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@@ -186,6 +194,9 @@ public class ToggleScreenMagnificationPreferenceFragment extends
|
|||||||
case DialogEnums.DIALOG_MAGNIFICATION_TRIPLE_TAP_WARNING:
|
case DialogEnums.DIALOG_MAGNIFICATION_TRIPLE_TAP_WARNING:
|
||||||
return Preconditions.checkNotNull(mMagnificationModeDialogDelegate)
|
return Preconditions.checkNotNull(mMagnificationModeDialogDelegate)
|
||||||
.onCreateDialog(dialogId);
|
.onCreateDialog(dialogId);
|
||||||
|
case DialogEnums.DIALOG_MAGNIFICATION_CURSOR_FOLLOWING_MODE:
|
||||||
|
return Preconditions.checkNotNull(mMagnificationCursorFollowingModeDialogDelegate)
|
||||||
|
.onCreateDialog(dialogId);
|
||||||
case DialogEnums.GESTURE_NAVIGATION_TUTORIAL:
|
case DialogEnums.GESTURE_NAVIGATION_TUTORIAL:
|
||||||
return AccessibilityShortcutsTutorial
|
return AccessibilityShortcutsTutorial
|
||||||
.showAccessibilityGestureTutorialDialog(getPrefContext());
|
.showAccessibilityGestureTutorialDialog(getPrefContext());
|
||||||
@@ -201,6 +212,11 @@ public class ToggleScreenMagnificationPreferenceFragment extends
|
|||||||
PackageManager.FEATURE_WINDOW_MAGNIFICATION);
|
PackageManager.FEATURE_WINDOW_MAGNIFICATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isMagnificationCursorFollowingModeDialogSupported() {
|
||||||
|
// TODO(b/398066000): Hide the setting when no pointer device exists for most form factors.
|
||||||
|
return com.android.settings.accessibility.Flags.enableMagnificationCursorFollowingDialog();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void initSettingsPreference() {
|
protected void initSettingsPreference() {
|
||||||
final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY);
|
final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY);
|
||||||
@@ -213,6 +229,7 @@ public class ToggleScreenMagnificationPreferenceFragment extends
|
|||||||
addJoystickSetting(generalCategory);
|
addJoystickSetting(generalCategory);
|
||||||
// LINT.ThenChange(:search_data)
|
// LINT.ThenChange(:search_data)
|
||||||
}
|
}
|
||||||
|
addCursorFollowingSetting(generalCategory);
|
||||||
addFeedbackSetting(generalCategory);
|
addFeedbackSetting(generalCategory);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,6 +303,31 @@ public class ToggleScreenMagnificationPreferenceFragment extends
|
|||||||
addPreferenceController(magnificationModePreferenceController);
|
addPreferenceController(magnificationModePreferenceController);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Preference createCursorFollowingPreference(Context context) {
|
||||||
|
final Preference pref = new Preference(context);
|
||||||
|
pref.setTitle(R.string.accessibility_magnification_cursor_following_title);
|
||||||
|
pref.setKey(MagnificationCursorFollowingModePreferenceController.PREF_KEY);
|
||||||
|
pref.setPersistent(false);
|
||||||
|
return pref;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addCursorFollowingSetting(PreferenceCategory generalCategory) {
|
||||||
|
if (!isMagnificationCursorFollowingModeDialogSupported()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
generalCategory.addPreference(createCursorFollowingPreference(getPrefContext()));
|
||||||
|
|
||||||
|
final MagnificationCursorFollowingModePreferenceController controller =
|
||||||
|
new MagnificationCursorFollowingModePreferenceController(
|
||||||
|
getContext(),
|
||||||
|
MagnificationCursorFollowingModePreferenceController.PREF_KEY);
|
||||||
|
controller.setDialogHelper(/* dialogHelper= */this);
|
||||||
|
mMagnificationCursorFollowingModeDialogDelegate = controller;
|
||||||
|
controller.displayPreference(getPreferenceScreen());
|
||||||
|
addPreferenceController(controller);
|
||||||
|
}
|
||||||
|
|
||||||
private static Preference createFollowTypingPreference(Context context) {
|
private static Preference createFollowTypingPreference(Context context) {
|
||||||
final Preference pref = new SwitchPreferenceCompat(context);
|
final Preference pref = new SwitchPreferenceCompat(context);
|
||||||
pref.setTitle(R.string.accessibility_screen_magnification_follow_typing_title);
|
pref.setTitle(R.string.accessibility_screen_magnification_follow_typing_title);
|
||||||
@@ -510,6 +552,9 @@ public class ToggleScreenMagnificationPreferenceFragment extends
|
|||||||
case DialogEnums.DIALOG_MAGNIFICATION_TRIPLE_TAP_WARNING:
|
case DialogEnums.DIALOG_MAGNIFICATION_TRIPLE_TAP_WARNING:
|
||||||
return Preconditions.checkNotNull(mMagnificationModeDialogDelegate)
|
return Preconditions.checkNotNull(mMagnificationModeDialogDelegate)
|
||||||
.getDialogMetricsCategory(dialogId);
|
.getDialogMetricsCategory(dialogId);
|
||||||
|
case DialogEnums.DIALOG_MAGNIFICATION_CURSOR_FOLLOWING_MODE:
|
||||||
|
return Preconditions.checkNotNull(mMagnificationCursorFollowingModeDialogDelegate)
|
||||||
|
.getDialogMetricsCategory(dialogId);
|
||||||
case DialogEnums.GESTURE_NAVIGATION_TUTORIAL:
|
case DialogEnums.GESTURE_NAVIGATION_TUTORIAL:
|
||||||
return SettingsEnums.DIALOG_TOGGLE_SCREEN_MAGNIFICATION_GESTURE_NAVIGATION;
|
return SettingsEnums.DIALOG_TOGGLE_SCREEN_MAGNIFICATION_GESTURE_NAVIGATION;
|
||||||
case DialogEnums.ACCESSIBILITY_BUTTON_TUTORIAL:
|
case DialogEnums.ACCESSIBILITY_BUTTON_TUTORIAL:
|
||||||
@@ -667,6 +712,11 @@ public class ToggleScreenMagnificationPreferenceFragment extends
|
|||||||
return rawData;
|
return rawData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add all preferences to search raw data so that they are included in
|
||||||
|
// indexing, which happens infrequently. Irrelevant preferences should be
|
||||||
|
// hidden from the live returned search results by `getNonIndexableKeys`,
|
||||||
|
// which is called every time a search occurs. This allows for dynamic search
|
||||||
|
// entries that hide or show depending on current device state.
|
||||||
rawData.add(createShortcutPreferenceSearchData(context));
|
rawData.add(createShortcutPreferenceSearchData(context));
|
||||||
Stream.of(
|
Stream.of(
|
||||||
createMagnificationModePreference(context),
|
createMagnificationModePreference(context),
|
||||||
@@ -674,6 +724,7 @@ public class ToggleScreenMagnificationPreferenceFragment extends
|
|||||||
createOneFingerPanningPreference(context),
|
createOneFingerPanningPreference(context),
|
||||||
createAlwaysOnPreference(context),
|
createAlwaysOnPreference(context),
|
||||||
createJoystickPreference(context),
|
createJoystickPreference(context),
|
||||||
|
createCursorFollowingPreference(context),
|
||||||
createFeedbackPreference(context)
|
createFeedbackPreference(context)
|
||||||
)
|
)
|
||||||
.forEach(pref ->
|
.forEach(pref ->
|
||||||
@@ -714,6 +765,10 @@ public class ToggleScreenMagnificationPreferenceFragment extends
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isMagnificationCursorFollowingModeDialogSupported()) {
|
||||||
|
niks.add(MagnificationCursorFollowingModePreferenceController.PREF_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
if (!Flags.enableLowVisionHats()) {
|
if (!Flags.enableLowVisionHats()) {
|
||||||
niks.add(MagnificationFeedbackPreferenceController.PREF_KEY);
|
niks.add(MagnificationFeedbackPreferenceController.PREF_KEY);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,10 @@ import android.media.MediaMetadata;
|
|||||||
import android.media.session.MediaSession;
|
import android.media.session.MediaSession;
|
||||||
import android.media.session.PlaybackState;
|
import android.media.session.PlaybackState;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.HandlerThread;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
|
import android.os.Process;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
|
|
||||||
@@ -51,24 +54,21 @@ import com.android.settingslib.bluetooth.BluetoothUtils;
|
|||||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
|
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
|
||||||
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
|
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
|
||||||
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
|
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
|
||||||
|
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState;
|
||||||
import com.android.settingslib.bluetooth.LocalBluetoothManager;
|
import com.android.settingslib.bluetooth.LocalBluetoothManager;
|
||||||
import com.android.settingslib.bluetooth.VolumeControlProfile;
|
import com.android.settingslib.bluetooth.VolumeControlProfile;
|
||||||
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
|
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
|
||||||
import com.android.settingslib.utils.ThreadUtils;
|
import com.android.settingslib.utils.ThreadUtils;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
public class AudioStreamMediaService extends Service {
|
public class AudioStreamMediaService extends Service {
|
||||||
static final String BROADCAST_ID = "audio_stream_media_service_broadcast_id";
|
static final String BROADCAST_ID = "audio_stream_media_service_broadcast_id";
|
||||||
static final String BROADCAST_TITLE = "audio_stream_media_service_broadcast_title";
|
static final String BROADCAST_TITLE = "audio_stream_media_service_broadcast_title";
|
||||||
static final String DEVICES = "audio_stream_media_service_devices";
|
static final String DEVICES = "audio_stream_media_service_devices";
|
||||||
private static final String TAG = "AudioStreamMediaService";
|
private static final String TAG = "AudioStreamMediaService";
|
||||||
private static final int NOTIFICATION_ID = 1;
|
private static final int NOTIFICATION_ID = R.string.audio_streams_title;
|
||||||
private static final int BROADCAST_LISTENING_NOW_TEXT = R.string.audio_streams_listening_now;
|
private static final int BROADCAST_LISTENING_NOW_TEXT = R.string.audio_streams_listening_now;
|
||||||
private static final int BROADCAST_STREAM_PAUSED_TEXT = R.string.audio_streams_present_now;
|
private static final int BROADCAST_STREAM_PAUSED_TEXT = R.string.audio_streams_present_now;
|
||||||
@VisibleForTesting static final String LEAVE_BROADCAST_ACTION = "leave_broadcast_action";
|
@VisibleForTesting static final String LEAVE_BROADCAST_ACTION = "leave_broadcast_action";
|
||||||
@@ -113,17 +113,16 @@ public class AudioStreamMediaService extends Service {
|
|||||||
|
|
||||||
private final MetricsFeatureProvider mMetricsFeatureProvider =
|
private final MetricsFeatureProvider mMetricsFeatureProvider =
|
||||||
FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
|
FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
|
||||||
private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
|
private final HandlerThread mHandlerThread = new HandlerThread(TAG,
|
||||||
private final AtomicBoolean mIsMuted = new AtomicBoolean(false);
|
Process.THREAD_PRIORITY_BACKGROUND);
|
||||||
private final AtomicBoolean mIsHysteresis = new AtomicBoolean(false);
|
private boolean mIsMuted = false;
|
||||||
// Set 25 as default as the volume range from `VolumeControlProfile` is from 0 to 255.
|
// Set 25 as default as the volume range from `VolumeControlProfile` is from 0 to 255.
|
||||||
// If the initial volume from `onDeviceVolumeChanged` is larger than zero (not muted), we will
|
// If the initial volume from `onDeviceVolumeChanged` is larger than zero (not muted), we will
|
||||||
// override this value. Otherwise, we raise the volume to 25 when the play button is clicked.
|
// override this value. Otherwise, we raise the volume to 25 when the play button is clicked.
|
||||||
private final AtomicInteger mLatestPositiveVolume = new AtomicInteger(25);
|
private int mLatestPositiveVolume = 25;
|
||||||
private final Object mLocalSessionLock = new Object();
|
|
||||||
private boolean mHysteresisModeFixAvailable;
|
private boolean mHysteresisModeFixAvailable;
|
||||||
private int mBroadcastId;
|
private int mBroadcastId;
|
||||||
@Nullable private List<BluetoothDevice> mDevices;
|
@Nullable private Map<BluetoothDevice, LocalBluetoothLeBroadcastSourceState> mStateByDevice;
|
||||||
@Nullable private LocalBluetoothManager mLocalBtManager;
|
@Nullable private LocalBluetoothManager mLocalBtManager;
|
||||||
@Nullable private AudioStreamsHelper mAudioStreamsHelper;
|
@Nullable private AudioStreamsHelper mAudioStreamsHelper;
|
||||||
@Nullable private LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
|
@Nullable private LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
|
||||||
@@ -154,7 +153,6 @@ public class AudioStreamMediaService extends Service {
|
|||||||
Log.w(TAG, "onCreate() : mLeBroadcastAssistant is null!");
|
Log.w(TAG, "onCreate() : mLeBroadcastAssistant is null!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mHysteresisModeFixAvailable = BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(this);
|
|
||||||
|
|
||||||
mNotificationManager = getSystemService(NotificationManager.class);
|
mNotificationManager = getSystemService(NotificationManager.class);
|
||||||
if (mNotificationManager == null) {
|
if (mNotificationManager == null) {
|
||||||
@@ -162,7 +160,8 @@ public class AudioStreamMediaService extends Service {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mExecutor.execute(
|
mHandlerThread.start();
|
||||||
|
getHandler().post(
|
||||||
() -> {
|
() -> {
|
||||||
if (mLocalBtManager == null
|
if (mLocalBtManager == null
|
||||||
|| mLeBroadcastAssistant == null
|
|| mLeBroadcastAssistant == null
|
||||||
@@ -184,45 +183,49 @@ public class AudioStreamMediaService extends Service {
|
|||||||
mVolumeControl = mLocalBtManager.getProfileManager().getVolumeControlProfile();
|
mVolumeControl = mLocalBtManager.getProfileManager().getVolumeControlProfile();
|
||||||
if (mVolumeControl != null) {
|
if (mVolumeControl != null) {
|
||||||
mVolumeControlCallback = new VolumeControlCallback();
|
mVolumeControlCallback = new VolumeControlCallback();
|
||||||
mVolumeControl.registerCallback(mExecutor, mVolumeControlCallback);
|
mVolumeControl.registerCallback(getHandler()::post, mVolumeControlCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
mBroadcastAssistantCallback = new AssistantCallback();
|
mBroadcastAssistantCallback = new AssistantCallback();
|
||||||
mLeBroadcastAssistant.registerServiceCallBack(
|
mLeBroadcastAssistant.registerServiceCallBack(
|
||||||
mExecutor, mBroadcastAssistantCallback);
|
getHandler()::post, mBroadcastAssistantCallback);
|
||||||
|
|
||||||
|
mHysteresisModeFixAvailable =
|
||||||
|
BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(this);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
Handler getHandler() {
|
||||||
|
return mHandlerThread.getThreadHandler();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
Log.d(TAG, "onDestroy()");
|
Log.d(TAG, "onDestroy()");
|
||||||
super.onDestroy();
|
getHandler().post(
|
||||||
if (BluetoothUtils.isAudioSharingUIAvailable(this)) {
|
() -> {
|
||||||
if (mDevices != null) {
|
if (mStateByDevice != null) {
|
||||||
mDevices.clear();
|
mStateByDevice.clear();
|
||||||
mDevices = null;
|
mStateByDevice = null;
|
||||||
}
|
}
|
||||||
synchronized (mLocalSessionLock) {
|
if (mLocalSession != null) {
|
||||||
if (mLocalSession != null) {
|
mLocalSession.release();
|
||||||
mLocalSession.release();
|
mLocalSession = null;
|
||||||
mLocalSession = null;
|
}
|
||||||
}
|
if (mLocalBtManager != null) {
|
||||||
}
|
mLocalBtManager.getEventManager().unregisterCallback(
|
||||||
mExecutor.execute(
|
mBluetoothCallback);
|
||||||
() -> {
|
}
|
||||||
if (mLocalBtManager != null) {
|
if (mLeBroadcastAssistant != null && mBroadcastAssistantCallback != null) {
|
||||||
mLocalBtManager.getEventManager().unregisterCallback(
|
mLeBroadcastAssistant.unregisterServiceCallBack(
|
||||||
mBluetoothCallback);
|
mBroadcastAssistantCallback);
|
||||||
}
|
}
|
||||||
if (mLeBroadcastAssistant != null && mBroadcastAssistantCallback != null) {
|
if (mVolumeControl != null && mVolumeControlCallback != null) {
|
||||||
mLeBroadcastAssistant.unregisterServiceCallBack(
|
mVolumeControl.unregisterCallback(mVolumeControlCallback);
|
||||||
mBroadcastAssistantCallback);
|
}
|
||||||
}
|
});
|
||||||
if (mVolumeControl != null && mVolumeControlCallback != null) {
|
mHandlerThread.quitSafely();
|
||||||
mVolumeControl.unregisterCallback(mVolumeControlCallback);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -233,53 +236,59 @@ public class AudioStreamMediaService extends Service {
|
|||||||
stopSelf();
|
stopSelf();
|
||||||
return START_NOT_STICKY;
|
return START_NOT_STICKY;
|
||||||
}
|
}
|
||||||
mBroadcastId = intent.getIntExtra(BROADCAST_ID, -1);
|
getHandler().post(() -> {
|
||||||
if (mBroadcastId == -1) {
|
mBroadcastId = intent.getIntExtra(BROADCAST_ID, -1);
|
||||||
Log.w(TAG, "Invalid broadcast ID. Service will not start.");
|
if (mBroadcastId == -1) {
|
||||||
stopSelf();
|
Log.w(TAG, "Invalid broadcast ID. Service will not start.");
|
||||||
return START_NOT_STICKY;
|
stopSelf();
|
||||||
}
|
return;
|
||||||
var extra = intent.getParcelableArrayListExtra(DEVICES, BluetoothDevice.class);
|
}
|
||||||
if (extra == null || extra.isEmpty()) {
|
var devices = intent.getParcelableArrayListExtra(DEVICES, BluetoothDevice.class);
|
||||||
Log.w(TAG, "No device. Service will not start.");
|
if (devices == null || devices.isEmpty()) {
|
||||||
stopSelf();
|
Log.w(TAG, "No device. Service will not start.");
|
||||||
return START_NOT_STICKY;
|
stopSelf();
|
||||||
}
|
} else {
|
||||||
mDevices = Collections.synchronizedList(extra);
|
mStateByDevice = new HashMap<>();
|
||||||
MediaSession.Token token =
|
devices.forEach(d -> mStateByDevice.put(d, STREAMING));
|
||||||
getOrCreateLocalMediaSession(intent.getStringExtra(BROADCAST_TITLE));
|
MediaSession.Token token =
|
||||||
startForeground(NOTIFICATION_ID, buildNotification(token));
|
getOrCreateLocalMediaSession(intent.getStringExtra(BROADCAST_TITLE));
|
||||||
|
startForeground(NOTIFICATION_ID, buildNotification(token));
|
||||||
|
}
|
||||||
|
});
|
||||||
return START_NOT_STICKY;
|
return START_NOT_STICKY;
|
||||||
}
|
}
|
||||||
|
|
||||||
private MediaSession.Token getOrCreateLocalMediaSession(String title) {
|
private MediaSession.Token getOrCreateLocalMediaSession(String title) {
|
||||||
synchronized (mLocalSessionLock) {
|
if (mLocalSession != null) {
|
||||||
if (mLocalSession != null) {
|
|
||||||
return mLocalSession.getSessionToken();
|
|
||||||
}
|
|
||||||
mLocalSession = new MediaSession(this, TAG);
|
|
||||||
mLocalSession.setMetadata(
|
|
||||||
new MediaMetadata.Builder()
|
|
||||||
.putString(MediaMetadata.METADATA_KEY_TITLE, title)
|
|
||||||
.putLong(MediaMetadata.METADATA_KEY_DURATION, STATIC_PLAYBACK_DURATION)
|
|
||||||
.build());
|
|
||||||
mLocalSession.setActive(true);
|
|
||||||
mLocalSession.setPlaybackState(getPlaybackState());
|
|
||||||
mMediaSessionCallback = new MediaSessionCallback();
|
|
||||||
mLocalSession.setCallback(mMediaSessionCallback);
|
|
||||||
return mLocalSession.getSessionToken();
|
return mLocalSession.getSessionToken();
|
||||||
}
|
}
|
||||||
|
mLocalSession = new MediaSession(this, TAG);
|
||||||
|
mLocalSession.setMetadata(
|
||||||
|
new MediaMetadata.Builder()
|
||||||
|
.putString(MediaMetadata.METADATA_KEY_TITLE, title)
|
||||||
|
.putLong(MediaMetadata.METADATA_KEY_DURATION, STATIC_PLAYBACK_DURATION)
|
||||||
|
.build());
|
||||||
|
mLocalSession.setActive(true);
|
||||||
|
mLocalSession.setPlaybackState(getPlaybackState());
|
||||||
|
mMediaSessionCallback = new MediaSessionCallback();
|
||||||
|
mLocalSession.setCallback(mMediaSessionCallback, getHandler());
|
||||||
|
return mLocalSession.getSessionToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
private PlaybackState getPlaybackState() {
|
private PlaybackState getPlaybackState() {
|
||||||
if (mIsHysteresis.get()) {
|
if (isAllDeviceHysteresis()) {
|
||||||
return mPlayStateHysteresisBuilder.build();
|
return mPlayStateHysteresisBuilder.build();
|
||||||
}
|
}
|
||||||
return mIsMuted.get() ? mPlayStatePausingBuilder.build() : mPlayStatePlayingBuilder.build();
|
return mIsMuted ? mPlayStatePausingBuilder.build() : mPlayStatePlayingBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isAllDeviceHysteresis() {
|
||||||
|
return mHysteresisModeFixAvailable && mStateByDevice != null
|
||||||
|
&& mStateByDevice.values().stream().allMatch(v -> v == PAUSED);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getDeviceName() {
|
private String getDeviceName() {
|
||||||
if (mDevices == null || mDevices.isEmpty() || mLocalBtManager == null) {
|
if (mStateByDevice == null || mStateByDevice.isEmpty() || mLocalBtManager == null) {
|
||||||
return DEFAULT_DEVICE_NAME;
|
return DEFAULT_DEVICE_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,7 +297,8 @@ public class AudioStreamMediaService extends Service {
|
|||||||
return DEFAULT_DEVICE_NAME;
|
return DEFAULT_DEVICE_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
CachedBluetoothDevice device = manager.findDevice(mDevices.get(0));
|
CachedBluetoothDevice device = manager.findDevice(
|
||||||
|
mStateByDevice.keySet().iterator().next());
|
||||||
return device != null ? device.getName() : DEFAULT_DEVICE_NAME;
|
return device != null ? device.getName() : DEFAULT_DEVICE_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,7 +314,7 @@ public class AudioStreamMediaService extends Service {
|
|||||||
.setSmallIcon(com.android.settingslib.R.drawable.ic_bt_le_audio_sharing)
|
.setSmallIcon(com.android.settingslib.R.drawable.ic_bt_le_audio_sharing)
|
||||||
.setStyle(mediaStyle)
|
.setStyle(mediaStyle)
|
||||||
.setContentText(getString(
|
.setContentText(getString(
|
||||||
mIsHysteresis.get() ? BROADCAST_STREAM_PAUSED_TEXT :
|
isAllDeviceHysteresis() ? BROADCAST_STREAM_PAUSED_TEXT :
|
||||||
BROADCAST_LISTENING_NOW_TEXT))
|
BROADCAST_LISTENING_NOW_TEXT))
|
||||||
.setSilent(true);
|
.setSilent(true);
|
||||||
return notificationBuilder.build();
|
return notificationBuilder.build();
|
||||||
@@ -333,7 +343,8 @@ public class AudioStreamMediaService extends Service {
|
|||||||
public void onReceiveStateChanged(
|
public void onReceiveStateChanged(
|
||||||
BluetoothDevice sink, int sourceId, BluetoothLeBroadcastReceiveState state) {
|
BluetoothDevice sink, int sourceId, BluetoothLeBroadcastReceiveState state) {
|
||||||
super.onReceiveStateChanged(sink, sourceId, state);
|
super.onReceiveStateChanged(sink, sourceId, state);
|
||||||
if (!mHysteresisModeFixAvailable || mDevices == null || !mDevices.contains(sink)) {
|
if (!mHysteresisModeFixAvailable || mStateByDevice == null
|
||||||
|
|| !mStateByDevice.containsKey(sink)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var sourceState = LocalBluetoothLeBroadcastAssistant.getLocalSourceState(state);
|
var sourceState = LocalBluetoothLeBroadcastAssistant.getLocalSourceState(state);
|
||||||
@@ -343,12 +354,10 @@ public class AudioStreamMediaService extends Service {
|
|||||||
if (!streaming && !paused) {
|
if (!streaming && !paused) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Atomically update mIsHysteresis if its current value is not the current paused state
|
boolean shouldUpdate = mStateByDevice.get(sink) != sourceState;
|
||||||
if (mIsHysteresis.compareAndSet(!paused, paused)) {
|
if (shouldUpdate) {
|
||||||
synchronized (mLocalSessionLock) {
|
mStateByDevice.put(sink, sourceState);
|
||||||
if (mLocalSession == null) {
|
if (mLocalSession != null) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
mLocalSession.setPlaybackState(getPlaybackState());
|
mLocalSession.setPlaybackState(getPlaybackState());
|
||||||
if (mNotificationManager != null) {
|
if (mNotificationManager != null) {
|
||||||
mNotificationManager.notify(
|
mNotificationManager.notify(
|
||||||
@@ -356,7 +365,7 @@ public class AudioStreamMediaService extends Service {
|
|||||||
buildNotification(mLocalSession.getSessionToken())
|
buildNotification(mLocalSession.getSessionToken())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Log.d(TAG, "updating hysteresis mode to : " + paused);
|
Log.d(TAG, "updating source state to : " + sourceState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -374,24 +383,22 @@ public class AudioStreamMediaService extends Service {
|
|||||||
@Override
|
@Override
|
||||||
public void onDeviceVolumeChanged(
|
public void onDeviceVolumeChanged(
|
||||||
@NonNull BluetoothDevice device, @IntRange(from = -255, to = 255) int volume) {
|
@NonNull BluetoothDevice device, @IntRange(from = -255, to = 255) int volume) {
|
||||||
if (mDevices == null || mDevices.isEmpty()) {
|
if (mStateByDevice == null || mStateByDevice.isEmpty()) {
|
||||||
Log.w(TAG, "active device or device has source is null!");
|
Log.w(TAG, "active device or device has source is null!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Log.d(
|
Log.d(
|
||||||
TAG,
|
TAG,
|
||||||
"onDeviceVolumeChanged() bluetoothDevice : " + device + " volume: " + volume);
|
"onDeviceVolumeChanged() bluetoothDevice : " + device + " volume: " + volume);
|
||||||
if (mDevices.contains(device)) {
|
if (mStateByDevice.containsKey(device)) {
|
||||||
if (volume == 0) {
|
if (volume == 0) {
|
||||||
mIsMuted.set(true);
|
mIsMuted = true;
|
||||||
} else {
|
} else {
|
||||||
mIsMuted.set(false);
|
mIsMuted = false;
|
||||||
mLatestPositiveVolume.set(volume);
|
mLatestPositiveVolume = volume;
|
||||||
}
|
}
|
||||||
synchronized (mLocalSessionLock) {
|
if (mLocalSession != null) {
|
||||||
if (mLocalSession != null) {
|
mLocalSession.setPlaybackState(getPlaybackState());
|
||||||
mLocalSession.setPlaybackState(getPlaybackState());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -400,10 +407,12 @@ public class AudioStreamMediaService extends Service {
|
|||||||
private class BtCallback implements BluetoothCallback {
|
private class BtCallback implements BluetoothCallback {
|
||||||
@Override
|
@Override
|
||||||
public void onBluetoothStateChanged(int bluetoothState) {
|
public void onBluetoothStateChanged(int bluetoothState) {
|
||||||
if (BluetoothAdapter.STATE_OFF == bluetoothState) {
|
getHandler().post(() -> {
|
||||||
Log.d(TAG, "onBluetoothStateChanged() : stopSelf");
|
if (BluetoothAdapter.STATE_OFF == bluetoothState) {
|
||||||
stopSelf();
|
Log.d(TAG, "onBluetoothStateChanged() : stopSelf");
|
||||||
}
|
stopSelf();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -411,24 +420,17 @@ public class AudioStreamMediaService extends Service {
|
|||||||
@NonNull CachedBluetoothDevice cachedDevice,
|
@NonNull CachedBluetoothDevice cachedDevice,
|
||||||
@ConnectionState int state,
|
@ConnectionState int state,
|
||||||
int bluetoothProfile) {
|
int bluetoothProfile) {
|
||||||
if (state == BluetoothAdapter.STATE_DISCONNECTED
|
getHandler().post(() -> {
|
||||||
&& bluetoothProfile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
|
if (state == BluetoothAdapter.STATE_DISCONNECTED
|
||||||
&& mDevices != null) {
|
&& bluetoothProfile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
|
||||||
mDevices.remove(cachedDevice.getDevice());
|
&& mStateByDevice != null) {
|
||||||
cachedDevice
|
mStateByDevice.remove(cachedDevice.getDevice());
|
||||||
.getMemberDevice()
|
}
|
||||||
.forEach(
|
if (mStateByDevice == null || mStateByDevice.isEmpty()) {
|
||||||
m -> {
|
Log.d(TAG, "onProfileConnectionStateChanged() : stopSelf");
|
||||||
// Check nullability to pass NullAway check
|
stopSelf();
|
||||||
if (mDevices != null) {
|
}
|
||||||
mDevices.remove(m.getDevice());
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (mDevices == null || mDevices.isEmpty()) {
|
|
||||||
Log.d(TAG, "onProfileConnectionStateChanged() : stopSelf");
|
|
||||||
stopSelf();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,10 +456,8 @@ public class AudioStreamMediaService extends Service {
|
|||||||
@Override
|
@Override
|
||||||
public void onSeekTo(long pos) {
|
public void onSeekTo(long pos) {
|
||||||
Log.d(TAG, "onSeekTo: " + pos);
|
Log.d(TAG, "onSeekTo: " + pos);
|
||||||
synchronized (mLocalSessionLock) {
|
if (mLocalSession != null) {
|
||||||
if (mLocalSession != null) {
|
mLocalSession.setPlaybackState(getPlaybackState());
|
||||||
mLocalSession.setPlaybackState(getPlaybackState());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,28 +484,26 @@ public class AudioStreamMediaService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void handleOnPlay() {
|
private void handleOnPlay() {
|
||||||
if (mDevices == null || mDevices.isEmpty()) {
|
if (mStateByDevice == null || mStateByDevice.isEmpty()) {
|
||||||
Log.w(TAG, "active device or device has source is null!");
|
Log.w(TAG, "active device or device has source is null!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Log.d(
|
mStateByDevice.keySet().forEach(device -> {
|
||||||
TAG,
|
Log.d(TAG, "onPlay() setting volume for device : " + device + " volume: "
|
||||||
"onPlay() setting volume for device : "
|
+ mLatestPositiveVolume);
|
||||||
+ mDevices.getFirst()
|
setDeviceVolume(device, mLatestPositiveVolume);
|
||||||
+ " volume: "
|
});
|
||||||
+ mLatestPositiveVolume.get());
|
|
||||||
setDeviceVolume(mDevices.getFirst(), mLatestPositiveVolume.get());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleOnPause() {
|
private void handleOnPause() {
|
||||||
if (mDevices == null || mDevices.isEmpty()) {
|
if (mStateByDevice == null || mStateByDevice.isEmpty()) {
|
||||||
Log.w(TAG, "active device or device has source is null!");
|
Log.w(TAG, "active device or device has source is null!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Log.d(
|
mStateByDevice.keySet().forEach(device -> {
|
||||||
TAG,
|
Log.d(TAG, "onPause() setting volume for device : " + device + " volume: " + 0);
|
||||||
"onPause() setting volume for device : " + mDevices.getFirst() + " volume: " + 0);
|
setDeviceVolume(device, /* volume= */ 0);
|
||||||
setDeviceVolume(mDevices.getFirst(), /* volume= */ 0);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setDeviceVolume(BluetoothDevice device, int volume) {
|
private void setDeviceVolume(BluetoothDevice device, int volume) {
|
||||||
@@ -514,7 +512,7 @@ public class AudioStreamMediaService extends Service {
|
|||||||
ThreadUtils.postOnBackgroundThread(
|
ThreadUtils.postOnBackgroundThread(
|
||||||
() -> {
|
() -> {
|
||||||
if (mVolumeControl != null) {
|
if (mVolumeControl != null) {
|
||||||
mVolumeControl.setDeviceVolume(device, volume, true);
|
mVolumeControl.setDeviceVolume(device, volume, false);
|
||||||
mMetricsFeatureProvider.action(
|
mMetricsFeatureProvider.action(
|
||||||
getApplicationContext(), event, volume == 0 ? 1 : 0);
|
getApplicationContext(), event, volume == 0 ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2025 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.connecteddevice.display
|
||||||
|
|
||||||
|
import android.window.DesktopExperienceFlags.DesktopExperienceFlag
|
||||||
|
import com.android.settings.flags.FeatureFlags
|
||||||
|
|
||||||
|
/** Class handling Settings flags, but using the Desktop Experience developer option overrides. */
|
||||||
|
class DesktopExperienceFlags(private val featureFlagsImpl: FeatureFlags) : FeatureFlags by featureFlagsImpl {
|
||||||
|
|
||||||
|
private val displayTopologyPaneInDisplayListFlag =
|
||||||
|
DesktopExperienceFlag(
|
||||||
|
featureFlagsImpl::displayTopologyPaneInDisplayList,
|
||||||
|
/* shouldOverrideByDevOption= */ true,
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun displayTopologyPaneInDisplayList(): Boolean =
|
||||||
|
displayTopologyPaneInDisplayListFlag.isTrue
|
||||||
|
|
||||||
|
private val displaySizeConnectedDisplaySettingFlag =
|
||||||
|
DesktopExperienceFlag(
|
||||||
|
featureFlagsImpl::displaySizeConnectedDisplaySetting,
|
||||||
|
/* shouldOverrideByDevOption= */ true,
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun displaySizeConnectedDisplaySetting(): Boolean =
|
||||||
|
displaySizeConnectedDisplaySettingFlag.isTrue
|
||||||
|
}
|
||||||
@@ -109,7 +109,8 @@ public class ExternalDisplaySettingsConfiguration {
|
|||||||
private final Handler mHandler;
|
private final Handler mHandler;
|
||||||
|
|
||||||
Injector(@Nullable Context context) {
|
Injector(@Nullable Context context) {
|
||||||
this(context, new FeatureFlagsImpl(), new Handler(Looper.getMainLooper()));
|
this(context, new DesktopExperienceFlags(new FeatureFlagsImpl()),
|
||||||
|
new Handler(Looper.getMainLooper()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Injector(@Nullable Context context, @NonNull FeatureFlags flags, @NonNull Handler handler) {
|
Injector(@Nullable Context context, @NonNull FeatureFlags flags, @NonNull Handler handler) {
|
||||||
|
|||||||
@@ -16,6 +16,9 @@
|
|||||||
|
|
||||||
package com.android.settings.fuelgauge.batteryusage;
|
package com.android.settings.fuelgauge.batteryusage;
|
||||||
|
|
||||||
|
import static com.android.settings.fuelgauge.batteryusage.BatteryChartViewModel.SELECTED_INDEX_ALL;
|
||||||
|
import static com.android.settings.fuelgauge.batteryusage.BatteryChartViewModel.SELECTED_INDEX_INVALID;
|
||||||
|
|
||||||
import android.animation.Animator;
|
import android.animation.Animator;
|
||||||
import android.animation.AnimatorListenerAdapter;
|
import android.animation.AnimatorListenerAdapter;
|
||||||
import android.app.settings.SettingsEnums;
|
import android.app.settings.SettingsEnums;
|
||||||
@@ -82,10 +85,10 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
|
|||||||
@VisibleForTesting TextView mChartSummaryTextView;
|
@VisibleForTesting TextView mChartSummaryTextView;
|
||||||
@VisibleForTesting BatteryChartView mDailyChartView;
|
@VisibleForTesting BatteryChartView mDailyChartView;
|
||||||
@VisibleForTesting BatteryChartView mHourlyChartView;
|
@VisibleForTesting BatteryChartView mHourlyChartView;
|
||||||
@VisibleForTesting int mDailyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL;
|
@VisibleForTesting int mDailyChartIndex = SELECTED_INDEX_ALL;
|
||||||
@VisibleForTesting int mHourlyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL;
|
@VisibleForTesting int mHourlyChartIndex = SELECTED_INDEX_ALL;
|
||||||
@VisibleForTesting int mDailyHighlightSlotIndex = BatteryChartViewModel.SELECTED_INDEX_INVALID;
|
@VisibleForTesting int mDailyHighlightSlotIndex = SELECTED_INDEX_INVALID;
|
||||||
@VisibleForTesting int mHourlyHighlightSlotIndex = BatteryChartViewModel.SELECTED_INDEX_INVALID;
|
@VisibleForTesting int mHourlyHighlightSlotIndex = SELECTED_INDEX_INVALID;
|
||||||
|
|
||||||
private boolean mIs24HourFormat;
|
private boolean mIs24HourFormat;
|
||||||
private View mBatteryChartViewGroup;
|
private View mBatteryChartViewGroup;
|
||||||
@@ -198,8 +201,8 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
|
|||||||
getTotalHours(batteryLevelData));
|
getTotalHours(batteryLevelData));
|
||||||
|
|
||||||
if (batteryLevelData == null) {
|
if (batteryLevelData == null) {
|
||||||
mDailyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL;
|
mDailyChartIndex = SELECTED_INDEX_ALL;
|
||||||
mHourlyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL;
|
mHourlyChartIndex = SELECTED_INDEX_ALL;
|
||||||
mDailyViewModel = null;
|
mDailyViewModel = null;
|
||||||
mHourlyViewModels = null;
|
mHourlyViewModels = null;
|
||||||
refreshUi();
|
refreshUi();
|
||||||
@@ -226,9 +229,9 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
|
|||||||
}
|
}
|
||||||
|
|
||||||
boolean isHighlightSlotFocused() {
|
boolean isHighlightSlotFocused() {
|
||||||
return (mDailyHighlightSlotIndex != BatteryChartViewModel.SELECTED_INDEX_INVALID
|
return (mDailyHighlightSlotIndex != SELECTED_INDEX_INVALID
|
||||||
&& mDailyHighlightSlotIndex == mDailyChartIndex
|
&& mDailyHighlightSlotIndex == mDailyChartIndex
|
||||||
&& mHourlyHighlightSlotIndex != BatteryChartViewModel.SELECTED_INDEX_INVALID
|
&& mHourlyHighlightSlotIndex != SELECTED_INDEX_INVALID
|
||||||
&& mHourlyHighlightSlotIndex == mHourlyChartIndex);
|
&& mHourlyHighlightSlotIndex == mHourlyChartIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,8 +245,8 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
|
|||||||
}
|
}
|
||||||
|
|
||||||
void selectHighlightSlotIndex() {
|
void selectHighlightSlotIndex() {
|
||||||
if (mDailyHighlightSlotIndex == BatteryChartViewModel.SELECTED_INDEX_INVALID
|
if (mDailyHighlightSlotIndex == SELECTED_INDEX_INVALID
|
||||||
|| mHourlyHighlightSlotIndex == BatteryChartViewModel.SELECTED_INDEX_INVALID) {
|
|| mHourlyHighlightSlotIndex == SELECTED_INDEX_INVALID) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (mDailyHighlightSlotIndex == mDailyChartIndex
|
if (mDailyHighlightSlotIndex == mDailyChartIndex
|
||||||
@@ -258,8 +261,11 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
|
|||||||
"onDailyChartSelect:%d, onHourlyChartSelect:%d",
|
"onDailyChartSelect:%d, onHourlyChartSelect:%d",
|
||||||
mDailyChartIndex, mHourlyChartIndex));
|
mDailyChartIndex, mHourlyChartIndex));
|
||||||
refreshUi();
|
refreshUi();
|
||||||
|
// The highlight slot must be selected.
|
||||||
mHandler.post(
|
mHandler.post(
|
||||||
() -> mDailyChartView.setAccessibilityPaneTitle(getAccessibilityAnnounceMessage()));
|
() ->
|
||||||
|
mDailyChartView.setAccessibilityPaneTitle(
|
||||||
|
getAccessibilityAnnounceMessage(/* isSlotSelected= */ true)));
|
||||||
if (mOnSelectedIndexUpdatedListener != null) {
|
if (mOnSelectedIndexUpdatedListener != null) {
|
||||||
mOnSelectedIndexUpdatedListener.onSelectedIndexUpdated();
|
mOnSelectedIndexUpdatedListener.onSelectedIndexUpdated();
|
||||||
}
|
}
|
||||||
@@ -295,15 +301,16 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
|
|||||||
}
|
}
|
||||||
Log.d(TAG, "onDailyChartSelect:" + trapezoidIndex);
|
Log.d(TAG, "onDailyChartSelect:" + trapezoidIndex);
|
||||||
mDailyChartIndex = trapezoidIndex;
|
mDailyChartIndex = trapezoidIndex;
|
||||||
mHourlyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL;
|
mHourlyChartIndex = SELECTED_INDEX_ALL;
|
||||||
refreshUi();
|
refreshUi();
|
||||||
mHandler.post(
|
mHandler.post(
|
||||||
() ->
|
() ->
|
||||||
mDailyChartView.setAccessibilityPaneTitle(
|
mDailyChartView.setAccessibilityPaneTitle(
|
||||||
getAccessibilityAnnounceMessage()));
|
getAccessibilityAnnounceMessage(
|
||||||
|
mDailyChartIndex != SELECTED_INDEX_ALL)));
|
||||||
mMetricsFeatureProvider.action(
|
mMetricsFeatureProvider.action(
|
||||||
mPrefContext,
|
mPrefContext,
|
||||||
trapezoidIndex == BatteryChartViewModel.SELECTED_INDEX_ALL
|
trapezoidIndex == SELECTED_INDEX_ALL
|
||||||
? SettingsEnums.ACTION_BATTERY_USAGE_DAILY_SHOW_ALL
|
? SettingsEnums.ACTION_BATTERY_USAGE_DAILY_SHOW_ALL
|
||||||
: SettingsEnums.ACTION_BATTERY_USAGE_DAILY_TIME_SLOT,
|
: SettingsEnums.ACTION_BATTERY_USAGE_DAILY_TIME_SLOT,
|
||||||
mDailyChartIndex);
|
mDailyChartIndex);
|
||||||
@@ -314,7 +321,7 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
|
|||||||
mHourlyChartView = hourlyChartView;
|
mHourlyChartView = hourlyChartView;
|
||||||
mHourlyChartView.setOnSelectListener(
|
mHourlyChartView.setOnSelectListener(
|
||||||
trapezoidIndex -> {
|
trapezoidIndex -> {
|
||||||
if (mDailyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL) {
|
if (mDailyChartIndex == SELECTED_INDEX_ALL) {
|
||||||
// This will happen when a daily slot and an hour slot are clicked together.
|
// This will happen when a daily slot and an hour slot are clicked together.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -327,10 +334,11 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
|
|||||||
mHandler.post(
|
mHandler.post(
|
||||||
() ->
|
() ->
|
||||||
mHourlyChartView.setAccessibilityPaneTitle(
|
mHourlyChartView.setAccessibilityPaneTitle(
|
||||||
getAccessibilityAnnounceMessage()));
|
getAccessibilityAnnounceMessage(
|
||||||
|
mHourlyChartIndex != SELECTED_INDEX_ALL)));
|
||||||
mMetricsFeatureProvider.action(
|
mMetricsFeatureProvider.action(
|
||||||
mPrefContext,
|
mPrefContext,
|
||||||
trapezoidIndex == BatteryChartViewModel.SELECTED_INDEX_ALL
|
trapezoidIndex == SELECTED_INDEX_ALL
|
||||||
? SettingsEnums.ACTION_BATTERY_USAGE_SHOW_ALL
|
? SettingsEnums.ACTION_BATTERY_USAGE_SHOW_ALL
|
||||||
: SettingsEnums.ACTION_BATTERY_USAGE_TIME_SLOT,
|
: SettingsEnums.ACTION_BATTERY_USAGE_TIME_SLOT,
|
||||||
mHourlyChartIndex);
|
mHourlyChartIndex);
|
||||||
@@ -378,27 +386,27 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
|
|||||||
} else {
|
} else {
|
||||||
mDailyChartView.setVisibility(View.VISIBLE);
|
mDailyChartView.setVisibility(View.VISIBLE);
|
||||||
if (mDailyChartIndex >= mDailyViewModel.size()) {
|
if (mDailyChartIndex >= mDailyViewModel.size()) {
|
||||||
mDailyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL;
|
mDailyChartIndex = SELECTED_INDEX_ALL;
|
||||||
}
|
}
|
||||||
mDailyViewModel.setSelectedIndex(mDailyChartIndex);
|
mDailyViewModel.setSelectedIndex(mDailyChartIndex);
|
||||||
mDailyViewModel.setHighlightSlotIndex(mDailyHighlightSlotIndex);
|
mDailyViewModel.setHighlightSlotIndex(mDailyHighlightSlotIndex);
|
||||||
mDailyChartView.setViewModel(mDailyViewModel);
|
mDailyChartView.setViewModel(mDailyViewModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mDailyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL) {
|
if (mDailyChartIndex == SELECTED_INDEX_ALL) {
|
||||||
// Multiple days are selected, hide the hourly chart view.
|
// Multiple days are selected, hide the hourly chart view.
|
||||||
animateBatteryHourlyChartView(/* visible= */ false);
|
animateBatteryHourlyChartView(/* visible= */ false);
|
||||||
} else {
|
} else {
|
||||||
animateBatteryHourlyChartView(/* visible= */ true);
|
animateBatteryHourlyChartView(/* visible= */ true);
|
||||||
final BatteryChartViewModel hourlyViewModel = mHourlyViewModels.get(mDailyChartIndex);
|
final BatteryChartViewModel hourlyViewModel = mHourlyViewModels.get(mDailyChartIndex);
|
||||||
if (mHourlyChartIndex >= hourlyViewModel.size()) {
|
if (mHourlyChartIndex >= hourlyViewModel.size()) {
|
||||||
mHourlyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL;
|
mHourlyChartIndex = SELECTED_INDEX_ALL;
|
||||||
}
|
}
|
||||||
hourlyViewModel.setSelectedIndex(mHourlyChartIndex);
|
hourlyViewModel.setSelectedIndex(mHourlyChartIndex);
|
||||||
hourlyViewModel.setHighlightSlotIndex(
|
hourlyViewModel.setHighlightSlotIndex(
|
||||||
(mDailyChartIndex == mDailyHighlightSlotIndex)
|
(mDailyChartIndex == mDailyHighlightSlotIndex)
|
||||||
? mHourlyHighlightSlotIndex
|
? mHourlyHighlightSlotIndex
|
||||||
: BatteryChartViewModel.SELECTED_INDEX_INVALID);
|
: SELECTED_INDEX_INVALID);
|
||||||
mHourlyChartView.setViewModel(hourlyViewModel);
|
mHourlyChartView.setViewModel(hourlyViewModel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -416,7 +424,7 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
|
|||||||
isAccessibilityText
|
isAccessibilityText
|
||||||
? mDailyViewModel.getContentDescription(mDailyChartIndex)
|
? mDailyViewModel.getContentDescription(mDailyChartIndex)
|
||||||
: mDailyViewModel.getFullText(mDailyChartIndex);
|
: mDailyViewModel.getFullText(mDailyChartIndex);
|
||||||
if (mHourlyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL) {
|
if (mHourlyChartIndex == SELECTED_INDEX_ALL) {
|
||||||
return selectedDayText;
|
return selectedDayText;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -441,15 +449,19 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mDailyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL
|
if (mDailyChartIndex == SELECTED_INDEX_ALL || mHourlyChartIndex == SELECTED_INDEX_ALL) {
|
||||||
|| mHourlyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL) {
|
|
||||||
return mDailyViewModel.getSlotBatteryLevelText(mDailyChartIndex);
|
return mDailyViewModel.getSlotBatteryLevelText(mDailyChartIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
return mHourlyViewModels.get(mDailyChartIndex).getSlotBatteryLevelText(mHourlyChartIndex);
|
return mHourlyViewModels.get(mDailyChartIndex).getSlotBatteryLevelText(mHourlyChartIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getAccessibilityAnnounceMessage() {
|
private String getAccessibilityAnnounceMessage(final boolean isSlotSelected) {
|
||||||
|
final String selectedInformation =
|
||||||
|
mPrefContext.getString(
|
||||||
|
isSlotSelected
|
||||||
|
? R.string.battery_chart_slot_status_selected
|
||||||
|
: R.string.battery_chart_slot_status_unselected);
|
||||||
final String slotInformation = getSlotInformation(/* isAccessibilityText= */ true);
|
final String slotInformation = getSlotInformation(/* isAccessibilityText= */ true);
|
||||||
final String slotInformationMessage =
|
final String slotInformationMessage =
|
||||||
slotInformation == null
|
slotInformation == null
|
||||||
@@ -460,7 +472,8 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
|
|||||||
final String batteryLevelPercentageMessage = getBatteryLevelPercentageInfo();
|
final String batteryLevelPercentageMessage = getBatteryLevelPercentageInfo();
|
||||||
|
|
||||||
return mPrefContext.getString(
|
return mPrefContext.getString(
|
||||||
R.string.battery_usage_time_info_and_battery_level,
|
R.string.battery_usage_status_time_info_and_battery_level,
|
||||||
|
selectedInformation,
|
||||||
slotInformationMessage,
|
slotInformationMessage,
|
||||||
batteryLevelPercentageMessage);
|
batteryLevelPercentageMessage);
|
||||||
}
|
}
|
||||||
@@ -533,9 +546,8 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean isAllSelected() {
|
private boolean isAllSelected() {
|
||||||
return (isBatteryLevelDataInOneDay()
|
return (isBatteryLevelDataInOneDay() || mDailyChartIndex == SELECTED_INDEX_ALL)
|
||||||
|| mDailyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL)
|
&& mHourlyChartIndex == SELECTED_INDEX_ALL;
|
||||||
&& mHourlyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@@ -571,9 +583,7 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
BatteryDiffData allBatteryDiffData =
|
BatteryDiffData allBatteryDiffData =
|
||||||
batteryUsageData
|
batteryUsageData.get(SELECTED_INDEX_ALL).get(SELECTED_INDEX_ALL);
|
||||||
.get(BatteryChartViewModel.SELECTED_INDEX_ALL)
|
|
||||||
.get(BatteryChartViewModel.SELECTED_INDEX_ALL);
|
|
||||||
return allBatteryDiffData == null ? null : allBatteryDiffData.getAppDiffEntryList();
|
return allBatteryDiffData == null ? null : allBatteryDiffData.getAppDiffEntryList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -613,12 +623,9 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String generateSlotBatteryLevelText(List<Integer> levels, int index) {
|
public String generateSlotBatteryLevelText(List<Integer> levels, int index) {
|
||||||
final int fromBatteryLevelIndex =
|
final int fromBatteryLevelIndex = index == SELECTED_INDEX_ALL ? 0 : index;
|
||||||
index == BatteryChartViewModel.SELECTED_INDEX_ALL ? 0 : index;
|
|
||||||
final int toBatteryLevelIndex =
|
final int toBatteryLevelIndex =
|
||||||
index == BatteryChartViewModel.SELECTED_INDEX_ALL
|
index == SELECTED_INDEX_ALL ? levels.size() - 1 : index + 1;
|
||||||
? levels.size() - 1
|
|
||||||
: index + 1;
|
|
||||||
return mPrefContext.getString(
|
return mPrefContext.getString(
|
||||||
R.string.battery_level_percentage,
|
R.string.battery_level_percentage,
|
||||||
generateBatteryLevelText(levels.get(fromBatteryLevelIndex)),
|
generateBatteryLevelText(levels.get(fromBatteryLevelIndex)),
|
||||||
@@ -687,9 +694,9 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
|
|||||||
return index == timestamps.size() - 1
|
return index == timestamps.size() - 1
|
||||||
? generateText(timestamps, index)
|
? generateText(timestamps, index)
|
||||||
: mContext.getString(
|
: mContext.getString(
|
||||||
R.string.battery_usage_timestamps_content_description,
|
R.string.battery_usage_timestamps_content_description,
|
||||||
generateText(timestamps, index),
|
generateText(timestamps, index),
|
||||||
generateText(timestamps, index + 1));
|
generateText(timestamps, index + 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
HourlyChartLabelTextGenerator updateSpecialCaseContext(
|
HourlyChartLabelTextGenerator updateSpecialCaseContext(
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ package com.android.settings.fuelgauge.batteryusage;
|
|||||||
|
|
||||||
import static com.android.settings.Utils.formatPercentage;
|
import static com.android.settings.Utils.formatPercentage;
|
||||||
import static com.android.settings.fuelgauge.batteryusage.BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS;
|
import static com.android.settings.fuelgauge.batteryusage.BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS;
|
||||||
|
import static com.android.settings.fuelgauge.batteryusage.BatteryChartViewModel.SELECTED_INDEX_ALL;
|
||||||
|
import static com.android.settings.fuelgauge.batteryusage.BatteryChartViewModel.SELECTED_INDEX_INVALID;
|
||||||
import static com.android.settingslib.fuelgauge.BatteryStatus.BATTERY_LEVEL_UNKNOWN;
|
import static com.android.settingslib.fuelgauge.BatteryStatus.BATTERY_LEVEL_UNKNOWN;
|
||||||
|
|
||||||
import static java.lang.Math.abs;
|
import static java.lang.Math.abs;
|
||||||
@@ -81,7 +83,7 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
|
|||||||
getContext().getResources().getConfiguration().getLayoutDirection();
|
getContext().getResources().getConfiguration().getLayoutDirection();
|
||||||
|
|
||||||
private BatteryChartViewModel mViewModel;
|
private BatteryChartViewModel mViewModel;
|
||||||
private int mHoveredIndex = BatteryChartViewModel.SELECTED_INDEX_INVALID;
|
private int mHoveredIndex = SELECTED_INDEX_INVALID;
|
||||||
private int mDividerWidth;
|
private int mDividerWidth;
|
||||||
private int mDividerHeight;
|
private int mDividerHeight;
|
||||||
private float mTrapezoidVOffset;
|
private float mTrapezoidVOffset;
|
||||||
@@ -245,9 +247,9 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
|
|||||||
// sent here.
|
// sent here.
|
||||||
return true;
|
return true;
|
||||||
case MotionEvent.ACTION_HOVER_EXIT:
|
case MotionEvent.ACTION_HOVER_EXIT:
|
||||||
if (mHoveredIndex != BatteryChartViewModel.SELECTED_INDEX_INVALID) {
|
if (mHoveredIndex != SELECTED_INDEX_INVALID) {
|
||||||
sendAccessibilityEventForHover(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
|
sendAccessibilityEventForHover(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
|
||||||
mHoveredIndex = BatteryChartViewModel.SELECTED_INDEX_INVALID; // reset
|
mHoveredIndex = SELECTED_INDEX_INVALID; // reset
|
||||||
invalidate();
|
invalidate();
|
||||||
}
|
}
|
||||||
// Ignore the super.onHoverEvent() because the hovered trapezoid has already been
|
// Ignore the super.onHoverEvent() because the hovered trapezoid has already been
|
||||||
@@ -262,7 +264,7 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
|
|||||||
public void onHoverChanged(boolean hovered) {
|
public void onHoverChanged(boolean hovered) {
|
||||||
super.onHoverChanged(hovered);
|
super.onHoverChanged(hovered);
|
||||||
if (!hovered) {
|
if (!hovered) {
|
||||||
mHoveredIndex = BatteryChartViewModel.SELECTED_INDEX_INVALID; // reset
|
mHoveredIndex = SELECTED_INDEX_INVALID; // reset
|
||||||
invalidate();
|
invalidate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -295,9 +297,7 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
|
|||||||
if (mOnSelectListener != null) {
|
if (mOnSelectListener != null) {
|
||||||
// Selects all if users click the same trapezoid item two times.
|
// Selects all if users click the same trapezoid item two times.
|
||||||
mOnSelectListener.onSelect(
|
mOnSelectListener.onSelect(
|
||||||
index == mViewModel.selectedIndex()
|
index == mViewModel.selectedIndex() ? SELECTED_INDEX_ALL : index);
|
||||||
? BatteryChartViewModel.SELECTED_INDEX_ALL
|
|
||||||
: index);
|
|
||||||
}
|
}
|
||||||
view.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
|
view.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
|
||||||
}
|
}
|
||||||
@@ -332,8 +332,8 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
|
|||||||
setBackgroundColor(Color.TRANSPARENT);
|
setBackgroundColor(Color.TRANSPARENT);
|
||||||
mTrapezoidSolidColor = Utils.getColorAccentDefaultColor(context);
|
mTrapezoidSolidColor = Utils.getColorAccentDefaultColor(context);
|
||||||
mTrapezoidColor = Utils.getDisabled(context, mTrapezoidSolidColor);
|
mTrapezoidColor = Utils.getDisabled(context, mTrapezoidSolidColor);
|
||||||
mTrapezoidHoverColor = context.getColor(
|
mTrapezoidHoverColor =
|
||||||
com.android.internal.R.color.materialColorSecondaryContainer);
|
context.getColor(com.android.internal.R.color.materialColorSecondaryContainer);
|
||||||
// Initializes the divider line paint.
|
// Initializes the divider line paint.
|
||||||
final Resources resources = getContext().getResources();
|
final Resources resources = getContext().getResources();
|
||||||
mDividerWidth = resources.getDimensionPixelSize(R.dimen.chartview_divider_width);
|
mDividerWidth = resources.getDimensionPixelSize(R.dimen.chartview_divider_width);
|
||||||
@@ -623,8 +623,7 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
|
|||||||
// Configures the trapezoid paint color.
|
// Configures the trapezoid paint color.
|
||||||
final int trapezoidColor =
|
final int trapezoidColor =
|
||||||
(mViewModel.selectedIndex() == index
|
(mViewModel.selectedIndex() == index
|
||||||
|| mViewModel.selectedIndex()
|
|| mViewModel.selectedIndex() == SELECTED_INDEX_ALL)
|
||||||
== BatteryChartViewModel.SELECTED_INDEX_ALL)
|
|
||||||
? mTrapezoidSolidColor
|
? mTrapezoidSolidColor
|
||||||
: mTrapezoidColor;
|
: mTrapezoidColor;
|
||||||
final boolean isHoverState =
|
final boolean isHoverState =
|
||||||
@@ -659,9 +658,7 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean isHighlightSlotValid() {
|
private boolean isHighlightSlotValid() {
|
||||||
return mViewModel != null
|
return mViewModel != null && mViewModel.getHighlightSlotIndex() != SELECTED_INDEX_INVALID;
|
||||||
&& mViewModel.getHighlightSlotIndex()
|
|
||||||
!= BatteryChartViewModel.SELECTED_INDEX_INVALID;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawTransomLine(Canvas canvas) {
|
private void drawTransomLine(Canvas canvas) {
|
||||||
@@ -715,7 +712,7 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
|
|||||||
// Searches the corresponding trapezoid index from x location.
|
// Searches the corresponding trapezoid index from x location.
|
||||||
private int getTrapezoidIndex(float x) {
|
private int getTrapezoidIndex(float x) {
|
||||||
if (mTrapezoidSlots == null) {
|
if (mTrapezoidSlots == null) {
|
||||||
return BatteryChartViewModel.SELECTED_INDEX_INVALID;
|
return SELECTED_INDEX_INVALID;
|
||||||
}
|
}
|
||||||
for (int index = 0; index < mTrapezoidSlots.length; index++) {
|
for (int index = 0; index < mTrapezoidSlots.length; index++) {
|
||||||
final TrapezoidSlot slot = mTrapezoidSlots[index];
|
final TrapezoidSlot slot = mTrapezoidSlots[index];
|
||||||
@@ -723,7 +720,7 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
|
|||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return BatteryChartViewModel.SELECTED_INDEX_INVALID;
|
return SELECTED_INDEX_INVALID;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeAxisLabelsBounds() {
|
private void initializeAxisLabelsBounds() {
|
||||||
@@ -796,7 +793,11 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
|
|||||||
childInfo.setText(slotTimeInfo);
|
childInfo.setText(slotTimeInfo);
|
||||||
childInfo.setContentDescription(
|
childInfo.setContentDescription(
|
||||||
mContext.getString(
|
mContext.getString(
|
||||||
R.string.battery_usage_time_info_and_battery_level,
|
R.string.battery_usage_status_time_info_and_battery_level,
|
||||||
|
mContext.getString(
|
||||||
|
mViewModel.selectedIndex() == virtualViewId
|
||||||
|
? R.string.battery_chart_slot_status_selected
|
||||||
|
: R.string.battery_chart_slot_status_unselected),
|
||||||
slotTimeInfo,
|
slotTimeInfo,
|
||||||
batteryLevelInfo));
|
batteryLevelInfo));
|
||||||
childInfo.setAccessibilityFocused(virtualViewId == mAccessibilityFocusNodeViewId);
|
childInfo.setAccessibilityFocused(virtualViewId == mAccessibilityFocusNodeViewId);
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import android.content.Context;
|
|||||||
|
|
||||||
import com.android.settings.core.PreferenceControllerMixin;
|
import com.android.settings.core.PreferenceControllerMixin;
|
||||||
import com.android.settings.network.SubscriptionUtil;
|
import com.android.settings.network.SubscriptionUtil;
|
||||||
|
import com.android.settingslib.Utils;
|
||||||
import com.android.settingslib.core.AbstractPreferenceController;
|
import com.android.settingslib.core.AbstractPreferenceController;
|
||||||
|
|
||||||
public class NetworkResetPreferenceController extends AbstractPreferenceController
|
public class NetworkResetPreferenceController extends AbstractPreferenceController
|
||||||
@@ -34,8 +35,9 @@ public class NetworkResetPreferenceController extends AbstractPreferenceControll
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isAvailable() {
|
public boolean isAvailable() {
|
||||||
return (SubscriptionUtil.isSimHardwareVisible(mContext) &&
|
return (SubscriptionUtil.isSimHardwareVisible(mContext)
|
||||||
(!mRestrictionChecker.hasUserRestriction()));
|
&& !Utils.isWifiOnly(mContext)
|
||||||
|
&& !mRestrictionChecker.hasUserRestriction());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import com.android.internal.net.VpnProfile;
|
|||||||
import com.android.net.module.util.ProxyUtils;
|
import com.android.net.module.util.ProxyUtils;
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.utils.AndroidKeystoreAliasLoader;
|
import com.android.settings.utils.AndroidKeystoreAliasLoader;
|
||||||
|
import com.android.settings.wifi.utils.TextInputGroup;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -70,16 +71,17 @@ class ConfigDialog extends AlertDialog implements TextWatcher,
|
|||||||
|
|
||||||
private View mView;
|
private View mView;
|
||||||
|
|
||||||
private TextView mName;
|
private TextInputGroup mNameInput;
|
||||||
private Spinner mType;
|
private Spinner mType;
|
||||||
private TextView mServer;
|
private TextInputGroup mServerInput;
|
||||||
private TextView mUsername;
|
private TextInputGroup mUsernameInput;
|
||||||
|
private TextInputGroup mPasswordInput;
|
||||||
private TextView mPassword;
|
private TextView mPassword;
|
||||||
private Spinner mProxySettings;
|
private Spinner mProxySettings;
|
||||||
private TextView mProxyHost;
|
private TextView mProxyHost;
|
||||||
private TextView mProxyPort;
|
private TextView mProxyPort;
|
||||||
private TextView mIpsecIdentifier;
|
private TextInputGroup mIpsecIdentifierInput;
|
||||||
private TextView mIpsecSecret;
|
private TextInputGroup mIpsecSecretInput;
|
||||||
private Spinner mIpsecUserCert;
|
private Spinner mIpsecUserCert;
|
||||||
private Spinner mIpsecCaCert;
|
private Spinner mIpsecCaCert;
|
||||||
private Spinner mIpsecServerCert;
|
private Spinner mIpsecServerCert;
|
||||||
@@ -106,16 +108,22 @@ class ConfigDialog extends AlertDialog implements TextWatcher,
|
|||||||
Context context = getContext();
|
Context context = getContext();
|
||||||
|
|
||||||
// First, find out all the fields.
|
// First, find out all the fields.
|
||||||
mName = (TextView) mView.findViewById(R.id.name);
|
mNameInput = new TextInputGroup(mView, R.id.name_layout, R.id.name,
|
||||||
|
R.string.vpn_field_required);
|
||||||
mType = (Spinner) mView.findViewById(R.id.type);
|
mType = (Spinner) mView.findViewById(R.id.type);
|
||||||
mServer = (TextView) mView.findViewById(R.id.server);
|
mServerInput = new TextInputGroup(mView, R.id.server_layout, R.id.server,
|
||||||
mUsername = (TextView) mView.findViewById(R.id.username);
|
R.string.vpn_field_required);
|
||||||
mPassword = (TextView) mView.findViewById(R.id.password);
|
mUsernameInput = new TextInputGroup(mView, R.id.username_layout, R.id.username,
|
||||||
|
R.string.vpn_field_required);
|
||||||
|
mPasswordInput = new TextInputGroup(mView, R.id.password_layout, R.id.password,
|
||||||
|
R.string.vpn_field_required);
|
||||||
mProxySettings = (Spinner) mView.findViewById(R.id.vpn_proxy_settings);
|
mProxySettings = (Spinner) mView.findViewById(R.id.vpn_proxy_settings);
|
||||||
mProxyHost = (TextView) mView.findViewById(R.id.vpn_proxy_host);
|
mProxyHost = (TextView) mView.findViewById(R.id.vpn_proxy_host);
|
||||||
mProxyPort = (TextView) mView.findViewById(R.id.vpn_proxy_port);
|
mProxyPort = (TextView) mView.findViewById(R.id.vpn_proxy_port);
|
||||||
mIpsecIdentifier = (TextView) mView.findViewById(R.id.ipsec_identifier);
|
mIpsecIdentifierInput = new TextInputGroup(mView, R.id.ipsec_identifier_layout,
|
||||||
mIpsecSecret = (TextView) mView.findViewById(R.id.ipsec_secret);
|
R.id.ipsec_identifier, R.string.vpn_field_required);
|
||||||
|
mIpsecSecretInput = new TextInputGroup(mView, R.id.ipsec_secret_layout, R.id.ipsec_secret,
|
||||||
|
R.string.vpn_field_required);
|
||||||
mIpsecUserCert = (Spinner) mView.findViewById(R.id.ipsec_user_cert);
|
mIpsecUserCert = (Spinner) mView.findViewById(R.id.ipsec_user_cert);
|
||||||
mIpsecCaCert = (Spinner) mView.findViewById(R.id.ipsec_ca_cert);
|
mIpsecCaCert = (Spinner) mView.findViewById(R.id.ipsec_ca_cert);
|
||||||
mIpsecServerCert = (Spinner) mView.findViewById(R.id.ipsec_server_cert);
|
mIpsecServerCert = (Spinner) mView.findViewById(R.id.ipsec_server_cert);
|
||||||
@@ -125,21 +133,21 @@ class ConfigDialog extends AlertDialog implements TextWatcher,
|
|||||||
mAlwaysOnInvalidReason = (TextView) mView.findViewById(R.id.always_on_invalid_reason);
|
mAlwaysOnInvalidReason = (TextView) mView.findViewById(R.id.always_on_invalid_reason);
|
||||||
|
|
||||||
// Second, copy values from the profile.
|
// Second, copy values from the profile.
|
||||||
mName.setText(mProfile.name);
|
mNameInput.setText(mProfile.name);
|
||||||
setTypesByFeature(mType);
|
setTypesByFeature(mType);
|
||||||
mType.setSelection(convertVpnProfileConstantToTypeIndex(mProfile.type));
|
mType.setSelection(convertVpnProfileConstantToTypeIndex(mProfile.type));
|
||||||
mServer.setText(mProfile.server);
|
mServerInput.setText(mProfile.server);
|
||||||
if (mProfile.saveLogin) {
|
if (mProfile.saveLogin) {
|
||||||
mUsername.setText(mProfile.username);
|
mUsernameInput.setText(mProfile.username);
|
||||||
mPassword.setText(mProfile.password);
|
mPasswordInput.setText(mProfile.password);
|
||||||
}
|
}
|
||||||
if (mProfile.proxy != null) {
|
if (mProfile.proxy != null) {
|
||||||
mProxyHost.setText(mProfile.proxy.getHost());
|
mProxyHost.setText(mProfile.proxy.getHost());
|
||||||
int port = mProfile.proxy.getPort();
|
int port = mProfile.proxy.getPort();
|
||||||
mProxyPort.setText(port == 0 ? "" : Integer.toString(port));
|
mProxyPort.setText(port == 0 ? "" : Integer.toString(port));
|
||||||
}
|
}
|
||||||
mIpsecIdentifier.setText(mProfile.ipsecIdentifier);
|
mIpsecIdentifierInput.setText(mProfile.ipsecIdentifier);
|
||||||
mIpsecSecret.setText(mProfile.ipsecSecret);
|
mIpsecSecretInput.setText(mProfile.ipsecSecret);
|
||||||
final AndroidKeystoreAliasLoader androidKeystoreAliasLoader =
|
final AndroidKeystoreAliasLoader androidKeystoreAliasLoader =
|
||||||
new AndroidKeystoreAliasLoader(null);
|
new AndroidKeystoreAliasLoader(null);
|
||||||
loadCertificates(mIpsecUserCert, androidKeystoreAliasLoader.getKeyCertAliases(), 0,
|
loadCertificates(mIpsecUserCert, androidKeystoreAliasLoader.getKeyCertAliases(), 0,
|
||||||
@@ -150,7 +158,8 @@ class ConfigDialog extends AlertDialog implements TextWatcher,
|
|||||||
R.string.vpn_no_server_cert, mProfile.ipsecServerCert);
|
R.string.vpn_no_server_cert, mProfile.ipsecServerCert);
|
||||||
mSaveLogin.setChecked(mProfile.saveLogin);
|
mSaveLogin.setChecked(mProfile.saveLogin);
|
||||||
mAlwaysOnVpn.setChecked(mProfile.key.equals(VpnUtils.getLockdownVpn()));
|
mAlwaysOnVpn.setChecked(mProfile.key.equals(VpnUtils.getLockdownVpn()));
|
||||||
mPassword.setTextAppearance(android.R.style.TextAppearance_DeviceDefault_Medium);
|
mPasswordInput.getEditText()
|
||||||
|
.setTextAppearance(android.R.style.TextAppearance_DeviceDefault_Medium);
|
||||||
|
|
||||||
// Hide lockdown VPN on devices that require IMS authentication
|
// Hide lockdown VPN on devices that require IMS authentication
|
||||||
if (SystemProperties.getBoolean("persist.radio.imsregrequired", false)) {
|
if (SystemProperties.getBoolean("persist.radio.imsregrequired", false)) {
|
||||||
@@ -158,16 +167,16 @@ class ConfigDialog extends AlertDialog implements TextWatcher,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Third, add listeners to required fields.
|
// Third, add listeners to required fields.
|
||||||
mName.addTextChangedListener(this);
|
mNameInput.addTextChangedListener(this);
|
||||||
mType.setOnItemSelectedListener(this);
|
mType.setOnItemSelectedListener(this);
|
||||||
mServer.addTextChangedListener(this);
|
mServerInput.addTextChangedListener(this);
|
||||||
mUsername.addTextChangedListener(this);
|
mUsernameInput.addTextChangedListener(this);
|
||||||
mPassword.addTextChangedListener(this);
|
mPasswordInput.addTextChangedListener(this);
|
||||||
mProxySettings.setOnItemSelectedListener(this);
|
mProxySettings.setOnItemSelectedListener(this);
|
||||||
mProxyHost.addTextChangedListener(this);
|
mProxyHost.addTextChangedListener(this);
|
||||||
mProxyPort.addTextChangedListener(this);
|
mProxyPort.addTextChangedListener(this);
|
||||||
mIpsecIdentifier.addTextChangedListener(this);
|
mIpsecIdentifierInput.addTextChangedListener(this);
|
||||||
mIpsecSecret.addTextChangedListener(this);
|
mIpsecSecretInput.addTextChangedListener(this);
|
||||||
mIpsecUserCert.setOnItemSelectedListener(this);
|
mIpsecUserCert.setOnItemSelectedListener(this);
|
||||||
mShowOptions.setOnClickListener(this);
|
mShowOptions.setOnClickListener(this);
|
||||||
mAlwaysOnVpn.setOnCheckedChangeListener(this);
|
mAlwaysOnVpn.setOnCheckedChangeListener(this);
|
||||||
@@ -202,6 +211,8 @@ class ConfigDialog extends AlertDialog implements TextWatcher,
|
|||||||
setTitle(context.getString(R.string.vpn_connect_to, mProfile.name));
|
setTitle(context.getString(R.string.vpn_connect_to, mProfile.name));
|
||||||
|
|
||||||
setUsernamePasswordVisibility(mProfile.type);
|
setUsernamePasswordVisibility(mProfile.type);
|
||||||
|
mUsernameInput.setHelperText(context.getString(R.string.vpn_required));
|
||||||
|
mPasswordInput.setHelperText(context.getString(R.string.vpn_required));
|
||||||
|
|
||||||
// Create a button to connect the network.
|
// Create a button to connect the network.
|
||||||
setButton(DialogInterface.BUTTON_POSITIVE,
|
setButton(DialogInterface.BUTTON_POSITIVE,
|
||||||
@@ -260,6 +271,10 @@ class ConfigDialog extends AlertDialog implements TextWatcher,
|
|||||||
updateProxyFieldsVisibility(position);
|
updateProxyFieldsVisibility(position);
|
||||||
}
|
}
|
||||||
updateUiControls();
|
updateUiControls();
|
||||||
|
mNameInput.setError("");
|
||||||
|
mServerInput.setError("");
|
||||||
|
mIpsecIdentifierInput.setError("");
|
||||||
|
mIpsecSecretInput.setError("");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -375,30 +390,16 @@ class ConfigDialog extends AlertDialog implements TextWatcher,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final int position = mType.getSelectedItemPosition();
|
|
||||||
final int type = VPN_TYPES.get(position);
|
|
||||||
if (!editing && requiresUsernamePassword(type)) {
|
|
||||||
return mUsername.getText().length() != 0 && mPassword.getText().length() != 0;
|
|
||||||
}
|
|
||||||
if (mName.getText().length() == 0 || mServer.getText().length() == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// All IKEv2 methods require an identifier
|
|
||||||
if (mIpsecIdentifier.getText().length() == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validateProxy()) {
|
if (!validateProxy()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (type) {
|
switch (getVpnType()) {
|
||||||
case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
|
case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
|
case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
|
||||||
return mIpsecSecret.getText().length() != 0;
|
return true;
|
||||||
|
|
||||||
case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
|
case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
|
||||||
return mIpsecUserCert.getSelectedItemPosition() != 0;
|
return mIpsecUserCert.getSelectedItemPosition() != 0;
|
||||||
@@ -406,6 +407,29 @@ class ConfigDialog extends AlertDialog implements TextWatcher,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean validate() {
|
||||||
|
boolean isValidate = true;
|
||||||
|
int type = getVpnType();
|
||||||
|
if (!mEditing && requiresUsernamePassword(type)) {
|
||||||
|
if (!mUsernameInput.validate()) isValidate = false;
|
||||||
|
if (!mPasswordInput.validate()) isValidate = false;
|
||||||
|
return isValidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mNameInput.validate()) isValidate = false;
|
||||||
|
if (!mServerInput.validate()) isValidate = false;
|
||||||
|
if (!mIpsecIdentifierInput.validate()) isValidate = false;
|
||||||
|
if (type == VpnProfile.TYPE_IKEV2_IPSEC_PSK && !mIpsecSecretInput.validate()) {
|
||||||
|
isValidate = false;
|
||||||
|
}
|
||||||
|
if (!isValidate) Log.w(TAG, "Failed to validate VPN profile!");
|
||||||
|
return isValidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getVpnType() {
|
||||||
|
return VPN_TYPES.get(mType.getSelectedItemPosition());
|
||||||
|
}
|
||||||
|
|
||||||
private void setTypesByFeature(Spinner typeSpinner) {
|
private void setTypesByFeature(Spinner typeSpinner) {
|
||||||
String[] types = getContext().getResources().getStringArray(R.array.vpn_types);
|
String[] types = getContext().getResources().getStringArray(R.array.vpn_types);
|
||||||
if (types.length != VPN_TYPES.size()) {
|
if (types.length != VPN_TYPES.size()) {
|
||||||
@@ -487,15 +511,14 @@ class ConfigDialog extends AlertDialog implements TextWatcher,
|
|||||||
VpnProfile getProfile() {
|
VpnProfile getProfile() {
|
||||||
// First, save common fields.
|
// First, save common fields.
|
||||||
VpnProfile profile = new VpnProfile(mProfile.key);
|
VpnProfile profile = new VpnProfile(mProfile.key);
|
||||||
profile.name = mName.getText().toString();
|
profile.name = mNameInput.getText();
|
||||||
final int position = mType.getSelectedItemPosition();
|
profile.type = getVpnType();
|
||||||
profile.type = VPN_TYPES.get(position);
|
profile.server = mServerInput.getText().trim();
|
||||||
profile.server = mServer.getText().toString().trim();
|
profile.username = mUsernameInput.getText();
|
||||||
profile.username = mUsername.getText().toString();
|
profile.password = mPasswordInput.getText();
|
||||||
profile.password = mPassword.getText().toString();
|
|
||||||
|
|
||||||
// Save fields based on VPN type.
|
// Save fields based on VPN type.
|
||||||
profile.ipsecIdentifier = mIpsecIdentifier.getText().toString();
|
profile.ipsecIdentifier = mIpsecIdentifierInput.getText();
|
||||||
|
|
||||||
if (hasProxy()) {
|
if (hasProxy()) {
|
||||||
String proxyHost = mProxyHost.getText().toString().trim();
|
String proxyHost = mProxyHost.getText().toString().trim();
|
||||||
@@ -517,7 +540,7 @@ class ConfigDialog extends AlertDialog implements TextWatcher,
|
|||||||
// Then, save type-specific fields.
|
// Then, save type-specific fields.
|
||||||
switch (profile.type) {
|
switch (profile.type) {
|
||||||
case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
|
case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
|
||||||
profile.ipsecSecret = mIpsecSecret.getText().toString();
|
profile.ipsecSecret = mIpsecSecretInput.getText();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
|
case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ public class ConfigDialogFragment extends InstrumentedDialogFragment implements
|
|||||||
VpnProfile profile = dialog.getProfile();
|
VpnProfile profile = dialog.getProfile();
|
||||||
|
|
||||||
if (button == DialogInterface.BUTTON_POSITIVE) {
|
if (button == DialogInterface.BUTTON_POSITIVE) {
|
||||||
|
if (!dialog.validate()) return;
|
||||||
// Possibly throw up a dialog to explain lockdown VPN.
|
// Possibly throw up a dialog to explain lockdown VPN.
|
||||||
final boolean shouldLockdown = dialog.isVpnAlwaysOn();
|
final boolean shouldLockdown = dialog.isVpnAlwaysOn();
|
||||||
final boolean shouldConnect = shouldLockdown || !dialog.isEditing();
|
final boolean shouldConnect = shouldLockdown || !dialog.isEditing();
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ import com.android.settings.utils.AndroidKeystoreAliasLoader;
|
|||||||
import com.android.settings.wifi.details2.WifiPrivacyPreferenceController;
|
import com.android.settings.wifi.details2.WifiPrivacyPreferenceController;
|
||||||
import com.android.settings.wifi.details2.WifiPrivacyPreferenceController2;
|
import com.android.settings.wifi.details2.WifiPrivacyPreferenceController2;
|
||||||
import com.android.settings.wifi.dpp.WifiDppUtils;
|
import com.android.settings.wifi.dpp.WifiDppUtils;
|
||||||
import com.android.settings.wifi.utils.SsidInputGroup;
|
import com.android.settings.wifi.utils.TextInputGroup;
|
||||||
import com.android.settingslib.Utils;
|
import com.android.settingslib.Utils;
|
||||||
import com.android.settingslib.utils.ThreadUtils;
|
import com.android.settingslib.utils.ThreadUtils;
|
||||||
import com.android.wifi.flags.Flags;
|
import com.android.wifi.flags.Flags;
|
||||||
@@ -229,7 +229,7 @@ public class WifiConfigController2 implements TextWatcher,
|
|||||||
private final boolean mHideMeteredAndPrivacy;
|
private final boolean mHideMeteredAndPrivacy;
|
||||||
private final WifiManager mWifiManager;
|
private final WifiManager mWifiManager;
|
||||||
private final AndroidKeystoreAliasLoader mAndroidKeystoreAliasLoader;
|
private final AndroidKeystoreAliasLoader mAndroidKeystoreAliasLoader;
|
||||||
private SsidInputGroup mSsidInputGroup;
|
private TextInputGroup mSsidInputGroup;
|
||||||
|
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
|
|
||||||
@@ -299,7 +299,8 @@ public class WifiConfigController2 implements TextWatcher,
|
|||||||
wepWarningLayout.setVisibility(View.VISIBLE);
|
wepWarningLayout.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
mSsidInputGroup = new SsidInputGroup(mContext, mView, R.id.ssid_layout, R.id.ssid);
|
mSsidInputGroup = new TextInputGroup(mView, R.id.ssid_layout, R.id.ssid,
|
||||||
|
R.string.wifi_ssid_hint);
|
||||||
mSsidScanButton = (ImageButton) mView.findViewById(R.id.ssid_scanner_button);
|
mSsidScanButton = (ImageButton) mView.findViewById(R.id.ssid_scanner_button);
|
||||||
mIpSettingsSpinner = (Spinner) mView.findViewById(R.id.ip_settings);
|
mIpSettingsSpinner = (Spinner) mView.findViewById(R.id.ip_settings);
|
||||||
mIpSettingsSpinner.setOnItemSelectedListener(this);
|
mIpSettingsSpinner.setOnItemSelectedListener(this);
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import android.widget.TextView;
|
|||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.wifi.utils.SsidInputGroup;
|
import com.android.settings.wifi.utils.TextInputGroup;
|
||||||
import com.android.settings.wifi.utils.WifiDialogHelper;
|
import com.android.settings.wifi.utils.WifiDialogHelper;
|
||||||
import com.android.settingslib.RestrictedLockUtils;
|
import com.android.settingslib.RestrictedLockUtils;
|
||||||
import com.android.settingslib.RestrictedLockUtilsInternal;
|
import com.android.settingslib.RestrictedLockUtilsInternal;
|
||||||
@@ -120,7 +120,8 @@ public class WifiDialog extends AlertDialog implements WifiConfigUiBase,
|
|||||||
}
|
}
|
||||||
|
|
||||||
mDialogHelper = new WifiDialogHelper(this,
|
mDialogHelper = new WifiDialogHelper(this,
|
||||||
new SsidInputGroup(getContext(), mView, R.id.ssid_layout, R.id.ssid));
|
new TextInputGroup(mView, R.id.ssid_layout, R.id.ssid,
|
||||||
|
R.string.vpn_field_required));
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("MissingSuperCall") // TODO: Fix me
|
@SuppressWarnings("MissingSuperCall") // TODO: Fix me
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2025 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.wifi.utils
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.view.View
|
|
||||||
import com.android.settings.R
|
|
||||||
|
|
||||||
/** TextInputGroup for Wi-Fi SSID. */
|
|
||||||
class SsidInputGroup(private val context: Context, view: View, layoutId: Int, editTextId: Int) :
|
|
||||||
TextInputGroup(view, layoutId, editTextId) {
|
|
||||||
|
|
||||||
fun validate(): Boolean {
|
|
||||||
if (getText().isEmpty()) {
|
|
||||||
setError(context.getString(R.string.wifi_ssid_hint))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -18,6 +18,7 @@ package com.android.settings.wifi.utils
|
|||||||
|
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
|
import android.util.Log
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
@@ -27,13 +28,17 @@ open class TextInputGroup(
|
|||||||
private val view: View,
|
private val view: View,
|
||||||
private val layoutId: Int,
|
private val layoutId: Int,
|
||||||
private val editTextId: Int,
|
private val editTextId: Int,
|
||||||
|
private val errorMessageId: Int,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val View.layout: TextInputLayout?
|
val layout: TextInputLayout
|
||||||
get() = findViewById(layoutId)
|
get() = view.requireViewById(layoutId)
|
||||||
|
|
||||||
private val View.editText: EditText?
|
val editText: EditText
|
||||||
get() = findViewById(editTextId)
|
get() = view.requireViewById(editTextId)
|
||||||
|
|
||||||
|
val errorMessage: String
|
||||||
|
get() = view.context.getString(errorMessageId)
|
||||||
|
|
||||||
private val textWatcher =
|
private val textWatcher =
|
||||||
object : TextWatcher {
|
object : TextWatcher {
|
||||||
@@ -42,7 +47,7 @@ open class TextInputGroup(
|
|||||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||||
|
|
||||||
override fun afterTextChanged(s: Editable?) {
|
override fun afterTextChanged(s: Editable?) {
|
||||||
view.layout?.isErrorEnabled = false
|
layout.isErrorEnabled = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,18 +56,37 @@ open class TextInputGroup(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun addTextChangedListener(watcher: TextWatcher) {
|
fun addTextChangedListener(watcher: TextWatcher) {
|
||||||
view.editText?.addTextChangedListener(watcher)
|
editText.addTextChangedListener(watcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getText(): String {
|
var text: String
|
||||||
return view.editText?.text?.toString() ?: ""
|
get() = editText.text?.toString() ?: ""
|
||||||
|
set(value) {
|
||||||
|
editText.setText(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
var helperText: String
|
||||||
|
get() = layout.helperText?.toString() ?: ""
|
||||||
|
set(value) {
|
||||||
|
layout.setHelperText(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
var error: String
|
||||||
|
get() = layout.error?.toString() ?: ""
|
||||||
|
set(value) {
|
||||||
|
layout.setError(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun validate(): Boolean {
|
||||||
|
val isValid = text.isNotEmpty()
|
||||||
|
if (!isValid) {
|
||||||
|
Log.w(TAG, "validate failed in ${layout.hint ?: "unknown"}")
|
||||||
|
error = errorMessage.toString()
|
||||||
|
}
|
||||||
|
return isValid
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setText(text: String) {
|
companion object {
|
||||||
view.editText?.setText(text)
|
const val TAG = "TextInputGroup"
|
||||||
}
|
|
||||||
|
|
||||||
fun setError(errorMessage: String?) {
|
|
||||||
view.layout?.apply { error = errorMessage }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import androidx.appcompat.app.AlertDialog
|
|||||||
|
|
||||||
class WifiDialogHelper(
|
class WifiDialogHelper(
|
||||||
alertDialog: AlertDialog,
|
alertDialog: AlertDialog,
|
||||||
private val ssidInputGroup: SsidInputGroup? = null,
|
private val ssidInputGroup: TextInputGroup? = null,
|
||||||
) : AlertDialogHelper(alertDialog) {
|
) : AlertDialogHelper(alertDialog) {
|
||||||
|
|
||||||
override fun canDismiss(): Boolean {
|
override fun canDismiss(): Boolean {
|
||||||
|
|||||||
@@ -0,0 +1,200 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2025 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.accessibility;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static com.google.common.truth.Truth.assertWithMessage;
|
||||||
|
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.provider.Settings;
|
||||||
|
import android.provider.Settings.Secure.AccessibilityMagnificationCursorFollowingMode;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.ListView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.preference.Preference;
|
||||||
|
import androidx.preference.PreferenceManager;
|
||||||
|
import androidx.preference.PreferenceScreen;
|
||||||
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
|
||||||
|
import com.android.settings.DialogCreatable;
|
||||||
|
import com.android.settings.R;
|
||||||
|
import com.android.settings.accessibility.MagnificationCursorFollowingModePreferenceController.ModeInfo;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Spy;
|
||||||
|
import org.mockito.junit.MockitoJUnit;
|
||||||
|
import org.mockito.junit.MockitoRule;
|
||||||
|
import org.robolectric.RobolectricTestRunner;
|
||||||
|
|
||||||
|
/** Tests for {@link MagnificationCursorFollowingModePreferenceController}. */
|
||||||
|
@RunWith(RobolectricTestRunner.class)
|
||||||
|
public class MagnificationCursorFollowingModePreferenceControllerTest {
|
||||||
|
private static final String PREF_KEY =
|
||||||
|
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE;
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public MockitoRule mocks = MockitoJUnit.rule();
|
||||||
|
|
||||||
|
@Spy
|
||||||
|
private TestDialogHelper mDialogHelper = new TestDialogHelper();
|
||||||
|
|
||||||
|
private PreferenceScreen mScreen;
|
||||||
|
private Context mContext;
|
||||||
|
private MagnificationCursorFollowingModePreferenceController mController;
|
||||||
|
private Preference mModePreference;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
mContext = ApplicationProvider.getApplicationContext();
|
||||||
|
mContext.setTheme(androidx.appcompat.R.style.Theme_AppCompat);
|
||||||
|
final PreferenceManager preferenceManager = new PreferenceManager(mContext);
|
||||||
|
mScreen = preferenceManager.createPreferenceScreen(mContext);
|
||||||
|
mModePreference = new Preference(mContext);
|
||||||
|
mModePreference.setKey(PREF_KEY);
|
||||||
|
mScreen.addPreference(mModePreference);
|
||||||
|
mController = new MagnificationCursorFollowingModePreferenceController(mContext, PREF_KEY);
|
||||||
|
mController.setDialogHelper(mDialogHelper);
|
||||||
|
mDialogHelper.setDialogDelegate(mController);
|
||||||
|
showPreferenceOnTheScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showPreferenceOnTheScreen() {
|
||||||
|
mController.displayPreference(mScreen);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AccessibilityMagnificationCursorFollowingMode
|
||||||
|
private int getCheckedModeFromDialog() {
|
||||||
|
final ListView listView = mController.mModeListView;
|
||||||
|
assertThat(listView).isNotNull();
|
||||||
|
|
||||||
|
final int checkedPosition = listView.getCheckedItemPosition();
|
||||||
|
assertWithMessage("No mode is checked").that(checkedPosition)
|
||||||
|
.isNotEqualTo(AdapterView.INVALID_POSITION);
|
||||||
|
|
||||||
|
final ModeInfo modeInfo = (ModeInfo) listView.getAdapter().getItem(checkedPosition);
|
||||||
|
return modeInfo.mMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void performItemClickWith(@AccessibilityMagnificationCursorFollowingMode int mode) {
|
||||||
|
final ListView listView = mController.mModeListView;
|
||||||
|
assertThat(listView).isNotNull();
|
||||||
|
|
||||||
|
int modeIndex = AdapterView.NO_ID;
|
||||||
|
for (int i = 0; i < listView.getAdapter().getCount(); i++) {
|
||||||
|
final ModeInfo modeInfo = (ModeInfo) listView.getAdapter().getItem(i);
|
||||||
|
if (modeInfo != null && modeInfo.mMode == mode) {
|
||||||
|
modeIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertWithMessage("The mode could not be found").that(modeIndex)
|
||||||
|
.isNotEqualTo(AdapterView.NO_ID);
|
||||||
|
|
||||||
|
listView.performItemClick(listView.getChildAt(modeIndex), modeIndex, modeIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void clickPreference_defaultMode_selectionIsDefault() {
|
||||||
|
mController.handlePreferenceTreeClick(mModePreference);
|
||||||
|
|
||||||
|
assertThat(getCheckedModeFromDialog()).isEqualTo(
|
||||||
|
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CONTINUOUS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void clickPreference_nonDefaultMode_selectionIsExpected() {
|
||||||
|
Settings.Secure.putInt(mContext.getContentResolver(), PREF_KEY,
|
||||||
|
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CENTER);
|
||||||
|
|
||||||
|
mController.handlePreferenceTreeClick(mModePreference);
|
||||||
|
|
||||||
|
assertThat(getCheckedModeFromDialog()).isEqualTo(
|
||||||
|
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CENTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void selectItemInDialog_selectionIsExpected() {
|
||||||
|
mController.handlePreferenceTreeClick(mModePreference);
|
||||||
|
|
||||||
|
performItemClickWith(
|
||||||
|
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_EDGE);
|
||||||
|
|
||||||
|
assertThat(getCheckedModeFromDialog()).isEqualTo(
|
||||||
|
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_EDGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void selectItemInDialog_dismissWithoutSave_selectionNotPersists() {
|
||||||
|
mController.handlePreferenceTreeClick(mModePreference);
|
||||||
|
|
||||||
|
performItemClickWith(
|
||||||
|
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_EDGE);
|
||||||
|
|
||||||
|
showPreferenceOnTheScreen();
|
||||||
|
|
||||||
|
mController.handlePreferenceTreeClick(mModePreference);
|
||||||
|
|
||||||
|
assertThat(getCheckedModeFromDialog()).isEqualTo(
|
||||||
|
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CONTINUOUS);
|
||||||
|
assertThat(TextUtils.equals(mController.getSummary(), mContext.getString(
|
||||||
|
R.string.accessibility_magnification_cursor_following_continuous))).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void selectItemInDialog_saveAndDismiss_selectionPersists() {
|
||||||
|
mController.handlePreferenceTreeClick(mModePreference);
|
||||||
|
|
||||||
|
performItemClickWith(
|
||||||
|
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_EDGE);
|
||||||
|
mController.onMagnificationCursorFollowingModeDialogPositiveButtonClicked(
|
||||||
|
mDialogHelper.getDialog(), DialogInterface.BUTTON_POSITIVE);
|
||||||
|
|
||||||
|
showPreferenceOnTheScreen();
|
||||||
|
|
||||||
|
mController.handlePreferenceTreeClick(mModePreference);
|
||||||
|
|
||||||
|
assertThat(getCheckedModeFromDialog()).isEqualTo(
|
||||||
|
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_EDGE);
|
||||||
|
assertThat(TextUtils.equals(mController.getSummary(), mContext.getString(
|
||||||
|
R.string.accessibility_magnification_cursor_following_edge))).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class TestDialogHelper implements DialogHelper {
|
||||||
|
private DialogCreatable mDialogDelegate;
|
||||||
|
private Dialog mDialog;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showDialog(int dialogId) {
|
||||||
|
mDialog = mDialogDelegate.onCreateDialog(dialogId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDialogDelegate(@NonNull DialogCreatable delegate) {
|
||||||
|
mDialogDelegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dialog getDialog() {
|
||||||
|
return mDialog;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,6 +39,7 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.icu.text.CaseMap;
|
import android.icu.text.CaseMap;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.platform.test.annotations.DisableFlags;
|
import android.platform.test.annotations.DisableFlags;
|
||||||
import android.platform.test.annotations.EnableFlags;
|
import android.platform.test.annotations.EnableFlags;
|
||||||
@@ -56,12 +57,14 @@ import androidx.fragment.app.FragmentActivity;
|
|||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
import androidx.preference.PreferenceScreen;
|
import androidx.preference.PreferenceScreen;
|
||||||
|
import androidx.preference.PreferenceViewHolder;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.flags.Flags;
|
import com.android.settings.flags.Flags;
|
||||||
import com.android.settings.testutils.shadow.ShadowAccessibilityManager;
|
import com.android.settings.testutils.shadow.ShadowAccessibilityManager;
|
||||||
import com.android.settings.testutils.shadow.ShadowFragment;
|
import com.android.settings.testutils.shadow.ShadowFragment;
|
||||||
|
import com.android.settingslib.widget.IllustrationPreference;
|
||||||
import com.android.settingslib.widget.TopIntroPreference;
|
import com.android.settingslib.widget.TopIntroPreference;
|
||||||
|
|
||||||
import com.google.android.setupcompat.util.WizardManagerHelper;
|
import com.google.android.setupcompat.util.WizardManagerHelper;
|
||||||
@@ -79,6 +82,7 @@ import org.robolectric.RobolectricTestRunner;
|
|||||||
import org.robolectric.annotation.Config;
|
import org.robolectric.annotation.Config;
|
||||||
import org.robolectric.shadow.api.Shadow;
|
import org.robolectric.shadow.api.Shadow;
|
||||||
import org.robolectric.shadows.ShadowApplication;
|
import org.robolectric.shadows.ShadowApplication;
|
||||||
|
import org.robolectric.shadows.ShadowLooper;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@@ -315,6 +319,45 @@ public class ToggleFeaturePreferenceFragmentTest {
|
|||||||
assertThat(mFragment.getPreferenceScreen().getPreferenceCount()).isEqualTo(0);
|
assertThat(mFragment.getPreferenceScreen().getPreferenceCount()).isEqualTo(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void initAnimatedImagePreference_isAnimatable_setContentDescription() {
|
||||||
|
mFragment.mFeatureName = "Test Feature";
|
||||||
|
final View view =
|
||||||
|
LayoutInflater.from(mContext).inflate(
|
||||||
|
com.android.settingslib.widget.preference.illustration
|
||||||
|
.R.layout.illustration_preference,
|
||||||
|
null);
|
||||||
|
IllustrationPreference preference = spy(new IllustrationPreference(mFragment.getContext()));
|
||||||
|
when(preference.isAnimatable()).thenReturn(true);
|
||||||
|
mFragment.initAnimatedImagePreference(mock(Uri.class), preference);
|
||||||
|
|
||||||
|
preference.onBindViewHolder(PreferenceViewHolder.createInstanceForTests(view));
|
||||||
|
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
|
||||||
|
|
||||||
|
String expectedContentDescription = mFragment.getString(
|
||||||
|
R.string.accessibility_illustration_content_description, mFragment.mFeatureName);
|
||||||
|
assertThat(preference.getContentDescription().toString())
|
||||||
|
.isEqualTo(expectedContentDescription);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void initAnimatedImagePreference_isNotAnimatable_notSetContentDescription() {
|
||||||
|
mFragment.mFeatureName = "Test Feature";
|
||||||
|
final View view =
|
||||||
|
LayoutInflater.from(mContext).inflate(
|
||||||
|
com.android.settingslib.widget.preference.illustration
|
||||||
|
.R.layout.illustration_preference,
|
||||||
|
null);
|
||||||
|
IllustrationPreference preference = spy(new IllustrationPreference(mFragment.getContext()));
|
||||||
|
when(preference.isAnimatable()).thenReturn(false);
|
||||||
|
mFragment.initAnimatedImagePreference(mock(Uri.class), preference);
|
||||||
|
|
||||||
|
preference.onBindViewHolder(PreferenceViewHolder.createInstanceForTests(view));
|
||||||
|
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
|
||||||
|
|
||||||
|
verify(preference, never()).setContentDescription(any());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@EnableFlags(Flags.FLAG_ACCESSIBILITY_SHOW_APP_INFO_BUTTON)
|
@EnableFlags(Flags.FLAG_ACCESSIBILITY_SHOW_APP_INFO_BUTTON)
|
||||||
public void createAppInfoPreference_withValidComponentName() {
|
public void createAppInfoPreference_withValidComponentName() {
|
||||||
|
|||||||
@@ -613,6 +613,24 @@ public class ToggleScreenMagnificationPreferenceFragmentTest {
|
|||||||
verify(dialogDelegate).getDialogMetricsCategory(dialogId);
|
verify(dialogDelegate).getDialogMetricsCategory(dialogId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnableFlags(com.android.settings.accessibility.Flags
|
||||||
|
.FLAG_ENABLE_MAGNIFICATION_CURSOR_FOLLOWING_DIALOG)
|
||||||
|
public void onCreateDialog_setCursorFollowingModeDialogDelegate_invokeDialogDelegate() {
|
||||||
|
ToggleScreenMagnificationPreferenceFragment fragment =
|
||||||
|
mFragController.create(
|
||||||
|
R.id.main_content, /* bundle= */ null).start().resume().get();
|
||||||
|
final DialogCreatable dialogDelegate = mock(DialogCreatable.class, RETURNS_DEEP_STUBS);
|
||||||
|
final int dialogId = DialogEnums.DIALOG_MAGNIFICATION_CURSOR_FOLLOWING_MODE;
|
||||||
|
when(dialogDelegate.getDialogMetricsCategory(anyInt())).thenReturn(dialogId);
|
||||||
|
fragment.setMagnificationCursorFollowingModeDialogDelegate(dialogDelegate);
|
||||||
|
|
||||||
|
fragment.onCreateDialog(dialogId);
|
||||||
|
fragment.getDialogMetricsCategory(dialogId);
|
||||||
|
verify(dialogDelegate).onCreateDialog(dialogId);
|
||||||
|
verify(dialogDelegate).getDialogMetricsCategory(dialogId);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getMetricsCategory_returnsCorrectCategory() {
|
public void getMetricsCategory_returnsCorrectCategory() {
|
||||||
ToggleScreenMagnificationPreferenceFragment fragment =
|
ToggleScreenMagnificationPreferenceFragment fragment =
|
||||||
@@ -826,6 +844,7 @@ public class ToggleScreenMagnificationPreferenceFragmentTest {
|
|||||||
MagnificationOneFingerPanningPreferenceController.PREF_KEY,
|
MagnificationOneFingerPanningPreferenceController.PREF_KEY,
|
||||||
MagnificationAlwaysOnPreferenceController.PREF_KEY,
|
MagnificationAlwaysOnPreferenceController.PREF_KEY,
|
||||||
MagnificationJoystickPreferenceController.PREF_KEY,
|
MagnificationJoystickPreferenceController.PREF_KEY,
|
||||||
|
MagnificationCursorFollowingModePreferenceController.PREF_KEY,
|
||||||
MagnificationFeedbackPreferenceController.PREF_KEY);
|
MagnificationFeedbackPreferenceController.PREF_KEY);
|
||||||
|
|
||||||
final List<SearchIndexableRaw> rawData = ToggleScreenMagnificationPreferenceFragment
|
final List<SearchIndexableRaw> rawData = ToggleScreenMagnificationPreferenceFragment
|
||||||
@@ -881,7 +900,9 @@ public class ToggleScreenMagnificationPreferenceFragmentTest {
|
|||||||
@EnableFlags({
|
@EnableFlags({
|
||||||
com.android.settings.accessibility.Flags.FLAG_FIX_A11Y_SETTINGS_SEARCH,
|
com.android.settings.accessibility.Flags.FLAG_FIX_A11Y_SETTINGS_SEARCH,
|
||||||
Flags.FLAG_ENABLE_MAGNIFICATION_ONE_FINGER_PANNING_GESTURE,
|
Flags.FLAG_ENABLE_MAGNIFICATION_ONE_FINGER_PANNING_GESTURE,
|
||||||
Flags.FLAG_ENABLE_LOW_VISION_HATS})
|
Flags.FLAG_ENABLE_LOW_VISION_HATS,
|
||||||
|
com.android.settings.accessibility.Flags
|
||||||
|
.FLAG_ENABLE_MAGNIFICATION_CURSOR_FOLLOWING_DIALOG})
|
||||||
public void getNonIndexableKeys_hasShortcutAndAllFeaturesEnabled_allItemsSearchable() {
|
public void getNonIndexableKeys_hasShortcutAndAllFeaturesEnabled_allItemsSearchable() {
|
||||||
mShadowAccessibilityManager.setAccessibilityShortcutTargets(
|
mShadowAccessibilityManager.setAccessibilityShortcutTargets(
|
||||||
TRIPLETAP, List.of(MAGNIFICATION_CONTROLLER_NAME));
|
TRIPLETAP, List.of(MAGNIFICATION_CONTROLLER_NAME));
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import android.content.Context;
|
|||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.os.UserManager;
|
import android.os.UserManager;
|
||||||
|
import android.platform.test.annotations.DisableFlags;
|
||||||
import android.platform.test.annotations.EnableFlags;
|
import android.platform.test.annotations.EnableFlags;
|
||||||
import android.platform.test.flag.junit.SetFlagsRule;
|
import android.platform.test.flag.junit.SetFlagsRule;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
@@ -175,6 +176,7 @@ public class BluetoothDevicePreferenceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DisableFlags(Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI)
|
||||||
public void onClicked_deviceNotBonded_shouldLogBluetoothPairEvent() {
|
public void onClicked_deviceNotBonded_shouldLogBluetoothPairEvent() {
|
||||||
when(mCachedBluetoothDevice.isConnected()).thenReturn(false);
|
when(mCachedBluetoothDevice.isConnected()).thenReturn(false);
|
||||||
when(mCachedBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
|
when(mCachedBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
|
||||||
@@ -192,6 +194,7 @@ public class BluetoothDevicePreferenceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DisableFlags(Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI)
|
||||||
public void onClicked_deviceNotBonded_shouldLogBluetoothPairEventAndPairWithoutNameEvent() {
|
public void onClicked_deviceNotBonded_shouldLogBluetoothPairEventAndPairWithoutNameEvent() {
|
||||||
when(mCachedBluetoothDevice.isConnected()).thenReturn(false);
|
when(mCachedBluetoothDevice.isConnected()).thenReturn(false);
|
||||||
when(mCachedBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
|
when(mCachedBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import static org.mockito.ArgumentMatchers.anyInt;
|
|||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.doReturn;
|
import static org.mockito.Mockito.doReturn;
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.spy;
|
import static org.mockito.Mockito.spy;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
@@ -52,7 +51,9 @@ import android.media.session.ISession;
|
|||||||
import android.media.session.ISessionController;
|
import android.media.session.ISessionController;
|
||||||
import android.media.session.MediaSessionManager;
|
import android.media.session.MediaSessionManager;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
|
import android.os.Looper;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.platform.test.flag.junit.SetFlagsRule;
|
import android.platform.test.flag.junit.SetFlagsRule;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
@@ -81,14 +82,12 @@ import org.mockito.Mock;
|
|||||||
import org.mockito.junit.MockitoJUnit;
|
import org.mockito.junit.MockitoJUnit;
|
||||||
import org.mockito.junit.MockitoRule;
|
import org.mockito.junit.MockitoRule;
|
||||||
import org.robolectric.RobolectricTestRunner;
|
import org.robolectric.RobolectricTestRunner;
|
||||||
import org.robolectric.android.util.concurrent.InlineExecutorService;
|
|
||||||
import org.robolectric.annotation.Config;
|
import org.robolectric.annotation.Config;
|
||||||
import org.robolectric.shadow.api.Shadow;
|
import org.robolectric.shadow.api.Shadow;
|
||||||
import org.robolectric.util.ReflectionHelpers;
|
import org.robolectric.util.ReflectionHelpers;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
@RunWith(RobolectricTestRunner.class)
|
@RunWith(RobolectricTestRunner.class)
|
||||||
@Config(
|
@Config(
|
||||||
@@ -122,6 +121,7 @@ public class AudioStreamMediaServiceTest {
|
|||||||
@Mock private PackageManager mPackageManager;
|
@Mock private PackageManager mPackageManager;
|
||||||
@Mock private DisplayMetrics mDisplayMetrics;
|
@Mock private DisplayMetrics mDisplayMetrics;
|
||||||
@Mock private Context mContext;
|
@Mock private Context mContext;
|
||||||
|
@Mock private Handler mHandler;
|
||||||
private FakeFeatureFactory mFeatureFactory;
|
private FakeFeatureFactory mFeatureFactory;
|
||||||
private AudioStreamMediaService mAudioStreamMediaService;
|
private AudioStreamMediaService mAudioStreamMediaService;
|
||||||
|
|
||||||
@@ -145,11 +145,18 @@ public class AudioStreamMediaServiceTest {
|
|||||||
when(mCachedBluetoothDevice.getName()).thenReturn(DEVICE_NAME);
|
when(mCachedBluetoothDevice.getName()).thenReturn(DEVICE_NAME);
|
||||||
when(mLocalBluetoothProfileManager.getVolumeControlProfile())
|
when(mLocalBluetoothProfileManager.getVolumeControlProfile())
|
||||||
.thenReturn(mVolumeControlProfile);
|
.thenReturn(mVolumeControlProfile);
|
||||||
|
when(mHandler.post(any(Runnable.class))).thenAnswer(invocation -> {
|
||||||
mAudioStreamMediaService = spy(new AudioStreamMediaService());
|
((Runnable) invocation.getArgument(0)).run();
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
when(mHandler.getLooper()).thenReturn(Looper.getMainLooper());
|
||||||
|
mAudioStreamMediaService = spy(new AudioStreamMediaService() {
|
||||||
|
@Override
|
||||||
|
Handler getHandler() {
|
||||||
|
return mHandler;
|
||||||
|
}
|
||||||
|
});
|
||||||
ReflectionHelpers.setField(mAudioStreamMediaService, "mBase", mContext);
|
ReflectionHelpers.setField(mAudioStreamMediaService, "mBase", mContext);
|
||||||
ReflectionHelpers.setField(
|
|
||||||
mAudioStreamMediaService, "mExecutor", new InlineExecutorService());
|
|
||||||
when(mAudioStreamMediaService.getSystemService(anyString()))
|
when(mAudioStreamMediaService.getSystemService(anyString()))
|
||||||
.thenReturn(mMediaSessionManager);
|
.thenReturn(mMediaSessionManager);
|
||||||
when(mMediaSessionManager.createSession(any(), anyString(), any())).thenReturn(mISession);
|
when(mMediaSessionManager.createSession(any(), anyString(), any())).thenReturn(mISession);
|
||||||
@@ -391,31 +398,6 @@ public class AudioStreamMediaServiceTest {
|
|||||||
verify(mAudioStreamMediaService).stopSelf();
|
verify(mAudioStreamMediaService).stopSelf();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void bluetoothCallback_onMemberDeviceDisconnect_stopSelf() {
|
|
||||||
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
|
||||||
when(mCachedBluetoothDevice.getDevice()).thenReturn(mock(BluetoothDevice.class));
|
|
||||||
CachedBluetoothDevice member = mock(CachedBluetoothDevice.class);
|
|
||||||
when(mCachedBluetoothDevice.getMemberDevice()).thenReturn(Set.of(member));
|
|
||||||
when(member.getDevice()).thenReturn(mDevice);
|
|
||||||
var devices = new ArrayList<BluetoothDevice>();
|
|
||||||
devices.add(mDevice);
|
|
||||||
|
|
||||||
Intent intent = new Intent();
|
|
||||||
intent.putExtra(BROADCAST_ID, 1);
|
|
||||||
intent.putParcelableArrayListExtra(DEVICES, devices);
|
|
||||||
|
|
||||||
mAudioStreamMediaService.onCreate();
|
|
||||||
assertThat(mAudioStreamMediaService.mBluetoothCallback).isNotNull();
|
|
||||||
mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0);
|
|
||||||
mAudioStreamMediaService.mBluetoothCallback.onProfileConnectionStateChanged(
|
|
||||||
mCachedBluetoothDevice,
|
|
||||||
BluetoothAdapter.STATE_DISCONNECTED,
|
|
||||||
BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
|
|
||||||
|
|
||||||
verify(mAudioStreamMediaService).stopSelf();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void mediaSessionCallback_onPause_setVolume() {
|
public void mediaSessionCallback_onPause_setVolume() {
|
||||||
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||||
|
|||||||
@@ -19,9 +19,16 @@ package com.android.settings.network;
|
|||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.spy;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.telephony.TelephonyManager;
|
||||||
|
|
||||||
|
import com.android.settings.R;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@@ -34,28 +41,66 @@ import org.robolectric.util.ReflectionHelpers;
|
|||||||
@RunWith(RobolectricTestRunner.class)
|
@RunWith(RobolectricTestRunner.class)
|
||||||
public class NetworkResetPreferenceControllerTest {
|
public class NetworkResetPreferenceControllerTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private TelephonyManager mTelephonyManager;
|
||||||
@Mock
|
@Mock
|
||||||
private NetworkResetRestrictionChecker mRestrictionChecker;
|
private NetworkResetRestrictionChecker mRestrictionChecker;
|
||||||
private NetworkResetPreferenceController mController;
|
private NetworkResetPreferenceController mController;
|
||||||
|
private Context mContext;
|
||||||
|
private Resources mResources;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
mController = new NetworkResetPreferenceController(RuntimeEnvironment.application);
|
mContext = spy(RuntimeEnvironment.application);
|
||||||
|
|
||||||
|
mResources = spy(mContext.getResources());
|
||||||
|
when(mContext.getResources()).thenReturn(mResources);
|
||||||
|
when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephonyManager);
|
||||||
|
|
||||||
|
mController = new NetworkResetPreferenceController(mContext);
|
||||||
ReflectionHelpers.setField(mController, "mRestrictionChecker", mRestrictionChecker);
|
ReflectionHelpers.setField(mController, "mRestrictionChecker", mRestrictionChecker);
|
||||||
|
|
||||||
|
// Availability defaults
|
||||||
|
when(mTelephonyManager.isDataCapable()).thenReturn(true);
|
||||||
|
when(mResources.getBoolean(R.bool.config_show_sim_info)).thenReturn(true);
|
||||||
|
when(mRestrictionChecker.isRestrictionEnforcedByAdmin()).thenReturn(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testIsAvailable_shouldReturnTrueWhenNoUserRestriction() {
|
public void testIsAvailable_showSimInfo_notWifiOnly() {
|
||||||
when(mRestrictionChecker.isRestrictionEnforcedByAdmin()).thenReturn(true);
|
assertThat(mController.isAvailable()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsAvailable_hideSimInfo_notWifiOnly() {
|
||||||
|
when(mResources.getBoolean(R.bool.config_show_sim_info)).thenReturn(false);
|
||||||
|
assertThat(mController.isAvailable()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsAvailable_showSimInfo_wifiOnly() {
|
||||||
|
when(mTelephonyManager.isDataCapable()).thenReturn(false);
|
||||||
|
assertThat(mController.isAvailable()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsAvailable_userRestriction() {
|
||||||
|
when(mRestrictionChecker.isRestrictionEnforcedByAdmin()).thenReturn(true);
|
||||||
when(mRestrictionChecker.hasUserRestriction()).thenReturn(true);
|
when(mRestrictionChecker.hasUserRestriction()).thenReturn(true);
|
||||||
|
|
||||||
assertThat(mController.isAvailable()).isFalse();
|
assertThat(mController.isAvailable()).isFalse();
|
||||||
|
|
||||||
|
verify(mRestrictionChecker, never()).isRestrictionEnforcedByAdmin();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsAvailable_noUserRestriction() {
|
||||||
|
when(mRestrictionChecker.isRestrictionEnforcedByAdmin()).thenReturn(true);
|
||||||
when(mRestrictionChecker.hasUserRestriction()).thenReturn(false);
|
when(mRestrictionChecker.hasUserRestriction()).thenReturn(false);
|
||||||
|
|
||||||
assertThat(mController.isAvailable()).isTrue();
|
assertThat(mController.isAvailable()).isTrue();
|
||||||
|
|
||||||
verify(mRestrictionChecker, never()).isRestrictionEnforcedByAdmin();
|
verify(mRestrictionChecker, never()).isRestrictionEnforcedByAdmin();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user