Snap for 12019843 from 5653acd533 to 24Q4-release

Change-Id: Ie6b1e342ceed6f47033f602a6a3b05812471fdbe
This commit is contained in:
Android Build Coastguard Worker
2024-06-26 23:22:45 +00:00
55 changed files with 1118 additions and 954 deletions

View File

@@ -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] --> <!-- 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> <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] --> <!-- 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] --> <!-- 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] --> <!-- 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] --> <!-- 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] --> <!-- 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] --> <!-- 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] --> <!-- 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] --> <!-- 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] --> <!-- 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]--> <!-- 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> <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]--> <!-- Message shown in a dialog which asks the user to confirm when a single fingerprint gets deleted. [CHAR LIMIT=NONE]-->

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

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

View File

@@ -156,6 +156,8 @@
<string name="bluetooth_hearing_device_settings_summary">Shortcut, hearing aid compatibility</string> <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] --> <!-- 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> <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] --> <!-- Message when selecting hearing aids presets failed. [CHAR LIMIT=NONE] -->
<string name="bluetooth_hearing_aids_presets_error">Couldn\u2019t update preset</string> <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] --> <!-- 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 /> <!-- Biometric settings --><skip />
<!-- Title shown for menu item that launches biometric settings. [CHAR LIMIT=66] --> <!-- Title shown for menu item that launches biometric settings. [CHAR LIMIT=66] -->
<string name="security_settings_biometric_preference_title">Fingerprint &amp; Face Unlock</string> <string name="security_settings_biometric_preference_title">Face &amp; Fingerprint Unlock</string>
<!-- Title shown for work menu item that launches biometric settings. [CHAR LIMIT=66] --> <!-- Title shown for work menu item that launches biometric settings. [CHAR LIMIT=66] -->
<string name="security_settings_work_biometric_preference_title">Face &amp; Fingerprint Unlock for work</string> <string name="security_settings_work_biometric_preference_title">Face &amp; Fingerprint Unlock for work</string>
<!-- Message shown in summary field of biometric settings. [CHAR LIMIT=66] --> <!-- Message shown in summary field of biometric settings. [CHAR LIMIT=66] -->
@@ -2784,6 +2786,9 @@
<string name="brightness">Brightness level</string> <string name="brightness">Brightness level</string>
<!-- Sound & display settings screen, setting option name to enable adaptive brightness [CHAR LIMIT=30] --> <!-- Sound & display settings screen, setting option name to enable adaptive brightness [CHAR LIMIT=30] -->
<string name="auto_brightness_title">Adaptive brightness</string> <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 --> <!-- 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> <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] --> <!-- Setting option summary when adaptive brightness is on [CHAR LIMIT=NONE] -->
@@ -8980,6 +8985,9 @@
<!-- [CHAR LIMIT=NONE] App notification settings: no channels --> <!-- [CHAR LIMIT=NONE] App notification settings: no channels -->
<string name="no_channels">This app has not posted any notifications</string> <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--> <!-- [CHAR LIMIT=NONE] App notification settings: link to app notification settings-->
<string name="app_settings_link">Additional settings in the app</string> <string name="app_settings_link">Additional settings in the app</string>

View File

@@ -55,6 +55,11 @@
android:key="channels" android:key="channels"
android:layout="@layout/empty_view" /> android:layout="@layout/empty_view" />
<Preference
android:key="more"
android:title="@string/no_recent_channels"
android:icon="@drawable/ic_expand"/>
<!-- Importance toggle --> <!-- Importance toggle -->
<com.android.settingslib.RestrictedSwitchPreference <com.android.settingslib.RestrictedSwitchPreference
android:key="allow_sound" android:key="allow_sound"

View File

@@ -52,6 +52,7 @@ import com.android.settings.network.SubscriptionUtil;
import com.android.settings.network.telephony.EuiccRacConnectivityDialogActivity; import com.android.settings.network.telephony.EuiccRacConnectivityDialogActivity;
import com.android.settings.password.ChooseLockSettingsHelper; import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settings.password.ConfirmLockPattern; import com.android.settings.password.ConfirmLockPattern;
import com.android.settings.system.reset.ResetNetworkConfirm;
import com.android.settingslib.development.DevelopmentSettingsEnabler; import com.android.settingslib.development.DevelopmentSettingsEnabler;
import java.util.ArrayList; import java.util.ArrayList;

View File

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

View File

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

View File

@@ -27,11 +27,13 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.preference.PreferenceScreen;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.display.AutoBrightnessSettings; import com.android.settings.display.AutoBrightnessSettings;
import com.android.settingslib.Utils; import com.android.settingslib.Utils;
import com.android.settingslib.widget.FooterPreference;
import com.google.android.setupcompat.template.FooterBarMixin; import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupdesign.GlifPreferenceLayout; import com.google.android.setupdesign.GlifPreferenceLayout;
@@ -41,10 +43,14 @@ import com.google.android.setupdesign.GlifPreferenceLayout;
*/ */
public class AutoBrightnessPreferenceFragmentForSetupWizard extends AutoBrightnessSettings { public class AutoBrightnessPreferenceFragmentForSetupWizard extends AutoBrightnessSettings {
private static final String FOOTER_PREFERENCE_KEY = "auto_brightness_footer";
@Override @Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
updateFooterContentDescription();
if (view instanceof GlifPreferenceLayout) { if (view instanceof GlifPreferenceLayout) {
final GlifPreferenceLayout layout = (GlifPreferenceLayout) view; final GlifPreferenceLayout layout = (GlifPreferenceLayout) view;
final String title = getContext().getString( final String title = getContext().getString(
@@ -78,4 +84,15 @@ public class AutoBrightnessPreferenceFragmentForSetupWizard extends AutoBrightne
public int getMetricsCategory() { public int getMetricsCategory() {
return SettingsEnums.SUW_ACCESSIBILITY_AUTO_BRIGHTNESS; 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);
}
}
} }

View File

@@ -356,6 +356,11 @@ public class FingerprintSettings extends SubSettings {
*/ */
protected void handleError(int errMsgId, CharSequence msg) { protected void handleError(int errMsgId, CharSequence msg) {
switch (errMsgId) { 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: case FingerprintManager.FINGERPRINT_ERROR_LOCKOUT:
mInFingerprintLockout = true; mInFingerprintLockout = true;
// We've been locked out. Reset after 30s. // We've been locked out. Reset after 30s.

View File

@@ -159,19 +159,22 @@ public class BluetoothDetailsHearingAidsPresetsController extends
mPreference.setEnabled(mCachedDevice.isConnectedHapClientDevice()); mPreference.setEnabled(mCachedDevice.isConnectedHapClientDevice());
loadAllPresetInfo(); loadAllPresetInfo();
mPreference.setSummary(null);
if (mPreference.getEntries().length == 0) { if (mPreference.getEntries().length == 0) {
if (DEBUG) { if (mPreference.isEnabled()) {
Log.w(TAG, "Disable the preference since preset info size = 0"); 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));
} }
mPreference.setEnabled(false);
} else { } else {
int activePresetIndex = mHapClientProfile.getActivePresetIndex( int activePresetIndex = mHapClientProfile.getActivePresetIndex(
mCachedDevice.getDevice()); mCachedDevice.getDevice());
if (activePresetIndex != BluetoothHapClient.PRESET_INDEX_UNAVAILABLE) { if (activePresetIndex != BluetoothHapClient.PRESET_INDEX_UNAVAILABLE) {
mPreference.setValue(Integer.toString(activePresetIndex)); mPreference.setValue(Integer.toString(activePresetIndex));
mPreference.setSummary(mPreference.getEntry()); mPreference.setSummary(mPreference.getEntry());
} else {
mPreference.setSummary(null);
} }
} }
} }
@@ -273,7 +276,8 @@ public class BluetoothDetailsHearingAidsPresetsController extends
return; return;
} }
List<BluetoothHapPresetInfo> infoList = mHapClientProfile.getAllPresetInfo( List<BluetoothHapPresetInfo> infoList = mHapClientProfile.getAllPresetInfo(
mCachedDevice.getDevice()); mCachedDevice.getDevice()).stream().filter(
BluetoothHapPresetInfo::isAvailable).toList();
CharSequence[] presetNames = new CharSequence[infoList.size()]; CharSequence[] presetNames = new CharSequence[infoList.size()];
CharSequence[] presetIndexes = new CharSequence[infoList.size()]; CharSequence[] presetIndexes = new CharSequence[infoList.size()];
for (int i = 0; i < infoList.size(); i++) { for (int i = 0; i < infoList.size(); i++) {

View File

@@ -258,6 +258,8 @@ public class AudioSharingDialogHandler {
boolean userTriggered) { boolean userTriggered) {
Map<Integer, List<CachedBluetoothDevice>> groupedDevices = Map<Integer, List<CachedBluetoothDevice>> groupedDevices =
AudioSharingUtils.fetchConnectedDevicesByGroupId(mLocalBtManager); AudioSharingUtils.fetchConnectedDevicesByGroupId(mLocalBtManager);
BluetoothDevice btDevice = cachedDevice.getDevice();
String deviceAddress = btDevice == null ? "" : btDevice.getAnonymizedAddress();
if (isBroadcasting) { if (isBroadcasting) {
// If another device within the same is already in the sharing session, add source to // If another device within the same is already in the sharing session, add source to
// the device automatically. // the device automatically.
@@ -271,10 +273,10 @@ public class AudioSharingDialogHandler {
Log.d( Log.d(
TAG, TAG,
"Automatically add another device within the same group to the sharing: " "Automatically add another device within the same group to the sharing: "
+ cachedDevice.getDevice().getAnonymizedAddress()); + deviceAddress);
if (mAssistant != null && mBroadcast != null) { if (mAssistant != null && mBroadcast != null) {
mAssistant.addSource( mAssistant.addSource(
cachedDevice.getDevice(), btDevice,
mBroadcast.getLatestBluetoothLeBroadcastMetadata(), mBroadcast.getLatestBluetoothLeBroadcastMetadata(),
/* isGroupOp= */ false); /* isGroupOp= */ false);
} }
@@ -313,6 +315,7 @@ public class AudioSharingDialogHandler {
cachedDevice, cachedDevice,
listener, listener,
eventData); eventData);
Log.d(TAG, "Show disconnect dialog, device = " + deviceAddress);
}); });
} else { } else {
// Show audio sharing join dialog when the first or second eligible (LE audio) // Show audio sharing join dialog when the first or second eligible (LE audio)
@@ -343,9 +346,11 @@ public class AudioSharingDialogHandler {
cachedDevice, cachedDevice,
listener, listener,
eventData); eventData);
Log.d(TAG, "Show join dialog, device = " + deviceAddress);
}); });
} }
} else { } else {
// Build a list of AudioSharingDeviceItem for connected devices other than cachedDevice.
List<AudioSharingDeviceItem> deviceItems = new ArrayList<>(); List<AudioSharingDeviceItem> deviceItems = new ArrayList<>();
for (List<CachedBluetoothDevice> devices : groupedDevices.values()) { for (List<CachedBluetoothDevice> devices : groupedDevices.values()) {
// Use random device in the group within the sharing session to represent the group. // 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 // Show audio sharing join dialog when the second eligible (LE audio) remote
// device connect and no sharing session. // device connect and no sharing session.
if (deviceItems.size() == 1) { if (groupedDevices.size() == 2 && deviceItems.size() == 1) {
AudioSharingJoinDialogFragment.DialogEventListener listener = AudioSharingJoinDialogFragment.DialogEventListener listener =
new AudioSharingJoinDialogFragment.DialogEventListener() { new AudioSharingJoinDialogFragment.DialogEventListener() {
@Override @Override
@@ -396,9 +401,13 @@ public class AudioSharingDialogHandler {
closeOpeningDialogsOtherThan(AudioSharingJoinDialogFragment.tag()); closeOpeningDialogsOtherThan(AudioSharingJoinDialogFragment.tag());
AudioSharingJoinDialogFragment.show( AudioSharingJoinDialogFragment.show(
mHostFragment, deviceItems, cachedDevice, listener, eventData); mHostFragment, deviceItems, cachedDevice, listener, eventData);
Log.d(TAG, "Show start dialog, device = " + deviceAddress);
}); });
} else if (userTriggered) { } else if (userTriggered) {
cachedDevice.setActive(); cachedDevice.setActive();
Log.d(TAG, "Set active device = " + deviceAddress);
} else {
Log.d(TAG, "Fail to handle LE audio device connected, device = " + deviceAddress);
} }
} }
} }

