Snap for 12019843 from 5653acd533 to 24Q4-release
Change-Id: Ie6b1e342ceed6f47033f602a6a3b05812471fdbe
This commit is contained in:
@@ -338,23 +338,23 @@
|
||||
<!-- Dialog text shown when the user tries to skip setting up a screen lock, warning that they can't continue to set up face. (default) [CHAR LIMIT=NONE] -->
|
||||
<string name="lock_screen_password_skip_face_message" product="default">A password is required to set up Face Unlock.\n\nA password protects the phone if it\u2019s lost or stolen.</string>
|
||||
<!-- Dialog text shown when the user tries to skip setting up a screen lock, warning that they can't continue to set up face or fingerprint. (tablet) [CHAR LIMIT=NONE] -->
|
||||
<string name="lock_screen_pin_skip_biometrics_message" product="tablet">A PIN is required to set up Face Unlock and Fingerprint Unlock.\n\nA PIN protects the tablet if it\u2019s lost or stolen.</string>
|
||||
<string name="lock_screen_pin_skip_biometrics_message" product="tablet">A PIN is required to set up Fingerprint Unlock and Face Unlock.\n\nA PIN protects the tablet if it\u2019s lost or stolen.</string>
|
||||
<!-- Dialog text shown when the user tries to skip setting up a screen lock, warning that they can't continue to set up face or fingerprint. (tablet) [CHAR LIMIT=NONE] -->
|
||||
<string name="lock_screen_pattern_skip_biometrics_message" product="tablet">A pattern is required to set up Face Unlock and Fingerprint Unlock.\n\nA pattern protects the tablet if it\u2019s lost or stolen.</string>
|
||||
<string name="lock_screen_pattern_skip_biometrics_message" product="tablet">A pattern is required to set up Fingerprint Unlock and Face Unlock.\n\nA pattern protects the tablet if it\u2019s lost or stolen.</string>
|
||||
<!-- Dialog text shown when the user tries to skip setting up a screen lock, warning that they can't continue to set up face or fingerprint. (tablet) [CHAR LIMIT=NONE] -->
|
||||
<string name="lock_screen_password_skip_biometrics_message" product="tablet">A password is required to set up Face Unlock and Fingerprint Unlock.\n\nA password protects the tablet if it\u2019s lost or stolen.</string>
|
||||
<string name="lock_screen_password_skip_biometrics_message" product="tablet">A password is required to set up Fingerprint Unlock and Face Unlock.\n\nA password protects the tablet if it\u2019s lost or stolen.</string>
|
||||
<!-- Dialog text shown when the user tries to skip setting up a screen lock, warning that they can't continue to set up face or fingerprint. (device) [CHAR LIMIT=NONE] -->
|
||||
<string name="lock_screen_pin_skip_biometrics_message" product="device">A PIN is required to set up Face Unlock and Fingerprint Unlock.\n\nA PIN protects the device if it\u2019s lost or stolen.</string>
|
||||
<string name="lock_screen_pin_skip_biometrics_message" product="device">A PIN is required to set up Fingerprint Unlock and Face Unlock.\n\nA PIN protects the device if it\u2019s lost or stolen.</string>
|
||||
<!-- Dialog text shown when the user tries to skip setting up a screen lock, warning that they can't continue to set up face or fingerprint. (device) [CHAR LIMIT=NONE] -->
|
||||
<string name="lock_screen_pattern_skip_biometrics_message" product="device">A pattern is required to set up Face Unlock and Fingerprint Unlock.\n\nA pattern protects the device if it\u2019s lost or stolen.</string>
|
||||
<string name="lock_screen_pattern_skip_biometrics_message" product="device">A pattern is required to set up Fingerprint Unlock and Face Unlock.\n\nA pattern protects the device if it\u2019s lost or stolen.</string>
|
||||
<!-- Dialog text shown when the user tries to skip setting up a screen lock, warning that they can't continue to set up face or fingerprint. (device) [CHAR LIMIT=NONE] -->
|
||||
<string name="lock_screen_password_skip_biometrics_message" product="device">A password is required to set up Face Unlock and Fingerprint Unlock.\n\nA password protects the device if it\u2019s lost or stolen.</string>
|
||||
<string name="lock_screen_password_skip_biometrics_message" product="device">A password is required to set up Fingerprint Unlock and Face Unlock.\n\nA password protects the device if it\u2019s lost or stolen.</string>
|
||||
<!-- Dialog text shown when the user tries to skip setting up a screen lock, warning that they can't continue to set up face or fingerprint. (default) [CHAR LIMIT=NONE] -->
|
||||
<string name="lock_screen_pin_skip_biometrics_message" product="default">A PIN is required to set up Face Unlock and Fingerprint Unlock.\n\nA PIN protects the phone if it\u2019s lost or stolen.</string>
|
||||
<string name="lock_screen_pin_skip_biometrics_message" product="default">A PIN is required to set up Fingerprint Unlock and Face Unlock.\n\nA PIN protects the phone if it\u2019s lost or stolen.</string>
|
||||
<!-- Dialog text shown when the user tries to skip setting up a screen lock, warning that they can't continue to set up face or fingerprint. (default) [CHAR LIMIT=NONE] -->
|
||||
<string name="lock_screen_pattern_skip_biometrics_message" product="default">A pattern is required to set up Face Unlock and Fingerprint Unlock.\n\nA pattern protects the phone if it\u2019s lost or stolen.</string>
|
||||
<string name="lock_screen_pattern_skip_biometrics_message" product="default">A pattern is required to set up Fingerprint Unlock and Face Unlock.\n\nA pattern protects the phone if it\u2019s lost or stolen.</string>
|
||||
<!-- Dialog text shown when the user tries to skip setting up a screen lock, warning that they can't continue to set up face or fingerprint. (default) [CHAR LIMIT=NONE] -->
|
||||
<string name="lock_screen_password_skip_biometrics_message" product="default">A password is required to set up Face Unlock and Fingerprint Unlock.\n\nA password protects the phone if it\u2019s lost or stolen.</string>
|
||||
<string name="lock_screen_password_skip_biometrics_message" product="default">A password is required to set up Fingerprint Unlock and Face Unlock.\n\nA password protects the phone if it\u2019s lost or stolen.</string>
|
||||
<!-- Message shown in a dialog which asks the user to confirm when a single fingerprint gets deleted. [CHAR LIMIT=NONE]-->
|
||||
<string name="fingerprint_v2_delete_message" product="default">This deletes the fingerprint images and model associated with \'<xliff:g id="fingerprint_id" example="Fingerprint 2">%1$s</xliff:g>\' that are stored on your phone</string>
|
||||
<!-- Message shown in a dialog which asks the user to confirm when a single fingerprint gets deleted. [CHAR LIMIT=NONE]-->
|
||||
|
||||
25
res/color/modes_icon_picker_item_background.xml
Normal file
25
res/color/modes_icon_picker_item_background.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<!--
|
||||
~ Copyright (C) 2024 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<!-- Color list for the background in each item in the icon picker list. -->
|
||||
<selector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
|
||||
|
||||
<item android:state_pressed="true" android:color="?androidprv:attr/materialColorPrimary" />
|
||||
<item android:state_selected="true" android:color="?androidprv:attr/materialColorPrimary" />
|
||||
<item android:color="?androidprv:attr/materialColorSecondaryContainer" />
|
||||
</selector>
|
||||
25
res/color/modes_icon_picker_item_icon.xml
Normal file
25
res/color/modes_icon_picker_item_icon.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<!--
|
||||
~ Copyright (C) 2024 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<!-- Color list for the icon in each item in the icon picker list. -->
|
||||
<selector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
|
||||
|
||||
<item android:state_pressed="true" android:color="?androidprv:attr/materialColorOnPrimary" />
|
||||
<item android:state_selected="true" android:color="?androidprv:attr/materialColorOnPrimary" />
|
||||
<item android:color="?androidprv:attr/materialColorOnSecondaryContainer" />
|
||||
</selector>
|
||||
@@ -156,6 +156,8 @@
|
||||
<string name="bluetooth_hearing_device_settings_summary">Shortcut, hearing aid compatibility</string>
|
||||
<!-- Connected devices settings. Title for hearing aids presets. A preset is a set of hearing aid settings. User can apply different settings in different environments (e.g. Outdoor, Restaurant, Home) [CHAR LIMIT=60] [BACKUP_MESSAGE_ID=5429761844739722885] -->
|
||||
<string name="bluetooth_hearing_aids_presets">Preset</string>
|
||||
<!-- Connected devices settings. Summary of the preference when no preset info is obtained from the remote device [CHAR LIMIT=60] -->
|
||||
<string name="bluetooth_hearing_aids_presets_empty_list_message">There are no presets programmed by your audiologist</string>
|
||||
<!-- Message when selecting hearing aids presets failed. [CHAR LIMIT=NONE] -->
|
||||
<string name="bluetooth_hearing_aids_presets_error">Couldn\u2019t update preset</string>
|
||||
<!-- Connected devices settings. Title of the preference to show the entrance of the audio output page. It can change different types of audio are played on phone or other bluetooth devices. [CHAR LIMIT=35] -->
|
||||
@@ -995,7 +997,7 @@
|
||||
|
||||
<!-- Biometric settings --><skip />
|
||||
<!-- Title shown for menu item that launches biometric settings. [CHAR LIMIT=66] -->
|
||||
<string name="security_settings_biometric_preference_title">Fingerprint & Face Unlock</string>
|
||||
<string name="security_settings_biometric_preference_title">Face & Fingerprint Unlock</string>
|
||||
<!-- Title shown for work menu item that launches biometric settings. [CHAR LIMIT=66] -->
|
||||
<string name="security_settings_work_biometric_preference_title">Face & Fingerprint Unlock for work</string>
|
||||
<!-- Message shown in summary field of biometric settings. [CHAR LIMIT=66] -->
|
||||
@@ -2784,6 +2786,9 @@
|
||||
<string name="brightness">Brightness level</string>
|
||||
<!-- Sound & display settings screen, setting option name to enable adaptive brightness [CHAR LIMIT=30] -->
|
||||
<string name="auto_brightness_title">Adaptive brightness</string>
|
||||
<!-- Note: The content description title is only applied in adaptive brightness detailed page in setup wizard flow, to make the consistency with other accessibility suw pages. -->
|
||||
<!-- ContentDescription title for adaptive brightness detailed page footer. [CHAR LIMIT=60] -->
|
||||
<string name="auto_brightness_content_description_title">About adaptive brightness</string>
|
||||
<!-- Description about the feature adaptive brightness -->
|
||||
<string name="auto_brightness_description">Your screen brightness will automatically adjust to your environment and activities. You can move the slider manually to help adaptive brightness learn your preferences.</string>
|
||||
<!-- Setting option summary when adaptive brightness is on [CHAR LIMIT=NONE] -->
|
||||
@@ -8980,6 +8985,9 @@
|
||||
<!-- [CHAR LIMIT=NONE] App notification settings: no channels -->
|
||||
<string name="no_channels">This app has not posted any notifications</string>
|
||||
|
||||
<!-- [CHAR LIMIT=NONE] App notification settings: has channels, but hasn't sent notifications recently -->
|
||||
<string name="no_recent_channels">Show unused categories</string>
|
||||
|
||||
<!-- [CHAR LIMIT=NONE] App notification settings: link to app notification settings-->
|
||||
<string name="app_settings_link">Additional settings in the app</string>
|
||||
|
||||
|
||||
@@ -55,6 +55,11 @@
|
||||
android:key="channels"
|
||||
android:layout="@layout/empty_view" />
|
||||
|
||||
<Preference
|
||||
android:key="more"
|
||||
android:title="@string/no_recent_channels"
|
||||
android:icon="@drawable/ic_expand"/>
|
||||
|
||||
<!-- Importance toggle -->
|
||||
<com.android.settingslib.RestrictedSwitchPreference
|
||||
android:key="allow_sound"
|
||||
|
||||
@@ -52,6 +52,7 @@ import com.android.settings.network.SubscriptionUtil;
|
||||
import com.android.settings.network.telephony.EuiccRacConnectivityDialogActivity;
|
||||
import com.android.settings.password.ChooseLockSettingsHelper;
|
||||
import com.android.settings.password.ConfirmLockPattern;
|
||||
import com.android.settings.system.reset.ResetNetworkConfirm;
|
||||
import com.android.settingslib.development.DevelopmentSettingsEnabler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -1,247 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.Looper;
|
||||
import android.telephony.SubscriptionManager;
|
||||
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import com.android.settings.core.InstrumentedFragment;
|
||||
import com.android.settings.network.ResetNetworkOperationBuilder;
|
||||
import com.android.settings.network.ResetNetworkRestrictionViewBuilder;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Confirm and execute a reset of the network settings to a clean "just out of the box"
|
||||
* state. Multiple confirmations are required: first, a general "are you sure
|
||||
* you want to do this?" prompt, followed by a keyguard pattern trace if the user
|
||||
* has defined one, followed by a final strongly-worded "THIS WILL RESET EVERYTHING"
|
||||
* prompt. If at any time the phone is allowed to go to sleep, is
|
||||
* locked, et cetera, then the confirmation sequence is abandoned.
|
||||
*
|
||||
* This is the confirmation screen.
|
||||
*/
|
||||
public class ResetNetworkConfirm extends InstrumentedFragment {
|
||||
private static final String TAG = "ResetNetworkConfirm";
|
||||
|
||||
@VisibleForTesting View mContentView;
|
||||
@VisibleForTesting ResetNetworkTask mResetNetworkTask;
|
||||
@VisibleForTesting Activity mActivity;
|
||||
@VisibleForTesting ResetNetworkRequest mResetNetworkRequest;
|
||||
private ProgressDialog mProgressDialog;
|
||||
private AlertDialog mAlertDialog;
|
||||
@VisibleForTesting ResetSubscriptionContract mResetSubscriptionContract;
|
||||
private OnSubscriptionsChangedListener mSubscriptionsChangedListener;
|
||||
|
||||
/**
|
||||
* Async task used to do all reset task. If error happens during
|
||||
* erasing eSIM profiles or timeout, an error msg is shown.
|
||||
*/
|
||||
private class ResetNetworkTask extends AsyncTask<Void, Void, Boolean> {
|
||||
private static final String TAG = "ResetNetworkTask";
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
ResetNetworkTask(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... params) {
|
||||
final AtomicBoolean resetEsimSuccess = new AtomicBoolean(true);
|
||||
|
||||
String resetEsimPackageName = mResetNetworkRequest.getResetEsimPackageName();
|
||||
ResetNetworkOperationBuilder builder = mResetNetworkRequest
|
||||
.toResetNetworkOperationBuilder(mContext, Looper.getMainLooper());
|
||||
if (resetEsimPackageName != null) {
|
||||
// Override reset eSIM option for the result of reset operation
|
||||
builder = builder.resetEsim(resetEsimPackageName,
|
||||
success -> { resetEsimSuccess.set(success); }
|
||||
);
|
||||
}
|
||||
builder.build().run();
|
||||
|
||||
boolean isResetSucceed = resetEsimSuccess.get();
|
||||
Log.d(TAG, "network factoryReset complete. succeeded: "
|
||||
+ String.valueOf(isResetSucceed));
|
||||
return isResetSucceed;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean succeeded) {
|
||||
if (mProgressDialog != null && mProgressDialog.isShowing()) {
|
||||
mProgressDialog.dismiss();
|
||||
}
|
||||
|
||||
if (succeeded) {
|
||||
Toast.makeText(mContext, R.string.reset_network_complete_toast, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
} else {
|
||||
mAlertDialog = new AlertDialog.Builder(mContext)
|
||||
.setTitle(R.string.reset_esim_error_title)
|
||||
.setMessage(R.string.reset_esim_error_msg)
|
||||
.setPositiveButton(android.R.string.ok, null /* listener */)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The user has gone through the multiple confirmation, so now we go ahead
|
||||
* and reset the network settings to its factory-default state.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
Button.OnClickListener mFinalClickListener = new Button.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (Utils.isMonkeyRunning()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// abandon execution if subscription no longer active
|
||||
Integer subId = mResetSubscriptionContract.getAnyMissingSubscriptionId();
|
||||
if (subId != null) {
|
||||
Log.w(TAG, "subId " + subId + " no longer active");
|
||||
getActivity().onBackPressed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Should dismiss the progress dialog firstly if it is showing
|
||||
// Or not the progress dialog maybe not dismissed in fast clicking.
|
||||
if (mProgressDialog != null && mProgressDialog.isShowing()) {
|
||||
mProgressDialog.dismiss();
|
||||
}
|
||||
|
||||
mProgressDialog = getProgressDialog(mActivity);
|
||||
mProgressDialog.show();
|
||||
|
||||
mResetNetworkTask = new ResetNetworkTask(mActivity);
|
||||
mResetNetworkTask.execute();
|
||||
}
|
||||
};
|
||||
|
||||
private ProgressDialog getProgressDialog(Context context) {
|
||||
final ProgressDialog progressDialog = new ProgressDialog(context);
|
||||
progressDialog.setIndeterminate(true);
|
||||
progressDialog.setCancelable(false);
|
||||
progressDialog.setMessage(
|
||||
context.getString(R.string.main_clear_progress_text));
|
||||
return progressDialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the UI for the final confirmation interaction
|
||||
*/
|
||||
private void establishFinalConfirmationState() {
|
||||
mContentView.findViewById(R.id.execute_reset_network)
|
||||
.setOnClickListener(mFinalClickListener);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setSubtitle() {
|
||||
if (mResetNetworkRequest.getResetEsimPackageName() != null) {
|
||||
((TextView) mContentView.findViewById(R.id.reset_network_confirm))
|
||||
.setText(R.string.reset_network_final_desc_esim);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View view = (new ResetNetworkRestrictionViewBuilder(mActivity)).build();
|
||||
if (view != null) {
|
||||
mResetSubscriptionContract.close();
|
||||
Log.w(TAG, "Access deny.");
|
||||
return view;
|
||||
}
|
||||
mContentView = inflater.inflate(R.layout.reset_network_confirm, null);
|
||||
establishFinalConfirmationState();
|
||||
setSubtitle();
|
||||
return mContentView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Bundle args = getArguments();
|
||||
if (args == null) {
|
||||
args = savedInstanceState;
|
||||
}
|
||||
mResetNetworkRequest = new ResetNetworkRequest(args);
|
||||
|
||||
mActivity = getActivity();
|
||||
|
||||
mResetSubscriptionContract = new ResetSubscriptionContract(getContext(),
|
||||
mResetNetworkRequest) {
|
||||
@Override
|
||||
public void onSubscriptionInactive(int subscriptionId) {
|
||||
// close UI if subscription no longer active
|
||||
Log.w(TAG, "subId " + subscriptionId + " no longer active.");
|
||||
getActivity().onBackPressed();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
mResetNetworkRequest.writeIntoBundle(outState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
if (mResetNetworkTask != null) {
|
||||
mResetNetworkTask.cancel(true /* mayInterruptIfRunning */);
|
||||
mResetNetworkTask = null;
|
||||
}
|
||||
if (mResetSubscriptionContract != null) {
|
||||
mResetSubscriptionContract.close();
|
||||
mResetSubscriptionContract = null;
|
||||
}
|
||||
if (mProgressDialog != null) {
|
||||
mProgressDialog.dismiss();
|
||||
}
|
||||
if (mAlertDialog != null) {
|
||||
mAlertDialog.dismiss();
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.RESET_NETWORK_CONFIRM;
|
||||
}
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings;
|
||||
|
||||
import android.content.Context;
|
||||
import android.telephony.SubscriptionManager;
|
||||
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* A Class monitoring the availability of subscription IDs provided within reset request.
|
||||
*
|
||||
* This is to detect the situation when user changing SIM card during the presenting of
|
||||
* confirmation UI.
|
||||
*/
|
||||
public class ResetSubscriptionContract implements AutoCloseable {
|
||||
private static final String TAG = "ResetSubscriptionContract";
|
||||
|
||||
private final Context mContext;
|
||||
private ExecutorService mExecutorService;
|
||||
private final int [] mResetSubscriptionIds;
|
||||
@VisibleForTesting
|
||||
protected OnSubscriptionsChangedListener mSubscriptionsChangedListener;
|
||||
private AtomicBoolean mSubscriptionsUpdateNotify = new AtomicBoolean();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param context Context
|
||||
* @param resetRequest the request object for perform network reset operation.
|
||||
*/
|
||||
public ResetSubscriptionContract(Context context, ResetNetworkRequest resetRequest) {
|
||||
mContext = context;
|
||||
// Only keeps specific subscription ID required to perform reset operation
|
||||
IntStream subIdStream = IntStream.of(
|
||||
resetRequest.getResetTelephonyAndNetworkPolicyManager(),
|
||||
resetRequest.getResetApnSubId(), resetRequest.getResetImsSubId());
|
||||
mResetSubscriptionIds = subIdStream.sorted().distinct()
|
||||
.filter(id -> SubscriptionManager.isUsableSubscriptionId(id))
|
||||
.toArray();
|
||||
|
||||
if (mResetSubscriptionIds.length <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Monitoring callback through background thread
|
||||
mExecutorService = Executors.newSingleThreadExecutor();
|
||||
startMonitorSubscriptionChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* A method for detecting if there's any subscription under monitor no longer active.
|
||||
* @return subscription ID which is no longer active.
|
||||
*/
|
||||
public Integer getAnyMissingSubscriptionId() {
|
||||
if (mResetSubscriptionIds.length <= 0) {
|
||||
return null;
|
||||
}
|
||||
SubscriptionManager mgr = getSubscriptionManager();
|
||||
if (mgr == null) {
|
||||
Log.w(TAG, "Fail to access subscription manager");
|
||||
return mResetSubscriptionIds[0];
|
||||
}
|
||||
for (int idx = 0; idx < mResetSubscriptionIds.length; idx++) {
|
||||
int subId = mResetSubscriptionIds[idx];
|
||||
if (mgr.getActiveSubscriptionInfo(subId) == null) {
|
||||
Log.w(TAG, "SubId " + subId + " no longer active.");
|
||||
return subId;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Async callback when detecting if there's any subscription under monitor no longer active.
|
||||
* @param subscriptionId subscription ID which is no longer active.
|
||||
*/
|
||||
public void onSubscriptionInactive(int subscriptionId) {}
|
||||
|
||||
@VisibleForTesting
|
||||
protected SubscriptionManager getSubscriptionManager() {
|
||||
return mContext.getSystemService(SubscriptionManager.class);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected OnSubscriptionsChangedListener getChangeListener() {
|
||||
return new OnSubscriptionsChangedListener() {
|
||||
@Override
|
||||
public void onSubscriptionsChanged() {
|
||||
/**
|
||||
* Reducing the processing time on main UI thread through a flag.
|
||||
* Once flag get into false, which means latest callback has been
|
||||
* processed.
|
||||
*/
|
||||
mSubscriptionsUpdateNotify.set(true);
|
||||
|
||||
// Back to main UI thread
|
||||
mContext.getMainExecutor().execute(() -> {
|
||||
// Remove notifications and perform checking.
|
||||
if (mSubscriptionsUpdateNotify.getAndSet(false)) {
|
||||
Integer subId = getAnyMissingSubscriptionId();
|
||||
if (subId != null) {
|
||||
onSubscriptionInactive(subId);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void startMonitorSubscriptionChange() {
|
||||
SubscriptionManager mgr = getSubscriptionManager();
|
||||
if (mgr == null) {
|
||||
return;
|
||||
}
|
||||
// update monitor listener
|
||||
mSubscriptionsChangedListener = getChangeListener();
|
||||
|
||||
mgr.addOnSubscriptionsChangedListener(
|
||||
mExecutorService, mSubscriptionsChangedListener);
|
||||
}
|
||||
|
||||
// Implementation of AutoCloseable
|
||||
public void close() {
|
||||
if (mExecutorService == null) {
|
||||
return;
|
||||
}
|
||||
// Stop monitoring subscription change
|
||||
SubscriptionManager mgr = getSubscriptionManager();
|
||||
if (mgr != null) {
|
||||
mgr.removeOnSubscriptionsChangedListener(mSubscriptionsChangedListener);
|
||||
}
|
||||
// Release Executor
|
||||
mExecutorService.shutdownNow();
|
||||
mExecutorService = null;
|
||||
}
|
||||
}
|
||||
@@ -27,11 +27,13 @@ import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.display.AutoBrightnessSettings;
|
||||
import com.android.settingslib.Utils;
|
||||
import com.android.settingslib.widget.FooterPreference;
|
||||
|
||||
import com.google.android.setupcompat.template.FooterBarMixin;
|
||||
import com.google.android.setupdesign.GlifPreferenceLayout;
|
||||
@@ -41,10 +43,14 @@ import com.google.android.setupdesign.GlifPreferenceLayout;
|
||||
*/
|
||||
public class AutoBrightnessPreferenceFragmentForSetupWizard extends AutoBrightnessSettings {
|
||||
|
||||
private static final String FOOTER_PREFERENCE_KEY = "auto_brightness_footer";
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
updateFooterContentDescription();
|
||||
|
||||
if (view instanceof GlifPreferenceLayout) {
|
||||
final GlifPreferenceLayout layout = (GlifPreferenceLayout) view;
|
||||
final String title = getContext().getString(
|
||||
@@ -78,4 +84,15 @@ public class AutoBrightnessPreferenceFragmentForSetupWizard extends AutoBrightne
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.SUW_ACCESSIBILITY_AUTO_BRIGHTNESS;
|
||||
}
|
||||
|
||||
private void updateFooterContentDescription() {
|
||||
final PreferenceScreen screen = getPreferenceScreen();
|
||||
final FooterPreference footerPreference = screen.findPreference(FOOTER_PREFERENCE_KEY);
|
||||
if (footerPreference != null) {
|
||||
String title = getString(R.string.auto_brightness_content_description_title);
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(title).append("\n\n").append(footerPreference.getTitle());
|
||||
footerPreference.setContentDescription(sb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,6 +356,11 @@ public class FingerprintSettings extends SubSettings {
|
||||
*/
|
||||
protected void handleError(int errMsgId, CharSequence msg) {
|
||||
switch (errMsgId) {
|
||||
case FingerprintManager.FINGERPRINT_ERROR_CANCELED:
|
||||
case FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED:
|
||||
// Only happens if we get preempted by another activity, or canceled by the
|
||||
// user (e.g. swipe up to home). Ignored.
|
||||
return;
|
||||
case FingerprintManager.FINGERPRINT_ERROR_LOCKOUT:
|
||||
mInFingerprintLockout = true;
|
||||
// We've been locked out. Reset after 30s.
|
||||
|
||||
@@ -159,19 +159,22 @@ public class BluetoothDetailsHearingAidsPresetsController extends
|
||||
mPreference.setEnabled(mCachedDevice.isConnectedHapClientDevice());
|
||||
|
||||
loadAllPresetInfo();
|
||||
mPreference.setSummary(null);
|
||||
if (mPreference.getEntries().length == 0) {
|
||||
if (mPreference.isEnabled()) {
|
||||
if (DEBUG) {
|
||||
Log.w(TAG, "Disable the preference since preset info size = 0");
|
||||
}
|
||||
mPreference.setEnabled(false);
|
||||
mPreference.setSummary(mContext.getString(
|
||||
R.string.bluetooth_hearing_aids_presets_empty_list_message));
|
||||
}
|
||||
} else {
|
||||
int activePresetIndex = mHapClientProfile.getActivePresetIndex(
|
||||
mCachedDevice.getDevice());
|
||||
if (activePresetIndex != BluetoothHapClient.PRESET_INDEX_UNAVAILABLE) {
|
||||
mPreference.setValue(Integer.toString(activePresetIndex));
|
||||
mPreference.setSummary(mPreference.getEntry());
|
||||
} else {
|
||||
mPreference.setSummary(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -273,7 +276,8 @@ public class BluetoothDetailsHearingAidsPresetsController extends
|
||||
return;
|
||||
}
|
||||
List<BluetoothHapPresetInfo> infoList = mHapClientProfile.getAllPresetInfo(
|
||||
mCachedDevice.getDevice());
|
||||
mCachedDevice.getDevice()).stream().filter(
|
||||
BluetoothHapPresetInfo::isAvailable).toList();
|
||||
CharSequence[] presetNames = new CharSequence[infoList.size()];
|
||||
CharSequence[] presetIndexes = new CharSequence[infoList.size()];
|
||||
for (int i = 0; i < infoList.size(); i++) {
|
||||
|
||||
@@ -258,6 +258,8 @@ public class AudioSharingDialogHandler {
|
||||
boolean userTriggered) {
|
||||
Map<Integer, List<CachedBluetoothDevice>> groupedDevices =
|
||||
AudioSharingUtils.fetchConnectedDevicesByGroupId(mLocalBtManager);
|
||||
BluetoothDevice btDevice = cachedDevice.getDevice();
|
||||
String deviceAddress = btDevice == null ? "" : btDevice.getAnonymizedAddress();
|
||||
if (isBroadcasting) {
|
||||
// If another device within the same is already in the sharing session, add source to
|
||||
// the device automatically.
|
||||
@@ -271,10 +273,10 @@ public class AudioSharingDialogHandler {
|
||||
Log.d(
|
||||
TAG,
|
||||
"Automatically add another device within the same group to the sharing: "
|
||||
+ cachedDevice.getDevice().getAnonymizedAddress());
|
||||
+ deviceAddress);
|
||||
if (mAssistant != null && mBroadcast != null) {
|
||||
mAssistant.addSource(
|
||||
cachedDevice.getDevice(),
|
||||
btDevice,
|
||||
mBroadcast.getLatestBluetoothLeBroadcastMetadata(),
|
||||
/* isGroupOp= */ false);
|
||||
}
|
||||
@@ -313,6 +315,7 @@ public class AudioSharingDialogHandler {
|
||||
cachedDevice,
|
||||
listener,
|
||||
eventData);
|
||||
Log.d(TAG, "Show disconnect dialog, device = " + deviceAddress);
|
||||
});
|
||||
} else {
|
||||
// Show audio sharing join dialog when the first or second eligible (LE audio)
|
||||
@@ -343,9 +346,11 @@ public class AudioSharingDialogHandler {
|
||||
cachedDevice,
|
||||
listener,
|
||||
eventData);
|
||||
Log.d(TAG, "Show join dialog, device = " + deviceAddress);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Build a list of AudioSharingDeviceItem for connected devices other than cachedDevice.
|
||||
List<AudioSharingDeviceItem> deviceItems = new ArrayList<>();
|
||||
for (List<CachedBluetoothDevice> devices : groupedDevices.values()) {
|
||||
// Use random device in the group within the sharing session to represent the group.
|
||||
@@ -358,7 +363,7 @@ public class AudioSharingDialogHandler {
|
||||
}
|
||||
// Show audio sharing join dialog when the second eligible (LE audio) remote
|
||||
// device connect and no sharing session.
|
||||
if (deviceItems.size() == 1) {
|
||||
if (groupedDevices.size() == 2 && deviceItems.size() == 1) {
|
||||
AudioSharingJoinDialogFragment.DialogEventListener listener =
|
||||
new AudioSharingJoinDialogFragment.DialogEventListener() {
|
||||
@Override
|
||||
@@ -396,9 +401,13 @@ public class AudioSharingDialogHandler {
|
||||
closeOpeningDialogsOtherThan(AudioSharingJoinDialogFragment.tag());
|
||||
AudioSharingJoinDialogFragment.show(
|
||||
mHostFragment, deviceItems, cachedDevice, listener, eventData);
|
||||
Log.d(TAG, "Show start dialog, device = " + deviceAddress);
|
||||
});
|
||||
} else if (userTriggered) {
|
||||
cachedDevice.setActive();
|
||||
Log.d(TAG, "Set active device = " + deviceAddress);
|
||||
} else {
|
||||
Log.d(TAG, "Fail to handle LE audio device connected, device = " + deviceAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -649,8 +649,12 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment
|
||||
DynamicDataObserver observer) {
|
||||
Log.d(TAG, "register observer: @" + Integer.toHexString(observer.hashCode())
|
||||
+ ", uri: " + observer.getUri());
|
||||
try {
|
||||
resolver.registerContentObserver(observer.getUri(), false, observer);
|
||||
mRegisteredObservers.add(observer);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Cannot register observer: " + observer.getUri(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void unregisterDynamicDataObservers(List<DynamicDataObserver> observers) {
|
||||
@@ -661,8 +665,13 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment
|
||||
observers.forEach(observer -> {
|
||||
Log.d(TAG, "unregister observer: @" + Integer.toHexString(observer.hashCode())
|
||||
+ ", uri: " + observer.getUri());
|
||||
mRegisteredObservers.remove(observer);
|
||||
if (mRegisteredObservers.remove(observer)) {
|
||||
try {
|
||||
resolver.unregisterContentObserver(observer);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Cannot unregister observer: " + observer.getUri(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,8 @@ public class BatteryInfo {
|
||||
public int batteryStatus;
|
||||
public int pluggedStatus;
|
||||
public boolean discharging = true;
|
||||
public boolean isBatteryDefender;
|
||||
public boolean isBatteryDefender = false;
|
||||
public boolean isLongLife = false;
|
||||
public boolean isFastCharging;
|
||||
public long remainingTimeUs = 0;
|
||||
public long averageTimeToDischarge = EstimateKt.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN;
|
||||
@@ -306,7 +307,7 @@ public class BatteryInfo {
|
||||
info.pluggedStatus = batteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
|
||||
info.mCharging = info.pluggedStatus != 0;
|
||||
info.averageTimeToDischarge = estimate.getAverageDischargeTime();
|
||||
info.isBatteryDefender =
|
||||
info.isLongLife =
|
||||
batteryBroadcast.getIntExtra(
|
||||
BatteryManager.EXTRA_CHARGING_STATUS,
|
||||
BatteryManager.CHARGING_POLICY_DEFAULT)
|
||||
@@ -319,7 +320,7 @@ public class BatteryInfo {
|
||||
info.isFastCharging =
|
||||
BatteryStatus.getChargingSpeed(context, batteryBroadcast)
|
||||
== BatteryStatus.CHARGING_FAST;
|
||||
if (info.isBatteryDefender) {
|
||||
if (info.isLongLife) {
|
||||
info.isBatteryDefender =
|
||||
FeatureFactory.getFeatureFactory()
|
||||
.getPowerUsageFeatureProvider()
|
||||
|
||||
@@ -600,7 +600,7 @@ public class BatteryUtils {
|
||||
context.getContentResolver(), SETTINGS_GLOBAL_DOCK_DEFENDER_BYPASS, 0)
|
||||
== 1) {
|
||||
return DockDefenderMode.TEMPORARILY_BYPASSED;
|
||||
} else if (batteryInfo.isBatteryDefender
|
||||
} else if (batteryInfo.isLongLife
|
||||
&& FeatureFactory.getFeatureFactory()
|
||||
.getPowerUsageFeatureProvider()
|
||||
.isExtraDefend()) {
|
||||
|
||||
@@ -23,6 +23,8 @@ import android.os.Bundle;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.SparseIntArray;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.settings.fuelgauge.batteryusage.BatteryDiffData;
|
||||
import com.android.settings.fuelgauge.batteryusage.DetectRequestSourceType;
|
||||
import com.android.settings.fuelgauge.batteryusage.PowerAnomalyEventList;
|
||||
@@ -162,5 +164,7 @@ public interface PowerUsageFeatureProvider {
|
||||
|
||||
/** Collect and process battery reattribute data if needed. */
|
||||
boolean processBatteryReattributeData(
|
||||
Context context, Map<Long, BatteryDiffData> batteryDiffDataMap);
|
||||
@NonNull Context context,
|
||||
@NonNull Map<Long, BatteryDiffData> batteryDiffDataMap,
|
||||
final boolean isFromPeriodJob);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.SparseIntArray;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
import com.android.settings.fuelgauge.batteryusage.BatteryDiffData;
|
||||
import com.android.settings.fuelgauge.batteryusage.DetectRequestSourceType;
|
||||
@@ -250,7 +252,9 @@ public class PowerUsageFeatureProviderImpl implements PowerUsageFeatureProvider
|
||||
|
||||
@Override
|
||||
public boolean processBatteryReattributeData(
|
||||
Context context, Map<Long, BatteryDiffData> batteryDiffDataMap) {
|
||||
@NonNull Context context,
|
||||
@NonNull Map<Long, BatteryDiffData> batteryDiffDataMap,
|
||||
final boolean isFromPeriodJob) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,7 +422,8 @@ public class BatteryDiffEntry {
|
||||
return;
|
||||
}
|
||||
final boolean isValidPackage =
|
||||
BatteryUtils.getInstance(mContext).getPackageUid(getPackageName())
|
||||
BatteryUtils.getInstance(mContext)
|
||||
.getPackageUidAsUser(getPackageName(), (int) mUserId)
|
||||
!= BatteryUtils.UID_NULL;
|
||||
if (!isValidPackage) {
|
||||
mValidForRestriction = false;
|
||||
|
||||
@@ -22,7 +22,6 @@ import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.IPackageManager;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.BatteryConsumer;
|
||||
@@ -176,19 +175,8 @@ public class BatteryEntry {
|
||||
}
|
||||
}
|
||||
if (mDefaultPackageName != null) {
|
||||
PackageManager pm = context.getPackageManager();
|
||||
try {
|
||||
ApplicationInfo appInfo =
|
||||
pm.getApplicationInfo(mDefaultPackageName, 0 /* no flags */);
|
||||
mName = pm.getApplicationLabel(appInfo).toString();
|
||||
} catch (NameNotFoundException e) {
|
||||
Log.d(
|
||||
TAG,
|
||||
"PackageManager failed to retrieve ApplicationInfo for: "
|
||||
+ mDefaultPackageName);
|
||||
mName = mDefaultPackageName;
|
||||
}
|
||||
}
|
||||
mTimeInForegroundMs =
|
||||
uidBatteryConsumer.getTimeInProcessStateMs(
|
||||
UidBatteryConsumer.PROCESS_STATE_FOREGROUND);
|
||||
|
||||
@@ -128,9 +128,6 @@ public final class BatteryUsageDataLoader {
|
||||
final PowerUsageFeatureProvider featureProvider =
|
||||
FeatureFactory.getFeatureFactory()
|
||||
.getPowerUsageFeatureProvider();
|
||||
// Collect and process battery reattribute data.
|
||||
featureProvider.processBatteryReattributeData(
|
||||
context, batteryDiffDataMap);
|
||||
DatabaseUtils.sendBatteryUsageSlotData(
|
||||
context,
|
||||
ConvertUtils.convertToBatteryUsageSlotList(
|
||||
|
||||
@@ -28,6 +28,8 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
|
||||
import com.android.settings.overlay.FeatureFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
@@ -78,6 +80,7 @@ public class DataProcessManager {
|
||||
// Raw start timestamp with round to the nearest hour.
|
||||
private final long mRawStartTimestamp;
|
||||
private final long mLastFullChargeTimestamp;
|
||||
private final boolean mIsFromPeriodJob;
|
||||
private final Context mContext;
|
||||
private final Handler mHandler;
|
||||
private final UserIdsSeries mUserIdsSeries;
|
||||
@@ -122,6 +125,7 @@ public class DataProcessManager {
|
||||
Context context,
|
||||
Handler handler,
|
||||
final UserIdsSeries userIdsSeries,
|
||||
final boolean isFromPeriodJob,
|
||||
final long rawStartTimestamp,
|
||||
final long lastFullChargeTimestamp,
|
||||
@NonNull final OnBatteryDiffDataMapLoadedListener callbackFunction,
|
||||
@@ -130,6 +134,7 @@ public class DataProcessManager {
|
||||
mContext = context.getApplicationContext();
|
||||
mHandler = handler;
|
||||
mUserIdsSeries = userIdsSeries;
|
||||
mIsFromPeriodJob = isFromPeriodJob;
|
||||
mRawStartTimestamp = rawStartTimestamp;
|
||||
mLastFullChargeTimestamp = lastFullChargeTimestamp;
|
||||
mCallbackFunction = callbackFunction;
|
||||
@@ -147,6 +152,7 @@ public class DataProcessManager {
|
||||
mHandler = handler;
|
||||
mUserIdsSeries = userIdsSeries;
|
||||
mCallbackFunction = callbackFunction;
|
||||
mIsFromPeriodJob = false;
|
||||
mRawStartTimestamp = 0L;
|
||||
mLastFullChargeTimestamp = 0L;
|
||||
mHourlyBatteryLevelsPerDay = null;
|
||||
@@ -158,14 +164,9 @@ public class DataProcessManager {
|
||||
|
||||
/** Starts the async tasks to load battery history data and app usage data. */
|
||||
public void start() {
|
||||
start(/* isFromPeriodJob= */ false);
|
||||
}
|
||||
|
||||
/** Starts the async tasks to load battery history data and app usage data. */
|
||||
public void start(boolean isFromPeriodJob) {
|
||||
// If we have battery level data, load the battery history map and app usage simultaneously.
|
||||
if (mHourlyBatteryLevelsPerDay != null) {
|
||||
if (isFromPeriodJob) {
|
||||
if (mIsFromPeriodJob) {
|
||||
mIsCurrentBatteryHistoryLoaded = true;
|
||||
mIsCurrentAppUsageLoaded = true;
|
||||
mIsBatteryUsageSlotLoaded = true;
|
||||
@@ -514,6 +515,14 @@ public class DataProcessManager {
|
||||
mAppUsagePeriodMap,
|
||||
getSystemAppsPackageNames(),
|
||||
getSystemAppsUids()));
|
||||
// Process the reattributate data for the following two cases:
|
||||
// 1) the latest slot for the timestamp "until now"
|
||||
// 2) walkthrough all BatteryDiffData again to handle "re-compute" case
|
||||
final PowerUsageFeatureProvider featureProvider =
|
||||
FeatureFactory.getFeatureFactory()
|
||||
.getPowerUsageFeatureProvider();
|
||||
featureProvider.processBatteryReattributeData(
|
||||
mContext, batteryDiffDataMap, mIsFromPeriodJob);
|
||||
|
||||
Log.d(
|
||||
TAG,
|
||||
@@ -683,12 +692,13 @@ public class DataProcessManager {
|
||||
context,
|
||||
handler,
|
||||
userIdsSeries,
|
||||
isFromPeriodJob,
|
||||
startTimestamp,
|
||||
lastFullChargeTime,
|
||||
onBatteryDiffDataMapLoadedListener,
|
||||
batteryLevelData.getHourlyBatteryLevelsPerDay(),
|
||||
processedBatteryHistoryMap)
|
||||
.start(isFromPeriodJob);
|
||||
.start();
|
||||
|
||||
return batteryLevelData;
|
||||
}
|
||||
|
||||
@@ -65,6 +65,8 @@ public class ResetNetworkOperationBuilder {
|
||||
|
||||
private Context mContext;
|
||||
private List<Runnable> mResetSequence = new ArrayList<Runnable>();
|
||||
@Nullable
|
||||
private Consumer<Boolean> mResetEsimResultCallback = null;
|
||||
|
||||
/**
|
||||
* Constructor of builder.
|
||||
@@ -129,31 +131,32 @@ public class ResetNetworkOperationBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a step of resetting E-SIM.
|
||||
* @param callerPackage package name of caller
|
||||
* Append a result callback of resetting E-SIM.
|
||||
* @param resultCallback a callback dealing with result of resetting eSIM
|
||||
* @return this
|
||||
*/
|
||||
public ResetNetworkOperationBuilder resetEsim(String callerPackage) {
|
||||
resetEsim(callerPackage, null);
|
||||
public ResetNetworkOperationBuilder resetEsimResultCallback(Consumer<Boolean> resultCallback) {
|
||||
mResetEsimResultCallback = resultCallback;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a step of resetting E-SIM.
|
||||
* @param callerPackage package name of caller
|
||||
* @param resultCallback a Consumer<Boolean> dealing with result of resetting eSIM
|
||||
* @return this
|
||||
*/
|
||||
public ResetNetworkOperationBuilder resetEsim(String callerPackage,
|
||||
Consumer<Boolean> resultCallback) {
|
||||
public ResetNetworkOperationBuilder resetEsim(String callerPackage) {
|
||||
Runnable runnable = () -> {
|
||||
long startTime = SystemClock.elapsedRealtime();
|
||||
|
||||
if (!DRY_RUN) {
|
||||
Boolean wipped = RecoverySystem.wipeEuiccData(mContext, callerPackage);
|
||||
if (resultCallback != null) {
|
||||
resultCallback.accept(wipped);
|
||||
boolean wipped;
|
||||
if (DRY_RUN) {
|
||||
wipped = true;
|
||||
} else {
|
||||
wipped = RecoverySystem.wipeEuiccData(mContext, callerPackage);
|
||||
}
|
||||
if (mResetEsimResultCallback != null) {
|
||||
mResetEsimResultCallback.accept(wipped);
|
||||
}
|
||||
|
||||
long endTime = SystemClock.elapsedRealtime();
|
||||
|
||||
@@ -25,14 +25,17 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.conflate
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class CallStateRepository(private val context: Context) {
|
||||
private val subscriptionManager = context.requireSubscriptionManager()
|
||||
class CallStateRepository(
|
||||
private val context: Context,
|
||||
private val subscriptionRepository: SubscriptionRepository = SubscriptionRepository(context),
|
||||
) {
|
||||
|
||||
/** Flow for call state of given [subId]. */
|
||||
fun callStateFlow(subId: Int): Flow<Int> = context.telephonyCallbackFlow(subId) {
|
||||
@@ -48,9 +51,8 @@ class CallStateRepository(private val context: Context) {
|
||||
*
|
||||
* @return true if any active subscription's call state is not idle.
|
||||
*/
|
||||
fun isInCallFlow(): Flow<Boolean> = context.subscriptionsChangedFlow()
|
||||
.flatMapLatest {
|
||||
val subIds = subscriptionManager.activeSubscriptionIdList
|
||||
fun isInCallFlow(): Flow<Boolean> = subscriptionRepository.activeSubscriptionIdListFlow()
|
||||
.flatMapLatest { subIds ->
|
||||
if (subIds.isEmpty()) {
|
||||
flowOf(false)
|
||||
} else {
|
||||
@@ -59,9 +61,10 @@ class CallStateRepository(private val context: Context) {
|
||||
}
|
||||
}
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.conflate()
|
||||
.flowOn(Dispatchers.Default)
|
||||
.onEach { Log.d(TAG, "isInCallFlow: $it") }
|
||||
.flowOn(Dispatchers.Default)
|
||||
|
||||
private companion object {
|
||||
private const val TAG = "CallStateRepository"
|
||||
|
||||
@@ -29,6 +29,7 @@ import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.conflate
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filterNot
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
@@ -68,20 +69,9 @@ class SubscriptionRepository(private val context: Context) {
|
||||
}
|
||||
|
||||
fun canDisablePhysicalSubscription() = subscriptionManager.canDisablePhysicalSubscription()
|
||||
}
|
||||
|
||||
val Context.subscriptionManager: SubscriptionManager?
|
||||
get() = getSystemService(SubscriptionManager::class.java)
|
||||
|
||||
fun Context.requireSubscriptionManager(): SubscriptionManager = subscriptionManager!!
|
||||
|
||||
fun Context.phoneNumberFlow(subscriptionInfo: SubscriptionInfo) = subscriptionsChangedFlow().map {
|
||||
SubscriptionUtil.getBidiFormattedPhoneNumber(this, subscriptionInfo)
|
||||
}.filterNot { it.isNullOrEmpty() }.flowOn(Dispatchers.Default)
|
||||
|
||||
fun Context.subscriptionsChangedFlow() = callbackFlow {
|
||||
val subscriptionManager = requireSubscriptionManager()
|
||||
|
||||
/** Flow for subscriptions changes. */
|
||||
fun subscriptionsChangedFlow() = callbackFlow {
|
||||
val listener = object : SubscriptionManager.OnSubscriptionsChangedListener() {
|
||||
override fun onSubscriptionsChanged() {
|
||||
trySend(Unit)
|
||||
@@ -94,7 +84,28 @@ fun Context.subscriptionsChangedFlow() = callbackFlow {
|
||||
)
|
||||
|
||||
awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(listener) }
|
||||
}.conflate().onEach { Log.d(TAG, "subscriptions changed") }.flowOn(Dispatchers.Default)
|
||||
}.conflate().onEach { Log.d(TAG, "subscriptions changed") }.flowOn(Dispatchers.Default)
|
||||
|
||||
/** Flow of active subscription ids. */
|
||||
fun activeSubscriptionIdListFlow(): Flow<List<Int>> = context.subscriptionsChangedFlow()
|
||||
.map { subscriptionManager.activeSubscriptionIdList.sorted() }
|
||||
.distinctUntilChanged()
|
||||
.conflate()
|
||||
.onEach { Log.d(TAG, "activeSubscriptionIdList: $it") }
|
||||
.flowOn(Dispatchers.Default)
|
||||
}
|
||||
|
||||
val Context.subscriptionManager: SubscriptionManager?
|
||||
get() = getSystemService(SubscriptionManager::class.java)
|
||||
|
||||
fun Context.requireSubscriptionManager(): SubscriptionManager = subscriptionManager!!
|
||||
|
||||
fun Context.phoneNumberFlow(subscriptionInfo: SubscriptionInfo) = subscriptionsChangedFlow().map {
|
||||
SubscriptionUtil.getBidiFormattedPhoneNumber(this, subscriptionInfo)
|
||||
}.filterNot { it.isNullOrEmpty() }.flowOn(Dispatchers.Default)
|
||||
|
||||
fun Context.subscriptionsChangedFlow(): Flow<Unit> =
|
||||
SubscriptionRepository(this).subscriptionsChangedFlow()
|
||||
|
||||
/**
|
||||
* Return a list of subscriptions that are available and visible to the user.
|
||||
|
||||
@@ -25,10 +25,9 @@ import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.android.settings.R
|
||||
import com.android.settings.network.telephony.MobileDataRepository
|
||||
import com.android.settings.network.telephony.SubscriptionRepository
|
||||
import com.android.settings.network.telephony.ims.ImsMmTelRepositoryImpl
|
||||
import com.android.settings.network.telephony.requireSubscriptionManager
|
||||
import com.android.settings.network.telephony.safeGetConfig
|
||||
import com.android.settings.network.telephony.subscriptionsChangedFlow
|
||||
import com.android.settings.network.telephony.telephonyManager
|
||||
import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -48,7 +47,7 @@ class CrossSimCallingViewModel(
|
||||
private val application: Application,
|
||||
) : AndroidViewModel(application) {
|
||||
|
||||
private val subscriptionManager = application.requireSubscriptionManager()
|
||||
private val subscriptionRepository = SubscriptionRepository(application)
|
||||
private val carrierConfigManager =
|
||||
application.getSystemService(CarrierConfigManager::class.java)!!
|
||||
private val scope = viewModelScope + Dispatchers.Default
|
||||
@@ -59,9 +58,8 @@ class CrossSimCallingViewModel(
|
||||
init {
|
||||
val resources = application.resources
|
||||
if (resources.getBoolean(R.bool.config_auto_data_switch_enables_cross_sim_calling)) {
|
||||
application.subscriptionsChangedFlow()
|
||||
.flatMapLatest {
|
||||
val activeSubIds = subscriptionManager.activeSubscriptionIdList.toList()
|
||||
subscriptionRepository.activeSubscriptionIdListFlow()
|
||||
.flatMapLatest { activeSubIds ->
|
||||
merge(
|
||||
activeSubIds.anyMobileDataEnableChangedFlow(),
|
||||
updateChannel.receiveAsFlow(),
|
||||
|
||||
@@ -21,6 +21,8 @@ import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED;
|
||||
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
|
||||
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER;
|
||||
|
||||
import static com.android.server.notification.Flags.notificationHideUnusedChannels;
|
||||
|
||||
import android.app.INotificationManager;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationChannelGroup;
|
||||
@@ -78,6 +80,9 @@ public class NotificationBackend {
|
||||
|
||||
public AppRow loadAppRow(Context context, PackageManager pm, ApplicationInfo app) {
|
||||
final AppRow row = new AppRow();
|
||||
if (notificationHideUnusedChannels()) {
|
||||
row.showAllChannels = false;
|
||||
}
|
||||
row.pkg = app.packageName;
|
||||
row.uid = app.uid;
|
||||
try {
|
||||
@@ -686,5 +691,6 @@ public class NotificationBackend {
|
||||
public int channelCount;
|
||||
public Map<String, NotificationsSentState> sentByChannel;
|
||||
public NotificationsSentState sentByApp;
|
||||
public boolean showAllChannels = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,16 +16,10 @@
|
||||
|
||||
package com.android.settings.notification.app;
|
||||
|
||||
import static com.android.server.notification.Flags.notificationHideUnusedChannels;
|
||||
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import com.android.internal.widget.LockPatternUtils;
|
||||
import com.android.settings.R;
|
||||
@@ -107,38 +101,8 @@ public class AppNotificationSettings extends NotificationSettings {
|
||||
mControllers.add(new BubbleSummaryPreferenceController(context, mBackend));
|
||||
mControllers.add(new NotificationsOffPreferenceController(context));
|
||||
mControllers.add(new DeletedChannelsPreferenceController(context, mBackend));
|
||||
mControllers.add(new ShowMorePreferenceController(
|
||||
context, mDependentFieldListener, mBackend));
|
||||
return new ArrayList<>(mControllers);
|
||||
}
|
||||
|
||||
private final int SHOW_ALL_CHANNELS = 1;
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
if (notificationHideUnusedChannels()) {
|
||||
menu.add(Menu.NONE, SHOW_ALL_CHANNELS, Menu.NONE,
|
||||
mShowAll ? R.string.hide_unused_channels : R.string.show_unused_channels);
|
||||
}
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (!notificationHideUnusedChannels()) {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
switch (item.getItemId()) {
|
||||
case SHOW_ALL_CHANNELS:
|
||||
mShowAll = !mShowAll;
|
||||
item.setTitle(mShowAll
|
||||
? R.string.hide_unused_channels
|
||||
: R.string.show_unused_channels);
|
||||
ChannelListPreferenceController list =
|
||||
use(ChannelListPreferenceController.class);
|
||||
list.setShowAll(mShowAll);
|
||||
list.updateState(findPreference(list.getPreferenceKey()));
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,8 +59,6 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr
|
||||
private List<NotificationChannelGroup> mChannelGroupList;
|
||||
private PreferenceCategory mPreference;
|
||||
|
||||
private boolean mShowAll;
|
||||
|
||||
public ChannelListPreferenceController(Context context, NotificationBackend backend) {
|
||||
super(context, backend);
|
||||
}
|
||||
@@ -100,7 +98,7 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr
|
||||
@Override
|
||||
protected Void doInBackground(Void... unused) {
|
||||
if (notificationHideUnusedChannels()) {
|
||||
if (mShowAll) {
|
||||
if (mAppRow.showAllChannels) {
|
||||
mChannelGroupList = mBackend.getGroups(mAppRow.pkg, mAppRow.uid).getList();
|
||||
} else {
|
||||
mChannelGroupList = mBackend.getGroupsWithRecentBlockedFilter(mAppRow.pkg,
|
||||
@@ -123,10 +121,6 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr
|
||||
}.execute();
|
||||
}
|
||||
|
||||
protected void setShowAll(boolean showAll) {
|
||||
mShowAll = showAll;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the preferences group to match the
|
||||
* @param groupPrefsList
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.settings.notification.app;
|
||||
|
||||
import static com.android.server.notification.Flags.notificationHideUnusedChannels;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
@@ -44,6 +46,9 @@ public class DeletedChannelsPreferenceController extends NotificationPreferenceC
|
||||
if (!super.isAvailable()) {
|
||||
return false;
|
||||
}
|
||||
if (notificationHideUnusedChannels()) {
|
||||
return false;
|
||||
}
|
||||
// only visible on app screen
|
||||
if (mChannel != null || hasValidGroup()) {
|
||||
return false;
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.notification.app;
|
||||
|
||||
import static com.android.server.notification.Flags.notificationHideUnusedChannels;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.android.settings.notification.NotificationBackend;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ShowMorePreferenceController extends NotificationPreferenceController {
|
||||
|
||||
private static final String KEY = "more";
|
||||
private NotificationSettings.DependentFieldListener mDependentFieldListener;
|
||||
|
||||
public ShowMorePreferenceController(Context context,
|
||||
NotificationSettings.DependentFieldListener dependentFieldListener,
|
||||
NotificationBackend backend) {
|
||||
super(context, backend);
|
||||
mDependentFieldListener = dependentFieldListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPreferenceKey() {
|
||||
return KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
if (!notificationHideUnusedChannels()) {
|
||||
return false;
|
||||
}
|
||||
if (mAppRow == null) {
|
||||
return false;
|
||||
}
|
||||
if (mAppRow.banned || mAppRow.showAllChannels) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isIncludedInFilter() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateState(Preference preference) {
|
||||
preference.setOnPreferenceClickListener(preference1 -> {
|
||||
mAppRow.showAllChannels = true;
|
||||
mDependentFieldListener.onFieldValueChanged();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -50,14 +50,16 @@ class IconUtil {
|
||||
|
||||
/**
|
||||
* Returns a variant of the supplied {@code icon} to be used in the icon picker. The inner icon
|
||||
* is 36x36dp and it's contained into a circle of diameter 54dp.
|
||||
* is 36x36dp and it's contained into a circle of diameter 54dp. It's also set up so that
|
||||
* selection and pressed states are represented in the color.
|
||||
*/
|
||||
static Drawable makeIconCircle(@NonNull Context context, @NonNull Drawable icon) {
|
||||
ShapeDrawable background = new ShapeDrawable(new OvalShape());
|
||||
background.getPaint().setColor(Utils.getColorAttrDefaultColor(context,
|
||||
com.android.internal.R.attr.materialColorSecondaryContainer));
|
||||
icon.setTint(Utils.getColorAttrDefaultColor(context,
|
||||
com.android.internal.R.attr.materialColorOnSecondaryContainer));
|
||||
background.setTintList(
|
||||
context.getColorStateList(R.color.modes_icon_picker_item_background));
|
||||
icon = icon.mutate();
|
||||
icon.setTintList(
|
||||
context.getColorStateList(R.color.modes_icon_picker_item_icon));
|
||||
|
||||
LayerDrawable layerDrawable = new LayerDrawable(new Drawable[] { background, icon });
|
||||
|
||||
|
||||
@@ -18,13 +18,14 @@ package com.android.settings.notification.modes;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Application;
|
||||
import android.app.AutomaticZenRule;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settingslib.applications.ApplicationsState;
|
||||
import com.android.settingslib.core.AbstractPreferenceController;
|
||||
@@ -72,11 +73,9 @@ public class ZenModeFragment extends ZenModeFragmentBase {
|
||||
|
||||
// Set title for the entire screen
|
||||
ZenMode mode = getMode();
|
||||
AutomaticZenRule azr = getAZR();
|
||||
if (mode == null || azr == null) {
|
||||
return;
|
||||
if (mode != null) {
|
||||
requireActivity().setTitle(mode.getName());
|
||||
}
|
||||
getActivity().setTitle(azr.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -92,7 +91,7 @@ public class ZenModeFragment extends ZenModeFragmentBase {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onOptionsItemSelected(MenuItem item, ZenMode zenMode) {
|
||||
protected boolean onOptionsItemSelected(MenuItem item, @NonNull ZenMode zenMode) {
|
||||
switch (item.getItemId()) {
|
||||
case DELETE_MODE:
|
||||
new AlertDialog.Builder(mContext)
|
||||
|
||||
@@ -18,7 +18,6 @@ package com.android.settings.notification.modes;
|
||||
|
||||
import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
|
||||
|
||||
import android.app.AutomaticZenRule;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
@@ -34,7 +33,10 @@ import com.android.settings.R;
|
||||
import com.android.settingslib.core.AbstractPreferenceController;
|
||||
import com.android.settingslib.notification.modes.ZenMode;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Base class for Settings pages used to configure individual modes.
|
||||
@@ -175,14 +177,15 @@ abstract class ZenModeFragmentBase extends ZenModesFragmentBase {
|
||||
return mZenMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get AutomaticZenRule associated with current mode data, or null if it doesn't exist.
|
||||
*/
|
||||
@Nullable
|
||||
public AutomaticZenRule getAZR() {
|
||||
if (mZenMode == null) {
|
||||
return null;
|
||||
protected final boolean saveMode(Consumer<ZenMode> updater) {
|
||||
Preconditions.checkState(mBackend != null);
|
||||
ZenMode mode = mZenMode;
|
||||
if (mode == null) {
|
||||
Log.wtf(TAG, "Cannot save mode, it hasn't been loaded (" + getClass() + ")");
|
||||
return false;
|
||||
}
|
||||
return mZenMode.getRule();
|
||||
updater.accept(mode);
|
||||
mBackend.updateMode(mode);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,16 @@ public class ZenModeIconPickerFragment extends ZenModeFragmentBase {
|
||||
return ImmutableList.of(
|
||||
new ZenModeIconPickerIconPreferenceController(context, "current_icon", this,
|
||||
mBackend),
|
||||
new ZenModeIconPickerListPreferenceController(context, "icon_list", this,
|
||||
new IconOptionsProviderImpl(mContext), mBackend));
|
||||
new ZenModeIconPickerListPreferenceController(context, "icon_list",
|
||||
mIconPickerListener, new IconOptionsProviderImpl(mContext), mBackend));
|
||||
}
|
||||
|
||||
private final ZenModeIconPickerListPreferenceController.IconPickerListener mIconPickerListener =
|
||||
new ZenModeIconPickerListPreferenceController.IconPickerListener() {
|
||||
@Override
|
||||
public void onIconSelected(int iconResId) {
|
||||
saveMode(mode -> mode.getRule().setIconResId(iconResId));
|
||||
finish();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.android.settings.notification.modes;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -25,31 +26,35 @@ import android.widget.ImageView;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.dashboard.DashboardFragment;
|
||||
import com.android.settingslib.notification.modes.ZenMode;
|
||||
import com.android.settingslib.notification.modes.ZenModesBackend;
|
||||
import com.android.settingslib.widget.LayoutPreference;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
class ZenModeIconPickerListPreferenceController extends AbstractZenModePreferenceController {
|
||||
|
||||
private final DashboardFragment mFragment;
|
||||
private final IconOptionsProvider mIconOptionsProvider;
|
||||
private final IconPickerListener mListener;
|
||||
@Nullable private IconAdapter mAdapter;
|
||||
private @DrawableRes int mCurrentIconResId;
|
||||
|
||||
ZenModeIconPickerListPreferenceController(@NonNull Context context, @NonNull String key,
|
||||
@NonNull DashboardFragment fragment, @NonNull IconOptionsProvider iconOptionsProvider,
|
||||
@NonNull IconPickerListener listener, @NonNull IconOptionsProvider iconOptionsProvider,
|
||||
@Nullable ZenModesBackend backend) {
|
||||
super(context, key, backend);
|
||||
mFragment = fragment;
|
||||
mListener = listener;
|
||||
mIconOptionsProvider = iconOptionsProvider;
|
||||
}
|
||||
|
||||
@@ -68,20 +73,34 @@ class ZenModeIconPickerListPreferenceController extends AbstractZenModePreferenc
|
||||
recyclerView.setLayoutManager(new AutoFitGridLayoutManager(mContext));
|
||||
recyclerView.setAdapter(mAdapter);
|
||||
recyclerView.setHasFixedSize(true);
|
||||
if (recyclerView.getItemAnimator() instanceof SimpleItemAnimator animator) {
|
||||
animator.setSupportsChangeAnimations(true);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void onIconSelected(@DrawableRes int resId) {
|
||||
saveMode(mode -> {
|
||||
mode.getRule().setIconResId(resId);
|
||||
return mode;
|
||||
});
|
||||
mFragment.finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
void updateState(Preference preference, @NonNull ZenMode zenMode) {
|
||||
// Nothing to do, the current icon is shown in a different preference.
|
||||
updateIconSelection(zenMode.getRule().getIconResId());
|
||||
}
|
||||
|
||||
private void updateIconSelection(@DrawableRes int iconResId) {
|
||||
if (iconResId != mCurrentIconResId) {
|
||||
int oldIconResId = mCurrentIconResId;
|
||||
mCurrentIconResId = iconResId;
|
||||
if (mAdapter != null) {
|
||||
mAdapter.notifyIconChanged(oldIconResId);
|
||||
mAdapter.notifyIconChanged(mCurrentIconResId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onIconSelected(@DrawableRes int iconResId) {
|
||||
updateIconSelection(iconResId);
|
||||
mListener.onIconSelected(iconResId);
|
||||
}
|
||||
|
||||
interface IconPickerListener {
|
||||
void onIconSelected(@DrawableRes int iconResId);
|
||||
}
|
||||
|
||||
private class IconHolder extends RecyclerView.ViewHolder {
|
||||
@@ -93,20 +112,25 @@ class ZenModeIconPickerListPreferenceController extends AbstractZenModePreferenc
|
||||
mImageView = itemView.findViewById(R.id.icon_image_view);
|
||||
}
|
||||
|
||||
void bindIcon(IconOptionsProvider.IconInfo icon) {
|
||||
mImageView.setImageDrawable(
|
||||
IconUtil.makeIconCircle(itemView.getContext(), icon.resId()));
|
||||
void bindIcon(IconOptionsProvider.IconInfo icon, Drawable iconDrawable) {
|
||||
mImageView.setImageDrawable(iconDrawable);
|
||||
itemView.setContentDescription(icon.description());
|
||||
itemView.setOnClickListener(v -> onIconSelected(icon.resId()));
|
||||
itemView.setOnClickListener(v -> {
|
||||
itemView.setSelected(true); // Immediately, to avoid flicker until we rebind.
|
||||
onIconSelected(icon.resId());
|
||||
});
|
||||
itemView.setSelected(icon.resId() == mCurrentIconResId);
|
||||
}
|
||||
}
|
||||
|
||||
private class IconAdapter extends RecyclerView.Adapter<IconHolder> {
|
||||
|
||||
private final ImmutableList<IconOptionsProvider.IconInfo> mIconResources;
|
||||
private final Map<IconOptionsProvider.IconInfo, Drawable> mIconCache;
|
||||
|
||||
private IconAdapter(IconOptionsProvider iconOptionsProvider) {
|
||||
mIconResources = iconOptionsProvider.getIcons();
|
||||
mIconCache = new HashMap<>();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@@ -119,13 +143,24 @@ class ZenModeIconPickerListPreferenceController extends AbstractZenModePreferenc
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull IconHolder holder, int position) {
|
||||
holder.bindIcon(mIconResources.get(position));
|
||||
IconOptionsProvider.IconInfo iconInfo = mIconResources.get(position);
|
||||
Drawable iconDrawable = mIconCache.computeIfAbsent(iconInfo,
|
||||
info -> IconUtil.makeIconCircle(mContext, info.resId()));
|
||||
holder.bindIcon(iconInfo, iconDrawable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mIconResources.size();
|
||||
}
|
||||
|
||||
private void notifyIconChanged(@DrawableRes int iconResId) {
|
||||
int position = Iterables.indexOf(mIconResources,
|
||||
iconInfo -> iconInfo.resId() == iconResId);
|
||||
if (position != -1) {
|
||||
notifyItemChanged(position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class AutoFitGridLayoutManager extends GridLayoutManager {
|
||||
|
||||
@@ -28,6 +28,7 @@ import android.telephony.SubscriptionInfo;
|
||||
import android.telephony.SubscriptionManager;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.telephony.UiccCardInfo;
|
||||
import android.telephony.UiccPortInfo;
|
||||
import android.telephony.UiccSlotInfo;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -91,10 +92,10 @@ public class SimSlotChangeHandler {
|
||||
Log.e(TAG, "Unable to find the removable slot. Do nothing.");
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i(TAG, "The removableSlotInfo: " + removableSlotInfo);
|
||||
int lastRemovableSlotState = getLastRemovableSimSlotState(mContext);
|
||||
int currentRemovableSlotState = removableSlotInfo.getCardStateInfo();
|
||||
Log.i(TAG,
|
||||
Log.d(TAG,
|
||||
"lastRemovableSlotState: " + lastRemovableSlotState + ",currentRemovableSlotState: "
|
||||
+ currentRemovableSlotState);
|
||||
boolean isRemovableSimInserted =
|
||||
@@ -115,8 +116,12 @@ public class SimSlotChangeHandler {
|
||||
|
||||
if (Flags.isDualSimOnboardingEnabled()) {
|
||||
// ForNewUi, when the user inserts the psim, showing the sim onboarding for the user
|
||||
// to setup the sim switching or the default data subscription.
|
||||
handleRemovableSimInsertWhenDsds();
|
||||
// to setup the sim switching or the default data subscription in DSDS.
|
||||
// Will show dialog for below case.
|
||||
// 1. the psim slot is not active.
|
||||
// 2. there are one or more active sim.
|
||||
handleRemovableSimInsertWhenDsds(removableSlotInfo);
|
||||
return;
|
||||
} else if (!isMultipleEnabledProfilesSupported()) {
|
||||
Log.d(TAG, "The device is already in DSDS mode and no MEP. Do nothing.");
|
||||
return;
|
||||
@@ -124,8 +129,6 @@ public class SimSlotChangeHandler {
|
||||
handleRemovableSimInsertUnderDsdsMep(removableSlotInfo);
|
||||
return;
|
||||
}
|
||||
Log.d(TAG, "the device is already in DSDS mode and have the DDS. Do nothing.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (isRemovableSimInserted) {
|
||||
@@ -258,15 +261,29 @@ public class SimSlotChangeHandler {
|
||||
startChooseSimActivity(false);
|
||||
}
|
||||
|
||||
private void handleRemovableSimInsertWhenDsds() {
|
||||
private boolean hasOtherActiveSubInfo(int psimSubId) {
|
||||
List<SubscriptionInfo> activeSubs = SubscriptionUtil.getActiveSubscriptions(mSubMgr);
|
||||
return activeSubs.stream()
|
||||
.anyMatch(subscriptionInfo -> subscriptionInfo.getSubscriptionId() != psimSubId);
|
||||
}
|
||||
|
||||
private boolean hasAnyPortActiveInSlot(UiccSlotInfo removableSlotInfo) {
|
||||
return removableSlotInfo.getPorts().stream().anyMatch(UiccPortInfo::isActive);
|
||||
}
|
||||
|
||||
private void handleRemovableSimInsertWhenDsds(UiccSlotInfo removableSlotInfo) {
|
||||
Log.i(TAG, "ForNewUi: Handle Removable SIM inserted");
|
||||
List<SubscriptionInfo> subscriptionInfos = getAvailableRemovableSubscription();
|
||||
if (subscriptionInfos.isEmpty()) {
|
||||
Log.e(TAG, "Unable to find the removable subscriptionInfo. Do nothing.");
|
||||
return;
|
||||
}
|
||||
Log.d(TAG, "ForNewUi and getAvailableRemovableSubscription:"
|
||||
+ subscriptionInfos);
|
||||
startSimConfirmDialogActivity(subscriptionInfos.get(0).getSubscriptionId());
|
||||
Log.d(TAG, "getAvailableRemovableSubscription:" + subscriptionInfos);
|
||||
int psimSubId = subscriptionInfos.get(0).getSubscriptionId();
|
||||
if (!hasAnyPortActiveInSlot(removableSlotInfo) || hasOtherActiveSubInfo(psimSubId)) {
|
||||
Log.d(TAG, "ForNewUi Start Setup flow");
|
||||
startSimConfirmDialogActivity(psimSubId);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleRemovableSimInsertUnderDsdsMep(UiccSlotInfo removableSlotInfo) {
|
||||
|
||||
@@ -81,24 +81,28 @@ private fun LabelSimPreference(
|
||||
onboardingService: SimOnboardingService,
|
||||
subInfo: SubscriptionInfo,
|
||||
) {
|
||||
val originalSimCarrierName = subInfo.displayName.toString()
|
||||
var titleSimName by remember {
|
||||
mutableStateOf(onboardingService.getSubscriptionInfoDisplayName(subInfo))
|
||||
val currentSimName = onboardingService.getSubscriptionInfoDisplayName(subInfo)
|
||||
var prefTitle by remember {
|
||||
mutableStateOf(currentSimName)
|
||||
}
|
||||
var dialogInputContent by remember {
|
||||
mutableStateOf(currentSimName)
|
||||
}
|
||||
val phoneNumber = phoneNumber(subInfo)
|
||||
val alertDialogPresenter = rememberAlertDialogPresenter(
|
||||
confirmButton = AlertDialogButton(
|
||||
stringResource(R.string.mobile_network_sim_name_rename),
|
||||
titleSimName.isNotBlank()
|
||||
dialogInputContent.isNotBlank()
|
||||
) {
|
||||
onboardingService.addItemForRenaming(
|
||||
subInfo, if (titleSimName.isEmpty()) originalSimCarrierName else titleSimName
|
||||
subInfo, dialogInputContent
|
||||
)
|
||||
prefTitle = dialogInputContent
|
||||
},
|
||||
dismissButton = AlertDialogButton(
|
||||
stringResource(R.string.cancel),
|
||||
) {
|
||||
titleSimName = onboardingService.getSubscriptionInfoDisplayName(subInfo)
|
||||
// Do nothing
|
||||
},
|
||||
title = stringResource(R.string.sim_onboarding_label_sim_dialog_title),
|
||||
text = {
|
||||
@@ -107,17 +111,19 @@ private fun LabelSimPreference(
|
||||
modifier = Modifier.padding(bottom = SettingsDimension.itemPaddingVertical)
|
||||
)
|
||||
SettingsOutlinedTextField(
|
||||
value = titleSimName,
|
||||
value = dialogInputContent,
|
||||
label = stringResource(R.string.sim_onboarding_label_sim_dialog_label),
|
||||
placeholder = {Text(text = originalSimCarrierName)},
|
||||
modifier = Modifier.fillMaxWidth().testTag("contentInput")
|
||||
placeholder = {Text(text = subInfo.displayName.toString())},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag("contentInput")
|
||||
) {
|
||||
titleSimName = it
|
||||
dialogInputContent = it
|
||||
}
|
||||
},
|
||||
)
|
||||
Preference(object : PreferenceModel {
|
||||
override val title = titleSimName
|
||||
override val title = prefTitle
|
||||
override val summary = { phoneNumber.value ?: "" }
|
||||
override val onClick = alertDialogPresenter::open
|
||||
})
|
||||
|
||||
217
src/com/android/settings/system/reset/ResetNetworkConfirm.kt
Normal file
217
src/com/android/settings/system/reset/ResetNetworkConfirm.kt
Normal file
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.system.reset
|
||||
|
||||
import android.app.ProgressDialog
|
||||
import android.app.settings.SettingsEnums
|
||||
import android.os.Bundle
|
||||
import android.os.Looper
|
||||
import android.telephony.SubscriptionManager
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.android.settings.R
|
||||
import com.android.settings.ResetNetworkRequest
|
||||
import com.android.settings.Utils
|
||||
import com.android.settings.core.InstrumentedFragment
|
||||
import com.android.settings.network.ResetNetworkRestrictionViewBuilder
|
||||
import com.android.settings.network.telephony.SubscriptionRepository
|
||||
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.conflate
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* Confirm and execute a reset of the network settings to a clean "just out of the box" state.
|
||||
* Multiple confirmations are required: first, a general "are you sure you want to do this?" prompt,
|
||||
* followed by a keyguard pattern trace if the user has defined one, followed by a final
|
||||
* strongly-worded "THIS WILL RESET EVERYTHING" prompt. If at any time the phone is allowed to go to
|
||||
* sleep, is locked, et cetera, then the confirmation sequence is abandoned.
|
||||
*
|
||||
* This is the confirmation screen.
|
||||
*/
|
||||
class ResetNetworkConfirm : InstrumentedFragment() {
|
||||
@VisibleForTesting lateinit var resetNetworkRequest: ResetNetworkRequest
|
||||
private var progressDialog: ProgressDialog? = null
|
||||
private var alertDialog: AlertDialog? = null
|
||||
private var resetStarted = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
Log.d(TAG, "onCreate: $arguments")
|
||||
resetNetworkRequest = ResetNetworkRequest(arguments)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
val view = ResetNetworkRestrictionViewBuilder(requireActivity()).build()
|
||||
if (view != null) {
|
||||
Log.w(TAG, "Access deny.")
|
||||
return view
|
||||
}
|
||||
return inflater.inflate(R.layout.reset_network_confirm, null).apply {
|
||||
establishFinalConfirmationState()
|
||||
setSubtitle()
|
||||
}
|
||||
}
|
||||
|
||||
/** Configure the UI for the final confirmation interaction */
|
||||
private fun View.establishFinalConfirmationState() {
|
||||
requireViewById<View>(R.id.execute_reset_network).setOnClickListener {
|
||||
if (!Utils.isMonkeyRunning() && !resetStarted) {
|
||||
resetStarted = true
|
||||
viewLifecycleOwner.lifecycleScope.launch { onResetClicked() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun View.setSubtitle() {
|
||||
if (resetNetworkRequest.resetEsimPackageName != null) {
|
||||
requireViewById<TextView>(R.id.reset_network_confirm)
|
||||
.setText(R.string.reset_network_final_desc_esim)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
invalidSubIdFlow().collectLatestWithLifecycle(viewLifecycleOwner) { invalidSubId ->
|
||||
// Reset process could triage this callback, so if reset has started, ignore the event.
|
||||
if (!resetStarted) {
|
||||
Log.w(TAG, "subId $invalidSubId no longer active.")
|
||||
requireActivity().finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Monitor the sub ids in the request, if any sub id becomes inactive, the request is abandoned.
|
||||
*/
|
||||
private fun invalidSubIdFlow(): Flow<Int> {
|
||||
val subIdsInRequest =
|
||||
listOf(
|
||||
resetNetworkRequest.resetTelephonyAndNetworkPolicyManager,
|
||||
resetNetworkRequest.resetApnSubId,
|
||||
resetNetworkRequest.resetImsSubId,
|
||||
)
|
||||
.distinct()
|
||||
.filter(SubscriptionManager::isUsableSubscriptionId)
|
||||
|
||||
if (subIdsInRequest.isEmpty()) return emptyFlow()
|
||||
|
||||
return SubscriptionRepository(requireContext())
|
||||
.activeSubscriptionIdListFlow()
|
||||
.mapNotNull { activeSubIds -> subIdsInRequest.firstOrNull { it !in activeSubIds } }
|
||||
.conflate()
|
||||
.flowOn(Dispatchers.Default)
|
||||
}
|
||||
|
||||
/**
|
||||
* The user has gone through the multiple confirmation, so now we go ahead and reset the network
|
||||
* settings to its factory-default state.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
suspend fun onResetClicked() {
|
||||
showProgressDialog()
|
||||
resetNetwork()
|
||||
}
|
||||
|
||||
private fun showProgressDialog() {
|
||||
progressDialog =
|
||||
ProgressDialog(requireContext()).apply {
|
||||
isIndeterminate = true
|
||||
setCancelable(false)
|
||||
setMessage(requireContext().getString(R.string.main_clear_progress_text))
|
||||
show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun dismissProgressDialog() {
|
||||
progressDialog?.let { progressDialog ->
|
||||
if (progressDialog.isShowing) {
|
||||
progressDialog.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Do all reset task.
|
||||
*
|
||||
* If error happens during erasing eSIM profiles or timeout, an error msg is shown.
|
||||
*/
|
||||
private suspend fun resetNetwork() {
|
||||
var resetEsimSuccess = true
|
||||
|
||||
withContext(Dispatchers.Default) {
|
||||
val builder =
|
||||
resetNetworkRequest.toResetNetworkOperationBuilder(
|
||||
requireContext(), Looper.getMainLooper())
|
||||
resetNetworkRequest.resetEsimPackageName?.let { resetEsimPackageName ->
|
||||
builder.resetEsim(resetEsimPackageName)
|
||||
builder.resetEsimResultCallback { resetEsimSuccess = it }
|
||||
}
|
||||
builder.build().run()
|
||||
}
|
||||
|
||||
Log.d(TAG, "network factoryReset complete. succeeded: $resetEsimSuccess")
|
||||
onResetFinished(resetEsimSuccess)
|
||||
}
|
||||
|
||||
private fun onResetFinished(resetEsimSuccess: Boolean) {
|
||||
dismissProgressDialog()
|
||||
val activity = requireActivity()
|
||||
|
||||
if (!resetEsimSuccess) {
|
||||
alertDialog =
|
||||
AlertDialog.Builder(activity)
|
||||
.setTitle(R.string.reset_esim_error_title)
|
||||
.setMessage(R.string.reset_esim_error_msg)
|
||||
.setPositiveButton(android.R.string.ok, /* listener= */ null)
|
||||
.show()
|
||||
} else {
|
||||
Toast.makeText(activity, R.string.reset_network_complete_toast, Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
}
|
||||
activity.finish()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
progressDialog?.dismiss()
|
||||
alertDialog?.dismiss()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun getMetricsCategory() = SettingsEnums.RESET_NETWORK_CONFIRM
|
||||
|
||||
private companion object {
|
||||
const val TAG = "ResetNetworkConfirm"
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.spy;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
|
||||
import com.android.settings.testutils.shadow.ShadowRecoverySystem;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
import org.robolectric.Robolectric;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.android.util.concurrent.PausedExecutorService;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.shadows.ShadowLooper;
|
||||
import org.robolectric.shadows.ShadowPausedAsyncTask;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(shadows = {ShadowRecoverySystem.class, ShadowBluetoothAdapter.class})
|
||||
public class ResetNetworkConfirmTest {
|
||||
@Rule
|
||||
public final MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||
|
||||
private static final String TEST_PACKAGE = "com.android.settings";
|
||||
|
||||
private FragmentActivity mActivity;
|
||||
|
||||
@Mock
|
||||
private ResetNetworkConfirm mResetNetworkConfirm;
|
||||
private PausedExecutorService mExecutorService;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mExecutorService = new PausedExecutorService();
|
||||
ShadowPausedAsyncTask.overrideExecutor(mExecutorService);
|
||||
mResetNetworkConfirm = new ResetNetworkConfirm();
|
||||
mActivity = spy(Robolectric.setupActivity(FragmentActivity.class));
|
||||
mResetNetworkConfirm.mActivity = mActivity;
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
ShadowRecoverySystem.reset();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResetNetworkData_notResetEsim() {
|
||||
mResetNetworkConfirm.mResetNetworkRequest =
|
||||
new ResetNetworkRequest(ResetNetworkRequest.RESET_NONE);
|
||||
mResetNetworkConfirm.mResetSubscriptionContract =
|
||||
new ResetSubscriptionContract(mActivity,
|
||||
mResetNetworkConfirm.mResetNetworkRequest) {
|
||||
@Override
|
||||
public void onSubscriptionInactive(int subscriptionId) {
|
||||
mActivity.onBackPressed();
|
||||
}
|
||||
};
|
||||
|
||||
mResetNetworkConfirm.mFinalClickListener.onClick(null /* View */);
|
||||
mExecutorService.runAll();
|
||||
ShadowLooper.idleMainLooper();
|
||||
|
||||
assertThat(ShadowRecoverySystem.getWipeEuiccCalledCount()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setSubtitle_eraseEsim() {
|
||||
mResetNetworkConfirm.mResetNetworkRequest =
|
||||
new ResetNetworkRequest(ResetNetworkRequest.RESET_NONE);
|
||||
mResetNetworkConfirm.mResetNetworkRequest.setResetEsim(TEST_PACKAGE);
|
||||
|
||||
mResetNetworkConfirm.mContentView =
|
||||
LayoutInflater.from(mActivity).inflate(R.layout.reset_network_confirm, null);
|
||||
|
||||
mResetNetworkConfirm.setSubtitle();
|
||||
|
||||
assertThat(((TextView) mResetNetworkConfirm.mContentView
|
||||
.findViewById(R.id.reset_network_confirm)).getText())
|
||||
.isEqualTo(mActivity.getString(R.string.reset_network_final_desc_esim));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setSubtitle_notEraseEsim() {
|
||||
mResetNetworkConfirm.mResetNetworkRequest =
|
||||
new ResetNetworkRequest(ResetNetworkRequest.RESET_NONE);
|
||||
|
||||
mResetNetworkConfirm.mContentView =
|
||||
LayoutInflater.from(mActivity).inflate(R.layout.reset_network_confirm, null);
|
||||
|
||||
mResetNetworkConfirm.setSubtitle();
|
||||
|
||||
assertThat(((TextView) mResetNetworkConfirm.mContentView
|
||||
.findViewById(R.id.reset_network_confirm)).getText())
|
||||
.isEqualTo(mActivity.getString(R.string.reset_network_final_desc));
|
||||
}
|
||||
}
|
||||
@@ -18,66 +18,97 @@ package com.android.settings.accessibility;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.fragment.app.FragmentFactory;
|
||||
import androidx.fragment.app.testing.FragmentScenario;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settingslib.widget.FooterPreference;
|
||||
|
||||
import com.google.android.setupcompat.template.FooterBarMixin;
|
||||
import com.google.android.setupdesign.GlifLayout;
|
||||
import com.google.android.setupdesign.GlifPreferenceLayout;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
/** Tests for {@link AutoBrightnessPreferenceFragmentForSetupWizard}. */
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class AutoBrightnessPreferenceFragmentForSetupWizardTest {
|
||||
|
||||
@Rule
|
||||
public final MockitoRule mMockito = MockitoJUnit.rule();
|
||||
// Same as AutoBrightnessPreferenceFragmentForSetupWizard#FOOTER_PREFERENCE_KEY
|
||||
private static final String FOOTER_PREFERENCE_KEY = "auto_brightness_footer";
|
||||
|
||||
private FragmentScenario<AutoBrightnessPreferenceFragmentForSetupWizard> mFragmentScenario;
|
||||
|
||||
@Spy
|
||||
private final Context mContext = ApplicationProvider.getApplicationContext();
|
||||
@Mock
|
||||
private GlifPreferenceLayout mGlifLayoutView;
|
||||
@Mock
|
||||
private FooterBarMixin mFooterBarMixin;
|
||||
private AutoBrightnessPreferenceFragmentForSetupWizard mFragment;
|
||||
private GlifLayout mGlifLayout;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mFragment = spy(new AutoBrightnessPreferenceFragmentForSetupWizard());
|
||||
doReturn(mock(LifecycleOwner.class)).when(mFragment).getViewLifecycleOwner();
|
||||
doReturn(mContext).when(mFragment).getContext();
|
||||
when(mGlifLayoutView.getMixin(eq(FooterBarMixin.class))).thenReturn(mFooterBarMixin);
|
||||
mFragmentScenario = FragmentScenario
|
||||
.launch(
|
||||
AutoBrightnessPreferenceFragmentForSetupWizard.class,
|
||||
/* fragmentArgs= */ (Bundle) null,
|
||||
R.style.GlifTheme,
|
||||
/* factory= */ (FragmentFactory) null)
|
||||
.moveToState(Lifecycle.State.RESUMED);
|
||||
mFragmentScenario.onFragment(fragment -> mFragment = fragment);
|
||||
|
||||
View view = mFragment.getView();
|
||||
assertThat(view).isInstanceOf(GlifPreferenceLayout.class);
|
||||
mGlifLayout = (GlifLayout) view;
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
mFragmentScenario.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setHeaderText_onViewCreated_verifyAction() {
|
||||
final String title = "title";
|
||||
doReturn(title).when(mContext).getString(R.string.auto_brightness_title);
|
||||
public void onViewCreated_verifyGlifHerderText() {
|
||||
assertThat(mGlifLayout.getHeaderText())
|
||||
.isEqualTo(mFragment.getString(R.string.auto_brightness_title));
|
||||
}
|
||||
|
||||
mFragment.onViewCreated(mGlifLayoutView, null);
|
||||
@Test
|
||||
public void onViewCreated_verifyGlifFooter() {
|
||||
FooterBarMixin footerMixin = mGlifLayout.getMixin(FooterBarMixin.class);
|
||||
assertThat(footerMixin).isNotNull();
|
||||
|
||||
verify(mGlifLayoutView).setHeaderText(title);
|
||||
Button footerButton = footerMixin.getPrimaryButtonView();
|
||||
assertThat(footerButton).isNotNull();
|
||||
assertThat(footerButton.getText().toString()).isEqualTo(mFragment.getString(R.string.done));
|
||||
|
||||
footerButton.performClick();
|
||||
assertThat(mFragment.getActivity().isFinishing()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onViewCreated_verifyFooterPreference() {
|
||||
Preference pref = mFragment.findPreference(FOOTER_PREFERENCE_KEY);
|
||||
assertThat(pref).isInstanceOf(FooterPreference.class);
|
||||
|
||||
FooterPreference footerPref = (FooterPreference) pref;
|
||||
String exactTitle = footerPref.getTitle().toString();
|
||||
assertThat(exactTitle).isEqualTo(mFragment.getString(R.string.auto_brightness_description));
|
||||
|
||||
// Ensure that footer content description has "About XXX" prefix for consistency with other
|
||||
// accessibility suw pages
|
||||
String expectedContentDescription =
|
||||
mFragment.getString(R.string.auto_brightness_content_description_title)
|
||||
+ "\n\n" + exactTitle;
|
||||
assertThat(footerPref.getContentDescription().toString())
|
||||
.isEqualTo(expectedContentDescription);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -85,11 +116,4 @@ public class AutoBrightnessPreferenceFragmentForSetupWizardTest {
|
||||
assertThat(mFragment.getMetricsCategory()).isEqualTo(
|
||||
SettingsEnums.SUW_ACCESSIBILITY_AUTO_BRIGHTNESS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onViewCreated_verifyAction() {
|
||||
mFragment.onViewCreated(mGlifLayoutView, null);
|
||||
|
||||
verify(mFooterBarMixin).setPrimaryButton(any());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package com.android.settings.biometrics.fingerprint;
|
||||
|
||||
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON;
|
||||
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_REAR;
|
||||
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL;
|
||||
|
||||
import static com.android.settings.biometrics.fingerprint.FingerprintSettings.FingerprintSettingsFragment;
|
||||
@@ -34,16 +33,13 @@ import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.hardware.biometrics.ComponentInfoInternal;
|
||||
import android.hardware.biometrics.SensorProperties;
|
||||
import android.hardware.fingerprint.Fingerprint;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.hardware.fingerprint.FingerprintSensorProperties;
|
||||
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
|
||||
@@ -84,7 +80,6 @@ import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(shadows = {ShadowSettingsPreferenceFragment.class, ShadowUtils.class, ShadowFragment.class,
|
||||
@@ -152,6 +147,7 @@ public class FingerprintSettingsFragmentTest {
|
||||
public void testCancellationSignalLifeCycle() {
|
||||
setUpFragment(false);
|
||||
|
||||
mFingerprintAuthenticateSidecar.setFingerprintManager(mFingerprintManager);
|
||||
|
||||
doNothing().when(mFingerprintManager).authenticate(any(),
|
||||
mCancellationSignalArgumentCaptor.capture(),
|
||||
@@ -217,7 +213,6 @@ public class FingerprintSettingsFragmentTest {
|
||||
doReturn(fragmentManager).when(mActivity).getSupportFragmentManager();
|
||||
|
||||
mFingerprintAuthenticateSidecar = new FingerprintAuthenticateSidecar();
|
||||
mFingerprintAuthenticateSidecar.setFingerprintManager(mFingerprintManager);
|
||||
doReturn(mFingerprintAuthenticateSidecar).when(fragmentManager).findFragmentByTag(
|
||||
"authenticate_sidecar");
|
||||
|
||||
@@ -251,27 +246,4 @@ public class FingerprintSettingsFragmentTest {
|
||||
true /* resetLockoutRequiresHardwareAuthToken */));
|
||||
doReturn(props).when(mFingerprintManager).getSensorPropertiesInternal();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthOnFragmentSetup() {
|
||||
doReturn(List.of(new Fingerprint("Finger 1", 1, 2, 3)))
|
||||
.when(mFingerprintManager).getEnrolledFingerprints(anyInt());
|
||||
setUpFragment(false, 1, TYPE_REAR);
|
||||
|
||||
verify(mFingerprintManager).authenticate(any(), any(),
|
||||
any(), any(), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testErrorCancelledRestartsAuth() {
|
||||
doReturn(List.of(new Fingerprint("Finger 1", 1, 2, 3)))
|
||||
.when(mFingerprintManager).getEnrolledFingerprints(anyInt());
|
||||
setUpFragment(false, 1, TYPE_REAR);
|
||||
|
||||
// When we receive a cancel, we should restart auth.
|
||||
mFragment.handleError(FingerprintManager.FINGERPRINT_ERROR_CANCELED, "blah");
|
||||
|
||||
verify(mFingerprintManager, times(2)).authenticate(any(), any(),
|
||||
any(), any(), anyInt());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ import android.bluetooth.BluetoothHapPresetInfo;
|
||||
import androidx.preference.ListPreference;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
|
||||
import com.android.settingslib.bluetooth.HapClientProfile;
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothManager;
|
||||
@@ -215,11 +216,13 @@ public class BluetoothDetailsHearingAidsPresetsControllerTest extends
|
||||
|
||||
assertThat(mController.getPreference()).isNotNull();
|
||||
assertThat(mController.getPreference().isEnabled()).isFalse();
|
||||
assertThat(String.valueOf(mController.getPreference().getSummary())).isEqualTo(
|
||||
mContext.getString(R.string.bluetooth_hearing_aids_presets_empty_list_message));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void refresh_validPresetInfo_preferenceEnabled() {
|
||||
BluetoothHapPresetInfo info = getTestPresetInfo();
|
||||
BluetoothHapPresetInfo info = getTestPresetInfo(true);
|
||||
when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(List.of(info));
|
||||
|
||||
mController.refresh();
|
||||
@@ -230,7 +233,7 @@ public class BluetoothDetailsHearingAidsPresetsControllerTest extends
|
||||
|
||||
@Test
|
||||
public void refresh_invalidActivePresetIndex_summaryIsNull() {
|
||||
BluetoothHapPresetInfo info = getTestPresetInfo();
|
||||
BluetoothHapPresetInfo info = getTestPresetInfo(true);
|
||||
when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(List.of(info));
|
||||
when(mHapClientProfile.getActivePresetIndex(mDevice)).thenReturn(PRESET_INDEX_UNAVAILABLE);
|
||||
|
||||
@@ -242,7 +245,7 @@ public class BluetoothDetailsHearingAidsPresetsControllerTest extends
|
||||
|
||||
@Test
|
||||
public void refresh_validActivePresetIndex_summaryIsNotNull() {
|
||||
BluetoothHapPresetInfo info = getTestPresetInfo();
|
||||
BluetoothHapPresetInfo info = getTestPresetInfo(true);
|
||||
when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(List.of(info));
|
||||
when(mHapClientProfile.getActivePresetIndex(mDevice)).thenReturn(TEST_PRESET_INDEX);
|
||||
|
||||
@@ -262,10 +265,30 @@ public class BluetoothDetailsHearingAidsPresetsControllerTest extends
|
||||
verify(mHapClientProfile).selectPreset(mDevice, TEST_PRESET_INDEX);
|
||||
}
|
||||
|
||||
private BluetoothHapPresetInfo getTestPresetInfo() {
|
||||
@Test
|
||||
public void loadAllPresetInfo_unavailablePreset_notAddedToEntries() {
|
||||
BluetoothHapPresetInfo info = getTestPresetInfo(false);
|
||||
when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(List.of(info));
|
||||
|
||||
mController.refresh();
|
||||
|
||||
assertThat(mController.getPreference().getEntries().length).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadAllPresetInfo_availablePreset_addedToEntries() {
|
||||
BluetoothHapPresetInfo info = getTestPresetInfo(true);
|
||||
when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(List.of(info));
|
||||
|
||||
mController.refresh();
|
||||
|
||||
assertThat(mController.getPreference().getEntries().length).isEqualTo(1);
|
||||
}
|
||||
private BluetoothHapPresetInfo getTestPresetInfo(boolean available) {
|
||||
BluetoothHapPresetInfo info = mock(BluetoothHapPresetInfo.class);
|
||||
when(info.getName()).thenReturn(TEST_PRESET_NAME);
|
||||
when(info.getIndex()).thenReturn(TEST_PRESET_INDEX);
|
||||
when(info.isAvailable()).thenReturn(available);
|
||||
return info;
|
||||
}
|
||||
|
||||
|
||||
@@ -248,6 +248,23 @@ public class AudioSharingDialogHandlerTest {
|
||||
verify(mCachedDevice1).setActive();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleUserTriggeredLeaDeviceConnected_noSharingLeaDeviceInErrorState_setActive() {
|
||||
setUpBroadcast(false);
|
||||
when(mCachedDevice1.getGroupId()).thenReturn(-1);
|
||||
when(mLeAudioProfile.getGroupId(mDevice1)).thenReturn(-1);
|
||||
ImmutableList<BluetoothDevice> deviceList = ImmutableList.of(mDevice1, mDevice3);
|
||||
when(mAssistant.getDevicesMatchingConnectionStates(
|
||||
new int[] {BluetoothProfile.STATE_CONNECTED}))
|
||||
.thenReturn(deviceList);
|
||||
when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of());
|
||||
mHandler.handleDeviceConnected(mCachedDevice1, /* userTriggered= */ true);
|
||||
shadowOf(Looper.getMainLooper()).idle();
|
||||
List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
|
||||
assertThat(childFragments).isEmpty();
|
||||
verify(mCachedDevice1).setActive();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleUserTriggeredLeaDeviceConnected_noSharingTwoLeaDevices_showJoinDialog() {
|
||||
setUpBroadcast(false);
|
||||
@@ -451,6 +468,23 @@ public class AudioSharingDialogHandlerTest {
|
||||
verify(mCachedDevice1, never()).setActive();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleLeaDeviceConnected_noSharingLeaDeviceInErrorState_doNothing() {
|
||||
setUpBroadcast(false);
|
||||
when(mCachedDevice1.getGroupId()).thenReturn(-1);
|
||||
when(mLeAudioProfile.getGroupId(mDevice1)).thenReturn(-1);
|
||||
ImmutableList<BluetoothDevice> deviceList = ImmutableList.of(mDevice1, mDevice3);
|
||||
when(mAssistant.getDevicesMatchingConnectionStates(
|
||||
new int[] {BluetoothProfile.STATE_CONNECTED}))
|
||||
.thenReturn(deviceList);
|
||||
when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of());
|
||||
mHandler.handleDeviceConnected(mCachedDevice1, /* userTriggered= */ false);
|
||||
shadowOf(Looper.getMainLooper()).idle();
|
||||
List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
|
||||
assertThat(childFragments).isEmpty();
|
||||
verify(mCachedDevice1, never()).setActive();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleLeaDeviceConnected_noSharingTwoLeaDevices_showJoinDialog() {
|
||||
setUpBroadcast(false);
|
||||
|
||||
@@ -789,6 +789,40 @@ public class BatteryInfoTest {
|
||||
expectedChargeLabel);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getBatteryInfo_longlife_shouldSetLonglife() {
|
||||
var batteryIntent = createIntentForLongLifeTest(/* hasLongLife= */ true);
|
||||
|
||||
var batteryInfo =
|
||||
BatteryInfo.getBatteryInfo(
|
||||
mContext,
|
||||
batteryIntent,
|
||||
mBatteryUsageStats,
|
||||
/* estimate= */ MOCK_ESTIMATE,
|
||||
/* elapsedRealtimeUs= */ 0L,
|
||||
/* shortString= */ false,
|
||||
/* currentTimeMs= */ 0L);
|
||||
|
||||
assertThat(batteryInfo.isLongLife).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getBatteryInfo_noLonglife_shouldNotLonglife() {
|
||||
var batteryIntent = createIntentForLongLifeTest(/* hasLongLife= */ false);
|
||||
|
||||
var batteryInfo =
|
||||
BatteryInfo.getBatteryInfo(
|
||||
mContext,
|
||||
batteryIntent,
|
||||
mBatteryUsageStats,
|
||||
/* estimate= */ MOCK_ESTIMATE,
|
||||
/* elapsedRealtimeUs= */ 0L,
|
||||
/* shortString= */ false,
|
||||
/* currentTimeMs= */ 0L);
|
||||
|
||||
assertThat(batteryInfo.isLongLife).isFalse();
|
||||
}
|
||||
|
||||
private enum ChargingSpeed {
|
||||
FAST,
|
||||
REGULAR,
|
||||
@@ -801,6 +835,15 @@ public class BatteryInfoTest {
|
||||
DOCKED
|
||||
}
|
||||
|
||||
private Intent createIntentForLongLifeTest(Boolean hasLongLife) {
|
||||
return new Intent(Intent.ACTION_BATTERY_CHANGED)
|
||||
.putExtra(
|
||||
BatteryManager.EXTRA_CHARGING_STATUS,
|
||||
hasLongLife
|
||||
? BatteryManager.CHARGING_POLICY_ADAPTIVE_LONGLIFE
|
||||
: BatteryManager.CHARGING_POLICY_DEFAULT);
|
||||
}
|
||||
|
||||
private Intent createIntentForGetBatteryInfoTest(
|
||||
ChargingType chargingType, ChargingSpeed chargingSpeed, int batteryLevel) {
|
||||
return createBatteryIntent(
|
||||
|
||||
@@ -494,6 +494,7 @@ public final class BatteryDiffEntryTest {
|
||||
final ContentValues values =
|
||||
getContentValuesWithType(ConvertUtils.CONSUMER_TYPE_UID_BATTERY);
|
||||
values.put(BatteryHistEntry.KEY_UID, /*invalid uid*/ 10001);
|
||||
values.put(BatteryHistEntry.KEY_USER_ID, /*valid userid*/ USER_ID);
|
||||
values.put(BatteryHistEntry.KEY_PACKAGE_NAME, fakePackageName);
|
||||
final BatteryDiffEntry entry = createBatteryDiffEntry(10, new BatteryHistEntry(values));
|
||||
|
||||
@@ -503,14 +504,16 @@ public final class BatteryDiffEntryTest {
|
||||
|
||||
doReturn(BatteryUtils.UID_NULL)
|
||||
.when(mMockPackageManager)
|
||||
.getPackageUid(entry.getPackageName(), PackageManager.GET_META_DATA);
|
||||
.getPackageUidAsUser(
|
||||
entry.getPackageName(), PackageManager.GET_META_DATA, USER_ID);
|
||||
entry.updateRestrictionFlagState();
|
||||
// Sets false if the app is invalid package name.
|
||||
assertThat(entry.mValidForRestriction).isFalse();
|
||||
|
||||
doReturn(1000)
|
||||
.when(mMockPackageManager)
|
||||
.getPackageUid(entry.getPackageName(), PackageManager.GET_META_DATA);
|
||||
.getPackageUidAsUser(
|
||||
entry.getPackageName(), PackageManager.GET_META_DATA, USER_ID);
|
||||
entry.updateRestrictionFlagState();
|
||||
// Sets false if the app PackageInfo cannot be found.
|
||||
assertThat(entry.mValidForRestriction).isFalse();
|
||||
|
||||
@@ -132,7 +132,7 @@ public class BatteryEntryTest {
|
||||
createBatteryEntryForApp(null, APP_DEFAULT_PACKAGE_NAME, HIGH_DRAIN_PACKAGE);
|
||||
|
||||
assertThat(entry.getDefaultPackageName()).isEqualTo(APP_DEFAULT_PACKAGE_NAME);
|
||||
assertThat(entry.getLabel()).isEqualTo(LABEL_PREFIX + APP_DEFAULT_PACKAGE_NAME);
|
||||
assertThat(entry.getLabel()).isEqualTo(APP_DEFAULT_PACKAGE_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -152,7 +152,7 @@ public class BatteryEntryTest {
|
||||
|
||||
BatteryEntry entry = createBatteryEntryForApp(null, null, HIGH_DRAIN_PACKAGE);
|
||||
|
||||
assertThat(entry.getLabel()).isEqualTo(LABEL_PREFIX + HIGH_DRAIN_PACKAGE);
|
||||
assertThat(entry.getLabel()).isEqualTo(HIGH_DRAIN_PACKAGE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -163,7 +163,7 @@ public class BatteryEntryTest {
|
||||
null,
|
||||
HIGH_DRAIN_PACKAGE);
|
||||
|
||||
assertThat(entry.getLabel()).isEqualTo(LABEL_PREFIX + HIGH_DRAIN_PACKAGE);
|
||||
assertThat(entry.getLabel()).isEqualTo(HIGH_DRAIN_PACKAGE);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -112,6 +112,7 @@ public final class DataProcessManagerTest {
|
||||
mContext,
|
||||
/* handler= */ null,
|
||||
mUserIdsSeries,
|
||||
/* isFromPeriodJob= */ false,
|
||||
/* rawStartTimestamp= */ 0L,
|
||||
/* lastFullChargeTimestamp= */ 0L,
|
||||
/* callbackFunction= */ null,
|
||||
@@ -258,6 +259,7 @@ public final class DataProcessManagerTest {
|
||||
mContext,
|
||||
/* handler= */ null,
|
||||
mUserIdsSeries,
|
||||
/* isFromPeriodJob= */ false,
|
||||
/* rawStartTimestamp= */ 2L,
|
||||
/* lastFullChargeTimestamp= */ 1L,
|
||||
/* callbackFunction= */ null,
|
||||
|
||||
@@ -31,12 +31,17 @@ import android.app.NotificationChannelGroup;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.os.UserManager;
|
||||
import android.platform.test.annotations.DisableFlags;
|
||||
import android.platform.test.annotations.EnableFlags;
|
||||
import android.platform.test.flag.junit.SetFlagsRule;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import com.android.server.notification.Flags;
|
||||
import com.android.settings.notification.NotificationBackend;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
@@ -60,6 +65,8 @@ public class DeletedChannelsPreferenceControllerTest {
|
||||
private UserManager mUm;
|
||||
|
||||
private DeletedChannelsPreferenceController mController;
|
||||
@Rule
|
||||
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
@@ -109,6 +116,16 @@ public class DeletedChannelsPreferenceControllerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFlags(Flags.FLAG_NOTIFICATION_HIDE_UNUSED_CHANNELS)
|
||||
public void isAvailable_notIfFlagEnabled() {
|
||||
when(mBackend.getDeletedChannelCount(any(), anyInt())).thenReturn(1);
|
||||
mController.onResume(
|
||||
new NotificationBackend.AppRow(), null, null, null, null, null, new ArrayList<>());
|
||||
assertFalse(mController.isAvailable());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisableFlags(Flags.FLAG_NOTIFICATION_HIDE_UNUSED_CHANNELS)
|
||||
public void isAvailable_appScreen() {
|
||||
when(mBackend.getDeletedChannelCount(any(), anyInt())).thenReturn(1);
|
||||
mController.onResume(
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.notification.app;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.app.NotificationChannelGroup;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.os.UserManager;
|
||||
import android.platform.test.annotations.EnableFlags;
|
||||
import android.platform.test.flag.junit.SetFlagsRule;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import com.android.server.notification.Flags;
|
||||
import com.android.settings.notification.NotificationBackend;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.shadows.ShadowApplication;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@EnableFlags(Flags.FLAG_NOTIFICATION_HIDE_UNUSED_CHANNELS)
|
||||
public class ShowMorePreferenceControllerTest {
|
||||
|
||||
private Context mContext;
|
||||
@Mock
|
||||
private NotificationBackend mBackend;
|
||||
@Mock
|
||||
private NotificationManager mNm;
|
||||
@Mock
|
||||
private UserManager mUm;
|
||||
@Mock
|
||||
private NotificationSettings.DependentFieldListener mDependentFieldListener;
|
||||
|
||||
private ShowMorePreferenceController mController;
|
||||
@Rule
|
||||
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
ShadowApplication shadowApplication = ShadowApplication.getInstance();
|
||||
shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNm);
|
||||
shadowApplication.setSystemService(Context.USER_SERVICE, mUm);
|
||||
mContext = RuntimeEnvironment.application;
|
||||
mController = new ShowMorePreferenceController(mContext, mDependentFieldListener, mBackend);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noCrashIfNoOnResume() {
|
||||
mController.isAvailable();
|
||||
mController.updateState(mock(Preference.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAvailable_notIfAppBlocked() {
|
||||
NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
|
||||
appRow.banned = true;
|
||||
appRow.showAllChannels = false;
|
||||
mController.onResume(appRow, null, null, null, null, null, null);
|
||||
assertFalse(mController.isAvailable());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAvailable_notIfShowingAll() {
|
||||
NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
|
||||
mController.onResume(appRow, null, mock(NotificationChannelGroup.class), null, null, null,
|
||||
null);
|
||||
assertFalse(mController.isAvailable());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateState() {
|
||||
NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
|
||||
appRow.banned = false;
|
||||
appRow.showAllChannels = false;
|
||||
mController.onResume(appRow, null, null, null, null, null, null);
|
||||
|
||||
Preference pref = new Preference(mContext);
|
||||
mController.updateState(pref);
|
||||
|
||||
pref.performClick();
|
||||
|
||||
verify(mDependentFieldListener).onFieldValueChanged();
|
||||
assertThat(appRow.showAllChannels).isTrue();
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import android.service.notification.ZenDeviceEffects;
|
||||
import android.service.notification.ZenModeConfig;
|
||||
import android.service.notification.ZenPolicy;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.settingslib.notification.modes.ZenMode;
|
||||
@@ -70,13 +71,13 @@ class TestModeBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public TestModeBuilder setName(String name) {
|
||||
TestModeBuilder setName(String name) {
|
||||
mRule.setName(name);
|
||||
mConfigZenRule.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TestModeBuilder setPackage(String pkg) {
|
||||
TestModeBuilder setPackage(String pkg) {
|
||||
mRule.setPackageName(pkg);
|
||||
mConfigZenRule.pkg = pkg;
|
||||
return this;
|
||||
@@ -114,7 +115,7 @@ class TestModeBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public TestModeBuilder setEnabled(boolean enabled) {
|
||||
TestModeBuilder setEnabled(boolean enabled) {
|
||||
mRule.setEnabled(enabled);
|
||||
mConfigZenRule.enabled = enabled;
|
||||
return this;
|
||||
@@ -126,12 +127,17 @@ class TestModeBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public TestModeBuilder setTriggerDescription(@Nullable String triggerDescription) {
|
||||
TestModeBuilder setTriggerDescription(@Nullable String triggerDescription) {
|
||||
mRule.setTriggerDescription(triggerDescription);
|
||||
mConfigZenRule.triggerDescription = triggerDescription;
|
||||
return this;
|
||||
}
|
||||
|
||||
TestModeBuilder setIconResId(@DrawableRes int iconResId) {
|
||||
mRule.setIconResId(iconResId);
|
||||
return this;
|
||||
}
|
||||
|
||||
TestModeBuilder setActive(boolean active) {
|
||||
if (active) {
|
||||
mConfigZenRule.enabled = true;
|
||||
|
||||
@@ -24,13 +24,15 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.dashboard.DashboardFragment;
|
||||
import com.android.settingslib.notification.modes.ZenMode;
|
||||
import com.android.settingslib.notification.modes.ZenModesBackend;
|
||||
import com.android.settingslib.widget.LayoutPreference;
|
||||
@@ -40,35 +42,34 @@ import com.google.common.collect.ImmutableList;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class ZenModeIconPickerListPreferenceControllerTest {
|
||||
|
||||
private static final ZenMode ZEN_MODE = TestModeBuilder.EXAMPLE;
|
||||
|
||||
private ZenModesBackend mBackend;
|
||||
private Context mContext;
|
||||
private ZenModeIconPickerListPreferenceController mController;
|
||||
private PreferenceScreen mPreferenceScreen;
|
||||
@Mock private PreferenceScreen mPreferenceScreen;
|
||||
private LayoutPreference mLayoutPreference;
|
||||
private RecyclerView mRecyclerView;
|
||||
@Mock private ZenModeIconPickerListPreferenceController.IconPickerListener mListener;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
Context context = RuntimeEnvironment.getApplication();
|
||||
mBackend = mock(ZenModesBackend.class);
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mContext = RuntimeEnvironment.getApplication();
|
||||
|
||||
DashboardFragment fragment = mock(DashboardFragment.class);
|
||||
mController = new ZenModeIconPickerListPreferenceController(
|
||||
RuntimeEnvironment.getApplication(), "icon_list", fragment,
|
||||
new TestIconOptionsProvider(), mBackend);
|
||||
RuntimeEnvironment.getApplication(), "icon_list", mListener,
|
||||
new TestIconOptionsProvider(), mock(ZenModesBackend.class));
|
||||
|
||||
mRecyclerView = new RecyclerView(context);
|
||||
mRecyclerView = new RecyclerView(mContext);
|
||||
mRecyclerView.setId(R.id.icon_list);
|
||||
LayoutPreference layoutPreference = new LayoutPreference(context, mRecyclerView);
|
||||
mPreferenceScreen = mock(PreferenceScreen.class);
|
||||
when(mPreferenceScreen.findPreference(eq("icon_list"))).thenReturn(layoutPreference);
|
||||
mLayoutPreference = new LayoutPreference(mContext, mRecyclerView);
|
||||
when(mPreferenceScreen.findPreference(eq("icon_list"))).thenReturn(mLayoutPreference);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -80,14 +81,32 @@ public class ZenModeIconPickerListPreferenceControllerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void selectIcon_updatesMode() {
|
||||
mController.setZenMode(ZEN_MODE);
|
||||
public void updateState_highlightsCurrentIcon() {
|
||||
ZenMode mode = new TestModeBuilder().setIconResId(R.drawable.ic_hearing).build();
|
||||
mController.displayPreference(mPreferenceScreen);
|
||||
|
||||
mController.onIconSelected(R.drawable.ic_android);
|
||||
mController.updateZenMode(mLayoutPreference, mode);
|
||||
|
||||
ArgumentCaptor<ZenMode> captor = ArgumentCaptor.forClass(ZenMode.class);
|
||||
verify(mBackend).updateMode(captor.capture());
|
||||
assertThat(captor.getValue().getRule().getIconResId()).isEqualTo(R.drawable.ic_android);
|
||||
assertThat(getItemViewAt(0).isSelected()).isFalse();
|
||||
assertThat(getItemViewAt(1).isSelected()).isFalse();
|
||||
assertThat(getItemViewAt(2).isSelected()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void performClick_onIconItem_notifiesListener() {
|
||||
mController.displayPreference(mPreferenceScreen);
|
||||
|
||||
getItemViewAt(1).performClick();
|
||||
|
||||
verify(mListener).onIconSelected(R.drawable.ic_info);
|
||||
}
|
||||
|
||||
private View getItemViewAt(int position) {
|
||||
ViewGroup fakeParent = new FrameLayout(mContext);
|
||||
RecyclerView.ViewHolder viewHolder = mRecyclerView.getAdapter().onCreateViewHolder(
|
||||
fakeParent, 0);
|
||||
mRecyclerView.getAdapter().bindViewHolder(viewHolder, position);
|
||||
return viewHolder.itemView;
|
||||
}
|
||||
|
||||
private static class TestIconOptionsProvider implements IconOptionsProvider {
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package com.android.settings.network.telephony
|
||||
|
||||
import android.content.Context
|
||||
import android.telephony.SubscriptionManager
|
||||
import android.telephony.TelephonyCallback
|
||||
import android.telephony.TelephonyManager
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
@@ -27,6 +26,7 @@ import com.android.settingslib.spa.testutils.toListWithTimeout
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
@@ -49,20 +49,15 @@ class CallStateRepositoryTest {
|
||||
}
|
||||
}
|
||||
|
||||
private val mockSubscriptionManager = mock<SubscriptionManager> {
|
||||
on { activeSubscriptionIdList } doReturn intArrayOf(SUB_ID)
|
||||
on { addOnSubscriptionsChangedListener(any(), any()) } doAnswer {
|
||||
val listener = it.arguments[1] as SubscriptionManager.OnSubscriptionsChangedListener
|
||||
listener.onSubscriptionsChanged()
|
||||
}
|
||||
private val mockSubscriptionRepository = mock<SubscriptionRepository> {
|
||||
on { activeSubscriptionIdListFlow() } doReturn flowOf(listOf(SUB_ID))
|
||||
}
|
||||
|
||||
private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
|
||||
on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager
|
||||
on { subscriptionManager } doReturn mockSubscriptionManager
|
||||
}
|
||||
|
||||
private val repository = CallStateRepository(context)
|
||||
private val repository = CallStateRepository(context, mockSubscriptionRepository)
|
||||
|
||||
@Test
|
||||
fun callStateFlow_initial_sendInitialState() = runBlocking {
|
||||
@@ -89,8 +84,8 @@ class CallStateRepositoryTest {
|
||||
|
||||
@Test
|
||||
fun isInCallFlow_noActiveSubscription() = runBlocking {
|
||||
mockSubscriptionManager.stub {
|
||||
on { activeSubscriptionIdList } doReturn intArrayOf()
|
||||
mockSubscriptionRepository.stub {
|
||||
on { activeSubscriptionIdListFlow() } doReturn flowOf(emptyList())
|
||||
}
|
||||
|
||||
val isInCall = repository.isInCallFlow().firstWithTimeoutOrNull()
|
||||
|
||||
@@ -77,7 +77,7 @@ class SubscriptionRepositoryTest {
|
||||
|
||||
@Test
|
||||
fun subscriptionsChangedFlow_hasInitialValue() = runBlocking {
|
||||
val initialValue = context.subscriptionsChangedFlow().firstWithTimeoutOrNull()
|
||||
val initialValue = repository.subscriptionsChangedFlow().firstWithTimeoutOrNull()
|
||||
|
||||
assertThat(initialValue).isSameInstanceAs(Unit)
|
||||
}
|
||||
@@ -85,7 +85,7 @@ class SubscriptionRepositoryTest {
|
||||
@Test
|
||||
fun subscriptionsChangedFlow_changed() = runBlocking {
|
||||
val listDeferred = async {
|
||||
context.subscriptionsChangedFlow().toListWithTimeout()
|
||||
repository.subscriptionsChangedFlow().toListWithTimeout()
|
||||
}
|
||||
delay(100)
|
||||
|
||||
@@ -94,6 +94,17 @@ class SubscriptionRepositoryTest {
|
||||
assertThat(listDeferred.await()).hasSize(2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun activeSubscriptionIdListFlow(): Unit = runBlocking {
|
||||
mockSubscriptionManager.stub {
|
||||
on { activeSubscriptionIdList } doReturn intArrayOf(SUB_ID_IN_SLOT_0)
|
||||
}
|
||||
|
||||
val activeSubIds = repository.activeSubscriptionIdListFlow().firstWithTimeoutOrNull()
|
||||
|
||||
assertThat(activeSubIds).containsExactly(SUB_ID_IN_SLOT_0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getSelectableSubscriptionInfoList_sortedBySimSlotIndex() {
|
||||
mockSubscriptionManager.stub {
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.system.reset
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.TextView
|
||||
import androidx.fragment.app.testing.launchFragment
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.android.settings.R
|
||||
import com.android.settings.ResetNetworkRequest
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.kotlin.any
|
||||
import org.mockito.kotlin.never
|
||||
import org.mockito.kotlin.spy
|
||||
import org.mockito.kotlin.verify
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ResetNetworkConfirmTest {
|
||||
private val context: Context = spy(ApplicationProvider.getApplicationContext()) {}
|
||||
|
||||
private val scenario = launchFragment<ResetNetworkConfirm>()
|
||||
|
||||
@Test
|
||||
fun resetNetworkData_notResetEsim() {
|
||||
scenario.recreate().onFragment { fragment ->
|
||||
fragment.resetNetworkRequest = ResetNetworkRequest(ResetNetworkRequest.RESET_NONE)
|
||||
|
||||
runBlocking { fragment.onResetClicked() }
|
||||
|
||||
verify(context, never()).getSystemService(any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setSubtitle_eraseEsim() {
|
||||
scenario.onFragment { fragment ->
|
||||
fragment.resetNetworkRequest =
|
||||
ResetNetworkRequest(ResetNetworkRequest.RESET_NONE).apply {
|
||||
setResetEsim(context.packageName)
|
||||
}
|
||||
|
||||
val view = fragment.onCreateView(LayoutInflater.from(context), null, null)
|
||||
|
||||
assertThat(view.requireViewById<TextView>(R.id.reset_network_confirm).text)
|
||||
.isEqualTo(context.getString(R.string.reset_network_final_desc_esim))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setSubtitle_notEraseEsim() {
|
||||
scenario.onFragment { fragment ->
|
||||
fragment.resetNetworkRequest = ResetNetworkRequest(ResetNetworkRequest.RESET_NONE)
|
||||
|
||||
val view = fragment.onCreateView(LayoutInflater.from(context), null, null)
|
||||
|
||||
assertThat(view.requireViewById<TextView>(R.id.reset_network_confirm).text)
|
||||
.isEqualTo(context.getString(R.string.reset_network_final_desc))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.telephony.SubscriptionInfo;
|
||||
import android.telephony.SubscriptionManager;
|
||||
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ResetSubscriptionContractTest {
|
||||
|
||||
private static final int SUB_ID_1 = 3;
|
||||
private static final int SUB_ID_2 = 8;
|
||||
|
||||
@Mock
|
||||
private SubscriptionManager mSubscriptionManager;
|
||||
@Mock
|
||||
private OnSubscriptionsChangedListener mOnSubscriptionsChangedListener;
|
||||
@Mock
|
||||
private SubscriptionInfo mSubscriptionInfo1;
|
||||
@Mock
|
||||
private SubscriptionInfo mSubscriptionInfo2;
|
||||
|
||||
private Context mContext;
|
||||
private ResetNetworkRequest mRequestArgs;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
mContext = spy(ApplicationProvider.getApplicationContext());
|
||||
mRequestArgs = new ResetNetworkRequest(new Bundle());
|
||||
}
|
||||
|
||||
private ResetSubscriptionContract createTestObject() {
|
||||
return new ResetSubscriptionContract(mContext, mRequestArgs) {
|
||||
@Override
|
||||
protected SubscriptionManager getSubscriptionManager() {
|
||||
return mSubscriptionManager;
|
||||
}
|
||||
@Override
|
||||
protected OnSubscriptionsChangedListener getChangeListener() {
|
||||
return mOnSubscriptionsChangedListener;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAnyMissingSubscriptionId_returnNull_whenNoSubscriptionChange() {
|
||||
mRequestArgs.setResetTelephonyAndNetworkPolicyManager(SUB_ID_1);
|
||||
doReturn(mSubscriptionInfo1).when(mSubscriptionManager)
|
||||
.getActiveSubscriptionInfo(SUB_ID_1);
|
||||
mRequestArgs.setResetApn(SUB_ID_2);
|
||||
doReturn(mSubscriptionInfo2).when(mSubscriptionManager)
|
||||
.getActiveSubscriptionInfo(SUB_ID_2);
|
||||
|
||||
ResetSubscriptionContract target = createTestObject();
|
||||
|
||||
verify(mSubscriptionManager).addOnSubscriptionsChangedListener(any(), any());
|
||||
|
||||
assertNull(target.getAnyMissingSubscriptionId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAnyMissingSubscriptionId_returnSubId_whenSubscriptionNotActive() {
|
||||
mRequestArgs.setResetTelephonyAndNetworkPolicyManager(SUB_ID_1);
|
||||
doReturn(mSubscriptionInfo1).when(mSubscriptionManager)
|
||||
.getActiveSubscriptionInfo(SUB_ID_1);
|
||||
mRequestArgs.setResetApn(SUB_ID_2);
|
||||
doReturn(null).when(mSubscriptionManager)
|
||||
.getActiveSubscriptionInfo(SUB_ID_2);
|
||||
|
||||
ResetSubscriptionContract target = createTestObject();
|
||||
|
||||
verify(mSubscriptionManager).addOnSubscriptionsChangedListener(any(), any());
|
||||
|
||||
assertEquals(target.getAnyMissingSubscriptionId(), new Integer(SUB_ID_2));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user