View File

@@ -649,8 +649,12 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment
DynamicDataObserver observer) { DynamicDataObserver observer) {
Log.d(TAG, "register observer: @" + Integer.toHexString(observer.hashCode()) Log.d(TAG, "register observer: @" + Integer.toHexString(observer.hashCode())
+ ", uri: " + observer.getUri()); + ", uri: " + observer.getUri());
resolver.registerContentObserver(observer.getUri(), false, observer); try {
mRegisteredObservers.add(observer); 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) { private void unregisterDynamicDataObservers(List<DynamicDataObserver> observers) {
@@ -661,8 +665,13 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment
observers.forEach(observer -> { observers.forEach(observer -> {
Log.d(TAG, "unregister observer: @" + Integer.toHexString(observer.hashCode()) Log.d(TAG, "unregister observer: @" + Integer.toHexString(observer.hashCode())
+ ", uri: " + observer.getUri()); + ", uri: " + observer.getUri());
mRegisteredObservers.remove(observer); if (mRegisteredObservers.remove(observer)) {
resolver.unregisterContentObserver(observer); try {
resolver.unregisterContentObserver(observer);
} catch (Exception e) {
Log.w(TAG, "Cannot unregister observer: " + observer.getUri(), e);
}
}
}); });
} }

View File

@@ -53,7 +53,8 @@ public class BatteryInfo {
public int batteryStatus; public int batteryStatus;
public int pluggedStatus; public int pluggedStatus;
public boolean discharging = true; public boolean discharging = true;
public boolean isBatteryDefender; public boolean isBatteryDefender = false;
public boolean isLongLife = false;
public boolean isFastCharging; public boolean isFastCharging;
public long remainingTimeUs = 0; public long remainingTimeUs = 0;
public long averageTimeToDischarge = EstimateKt.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN; 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.pluggedStatus = batteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
info.mCharging = info.pluggedStatus != 0; info.mCharging = info.pluggedStatus != 0;
info.averageTimeToDischarge = estimate.getAverageDischargeTime(); info.averageTimeToDischarge = estimate.getAverageDischargeTime();
info.isBatteryDefender = info.isLongLife =
batteryBroadcast.getIntExtra( batteryBroadcast.getIntExtra(
BatteryManager.EXTRA_CHARGING_STATUS, BatteryManager.EXTRA_CHARGING_STATUS,
BatteryManager.CHARGING_POLICY_DEFAULT) BatteryManager.CHARGING_POLICY_DEFAULT)
@@ -319,7 +320,7 @@ public class BatteryInfo {
info.isFastCharging = info.isFastCharging =
BatteryStatus.getChargingSpeed(context, batteryBroadcast) BatteryStatus.getChargingSpeed(context, batteryBroadcast)
== BatteryStatus.CHARGING_FAST; == BatteryStatus.CHARGING_FAST;
if (info.isBatteryDefender) { if (info.isLongLife) {
info.isBatteryDefender = info.isBatteryDefender =
FeatureFactory.getFeatureFactory() FeatureFactory.getFeatureFactory()
.getPowerUsageFeatureProvider() .getPowerUsageFeatureProvider()

View File

@@ -600,7 +600,7 @@ public class BatteryUtils {
context.getContentResolver(), SETTINGS_GLOBAL_DOCK_DEFENDER_BYPASS, 0) context.getContentResolver(), SETTINGS_GLOBAL_DOCK_DEFENDER_BYPASS, 0)
== 1) { == 1) {
return DockDefenderMode.TEMPORARILY_BYPASSED; return DockDefenderMode.TEMPORARILY_BYPASSED;
} else if (batteryInfo.isBatteryDefender } else if (batteryInfo.isLongLife
&& FeatureFactory.getFeatureFactory() && FeatureFactory.getFeatureFactory()
.getPowerUsageFeatureProvider() .getPowerUsageFeatureProvider()
.isExtraDefend()) { .isExtraDefend()) {

View File

@@ -23,6 +23,8 @@ import android.os.Bundle;
import android.util.ArrayMap; import android.util.ArrayMap;
import android.util.SparseIntArray; import android.util.SparseIntArray;
import androidx.annotation.NonNull;
import com.android.settings.fuelgauge.batteryusage.BatteryDiffData; import com.android.settings.fuelgauge.batteryusage.BatteryDiffData;
import com.android.settings.fuelgauge.batteryusage.DetectRequestSourceType; import com.android.settings.fuelgauge.batteryusage.DetectRequestSourceType;
import com.android.settings.fuelgauge.batteryusage.PowerAnomalyEventList; import com.android.settings.fuelgauge.batteryusage.PowerAnomalyEventList;
@@ -162,5 +164,7 @@ public interface PowerUsageFeatureProvider {
/** Collect and process battery reattribute data if needed. */ /** Collect and process battery reattribute data if needed. */
boolean processBatteryReattributeData( boolean processBatteryReattributeData(
Context context, Map<Long, BatteryDiffData> batteryDiffDataMap); @NonNull Context context,
@NonNull Map<Long, BatteryDiffData> batteryDiffDataMap,
final boolean isFromPeriodJob);
} }

View File

@@ -27,6 +27,8 @@ import android.util.ArrayMap;
import android.util.ArraySet; import android.util.ArraySet;
import android.util.SparseIntArray; import android.util.SparseIntArray;
import androidx.annotation.NonNull;
import com.android.internal.util.ArrayUtils; import com.android.internal.util.ArrayUtils;
import com.android.settings.fuelgauge.batteryusage.BatteryDiffData; import com.android.settings.fuelgauge.batteryusage.BatteryDiffData;
import com.android.settings.fuelgauge.batteryusage.DetectRequestSourceType; import com.android.settings.fuelgauge.batteryusage.DetectRequestSourceType;
@@ -250,7 +252,9 @@ public class PowerUsageFeatureProviderImpl implements PowerUsageFeatureProvider
@Override @Override
public boolean processBatteryReattributeData( public boolean processBatteryReattributeData(
Context context, Map<Long, BatteryDiffData> batteryDiffDataMap) { @NonNull Context context,
@NonNull Map<Long, BatteryDiffData> batteryDiffDataMap,
final boolean isFromPeriodJob) {
return false; return false;
} }
} }

View File

@@ -422,7 +422,8 @@ public class BatteryDiffEntry {
return; return;
} }
final boolean isValidPackage = final boolean isValidPackage =
BatteryUtils.getInstance(mContext).getPackageUid(getPackageName()) BatteryUtils.getInstance(mContext)
.getPackageUidAsUser(getPackageName(), (int) mUserId)
!= BatteryUtils.UID_NULL; != BatteryUtils.UID_NULL;
if (!isValidPackage) { if (!isValidPackage) {
mValidForRestriction = false; mValidForRestriction = false;

View File

@@ -22,7 +22,6 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager; import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo; import android.content.pm.UserInfo;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.BatteryConsumer; import android.os.BatteryConsumer;
@@ -176,18 +175,7 @@ public class BatteryEntry {
} }
} }
if (mDefaultPackageName != null) { if (mDefaultPackageName != null) {
PackageManager pm = context.getPackageManager(); mName = mDefaultPackageName;
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 = mTimeInForegroundMs =
uidBatteryConsumer.getTimeInProcessStateMs( uidBatteryConsumer.getTimeInProcessStateMs(

View File

@@ -128,9 +128,6 @@ public final class BatteryUsageDataLoader {
final PowerUsageFeatureProvider featureProvider = final PowerUsageFeatureProvider featureProvider =
FeatureFactory.getFeatureFactory() FeatureFactory.getFeatureFactory()
.getPowerUsageFeatureProvider(); .getPowerUsageFeatureProvider();
// Collect and process battery reattribute data.
featureProvider.processBatteryReattributeData(
context, batteryDiffDataMap);
DatabaseUtils.sendBatteryUsageSlotData( DatabaseUtils.sendBatteryUsageSlotData(
context, context,
ConvertUtils.convertToBatteryUsageSlotList( ConvertUtils.convertToBatteryUsageSlotList(

View File

@@ -28,6 +28,8 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting; 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.ArrayList;
import java.util.Calendar; import java.util.Calendar;
@@ -78,6 +80,7 @@ public class DataProcessManager {
// Raw start timestamp with round to the nearest hour. // Raw start timestamp with round to the nearest hour.
private final long mRawStartTimestamp; private final long mRawStartTimestamp;
private final long mLastFullChargeTimestamp; private final long mLastFullChargeTimestamp;
private final boolean mIsFromPeriodJob;
private final Context mContext; private final Context mContext;
private final Handler mHandler; private final Handler mHandler;
private final UserIdsSeries mUserIdsSeries; private final UserIdsSeries mUserIdsSeries;
@@ -122,6 +125,7 @@ public class DataProcessManager {
Context context, Context context,
Handler handler, Handler handler,
final UserIdsSeries userIdsSeries, final UserIdsSeries userIdsSeries,
final boolean isFromPeriodJob,
final long rawStartTimestamp, final long rawStartTimestamp,
final long lastFullChargeTimestamp, final long lastFullChargeTimestamp,
@NonNull final OnBatteryDiffDataMapLoadedListener callbackFunction, @NonNull final OnBatteryDiffDataMapLoadedListener callbackFunction,
@@ -130,6 +134,7 @@ public class DataProcessManager {
mContext = context.getApplicationContext(); mContext = context.getApplicationContext();
mHandler = handler; mHandler = handler;
mUserIdsSeries = userIdsSeries; mUserIdsSeries = userIdsSeries;
mIsFromPeriodJob = isFromPeriodJob;
mRawStartTimestamp = rawStartTimestamp; mRawStartTimestamp = rawStartTimestamp;
mLastFullChargeTimestamp = lastFullChargeTimestamp; mLastFullChargeTimestamp = lastFullChargeTimestamp;
mCallbackFunction = callbackFunction; mCallbackFunction = callbackFunction;
@@ -147,6 +152,7 @@ public class DataProcessManager {
mHandler = handler; mHandler = handler;
mUserIdsSeries = userIdsSeries; mUserIdsSeries = userIdsSeries;
mCallbackFunction = callbackFunction; mCallbackFunction = callbackFunction;
mIsFromPeriodJob = false;
mRawStartTimestamp = 0L; mRawStartTimestamp = 0L;
mLastFullChargeTimestamp = 0L; mLastFullChargeTimestamp = 0L;
mHourlyBatteryLevelsPerDay = null; mHourlyBatteryLevelsPerDay = null;
@@ -158,14 +164,9 @@ public class DataProcessManager {
/** Starts the async tasks to load battery history data and app usage data. */ /** Starts the async tasks to load battery history data and app usage data. */
public void start() { 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 we have battery level data, load the battery history map and app usage simultaneously.
if (mHourlyBatteryLevelsPerDay != null) { if (mHourlyBatteryLevelsPerDay != null) {
if (isFromPeriodJob) { if (mIsFromPeriodJob) {
mIsCurrentBatteryHistoryLoaded = true; mIsCurrentBatteryHistoryLoaded = true;
mIsCurrentAppUsageLoaded = true; mIsCurrentAppUsageLoaded = true;
mIsBatteryUsageSlotLoaded = true; mIsBatteryUsageSlotLoaded = true;
@@ -514,6 +515,14 @@ public class DataProcessManager {
mAppUsagePeriodMap, mAppUsagePeriodMap,
getSystemAppsPackageNames(), getSystemAppsPackageNames(),
getSystemAppsUids())); 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( Log.d(
TAG, TAG,
@@ -683,12 +692,13 @@ public class DataProcessManager {
context, context,
handler, handler,
userIdsSeries, userIdsSeries,
isFromPeriodJob,
startTimestamp, startTimestamp,
lastFullChargeTime, lastFullChargeTime,
onBatteryDiffDataMapLoadedListener, onBatteryDiffDataMapLoadedListener,
batteryLevelData.getHourlyBatteryLevelsPerDay(), batteryLevelData.getHourlyBatteryLevelsPerDay(),
processedBatteryHistoryMap) processedBatteryHistoryMap)
.start(isFromPeriodJob); .start();
return batteryLevelData; return batteryLevelData;
} }

View File

@@ -65,6 +65,8 @@ public class ResetNetworkOperationBuilder {
private Context mContext; private Context mContext;
private List<Runnable> mResetSequence = new ArrayList<Runnable>(); private List<Runnable> mResetSequence = new ArrayList<Runnable>();
@Nullable
private Consumer<Boolean> mResetEsimResultCallback = null;
/** /**
* Constructor of builder. * Constructor of builder.
@@ -129,31 +131,32 @@ public class ResetNetworkOperationBuilder {
} }
/** /**
* Append a step of resetting E-SIM. * Append a result callback of resetting E-SIM.
* @param callerPackage package name of caller * @param resultCallback a callback dealing with result of resetting eSIM
* @return this * @return this
*/ */
public ResetNetworkOperationBuilder resetEsim(String callerPackage) { public ResetNetworkOperationBuilder resetEsimResultCallback(Consumer<Boolean> resultCallback) {
resetEsim(callerPackage, null); mResetEsimResultCallback = resultCallback;
return this; return this;
} }
/** /**
* Append a step of resetting E-SIM. * Append a step of resetting E-SIM.
* @param callerPackage package name of caller * @param callerPackage package name of caller
* @param resultCallback a Consumer<Boolean> dealing with result of resetting eSIM
* @return this * @return this
*/ */
public ResetNetworkOperationBuilder resetEsim(String callerPackage, public ResetNetworkOperationBuilder resetEsim(String callerPackage) {
Consumer<Boolean> resultCallback) {
Runnable runnable = () -> { Runnable runnable = () -> {
long startTime = SystemClock.elapsedRealtime(); long startTime = SystemClock.elapsedRealtime();
if (!DRY_RUN) { boolean wipped;
Boolean wipped = RecoverySystem.wipeEuiccData(mContext, callerPackage); if (DRY_RUN) {
if (resultCallback != null) { wipped = true;
resultCallback.accept(wipped); } else {
} wipped = RecoverySystem.wipeEuiccData(mContext, callerPackage);
}
if (mResetEsimResultCallback != null) {
mResetEsimResultCallback.accept(wipped);
} }
long endTime = SystemClock.elapsedRealtime(); long endTime = SystemClock.elapsedRealtime();

View File

@@ -25,14 +25,17 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
class CallStateRepository(private val context: Context) { class CallStateRepository(
private val subscriptionManager = context.requireSubscriptionManager() private val context: Context,
private val subscriptionRepository: SubscriptionRepository = SubscriptionRepository(context),
) {
/** Flow for call state of given [subId]. */ /** Flow for call state of given [subId]. */
fun callStateFlow(subId: Int): Flow<Int> = context.telephonyCallbackFlow(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. * @return true if any active subscription's call state is not idle.
*/ */
fun isInCallFlow(): Flow<Boolean> = context.subscriptionsChangedFlow() fun isInCallFlow(): Flow<Boolean> = subscriptionRepository.activeSubscriptionIdListFlow()
.flatMapLatest { .flatMapLatest { subIds ->
val subIds = subscriptionManager.activeSubscriptionIdList
if (subIds.isEmpty()) { if (subIds.isEmpty()) {
flowOf(false) flowOf(false)
} else { } else {
@@ -59,9 +61,10 @@ class CallStateRepository(private val context: Context) {
} }
} }
} }
.distinctUntilChanged()
.conflate() .conflate()
.flowOn(Dispatchers.Default)
.onEach { Log.d(TAG, "isInCallFlow: $it") } .onEach { Log.d(TAG, "isInCallFlow: $it") }
.flowOn(Dispatchers.Default)
private companion object { private companion object {
private const val TAG = "CallStateRepository" private const val TAG = "CallStateRepository"

View File

@@ -29,6 +29,7 @@ import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNot import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
@@ -68,6 +69,30 @@ class SubscriptionRepository(private val context: Context) {
} }
fun canDisablePhysicalSubscription() = subscriptionManager.canDisablePhysicalSubscription() fun canDisablePhysicalSubscription() = subscriptionManager.canDisablePhysicalSubscription()
/** Flow for subscriptions changes. */
fun subscriptionsChangedFlow() = callbackFlow {
val listener = object : SubscriptionManager.OnSubscriptionsChangedListener() {
override fun onSubscriptionsChanged() {
trySend(Unit)
}
}
subscriptionManager.addOnSubscriptionsChangedListener(
Dispatchers.Default.asExecutor(),
listener,
)
awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(listener) }
}.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? val Context.subscriptionManager: SubscriptionManager?
@@ -79,22 +104,8 @@ fun Context.phoneNumberFlow(subscriptionInfo: SubscriptionInfo) = subscriptionsC
SubscriptionUtil.getBidiFormattedPhoneNumber(this, subscriptionInfo) SubscriptionUtil.getBidiFormattedPhoneNumber(this, subscriptionInfo)
}.filterNot { it.isNullOrEmpty() }.flowOn(Dispatchers.Default) }.filterNot { it.isNullOrEmpty() }.flowOn(Dispatchers.Default)
fun Context.subscriptionsChangedFlow() = callbackFlow { fun Context.subscriptionsChangedFlow(): Flow<Unit> =
val subscriptionManager = requireSubscriptionManager() SubscriptionRepository(this).subscriptionsChangedFlow()
val listener = object : SubscriptionManager.OnSubscriptionsChangedListener() {
override fun onSubscriptionsChanged() {
trySend(Unit)
}
}
subscriptionManager.addOnSubscriptionsChangedListener(
Dispatchers.Default.asExecutor(),
listener,
)
awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(listener) }
}.conflate().onEach { Log.d(TAG, "subscriptions changed") }.flowOn(Dispatchers.Default)
/** /**
* Return a list of subscriptions that are available and visible to the user. * Return a list of subscriptions that are available and visible to the user.

View File

@@ -25,10 +25,9 @@ import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.android.settings.R import com.android.settings.R
import com.android.settings.network.telephony.MobileDataRepository 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.ims.ImsMmTelRepositoryImpl
import com.android.settings.network.telephony.requireSubscriptionManager
import com.android.settings.network.telephony.safeGetConfig 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.network.telephony.telephonyManager
import com.android.settings.overlay.FeatureFactory.Companion.featureFactory import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -48,7 +47,7 @@ class CrossSimCallingViewModel(
private val application: Application, private val application: Application,
) : AndroidViewModel(application) { ) : AndroidViewModel(application) {
private val subscriptionManager = application.requireSubscriptionManager() private val subscriptionRepository = SubscriptionRepository(application)
private val carrierConfigManager = private val carrierConfigManager =
application.getSystemService(CarrierConfigManager::class.java)!! application.getSystemService(CarrierConfigManager::class.java)!!
private val scope = viewModelScope + Dispatchers.Default private val scope = viewModelScope + Dispatchers.Default
@@ -59,9 +58,8 @@ class CrossSimCallingViewModel(
init { init {
val resources = application.resources val resources = application.resources
if (resources.getBoolean(R.bool.config_auto_data_switch_enables_cross_sim_calling)) { if (resources.getBoolean(R.bool.config_auto_data_switch_enables_cross_sim_calling)) {
application.subscriptionsChangedFlow() subscriptionRepository.activeSubscriptionIdListFlow()
.flatMapLatest { .flatMapLatest { activeSubIds ->
val activeSubIds = subscriptionManager.activeSubscriptionIdList.toList()
merge( merge(
activeSubIds.anyMobileDataEnableChangedFlow(), activeSubIds.anyMobileDataEnableChangedFlow(),
updateChannel.receiveAsFlow(), updateChannel.receiveAsFlow(),

View File

@@ -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_DYNAMIC;
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER; 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.INotificationManager;
import android.app.NotificationChannel; import android.app.NotificationChannel;
import android.app.NotificationChannelGroup; import android.app.NotificationChannelGroup;
@@ -78,6 +80,9 @@ public class NotificationBackend {
public AppRow loadAppRow(Context context, PackageManager pm, ApplicationInfo app) { public AppRow loadAppRow(Context context, PackageManager pm, ApplicationInfo app) {
final AppRow row = new AppRow(); final AppRow row = new AppRow();
if (notificationHideUnusedChannels()) {
row.showAllChannels = false;
}
row.pkg = app.packageName; row.pkg = app.packageName;
row.uid = app.uid; row.uid = app.uid;
try { try {
@@ -686,5 +691,6 @@ public class NotificationBackend {
public int channelCount; public int channelCount;
public Map<String, NotificationsSentState> sentByChannel; public Map<String, NotificationsSentState> sentByChannel;
public NotificationsSentState sentByApp; public NotificationsSentState sentByApp;
public boolean showAllChannels = true;
} }
} }

View File

@@ -16,16 +16,10 @@
package com.android.settings.notification.app; package com.android.settings.notification.app;
import static com.android.server.notification.Flags.notificationHideUnusedChannels;
import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums;
import android.content.Context; import android.content.Context;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; 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.internal.widget.LockPatternUtils;
import com.android.settings.R; import com.android.settings.R;
@@ -107,38 +101,8 @@ public class AppNotificationSettings extends NotificationSettings {
mControllers.add(new BubbleSummaryPreferenceController(context, mBackend)); mControllers.add(new BubbleSummaryPreferenceController(context, mBackend));
mControllers.add(new NotificationsOffPreferenceController(context)); mControllers.add(new NotificationsOffPreferenceController(context));
mControllers.add(new DeletedChannelsPreferenceController(context, mBackend)); mControllers.add(new DeletedChannelsPreferenceController(context, mBackend));
mControllers.add(new ShowMorePreferenceController(
context, mDependentFieldListener, mBackend));
return new ArrayList<>(mControllers); 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);
}
}
} }

View File

@@ -59,8 +59,6 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr
private List<NotificationChannelGroup> mChannelGroupList; private List<NotificationChannelGroup> mChannelGroupList;
private PreferenceCategory mPreference; private PreferenceCategory mPreference;
private boolean mShowAll;
public ChannelListPreferenceController(Context context, NotificationBackend backend) { public ChannelListPreferenceController(Context context, NotificationBackend backend) {
super(context, backend); super(context, backend);
} }
@@ -100,7 +98,7 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr
@Override @Override
protected Void doInBackground(Void... unused) { protected Void doInBackground(Void... unused) {
if (notificationHideUnusedChannels()) { if (notificationHideUnusedChannels()) {
if (mShowAll) { if (mAppRow.showAllChannels) {
mChannelGroupList = mBackend.getGroups(mAppRow.pkg, mAppRow.uid).getList(); mChannelGroupList = mBackend.getGroups(mAppRow.pkg, mAppRow.uid).getList();
} else { } else {
mChannelGroupList = mBackend.getGroupsWithRecentBlockedFilter(mAppRow.pkg, mChannelGroupList = mBackend.getGroupsWithRecentBlockedFilter(mAppRow.pkg,
@@ -123,10 +121,6 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr
}.execute(); }.execute();
} }
protected void setShowAll(boolean showAll) {
mShowAll = showAll;
}
/** /**
* Update the preferences group to match the * Update the preferences group to match the
* @param groupPrefsList * @param groupPrefsList

View File

@@ -16,6 +16,8 @@
package com.android.settings.notification.app; package com.android.settings.notification.app;
import static com.android.server.notification.Flags.notificationHideUnusedChannels;
import android.content.Context; import android.content.Context;
import androidx.preference.Preference; import androidx.preference.Preference;
@@ -44,6 +46,9 @@ public class DeletedChannelsPreferenceController extends NotificationPreferenceC
if (!super.isAvailable()) { if (!super.isAvailable()) {
return false; return false;
} }
if (notificationHideUnusedChannels()) {
return false;
}
// only visible on app screen // only visible on app screen
if (mChannel != null || hasValidGroup()) { if (mChannel != null || hasValidGroup()) {
return false; return false;

View File

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

View File

@@ -50,14 +50,16 @@ class IconUtil {
/** /**
* Returns a variant of the supplied {@code icon} to be used in the icon picker. The inner icon * 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) { static Drawable makeIconCircle(@NonNull Context context, @NonNull Drawable icon) {
ShapeDrawable background = new ShapeDrawable(new OvalShape()); ShapeDrawable background = new ShapeDrawable(new OvalShape());
background.getPaint().setColor(Utils.getColorAttrDefaultColor(context, background.setTintList(
com.android.internal.R.attr.materialColorSecondaryContainer)); context.getColorStateList(R.color.modes_icon_picker_item_background));
icon.setTint(Utils.getColorAttrDefaultColor(context, icon = icon.mutate();
com.android.internal.R.attr.materialColorOnSecondaryContainer)); icon.setTintList(
context.getColorStateList(R.color.modes_icon_picker_item_icon));
LayerDrawable layerDrawable = new LayerDrawable(new Drawable[] { background, icon }); LayerDrawable layerDrawable = new LayerDrawable(new Drawable[] { background, icon });

View File

@@ -18,13 +18,14 @@ package com.android.settings.notification.modes;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Application; import android.app.Application;
import android.app.AutomaticZenRule;
import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums;
import android.content.Context; import android.content.Context;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import androidx.annotation.NonNull;
import com.android.settings.R; import com.android.settings.R;
import com.android.settingslib.applications.ApplicationsState; import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.AbstractPreferenceController;
@@ -72,11 +73,9 @@ public class ZenModeFragment extends ZenModeFragmentBase {
// Set title for the entire screen // Set title for the entire screen
ZenMode mode = getMode(); ZenMode mode = getMode();
AutomaticZenRule azr = getAZR(); if (mode != null) {
if (mode == null || azr == null) { requireActivity().setTitle(mode.getName());
return;
} }
getActivity().setTitle(azr.getName());
} }
@Override @Override
@@ -92,7 +91,7 @@ public class ZenModeFragment extends ZenModeFragmentBase {
} }
@Override @Override
protected boolean onOptionsItemSelected(MenuItem item, ZenMode zenMode) { protected boolean onOptionsItemSelected(MenuItem item, @NonNull ZenMode zenMode) {
switch (item.getItemId()) { switch (item.getItemId()) {
case DELETE_MODE: case DELETE_MODE:
new AlertDialog.Builder(mContext) new AlertDialog.Builder(mContext)

View File

@@ -18,7 +18,6 @@ package com.android.settings.notification.modes;
import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID; import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
import android.app.AutomaticZenRule;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; import android.util.Log;
@@ -34,7 +33,10 @@ import com.android.settings.R;
import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.notification.modes.ZenMode; import com.android.settingslib.notification.modes.ZenMode;
import com.google.common.base.Preconditions;
import java.util.List; import java.util.List;
import java.util.function.Consumer;
/** /**
* Base class for Settings pages used to configure individual modes. * Base class for Settings pages used to configure individual modes.
@@ -175,14 +177,15 @@ abstract class ZenModeFragmentBase extends ZenModesFragmentBase {
return mZenMode; return mZenMode;
} }
/** protected final boolean saveMode(Consumer<ZenMode> updater) {
* Get AutomaticZenRule associated with current mode data, or null if it doesn't exist. Preconditions.checkState(mBackend != null);
*/ ZenMode mode = mZenMode;
@Nullable if (mode == null) {
public AutomaticZenRule getAZR() { Log.wtf(TAG, "Cannot save mode, it hasn't been loaded (" + getClass() + ")");
if (mZenMode == null) { return false;
return null;
} }
return mZenMode.getRule(); updater.accept(mode);
mBackend.updateMode(mode);
return true;
} }
} }

View File

@@ -43,7 +43,16 @@ public class ZenModeIconPickerFragment extends ZenModeFragmentBase {
return ImmutableList.of( return ImmutableList.of(
new ZenModeIconPickerIconPreferenceController(context, "current_icon", this, new ZenModeIconPickerIconPreferenceController(context, "current_icon", this,
mBackend), mBackend),
new ZenModeIconPickerListPreferenceController(context, "icon_list", this, new ZenModeIconPickerListPreferenceController(context, "icon_list",
new IconOptionsProviderImpl(mContext), mBackend)); 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();
}
};
} }

View File

@@ -17,6 +17,7 @@
package com.android.settings.notification.modes; package com.android.settings.notification.modes;
import android.content.Context; import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@@ -25,31 +26,35 @@ import android.widget.ImageView;
import androidx.annotation.DrawableRes; import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceScreen;
import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.SimpleItemAnimator;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settingslib.notification.modes.ZenMode; import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend; import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.LayoutPreference; import com.android.settingslib.widget.LayoutPreference;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import java.util.HashMap;
import java.util.Map;
class ZenModeIconPickerListPreferenceController extends AbstractZenModePreferenceController { class ZenModeIconPickerListPreferenceController extends AbstractZenModePreferenceController {
private final DashboardFragment mFragment;
private final IconOptionsProvider mIconOptionsProvider; private final IconOptionsProvider mIconOptionsProvider;
private final IconPickerListener mListener;
@Nullable private IconAdapter mAdapter; @Nullable private IconAdapter mAdapter;
private @DrawableRes int mCurrentIconResId;
ZenModeIconPickerListPreferenceController(@NonNull Context context, @NonNull String key, ZenModeIconPickerListPreferenceController(@NonNull Context context, @NonNull String key,
@NonNull DashboardFragment fragment, @NonNull IconOptionsProvider iconOptionsProvider, @NonNull IconPickerListener listener, @NonNull IconOptionsProvider iconOptionsProvider,
@Nullable ZenModesBackend backend) { @Nullable ZenModesBackend backend) {
super(context, key, backend); super(context, key, backend);
mFragment = fragment; mListener = listener;
mIconOptionsProvider = iconOptionsProvider; mIconOptionsProvider = iconOptionsProvider;
} }
@@ -68,20 +73,34 @@ class ZenModeIconPickerListPreferenceController extends AbstractZenModePreferenc
recyclerView.setLayoutManager(new AutoFitGridLayoutManager(mContext)); recyclerView.setLayoutManager(new AutoFitGridLayoutManager(mContext));
recyclerView.setAdapter(mAdapter); recyclerView.setAdapter(mAdapter);
recyclerView.setHasFixedSize(true); 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 @Override
void updateState(Preference preference, @NonNull ZenMode zenMode) { 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 { private class IconHolder extends RecyclerView.ViewHolder {
@@ -93,20 +112,25 @@ class ZenModeIconPickerListPreferenceController extends AbstractZenModePreferenc
mImageView = itemView.findViewById(R.id.icon_image_view); mImageView = itemView.findViewById(R.id.icon_image_view);
} }
void bindIcon(IconOptionsProvider.IconInfo icon) { void bindIcon(IconOptionsProvider.IconInfo icon, Drawable iconDrawable) {
mImageView.setImageDrawable( mImageView.setImageDrawable(iconDrawable);
IconUtil.makeIconCircle(itemView.getContext(), icon.resId()));
itemView.setContentDescription(icon.description()); 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 class IconAdapter extends RecyclerView.Adapter<IconHolder> {
private final ImmutableList<IconOptionsProvider.IconInfo> mIconResources; private final ImmutableList<IconOptionsProvider.IconInfo> mIconResources;
private final Map<IconOptionsProvider.IconInfo, Drawable> mIconCache;
private IconAdapter(IconOptionsProvider iconOptionsProvider) { private IconAdapter(IconOptionsProvider iconOptionsProvider) {
mIconResources = iconOptionsProvider.getIcons(); mIconResources = iconOptionsProvider.getIcons();
mIconCache = new HashMap<>();
} }
@NonNull @NonNull
@@ -119,13 +143,24 @@ class ZenModeIconPickerListPreferenceController extends AbstractZenModePreferenc
@Override @Override
public void onBindViewHolder(@NonNull IconHolder holder, int position) { 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 @Override
public int getItemCount() { public int getItemCount() {
return mIconResources.size(); 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 { private static class AutoFitGridLayoutManager extends GridLayoutManager {

View File

@@ -28,6 +28,7 @@ import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager; import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager; import android.telephony.TelephonyManager;
import android.telephony.UiccCardInfo; import android.telephony.UiccCardInfo;
import android.telephony.UiccPortInfo;
import android.telephony.UiccSlotInfo; import android.telephony.UiccSlotInfo;
import android.util.Log; import android.util.Log;
@@ -91,10 +92,10 @@ public class SimSlotChangeHandler {
Log.e(TAG, "Unable to find the removable slot. Do nothing."); Log.e(TAG, "Unable to find the removable slot. Do nothing.");
return; return;
} }
Log.i(TAG, "The removableSlotInfo: " + removableSlotInfo);
int lastRemovableSlotState = getLastRemovableSimSlotState(mContext); int lastRemovableSlotState = getLastRemovableSimSlotState(mContext);
int currentRemovableSlotState = removableSlotInfo.getCardStateInfo(); int currentRemovableSlotState = removableSlotInfo.getCardStateInfo();
Log.i(TAG, Log.d(TAG,
"lastRemovableSlotState: " + lastRemovableSlotState + ",currentRemovableSlotState: " "lastRemovableSlotState: " + lastRemovableSlotState + ",currentRemovableSlotState: "
+ currentRemovableSlotState); + currentRemovableSlotState);
boolean isRemovableSimInserted = boolean isRemovableSimInserted =
@@ -115,8 +116,12 @@ public class SimSlotChangeHandler {
if (Flags.isDualSimOnboardingEnabled()) { if (Flags.isDualSimOnboardingEnabled()) {
// ForNewUi, when the user inserts the psim, showing the sim onboarding for the user // ForNewUi, when the user inserts the psim, showing the sim onboarding for the user
// to setup the sim switching or the default data subscription. // to setup the sim switching or the default data subscription in DSDS.
handleRemovableSimInsertWhenDsds(); // 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()) { } else if (!isMultipleEnabledProfilesSupported()) {
Log.d(TAG, "The device is already in DSDS mode and no MEP. Do nothing."); Log.d(TAG, "The device is already in DSDS mode and no MEP. Do nothing.");
return; return;
@@ -124,8 +129,6 @@ public class SimSlotChangeHandler {
handleRemovableSimInsertUnderDsdsMep(removableSlotInfo); handleRemovableSimInsertUnderDsdsMep(removableSlotInfo);
return; return;
} }
Log.d(TAG, "the device is already in DSDS mode and have the DDS. Do nothing.");
return;
} }
if (isRemovableSimInserted) { if (isRemovableSimInserted) {
@@ -258,15 +261,29 @@ public class SimSlotChangeHandler {
startChooseSimActivity(false); 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(); List<SubscriptionInfo> subscriptionInfos = getAvailableRemovableSubscription();
if (subscriptionInfos.isEmpty()) { if (subscriptionInfos.isEmpty()) {
Log.e(TAG, "Unable to find the removable subscriptionInfo. Do nothing."); Log.e(TAG, "Unable to find the removable subscriptionInfo. Do nothing.");
return; return;
} }
Log.d(TAG, "ForNewUi and getAvailableRemovableSubscription:" Log.d(TAG, "getAvailableRemovableSubscription:" + subscriptionInfos);
+ subscriptionInfos); int psimSubId = subscriptionInfos.get(0).getSubscriptionId();
startSimConfirmDialogActivity(subscriptionInfos.get(0).getSubscriptionId()); if (!hasAnyPortActiveInSlot(removableSlotInfo) || hasOtherActiveSubInfo(psimSubId)) {
Log.d(TAG, "ForNewUi Start Setup flow");
startSimConfirmDialogActivity(psimSubId);
}
} }
private void handleRemovableSimInsertUnderDsdsMep(UiccSlotInfo removableSlotInfo) { private void handleRemovableSimInsertUnderDsdsMep(UiccSlotInfo removableSlotInfo) {

View File

@@ -81,24 +81,28 @@ private fun LabelSimPreference(
onboardingService: SimOnboardingService, onboardingService: SimOnboardingService,
subInfo: SubscriptionInfo, subInfo: SubscriptionInfo,
) { ) {
val originalSimCarrierName = subInfo.displayName.toString() val currentSimName = onboardingService.getSubscriptionInfoDisplayName(subInfo)
var titleSimName by remember { var prefTitle by remember {
mutableStateOf(onboardingService.getSubscriptionInfoDisplayName(subInfo)) mutableStateOf(currentSimName)
}
var dialogInputContent by remember {
mutableStateOf(currentSimName)
} }
val phoneNumber = phoneNumber(subInfo) val phoneNumber = phoneNumber(subInfo)
val alertDialogPresenter = rememberAlertDialogPresenter( val alertDialogPresenter = rememberAlertDialogPresenter(
confirmButton = AlertDialogButton( confirmButton = AlertDialogButton(
stringResource(R.string.mobile_network_sim_name_rename), stringResource(R.string.mobile_network_sim_name_rename),
titleSimName.isNotBlank() dialogInputContent.isNotBlank()
) { ) {
onboardingService.addItemForRenaming( onboardingService.addItemForRenaming(
subInfo, if (titleSimName.isEmpty()) originalSimCarrierName else titleSimName subInfo, dialogInputContent
) )
prefTitle = dialogInputContent
}, },
dismissButton = AlertDialogButton( dismissButton = AlertDialogButton(
stringResource(R.string.cancel), stringResource(R.string.cancel),
) { ) {
titleSimName = onboardingService.getSubscriptionInfoDisplayName(subInfo) // Do nothing
}, },
title = stringResource(R.string.sim_onboarding_label_sim_dialog_title), title = stringResource(R.string.sim_onboarding_label_sim_dialog_title),
text = { text = {
@@ -107,17 +111,19 @@ private fun LabelSimPreference(
modifier = Modifier.padding(bottom = SettingsDimension.itemPaddingVertical) modifier = Modifier.padding(bottom = SettingsDimension.itemPaddingVertical)
) )
SettingsOutlinedTextField( SettingsOutlinedTextField(
value = titleSimName, value = dialogInputContent,
label = stringResource(R.string.sim_onboarding_label_sim_dialog_label), label = stringResource(R.string.sim_onboarding_label_sim_dialog_label),
placeholder = {Text(text = originalSimCarrierName)}, placeholder = {Text(text = subInfo.displayName.toString())},
modifier = Modifier.fillMaxWidth().testTag("contentInput") modifier = Modifier
.fillMaxWidth()
.testTag("contentInput")
) { ) {
titleSimName = it dialogInputContent = it
} }
}, },
) )
Preference(object : PreferenceModel { Preference(object : PreferenceModel {
override val title = titleSimName override val title = prefTitle
override val summary = { phoneNumber.value ?: "" } override val summary = { phoneNumber.value ?: "" }
override val onClick = alertDialogPresenter::open override val onClick = alertDialogPresenter::open
}) })

View 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"
}
}

View File

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

View File

@@ -18,66 +18,97 @@ package com.android.settings.accessibility;
import static com.google.common.truth.Truth.assertThat; 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.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.fragment.app.FragmentFactory;
import androidx.test.core.app.ApplicationProvider; import androidx.fragment.app.testing.FragmentScenario;
import androidx.lifecycle.Lifecycle;
import androidx.preference.Preference;
import com.android.settings.R; import com.android.settings.R;
import com.android.settingslib.widget.FooterPreference;
import com.google.android.setupcompat.template.FooterBarMixin; import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupdesign.GlifLayout;
import com.google.android.setupdesign.GlifPreferenceLayout; import com.google.android.setupdesign.GlifPreferenceLayout;
import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; 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; import org.robolectric.RobolectricTestRunner;
/** Tests for {@link AutoBrightnessPreferenceFragmentForSetupWizard}. */ /** Tests for {@link AutoBrightnessPreferenceFragmentForSetupWizard}. */
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
public class AutoBrightnessPreferenceFragmentForSetupWizardTest { public class AutoBrightnessPreferenceFragmentForSetupWizardTest {
@Rule // Same as AutoBrightnessPreferenceFragmentForSetupWizard#FOOTER_PREFERENCE_KEY
public final MockitoRule mMockito = MockitoJUnit.rule(); 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 AutoBrightnessPreferenceFragmentForSetupWizard mFragment;
private GlifLayout mGlifLayout;
@Before @Before
public void setUp() { public void setUp() {
mFragment = spy(new AutoBrightnessPreferenceFragmentForSetupWizard()); mFragmentScenario = FragmentScenario
doReturn(mock(LifecycleOwner.class)).when(mFragment).getViewLifecycleOwner(); .launch(
doReturn(mContext).when(mFragment).getContext(); AutoBrightnessPreferenceFragmentForSetupWizard.class,
when(mGlifLayoutView.getMixin(eq(FooterBarMixin.class))).thenReturn(mFooterBarMixin); /* 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 @Test
public void setHeaderText_onViewCreated_verifyAction() { public void onViewCreated_verifyGlifHerderText() {
final String title = "title"; assertThat(mGlifLayout.getHeaderText())
doReturn(title).when(mContext).getString(R.string.auto_brightness_title); .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 @Test
@@ -85,11 +116,4 @@ public class AutoBrightnessPreferenceFragmentForSetupWizardTest {
assertThat(mFragment.getMetricsCategory()).isEqualTo( assertThat(mFragment.getMetricsCategory()).isEqualTo(
SettingsEnums.SUW_ACCESSIBILITY_AUTO_BRIGHTNESS); SettingsEnums.SUW_ACCESSIBILITY_AUTO_BRIGHTNESS);
} }
@Test
public void onViewCreated_verifyAction() {
mFragment.onViewCreated(mGlifLayoutView, null);
verify(mFooterBarMixin).setPrimaryButton(any());
}
} }

View File

@@ -17,7 +17,6 @@
package com.android.settings.biometrics.fingerprint; package com.android.settings.biometrics.fingerprint;
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON; 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 android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL;
import static com.android.settings.biometrics.fingerprint.FingerprintSettings.FingerprintSettingsFragment; 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.doReturn;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.UserInfo; import android.content.pm.UserInfo;
import android.hardware.biometrics.ComponentInfoInternal; import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.SensorProperties; import android.hardware.biometrics.SensorProperties;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties; import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -84,7 +80,6 @@ import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowSettingsPreferenceFragment.class, ShadowUtils.class, ShadowFragment.class, @Config(shadows = {ShadowSettingsPreferenceFragment.class, ShadowUtils.class, ShadowFragment.class,
@@ -152,6 +147,7 @@ public class FingerprintSettingsFragmentTest {
public void testCancellationSignalLifeCycle() { public void testCancellationSignalLifeCycle() {
setUpFragment(false); setUpFragment(false);
mFingerprintAuthenticateSidecar.setFingerprintManager(mFingerprintManager);
doNothing().when(mFingerprintManager).authenticate(any(), doNothing().when(mFingerprintManager).authenticate(any(),
mCancellationSignalArgumentCaptor.capture(), mCancellationSignalArgumentCaptor.capture(),
@@ -217,7 +213,6 @@ public class FingerprintSettingsFragmentTest {
doReturn(fragmentManager).when(mActivity).getSupportFragmentManager(); doReturn(fragmentManager).when(mActivity).getSupportFragmentManager();
mFingerprintAuthenticateSidecar = new FingerprintAuthenticateSidecar(); mFingerprintAuthenticateSidecar = new FingerprintAuthenticateSidecar();
mFingerprintAuthenticateSidecar.setFingerprintManager(mFingerprintManager);
doReturn(mFingerprintAuthenticateSidecar).when(fragmentManager).findFragmentByTag( doReturn(mFingerprintAuthenticateSidecar).when(fragmentManager).findFragmentByTag(
"authenticate_sidecar"); "authenticate_sidecar");
@@ -251,27 +246,4 @@ public class FingerprintSettingsFragmentTest {
true /* resetLockoutRequiresHardwareAuthToken */)); true /* resetLockoutRequiresHardwareAuthToken */));
doReturn(props).when(mFingerprintManager).getSensorPropertiesInternal(); 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());
}
} }

View File

@@ -38,6 +38,7 @@ import android.bluetooth.BluetoothHapPresetInfo;
import androidx.preference.ListPreference; import androidx.preference.ListPreference;
import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceCategory;
import com.android.settings.R;
import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.HapClientProfile; import com.android.settingslib.bluetooth.HapClientProfile;
import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothManager;
@@ -215,11 +216,13 @@ public class BluetoothDetailsHearingAidsPresetsControllerTest extends
assertThat(mController.getPreference()).isNotNull(); assertThat(mController.getPreference()).isNotNull();
assertThat(mController.getPreference().isEnabled()).isFalse(); assertThat(mController.getPreference().isEnabled()).isFalse();
assertThat(String.valueOf(mController.getPreference().getSummary())).isEqualTo(
mContext.getString(R.string.bluetooth_hearing_aids_presets_empty_list_message));
} }
@Test @Test
public void refresh_validPresetInfo_preferenceEnabled() { public void refresh_validPresetInfo_preferenceEnabled() {
BluetoothHapPresetInfo info = getTestPresetInfo(); BluetoothHapPresetInfo info = getTestPresetInfo(true);
when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(List.of(info)); when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(List.of(info));
mController.refresh(); mController.refresh();
@@ -230,7 +233,7 @@ public class BluetoothDetailsHearingAidsPresetsControllerTest extends
@Test @Test
public void refresh_invalidActivePresetIndex_summaryIsNull() { public void refresh_invalidActivePresetIndex_summaryIsNull() {
BluetoothHapPresetInfo info = getTestPresetInfo(); BluetoothHapPresetInfo info = getTestPresetInfo(true);
when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(List.of(info)); when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(List.of(info));
when(mHapClientProfile.getActivePresetIndex(mDevice)).thenReturn(PRESET_INDEX_UNAVAILABLE); when(mHapClientProfile.getActivePresetIndex(mDevice)).thenReturn(PRESET_INDEX_UNAVAILABLE);
@@ -242,7 +245,7 @@ public class BluetoothDetailsHearingAidsPresetsControllerTest extends
@Test @Test
public void refresh_validActivePresetIndex_summaryIsNotNull() { public void refresh_validActivePresetIndex_summaryIsNotNull() {
BluetoothHapPresetInfo info = getTestPresetInfo(); BluetoothHapPresetInfo info = getTestPresetInfo(true);
when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(List.of(info)); when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(List.of(info));
when(mHapClientProfile.getActivePresetIndex(mDevice)).thenReturn(TEST_PRESET_INDEX); when(mHapClientProfile.getActivePresetIndex(mDevice)).thenReturn(TEST_PRESET_INDEX);
@@ -262,10 +265,30 @@ public class BluetoothDetailsHearingAidsPresetsControllerTest extends
verify(mHapClientProfile).selectPreset(mDevice, TEST_PRESET_INDEX); 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); BluetoothHapPresetInfo info = mock(BluetoothHapPresetInfo.class);
when(info.getName()).thenReturn(TEST_PRESET_NAME); when(info.getName()).thenReturn(TEST_PRESET_NAME);
when(info.getIndex()).thenReturn(TEST_PRESET_INDEX); when(info.getIndex()).thenReturn(TEST_PRESET_INDEX);
when(info.isAvailable()).thenReturn(available);
return info; return info;
} }

View File

@@ -248,6 +248,23 @@ public class AudioSharingDialogHandlerTest {
verify(mCachedDevice1).setActive(); 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 @Test
public void handleUserTriggeredLeaDeviceConnected_noSharingTwoLeaDevices_showJoinDialog() { public void handleUserTriggeredLeaDeviceConnected_noSharingTwoLeaDevices_showJoinDialog() {
setUpBroadcast(false); setUpBroadcast(false);
@@ -451,6 +468,23 @@ public class AudioSharingDialogHandlerTest {
verify(mCachedDevice1, never()).setActive(); 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 @Test
public void handleLeaDeviceConnected_noSharingTwoLeaDevices_showJoinDialog() { public void handleLeaDeviceConnected_noSharingTwoLeaDevices_showJoinDialog() {
setUpBroadcast(false); setUpBroadcast(false);

View File

@@ -789,6 +789,40 @@ public class BatteryInfoTest {
expectedChargeLabel); 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 { private enum ChargingSpeed {
FAST, FAST,
REGULAR, REGULAR,
@@ -801,6 +835,15 @@ public class BatteryInfoTest {
DOCKED 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( private Intent createIntentForGetBatteryInfoTest(
ChargingType chargingType, ChargingSpeed chargingSpeed, int batteryLevel) { ChargingType chargingType, ChargingSpeed chargingSpeed, int batteryLevel) {
return createBatteryIntent( return createBatteryIntent(

View File

@@ -494,6 +494,7 @@ public final class BatteryDiffEntryTest {
final ContentValues values = final ContentValues values =
getContentValuesWithType(ConvertUtils.CONSUMER_TYPE_UID_BATTERY); getContentValuesWithType(ConvertUtils.CONSUMER_TYPE_UID_BATTERY);
values.put(BatteryHistEntry.KEY_UID, /*invalid uid*/ 10001); 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); values.put(BatteryHistEntry.KEY_PACKAGE_NAME, fakePackageName);
final BatteryDiffEntry entry = createBatteryDiffEntry(10, new BatteryHistEntry(values)); final BatteryDiffEntry entry = createBatteryDiffEntry(10, new BatteryHistEntry(values));
@@ -503,14 +504,16 @@ public final class BatteryDiffEntryTest {
doReturn(BatteryUtils.UID_NULL) doReturn(BatteryUtils.UID_NULL)
.when(mMockPackageManager) .when(mMockPackageManager)
.getPackageUid(entry.getPackageName(), PackageManager.GET_META_DATA); .getPackageUidAsUser(
entry.getPackageName(), PackageManager.GET_META_DATA, USER_ID);
entry.updateRestrictionFlagState(); entry.updateRestrictionFlagState();
// Sets false if the app is invalid package name. // Sets false if the app is invalid package name.
assertThat(entry.mValidForRestriction).isFalse(); assertThat(entry.mValidForRestriction).isFalse();
doReturn(1000) doReturn(1000)
.when(mMockPackageManager) .when(mMockPackageManager)
.getPackageUid(entry.getPackageName(), PackageManager.GET_META_DATA); .getPackageUidAsUser(
entry.getPackageName(), PackageManager.GET_META_DATA, USER_ID);
entry.updateRestrictionFlagState(); entry.updateRestrictionFlagState();
// Sets false if the app PackageInfo cannot be found. // Sets false if the app PackageInfo cannot be found.
assertThat(entry.mValidForRestriction).isFalse(); assertThat(entry.mValidForRestriction).isFalse();

View File

@@ -132,7 +132,7 @@ public class BatteryEntryTest {
createBatteryEntryForApp(null, APP_DEFAULT_PACKAGE_NAME, HIGH_DRAIN_PACKAGE); createBatteryEntryForApp(null, APP_DEFAULT_PACKAGE_NAME, HIGH_DRAIN_PACKAGE);
assertThat(entry.getDefaultPackageName()).isEqualTo(APP_DEFAULT_PACKAGE_NAME); 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 @Test
@@ -152,7 +152,7 @@ public class BatteryEntryTest {
BatteryEntry entry = createBatteryEntryForApp(null, null, HIGH_DRAIN_PACKAGE); 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 @Test
@@ -163,7 +163,7 @@ public class BatteryEntryTest {
null, null,
HIGH_DRAIN_PACKAGE); HIGH_DRAIN_PACKAGE);
assertThat(entry.getLabel()).isEqualTo(LABEL_PREFIX + HIGH_DRAIN_PACKAGE); assertThat(entry.getLabel()).isEqualTo(HIGH_DRAIN_PACKAGE);
} }
@Test @Test

View File

@@ -112,6 +112,7 @@ public final class DataProcessManagerTest {
mContext, mContext,
/* handler= */ null, /* handler= */ null,
mUserIdsSeries, mUserIdsSeries,
/* isFromPeriodJob= */ false,
/* rawStartTimestamp= */ 0L, /* rawStartTimestamp= */ 0L,
/* lastFullChargeTimestamp= */ 0L, /* lastFullChargeTimestamp= */ 0L,
/* callbackFunction= */ null, /* callbackFunction= */ null,
@@ -258,6 +259,7 @@ public final class DataProcessManagerTest {
mContext, mContext,
/* handler= */ null, /* handler= */ null,
mUserIdsSeries, mUserIdsSeries,
/* isFromPeriodJob= */ false,
/* rawStartTimestamp= */ 2L, /* rawStartTimestamp= */ 2L,
/* lastFullChargeTimestamp= */ 1L, /* lastFullChargeTimestamp= */ 1L,
/* callbackFunction= */ null, /* callbackFunction= */ null,

View File

@@ -31,12 +31,17 @@ import android.app.NotificationChannelGroup;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.content.Context; import android.content.Context;
import android.os.UserManager; 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 androidx.preference.Preference;
import com.android.server.notification.Flags;
import com.android.settings.notification.NotificationBackend; import com.android.settings.notification.NotificationBackend;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
@@ -60,6 +65,8 @@ public class DeletedChannelsPreferenceControllerTest {
private UserManager mUm; private UserManager mUm;
private DeletedChannelsPreferenceController mController; private DeletedChannelsPreferenceController mController;
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before @Before
public void setUp() { public void setUp() {
@@ -109,6 +116,16 @@ public class DeletedChannelsPreferenceControllerTest {
} }
@Test @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() { public void isAvailable_appScreen() {
when(mBackend.getDeletedChannelCount(any(), anyInt())).thenReturn(1); when(mBackend.getDeletedChannelCount(any(), anyInt())).thenReturn(1);
mController.onResume( mController.onResume(

View File

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

View File

@@ -24,6 +24,7 @@ import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenModeConfig; import android.service.notification.ZenModeConfig;
import android.service.notification.ZenPolicy; import android.service.notification.ZenPolicy;
import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.android.settingslib.notification.modes.ZenMode; import com.android.settingslib.notification.modes.ZenMode;
@@ -70,13 +71,13 @@ class TestModeBuilder {
return this; return this;
} }
public TestModeBuilder setName(String name) { TestModeBuilder setName(String name) {
mRule.setName(name); mRule.setName(name);
mConfigZenRule.name = name; mConfigZenRule.name = name;
return this; return this;
} }
public TestModeBuilder setPackage(String pkg) { TestModeBuilder setPackage(String pkg) {
mRule.setPackageName(pkg); mRule.setPackageName(pkg);
mConfigZenRule.pkg = pkg; mConfigZenRule.pkg = pkg;
return this; return this;
@@ -114,7 +115,7 @@ class TestModeBuilder {
return this; return this;
} }
public TestModeBuilder setEnabled(boolean enabled) { TestModeBuilder setEnabled(boolean enabled) {
mRule.setEnabled(enabled); mRule.setEnabled(enabled);
mConfigZenRule.enabled = enabled; mConfigZenRule.enabled = enabled;
return this; return this;
@@ -126,12 +127,17 @@ class TestModeBuilder {
return this; return this;
} }
public TestModeBuilder setTriggerDescription(@Nullable String triggerDescription) { TestModeBuilder setTriggerDescription(@Nullable String triggerDescription) {
mRule.setTriggerDescription(triggerDescription); mRule.setTriggerDescription(triggerDescription);
mConfigZenRule.triggerDescription = triggerDescription; mConfigZenRule.triggerDescription = triggerDescription;
return this; return this;
} }
TestModeBuilder setIconResId(@DrawableRes int iconResId) {
mRule.setIconResId(iconResId);
return this;
}
TestModeBuilder setActive(boolean active) { TestModeBuilder setActive(boolean active) {
if (active) { if (active) {
mConfigZenRule.enabled = true; mConfigZenRule.enabled = true;

View File

@@ -24,13 +24,15 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import android.content.Context; import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceScreen;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settingslib.notification.modes.ZenMode; import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend; import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.LayoutPreference; import com.android.settingslib.widget.LayoutPreference;
@@ -40,35 +42,34 @@ import com.google.common.collect.ImmutableList;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor; import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment; import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
public class ZenModeIconPickerListPreferenceControllerTest { public class ZenModeIconPickerListPreferenceControllerTest {
private static final ZenMode ZEN_MODE = TestModeBuilder.EXAMPLE; private Context mContext;
private ZenModesBackend mBackend;
private ZenModeIconPickerListPreferenceController mController; private ZenModeIconPickerListPreferenceController mController;
private PreferenceScreen mPreferenceScreen; @Mock private PreferenceScreen mPreferenceScreen;
private LayoutPreference mLayoutPreference;
private RecyclerView mRecyclerView; private RecyclerView mRecyclerView;
@Mock private ZenModeIconPickerListPreferenceController.IconPickerListener mListener;
@Before @Before
public void setUp() { public void setUp() {
Context context = RuntimeEnvironment.getApplication(); MockitoAnnotations.initMocks(this);
mBackend = mock(ZenModesBackend.class); mContext = RuntimeEnvironment.getApplication();
DashboardFragment fragment = mock(DashboardFragment.class);
mController = new ZenModeIconPickerListPreferenceController( mController = new ZenModeIconPickerListPreferenceController(
RuntimeEnvironment.getApplication(), "icon_list", fragment, RuntimeEnvironment.getApplication(), "icon_list", mListener,
new TestIconOptionsProvider(), mBackend); new TestIconOptionsProvider(), mock(ZenModesBackend.class));
mRecyclerView = new RecyclerView(context); mRecyclerView = new RecyclerView(mContext);
mRecyclerView.setId(R.id.icon_list); mRecyclerView.setId(R.id.icon_list);
LayoutPreference layoutPreference = new LayoutPreference(context, mRecyclerView); mLayoutPreference = new LayoutPreference(mContext, mRecyclerView);
mPreferenceScreen = mock(PreferenceScreen.class); when(mPreferenceScreen.findPreference(eq("icon_list"))).thenReturn(mLayoutPreference);
when(mPreferenceScreen.findPreference(eq("icon_list"))).thenReturn(layoutPreference);
} }
@Test @Test
@@ -80,14 +81,32 @@ public class ZenModeIconPickerListPreferenceControllerTest {
} }
@Test @Test
public void selectIcon_updatesMode() { public void updateState_highlightsCurrentIcon() {
mController.setZenMode(ZEN_MODE); 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); assertThat(getItemViewAt(0).isSelected()).isFalse();
verify(mBackend).updateMode(captor.capture()); assertThat(getItemViewAt(1).isSelected()).isFalse();
assertThat(captor.getValue().getRule().getIconResId()).isEqualTo(R.drawable.ic_android); 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 { private static class TestIconOptionsProvider implements IconOptionsProvider {

View File

@@ -17,7 +17,6 @@
package com.android.settings.network.telephony package com.android.settings.network.telephony
import android.content.Context import android.content.Context
import android.telephony.SubscriptionManager
import android.telephony.TelephonyCallback import android.telephony.TelephonyCallback
import android.telephony.TelephonyManager import android.telephony.TelephonyManager
import androidx.test.core.app.ApplicationProvider 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 com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@@ -49,20 +49,15 @@ class CallStateRepositoryTest {
} }
} }
private val mockSubscriptionManager = mock<SubscriptionManager> { private val mockSubscriptionRepository = mock<SubscriptionRepository> {
on { activeSubscriptionIdList } doReturn intArrayOf(SUB_ID) on { activeSubscriptionIdListFlow() } doReturn flowOf(listOf(SUB_ID))
on { addOnSubscriptionsChangedListener(any(), any()) } doAnswer {
val listener = it.arguments[1] as SubscriptionManager.OnSubscriptionsChangedListener
listener.onSubscriptionsChanged()
}
} }
private val context: Context = spy(ApplicationProvider.getApplicationContext()) { private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager
on { subscriptionManager } doReturn mockSubscriptionManager
} }
private val repository = CallStateRepository(context) private val repository = CallStateRepository(context, mockSubscriptionRepository)
@Test @Test
fun callStateFlow_initial_sendInitialState() = runBlocking { fun callStateFlow_initial_sendInitialState() = runBlocking {
@@ -89,8 +84,8 @@ class CallStateRepositoryTest {
@Test @Test
fun isInCallFlow_noActiveSubscription() = runBlocking { fun isInCallFlow_noActiveSubscription() = runBlocking {
mockSubscriptionManager.stub { mockSubscriptionRepository.stub {
on { activeSubscriptionIdList } doReturn intArrayOf() on { activeSubscriptionIdListFlow() } doReturn flowOf(emptyList())
} }
val isInCall = repository.isInCallFlow().firstWithTimeoutOrNull() val isInCall = repository.isInCallFlow().firstWithTimeoutOrNull()

View File

@@ -77,7 +77,7 @@ class SubscriptionRepositoryTest {
@Test @Test
fun subscriptionsChangedFlow_hasInitialValue() = runBlocking { fun subscriptionsChangedFlow_hasInitialValue() = runBlocking {
val initialValue = context.subscriptionsChangedFlow().firstWithTimeoutOrNull() val initialValue = repository.subscriptionsChangedFlow().firstWithTimeoutOrNull()
assertThat(initialValue).isSameInstanceAs(Unit) assertThat(initialValue).isSameInstanceAs(Unit)
} }
@@ -85,7 +85,7 @@ class SubscriptionRepositoryTest {
@Test @Test
fun subscriptionsChangedFlow_changed() = runBlocking { fun subscriptionsChangedFlow_changed() = runBlocking {
val listDeferred = async { val listDeferred = async {
context.subscriptionsChangedFlow().toListWithTimeout() repository.subscriptionsChangedFlow().toListWithTimeout()
} }
delay(100) delay(100)
@@ -94,6 +94,17 @@ class SubscriptionRepositoryTest {
assertThat(listDeferred.await()).hasSize(2) 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 @Test
fun getSelectableSubscriptionInfoList_sortedBySimSlotIndex() { fun getSelectableSubscriptionInfoList_sortedBySimSlotIndex() {
mockSubscriptionManager.stub { mockSubscriptionManager.stub {

View File

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

View File

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