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] -->
<string name="lock_screen_password_skip_face_message" product="default">A password is required to set up Face Unlock.\n\nA password protects the phone if it\u2019s lost or stolen.</string>
<!-- Dialog text shown when the user tries to skip setting up a screen lock, warning that they can't continue to set up face or fingerprint. (tablet) [CHAR LIMIT=NONE] -->
<string name="lock_screen_pin_skip_biometrics_message" product="tablet">A PIN is required to set up Face Unlock and Fingerprint Unlock.\n\nA PIN protects the tablet if it\u2019s lost or stolen.</string>
<string name="lock_screen_pin_skip_biometrics_message" product="tablet">A PIN is required to set up Fingerprint Unlock and Face Unlock.\n\nA PIN protects the tablet if it\u2019s lost or stolen.</string>
<!-- Dialog text shown when the user tries to skip setting up a screen lock, warning that they can't continue to set up face or fingerprint. (tablet) [CHAR LIMIT=NONE] -->
<string name="lock_screen_pattern_skip_biometrics_message" product="tablet">A pattern is required to set up Face Unlock and Fingerprint Unlock.\n\nA pattern protects the tablet if it\u2019s lost or stolen.</string>
<string name="lock_screen_pattern_skip_biometrics_message" product="tablet">A pattern is required to set up Fingerprint Unlock and Face Unlock.\n\nA pattern protects the tablet if it\u2019s lost or stolen.</string>
<!-- Dialog text shown when the user tries to skip setting up a screen lock, warning that they can't continue to set up face or fingerprint. (tablet) [CHAR LIMIT=NONE] -->
<string name="lock_screen_password_skip_biometrics_message" product="tablet">A password is required to set up Face Unlock and Fingerprint Unlock.\n\nA password protects the tablet if it\u2019s lost or stolen.</string>
<string name="lock_screen_password_skip_biometrics_message" product="tablet">A password is required to set up Fingerprint Unlock and Face Unlock.\n\nA password protects the tablet if it\u2019s lost or stolen.</string>
<!-- Dialog text shown when the user tries to skip setting up a screen lock, warning that they can't continue to set up face or fingerprint. (device) [CHAR LIMIT=NONE] -->
<string name="lock_screen_pin_skip_biometrics_message" product="device">A PIN is required to set up Face Unlock and Fingerprint Unlock.\n\nA PIN protects the device if it\u2019s lost or stolen.</string>
<string name="lock_screen_pin_skip_biometrics_message" product="device">A PIN is required to set up Fingerprint Unlock and Face Unlock.\n\nA PIN protects the device if it\u2019s lost or stolen.</string>
<!-- Dialog text shown when the user tries to skip setting up a screen lock, warning that they can't continue to set up face or fingerprint. (device) [CHAR LIMIT=NONE] -->
<string name="lock_screen_pattern_skip_biometrics_message" product="device">A pattern is required to set up Face Unlock and Fingerprint Unlock.\n\nA pattern protects the device if it\u2019s lost or stolen.</string>
<string name="lock_screen_pattern_skip_biometrics_message" product="device">A pattern is required to set up Fingerprint Unlock and Face Unlock.\n\nA pattern protects the device if it\u2019s lost or stolen.</string>
<!-- Dialog text shown when the user tries to skip setting up a screen lock, warning that they can't continue to set up face or fingerprint. (device) [CHAR LIMIT=NONE] -->
<string name="lock_screen_password_skip_biometrics_message" product="device">A password is required to set up Face Unlock and Fingerprint Unlock.\n\nA password protects the device if it\u2019s lost or stolen.</string>
<string name="lock_screen_password_skip_biometrics_message" product="device">A password is required to set up Fingerprint Unlock and Face Unlock.\n\nA password protects the device if it\u2019s lost or stolen.</string>
<!-- Dialog text shown when the user tries to skip setting up a screen lock, warning that they can't continue to set up face or fingerprint. (default) [CHAR LIMIT=NONE] -->
<string name="lock_screen_pin_skip_biometrics_message" product="default">A PIN is required to set up Face Unlock and Fingerprint Unlock.\n\nA PIN protects the phone if it\u2019s lost or stolen.</string>
<string name="lock_screen_pin_skip_biometrics_message" product="default">A PIN is required to set up Fingerprint Unlock and Face Unlock.\n\nA PIN protects the phone if it\u2019s lost or stolen.</string>
<!-- Dialog text shown when the user tries to skip setting up a screen lock, warning that they can't continue to set up face or fingerprint. (default) [CHAR LIMIT=NONE] -->
<string name="lock_screen_pattern_skip_biometrics_message" product="default">A pattern is required to set up Face Unlock and Fingerprint Unlock.\n\nA pattern protects the phone if it\u2019s lost or stolen.</string>
<string name="lock_screen_pattern_skip_biometrics_message" product="default">A pattern is required to set up Fingerprint Unlock and Face Unlock.\n\nA pattern protects the phone if it\u2019s lost or stolen.</string>
<!-- Dialog text shown when the user tries to skip setting up a screen lock, warning that they can't continue to set up face or fingerprint. (default) [CHAR LIMIT=NONE] -->
<string name="lock_screen_password_skip_biometrics_message" product="default">A password is required to set up Face Unlock and Fingerprint Unlock.\n\nA password protects the phone if it\u2019s lost or stolen.</string>
<string name="lock_screen_password_skip_biometrics_message" product="default">A password is required to set up Fingerprint Unlock and Face Unlock.\n\nA password protects the phone if it\u2019s lost or stolen.</string>
<!-- Message shown in a dialog which asks the user to confirm when a single fingerprint gets deleted. [CHAR LIMIT=NONE]-->
<string name="fingerprint_v2_delete_message" product="default">This deletes the fingerprint images and model associated with \'<xliff:g id="fingerprint_id" example="Fingerprint 2">%1$s</xliff:g>\' that are stored on your phone</string>
<!-- Message shown in a dialog which asks the user to confirm when a single fingerprint gets deleted. [CHAR LIMIT=NONE]-->

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>
<!-- Connected devices settings. Title for hearing aids presets. A preset is a set of hearing aid settings. User can apply different settings in different environments (e.g. Outdoor, Restaurant, Home) [CHAR LIMIT=60] [BACKUP_MESSAGE_ID=5429761844739722885] -->
<string name="bluetooth_hearing_aids_presets">Preset</string>
<!-- Connected devices settings. Summary of the preference when no preset info is obtained from the remote device [CHAR LIMIT=60] -->
<string name="bluetooth_hearing_aids_presets_empty_list_message">There are no presets programmed by your audiologist</string>
<!-- Message when selecting hearing aids presets failed. [CHAR LIMIT=NONE] -->
<string name="bluetooth_hearing_aids_presets_error">Couldn\u2019t update preset</string>
<!-- Connected devices settings. Title of the preference to show the entrance of the audio output page. It can change different types of audio are played on phone or other bluetooth devices. [CHAR LIMIT=35] -->
@@ -995,7 +997,7 @@
<!-- Biometric settings --><skip />
<!-- Title shown for menu item that launches biometric settings. [CHAR LIMIT=66] -->
<string name="security_settings_biometric_preference_title">Fingerprint &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] -->
<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] -->
@@ -2784,6 +2786,9 @@
<string name="brightness">Brightness level</string>
<!-- Sound & display settings screen, setting option name to enable adaptive brightness [CHAR LIMIT=30] -->
<string name="auto_brightness_title">Adaptive brightness</string>
<!-- Note: The content description title is only applied in adaptive brightness detailed page in setup wizard flow, to make the consistency with other accessibility suw pages. -->
<!-- ContentDescription title for adaptive brightness detailed page footer. [CHAR LIMIT=60] -->
<string name="auto_brightness_content_description_title">About adaptive brightness</string>
<!-- Description about the feature adaptive brightness -->
<string name="auto_brightness_description">Your screen brightness will automatically adjust to your environment and activities. You can move the slider manually to help adaptive brightness learn your preferences.</string>
<!-- Setting option summary when adaptive brightness is on [CHAR LIMIT=NONE] -->
@@ -8980,6 +8985,9 @@
<!-- [CHAR LIMIT=NONE] App notification settings: no channels -->
<string name="no_channels">This app has not posted any notifications</string>
<!-- [CHAR LIMIT=NONE] App notification settings: has channels, but hasn't sent notifications recently -->
<string name="no_recent_channels">Show unused categories</string>
<!-- [CHAR LIMIT=NONE] App notification settings: link to app notification settings-->
<string name="app_settings_link">Additional settings in the app</string>

View File

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

View File

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

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

View File

@@ -356,6 +356,11 @@ public class FingerprintSettings extends SubSettings {
*/
protected void handleError(int errMsgId, CharSequence msg) {
switch (errMsgId) {
case FingerprintManager.FINGERPRINT_ERROR_CANCELED:
case FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED:
// Only happens if we get preempted by another activity, or canceled by the
// user (e.g. swipe up to home). Ignored.
return;
case FingerprintManager.FINGERPRINT_ERROR_LOCKOUT:
mInFingerprintLockout = true;
// We've been locked out. Reset after 30s.

View File

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

View File

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

View File

@@ -649,8 +649,12 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment
DynamicDataObserver observer) {
Log.d(TAG, "register observer: @" + Integer.toHexString(observer.hashCode())
+ ", uri: " + observer.getUri());
resolver.registerContentObserver(observer.getUri(), false, observer);
mRegisteredObservers.add(observer);
try {
resolver.registerContentObserver(observer.getUri(), false, observer);
mRegisteredObservers.add(observer);
} catch (Exception e) {
Log.w(TAG, "Cannot register observer: " + observer.getUri(), e);
}
}
private void unregisterDynamicDataObservers(List<DynamicDataObserver> observers) {
@@ -661,8 +665,13 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment
observers.forEach(observer -> {
Log.d(TAG, "unregister observer: @" + Integer.toHexString(observer.hashCode())
+ ", uri: " + observer.getUri());
mRegisteredObservers.remove(observer);
resolver.unregisterContentObserver(observer);
if (mRegisteredObservers.remove(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 pluggedStatus;
public boolean discharging = true;
public boolean isBatteryDefender;
public boolean isBatteryDefender = false;
public boolean isLongLife = false;
public boolean isFastCharging;
public long remainingTimeUs = 0;
public long averageTimeToDischarge = EstimateKt.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN;
@@ -306,7 +307,7 @@ public class BatteryInfo {
info.pluggedStatus = batteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
info.mCharging = info.pluggedStatus != 0;
info.averageTimeToDischarge = estimate.getAverageDischargeTime();
info.isBatteryDefender =
info.isLongLife =
batteryBroadcast.getIntExtra(
BatteryManager.EXTRA_CHARGING_STATUS,
BatteryManager.CHARGING_POLICY_DEFAULT)
@@ -319,7 +320,7 @@ public class BatteryInfo {
info.isFastCharging =
BatteryStatus.getChargingSpeed(context, batteryBroadcast)
== BatteryStatus.CHARGING_FAST;
if (info.isBatteryDefender) {
if (info.isLongLife) {
info.isBatteryDefender =
FeatureFactory.getFeatureFactory()
.getPowerUsageFeatureProvider()

View File

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

View File

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

View File

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

View File

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

View File

@@ -22,7 +22,6 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
import android.graphics.drawable.Drawable;
import android.os.BatteryConsumer;
@@ -176,18 +175,7 @@ public class BatteryEntry {
}
}
if (mDefaultPackageName != null) {
PackageManager pm = context.getPackageManager();
try {
ApplicationInfo appInfo =
pm.getApplicationInfo(mDefaultPackageName, 0 /* no flags */);
mName = pm.getApplicationLabel(appInfo).toString();
} catch (NameNotFoundException e) {
Log.d(
TAG,
"PackageManager failed to retrieve ApplicationInfo for: "
+ mDefaultPackageName);
mName = mDefaultPackageName;
}
mName = mDefaultPackageName;
}
mTimeInForegroundMs =
uidBatteryConsumer.getTimeInProcessStateMs(

View File

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

View File

@@ -28,6 +28,8 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
import com.android.settings.overlay.FeatureFactory;
import java.util.ArrayList;
import java.util.Calendar;
@@ -78,6 +80,7 @@ public class DataProcessManager {
// Raw start timestamp with round to the nearest hour.
private final long mRawStartTimestamp;
private final long mLastFullChargeTimestamp;
private final boolean mIsFromPeriodJob;
private final Context mContext;
private final Handler mHandler;
private final UserIdsSeries mUserIdsSeries;
@@ -122,6 +125,7 @@ public class DataProcessManager {
Context context,
Handler handler,
final UserIdsSeries userIdsSeries,
final boolean isFromPeriodJob,
final long rawStartTimestamp,
final long lastFullChargeTimestamp,
@NonNull final OnBatteryDiffDataMapLoadedListener callbackFunction,
@@ -130,6 +134,7 @@ public class DataProcessManager {
mContext = context.getApplicationContext();
mHandler = handler;
mUserIdsSeries = userIdsSeries;
mIsFromPeriodJob = isFromPeriodJob;
mRawStartTimestamp = rawStartTimestamp;
mLastFullChargeTimestamp = lastFullChargeTimestamp;
mCallbackFunction = callbackFunction;
@@ -147,6 +152,7 @@ public class DataProcessManager {
mHandler = handler;
mUserIdsSeries = userIdsSeries;
mCallbackFunction = callbackFunction;
mIsFromPeriodJob = false;
mRawStartTimestamp = 0L;
mLastFullChargeTimestamp = 0L;
mHourlyBatteryLevelsPerDay = null;
@@ -158,14 +164,9 @@ public class DataProcessManager {
/** Starts the async tasks to load battery history data and app usage data. */
public void start() {
start(/* isFromPeriodJob= */ false);
}
/** Starts the async tasks to load battery history data and app usage data. */
public void start(boolean isFromPeriodJob) {
// If we have battery level data, load the battery history map and app usage simultaneously.
if (mHourlyBatteryLevelsPerDay != null) {
if (isFromPeriodJob) {
if (mIsFromPeriodJob) {
mIsCurrentBatteryHistoryLoaded = true;
mIsCurrentAppUsageLoaded = true;
mIsBatteryUsageSlotLoaded = true;
@@ -514,6 +515,14 @@ public class DataProcessManager {
mAppUsagePeriodMap,
getSystemAppsPackageNames(),
getSystemAppsUids()));
// Process the reattributate data for the following two cases:
// 1) the latest slot for the timestamp "until now"
// 2) walkthrough all BatteryDiffData again to handle "re-compute" case
final PowerUsageFeatureProvider featureProvider =
FeatureFactory.getFeatureFactory()
.getPowerUsageFeatureProvider();
featureProvider.processBatteryReattributeData(
mContext, batteryDiffDataMap, mIsFromPeriodJob);
Log.d(
TAG,
@@ -683,12 +692,13 @@ public class DataProcessManager {
context,
handler,
userIdsSeries,
isFromPeriodJob,
startTimestamp,
lastFullChargeTime,
onBatteryDiffDataMapLoadedListener,
batteryLevelData.getHourlyBatteryLevelsPerDay(),
processedBatteryHistoryMap)
.start(isFromPeriodJob);
.start();
return batteryLevelData;
}

View File

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

View File

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

View File

@@ -29,6 +29,7 @@ import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
@@ -68,6 +69,30 @@ class SubscriptionRepository(private val context: Context) {
}
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?
@@ -79,22 +104,8 @@ fun Context.phoneNumberFlow(subscriptionInfo: SubscriptionInfo) = subscriptionsC
SubscriptionUtil.getBidiFormattedPhoneNumber(this, subscriptionInfo)
}.filterNot { it.isNullOrEmpty() }.flowOn(Dispatchers.Default)
fun Context.subscriptionsChangedFlow() = callbackFlow {
val subscriptionManager = requireSubscriptionManager()
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)
fun Context.subscriptionsChangedFlow(): Flow<Unit> =
SubscriptionRepository(this).subscriptionsChangedFlow()
/**
* 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 com.android.settings.R
import com.android.settings.network.telephony.MobileDataRepository
import com.android.settings.network.telephony.SubscriptionRepository
import com.android.settings.network.telephony.ims.ImsMmTelRepositoryImpl
import com.android.settings.network.telephony.requireSubscriptionManager
import com.android.settings.network.telephony.safeGetConfig
import com.android.settings.network.telephony.subscriptionsChangedFlow
import com.android.settings.network.telephony.telephonyManager
import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
import kotlinx.coroutines.Dispatchers
@@ -48,7 +47,7 @@ class CrossSimCallingViewModel(
private val application: Application,
) : AndroidViewModel(application) {
private val subscriptionManager = application.requireSubscriptionManager()
private val subscriptionRepository = SubscriptionRepository(application)
private val carrierConfigManager =
application.getSystemService(CarrierConfigManager::class.java)!!
private val scope = viewModelScope + Dispatchers.Default
@@ -59,9 +58,8 @@ class CrossSimCallingViewModel(
init {
val resources = application.resources
if (resources.getBoolean(R.bool.config_auto_data_switch_enables_cross_sim_calling)) {
application.subscriptionsChangedFlow()
.flatMapLatest {
val activeSubIds = subscriptionManager.activeSubscriptionIdList.toList()
subscriptionRepository.activeSubscriptionIdListFlow()
.flatMapLatest { activeSubIds ->
merge(
activeSubIds.anyMobileDataEnableChangedFlow(),
updateChannel.receiveAsFlow(),

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

View File

@@ -16,16 +16,10 @@
package com.android.settings.notification.app;
import static com.android.server.notification.Flags.notificationHideUnusedChannels;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R;
@@ -107,38 +101,8 @@ public class AppNotificationSettings extends NotificationSettings {
mControllers.add(new BubbleSummaryPreferenceController(context, mBackend));
mControllers.add(new NotificationsOffPreferenceController(context));
mControllers.add(new DeletedChannelsPreferenceController(context, mBackend));
mControllers.add(new ShowMorePreferenceController(
context, mDependentFieldListener, mBackend));
return new ArrayList<>(mControllers);
}
private final int SHOW_ALL_CHANNELS = 1;
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
if (notificationHideUnusedChannels()) {
menu.add(Menu.NONE, SHOW_ALL_CHANNELS, Menu.NONE,
mShowAll ? R.string.hide_unused_channels : R.string.show_unused_channels);
}
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (!notificationHideUnusedChannels()) {
return super.onOptionsItemSelected(item);
}
switch (item.getItemId()) {
case SHOW_ALL_CHANNELS:
mShowAll = !mShowAll;
item.setTitle(mShowAll
? R.string.hide_unused_channels
: R.string.show_unused_channels);
ChannelListPreferenceController list =
use(ChannelListPreferenceController.class);
list.setShowAll(mShowAll);
list.updateState(findPreference(list.getPreferenceKey()));
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}

View File

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

View File

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

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
* is 36x36dp and it's contained into a circle of diameter 54dp.
* is 36x36dp and it's contained into a circle of diameter 54dp. It's also set up so that
* selection and pressed states are represented in the color.
*/
static Drawable makeIconCircle(@NonNull Context context, @NonNull Drawable icon) {
ShapeDrawable background = new ShapeDrawable(new OvalShape());
background.getPaint().setColor(Utils.getColorAttrDefaultColor(context,
com.android.internal.R.attr.materialColorSecondaryContainer));
icon.setTint(Utils.getColorAttrDefaultColor(context,
com.android.internal.R.attr.materialColorOnSecondaryContainer));
background.setTintList(
context.getColorStateList(R.color.modes_icon_picker_item_background));
icon = icon.mutate();
icon.setTintList(
context.getColorStateList(R.color.modes_icon_picker_item_icon));
LayerDrawable layerDrawable = new LayerDrawable(new Drawable[] { background, icon });

View File

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

View File

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

View File

@@ -43,7 +43,16 @@ public class ZenModeIconPickerFragment extends ZenModeFragmentBase {
return ImmutableList.of(
new ZenModeIconPickerIconPreferenceController(context, "current_icon", this,
mBackend),
new ZenModeIconPickerListPreferenceController(context, "icon_list", this,
new IconOptionsProviderImpl(mContext), mBackend));
new ZenModeIconPickerListPreferenceController(context, "icon_list",
mIconPickerListener, new IconOptionsProviderImpl(mContext), mBackend));
}
private final ZenModeIconPickerListPreferenceController.IconPickerListener mIconPickerListener =
new ZenModeIconPickerListPreferenceController.IconPickerListener() {
@Override
public void onIconSelected(int iconResId) {
saveMode(mode -> mode.getRule().setIconResId(iconResId));
finish();
}
};
}

View File

@@ -17,6 +17,7 @@
package com.android.settings.notification.modes;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -25,31 +26,35 @@ import android.widget.ImageView;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.SimpleItemAnimator;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.LayoutPreference;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import java.util.HashMap;
import java.util.Map;
class ZenModeIconPickerListPreferenceController extends AbstractZenModePreferenceController {
private final DashboardFragment mFragment;
private final IconOptionsProvider mIconOptionsProvider;
private final IconPickerListener mListener;
@Nullable private IconAdapter mAdapter;
private @DrawableRes int mCurrentIconResId;
ZenModeIconPickerListPreferenceController(@NonNull Context context, @NonNull String key,
@NonNull DashboardFragment fragment, @NonNull IconOptionsProvider iconOptionsProvider,
@NonNull IconPickerListener listener, @NonNull IconOptionsProvider iconOptionsProvider,
@Nullable ZenModesBackend backend) {
super(context, key, backend);
mFragment = fragment;
mListener = listener;
mIconOptionsProvider = iconOptionsProvider;
}
@@ -68,20 +73,34 @@ class ZenModeIconPickerListPreferenceController extends AbstractZenModePreferenc
recyclerView.setLayoutManager(new AutoFitGridLayoutManager(mContext));
recyclerView.setAdapter(mAdapter);
recyclerView.setHasFixedSize(true);
}
@VisibleForTesting
void onIconSelected(@DrawableRes int resId) {
saveMode(mode -> {
mode.getRule().setIconResId(resId);
return mode;
});
mFragment.finish();
if (recyclerView.getItemAnimator() instanceof SimpleItemAnimator animator) {
animator.setSupportsChangeAnimations(true);
}
}
@Override
void updateState(Preference preference, @NonNull ZenMode zenMode) {
// Nothing to do, the current icon is shown in a different preference.
updateIconSelection(zenMode.getRule().getIconResId());
}
private void updateIconSelection(@DrawableRes int iconResId) {
if (iconResId != mCurrentIconResId) {
int oldIconResId = mCurrentIconResId;
mCurrentIconResId = iconResId;
if (mAdapter != null) {
mAdapter.notifyIconChanged(oldIconResId);
mAdapter.notifyIconChanged(mCurrentIconResId);
}
}
}
private void onIconSelected(@DrawableRes int iconResId) {
updateIconSelection(iconResId);
mListener.onIconSelected(iconResId);
}
interface IconPickerListener {
void onIconSelected(@DrawableRes int iconResId);
}
private class IconHolder extends RecyclerView.ViewHolder {
@@ -93,20 +112,25 @@ class ZenModeIconPickerListPreferenceController extends AbstractZenModePreferenc
mImageView = itemView.findViewById(R.id.icon_image_view);
}
void bindIcon(IconOptionsProvider.IconInfo icon) {
mImageView.setImageDrawable(
IconUtil.makeIconCircle(itemView.getContext(), icon.resId()));
void bindIcon(IconOptionsProvider.IconInfo icon, Drawable iconDrawable) {
mImageView.setImageDrawable(iconDrawable);
itemView.setContentDescription(icon.description());
itemView.setOnClickListener(v -> onIconSelected(icon.resId()));
itemView.setOnClickListener(v -> {
itemView.setSelected(true); // Immediately, to avoid flicker until we rebind.
onIconSelected(icon.resId());
});
itemView.setSelected(icon.resId() == mCurrentIconResId);
}
}
private class IconAdapter extends RecyclerView.Adapter<IconHolder> {
private final ImmutableList<IconOptionsProvider.IconInfo> mIconResources;
private final Map<IconOptionsProvider.IconInfo, Drawable> mIconCache;
private IconAdapter(IconOptionsProvider iconOptionsProvider) {
mIconResources = iconOptionsProvider.getIcons();
mIconCache = new HashMap<>();
}
@NonNull
@@ -119,13 +143,24 @@ class ZenModeIconPickerListPreferenceController extends AbstractZenModePreferenc
@Override
public void onBindViewHolder(@NonNull IconHolder holder, int position) {
holder.bindIcon(mIconResources.get(position));
IconOptionsProvider.IconInfo iconInfo = mIconResources.get(position);
Drawable iconDrawable = mIconCache.computeIfAbsent(iconInfo,
info -> IconUtil.makeIconCircle(mContext, info.resId()));
holder.bindIcon(iconInfo, iconDrawable);
}
@Override
public int getItemCount() {
return mIconResources.size();
}
private void notifyIconChanged(@DrawableRes int iconResId) {
int position = Iterables.indexOf(mIconResources,
iconInfo -> iconInfo.resId() == iconResId);
if (position != -1) {
notifyItemChanged(position);
}
}
}
private static class AutoFitGridLayoutManager extends GridLayoutManager {

View File

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

View File

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

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 org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import androidx.lifecycle.LifecycleOwner;
import androidx.test.core.app.ApplicationProvider;
import androidx.fragment.app.FragmentFactory;
import androidx.fragment.app.testing.FragmentScenario;
import androidx.lifecycle.Lifecycle;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settingslib.widget.FooterPreference;
import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupdesign.GlifLayout;
import com.google.android.setupdesign.GlifPreferenceLayout;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
/** Tests for {@link AutoBrightnessPreferenceFragmentForSetupWizard}. */
@RunWith(RobolectricTestRunner.class)
public class AutoBrightnessPreferenceFragmentForSetupWizardTest {
@Rule
public final MockitoRule mMockito = MockitoJUnit.rule();
// Same as AutoBrightnessPreferenceFragmentForSetupWizard#FOOTER_PREFERENCE_KEY
private static final String FOOTER_PREFERENCE_KEY = "auto_brightness_footer";
private FragmentScenario<AutoBrightnessPreferenceFragmentForSetupWizard> mFragmentScenario;
@Spy
private final Context mContext = ApplicationProvider.getApplicationContext();
@Mock
private GlifPreferenceLayout mGlifLayoutView;
@Mock
private FooterBarMixin mFooterBarMixin;
private AutoBrightnessPreferenceFragmentForSetupWizard mFragment;
private GlifLayout mGlifLayout;
@Before
public void setUp() {
mFragment = spy(new AutoBrightnessPreferenceFragmentForSetupWizard());
doReturn(mock(LifecycleOwner.class)).when(mFragment).getViewLifecycleOwner();
doReturn(mContext).when(mFragment).getContext();
when(mGlifLayoutView.getMixin(eq(FooterBarMixin.class))).thenReturn(mFooterBarMixin);
mFragmentScenario = FragmentScenario
.launch(
AutoBrightnessPreferenceFragmentForSetupWizard.class,
/* fragmentArgs= */ (Bundle) null,
R.style.GlifTheme,
/* factory= */ (FragmentFactory) null)
.moveToState(Lifecycle.State.RESUMED);
mFragmentScenario.onFragment(fragment -> mFragment = fragment);
View view = mFragment.getView();
assertThat(view).isInstanceOf(GlifPreferenceLayout.class);
mGlifLayout = (GlifLayout) view;
}
@After
public void tearDown() {
mFragmentScenario.close();
}
@Test
public void setHeaderText_onViewCreated_verifyAction() {
final String title = "title";
doReturn(title).when(mContext).getString(R.string.auto_brightness_title);
public void onViewCreated_verifyGlifHerderText() {
assertThat(mGlifLayout.getHeaderText())
.isEqualTo(mFragment.getString(R.string.auto_brightness_title));
}
mFragment.onViewCreated(mGlifLayoutView, null);
@Test
public void onViewCreated_verifyGlifFooter() {
FooterBarMixin footerMixin = mGlifLayout.getMixin(FooterBarMixin.class);
assertThat(footerMixin).isNotNull();
verify(mGlifLayoutView).setHeaderText(title);
Button footerButton = footerMixin.getPrimaryButtonView();
assertThat(footerButton).isNotNull();
assertThat(footerButton.getText().toString()).isEqualTo(mFragment.getString(R.string.done));
footerButton.performClick();
assertThat(mFragment.getActivity().isFinishing()).isTrue();
}
@Test
public void onViewCreated_verifyFooterPreference() {
Preference pref = mFragment.findPreference(FOOTER_PREFERENCE_KEY);
assertThat(pref).isInstanceOf(FooterPreference.class);
FooterPreference footerPref = (FooterPreference) pref;
String exactTitle = footerPref.getTitle().toString();
assertThat(exactTitle).isEqualTo(mFragment.getString(R.string.auto_brightness_description));
// Ensure that footer content description has "About XXX" prefix for consistency with other
// accessibility suw pages
String expectedContentDescription =
mFragment.getString(R.string.auto_brightness_content_description_title)
+ "\n\n" + exactTitle;
assertThat(footerPref.getContentDescription().toString())
.isEqualTo(expectedContentDescription);
}
@Test
@@ -85,11 +116,4 @@ public class AutoBrightnessPreferenceFragmentForSetupWizardTest {
assertThat(mFragment.getMetricsCategory()).isEqualTo(
SettingsEnums.SUW_ACCESSIBILITY_AUTO_BRIGHTNESS);
}
@Test
public void onViewCreated_verifyAction() {
mFragment.onViewCreated(mGlifLayoutView, null);
verify(mFooterBarMixin).setPrimaryButton(any());
}
}

View File

@@ -17,7 +17,6 @@
package com.android.settings.biometrics.fingerprint;
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON;
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_REAR;
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL;
import static com.android.settings.biometrics.fingerprint.FingerprintSettings.FingerprintSettingsFragment;
@@ -34,16 +33,13 @@ import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.Intent;
import android.content.pm.UserInfo;
import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.SensorProperties;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -84,7 +80,6 @@ import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import java.util.ArrayList;
import java.util.List;
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowSettingsPreferenceFragment.class, ShadowUtils.class, ShadowFragment.class,
@@ -152,6 +147,7 @@ public class FingerprintSettingsFragmentTest {
public void testCancellationSignalLifeCycle() {
setUpFragment(false);
mFingerprintAuthenticateSidecar.setFingerprintManager(mFingerprintManager);
doNothing().when(mFingerprintManager).authenticate(any(),
mCancellationSignalArgumentCaptor.capture(),
@@ -217,7 +213,6 @@ public class FingerprintSettingsFragmentTest {
doReturn(fragmentManager).when(mActivity).getSupportFragmentManager();
mFingerprintAuthenticateSidecar = new FingerprintAuthenticateSidecar();
mFingerprintAuthenticateSidecar.setFingerprintManager(mFingerprintManager);
doReturn(mFingerprintAuthenticateSidecar).when(fragmentManager).findFragmentByTag(
"authenticate_sidecar");
@@ -251,27 +246,4 @@ public class FingerprintSettingsFragmentTest {
true /* resetLockoutRequiresHardwareAuthToken */));
doReturn(props).when(mFingerprintManager).getSensorPropertiesInternal();
}
@Test
public void testAuthOnFragmentSetup() {
doReturn(List.of(new Fingerprint("Finger 1", 1, 2, 3)))
.when(mFingerprintManager).getEnrolledFingerprints(anyInt());
setUpFragment(false, 1, TYPE_REAR);
verify(mFingerprintManager).authenticate(any(), any(),
any(), any(), anyInt());
}
@Test
public void testErrorCancelledRestartsAuth() {
doReturn(List.of(new Fingerprint("Finger 1", 1, 2, 3)))
.when(mFingerprintManager).getEnrolledFingerprints(anyInt());
setUpFragment(false, 1, TYPE_REAR);
// When we receive a cancel, we should restart auth.
mFragment.handleError(FingerprintManager.FINGERPRINT_ERROR_CANCELED, "blah");
verify(mFingerprintManager, times(2)).authenticate(any(), any(),
any(), any(), anyInt());
}
}

View File

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

View File

@@ -248,6 +248,23 @@ public class AudioSharingDialogHandlerTest {
verify(mCachedDevice1).setActive();
}
@Test
public void handleUserTriggeredLeaDeviceConnected_noSharingLeaDeviceInErrorState_setActive() {
setUpBroadcast(false);
when(mCachedDevice1.getGroupId()).thenReturn(-1);
when(mLeAudioProfile.getGroupId(mDevice1)).thenReturn(-1);
ImmutableList<BluetoothDevice> deviceList = ImmutableList.of(mDevice1, mDevice3);
when(mAssistant.getDevicesMatchingConnectionStates(
new int[] {BluetoothProfile.STATE_CONNECTED}))
.thenReturn(deviceList);
when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of());
mHandler.handleDeviceConnected(mCachedDevice1, /* userTriggered= */ true);
shadowOf(Looper.getMainLooper()).idle();
List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
assertThat(childFragments).isEmpty();
verify(mCachedDevice1).setActive();
}
@Test
public void handleUserTriggeredLeaDeviceConnected_noSharingTwoLeaDevices_showJoinDialog() {
setUpBroadcast(false);
@@ -451,6 +468,23 @@ public class AudioSharingDialogHandlerTest {
verify(mCachedDevice1, never()).setActive();
}
@Test
public void handleLeaDeviceConnected_noSharingLeaDeviceInErrorState_doNothing() {
setUpBroadcast(false);
when(mCachedDevice1.getGroupId()).thenReturn(-1);
when(mLeAudioProfile.getGroupId(mDevice1)).thenReturn(-1);
ImmutableList<BluetoothDevice> deviceList = ImmutableList.of(mDevice1, mDevice3);
when(mAssistant.getDevicesMatchingConnectionStates(
new int[] {BluetoothProfile.STATE_CONNECTED}))
.thenReturn(deviceList);
when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of());
mHandler.handleDeviceConnected(mCachedDevice1, /* userTriggered= */ false);
shadowOf(Looper.getMainLooper()).idle();
List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
assertThat(childFragments).isEmpty();
verify(mCachedDevice1, never()).setActive();
}
@Test
public void handleLeaDeviceConnected_noSharingTwoLeaDevices_showJoinDialog() {
setUpBroadcast(false);

View File

@@ -789,6 +789,40 @@ public class BatteryInfoTest {
expectedChargeLabel);
}
@Test
public void getBatteryInfo_longlife_shouldSetLonglife() {
var batteryIntent = createIntentForLongLifeTest(/* hasLongLife= */ true);
var batteryInfo =
BatteryInfo.getBatteryInfo(
mContext,
batteryIntent,
mBatteryUsageStats,
/* estimate= */ MOCK_ESTIMATE,
/* elapsedRealtimeUs= */ 0L,
/* shortString= */ false,
/* currentTimeMs= */ 0L);
assertThat(batteryInfo.isLongLife).isTrue();
}
@Test
public void getBatteryInfo_noLonglife_shouldNotLonglife() {
var batteryIntent = createIntentForLongLifeTest(/* hasLongLife= */ false);
var batteryInfo =
BatteryInfo.getBatteryInfo(
mContext,
batteryIntent,
mBatteryUsageStats,
/* estimate= */ MOCK_ESTIMATE,
/* elapsedRealtimeUs= */ 0L,
/* shortString= */ false,
/* currentTimeMs= */ 0L);
assertThat(batteryInfo.isLongLife).isFalse();
}
private enum ChargingSpeed {
FAST,
REGULAR,
@@ -801,6 +835,15 @@ public class BatteryInfoTest {
DOCKED
}
private Intent createIntentForLongLifeTest(Boolean hasLongLife) {
return new Intent(Intent.ACTION_BATTERY_CHANGED)
.putExtra(
BatteryManager.EXTRA_CHARGING_STATUS,
hasLongLife
? BatteryManager.CHARGING_POLICY_ADAPTIVE_LONGLIFE
: BatteryManager.CHARGING_POLICY_DEFAULT);
}
private Intent createIntentForGetBatteryInfoTest(
ChargingType chargingType, ChargingSpeed chargingSpeed, int batteryLevel) {
return createBatteryIntent(

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

@@ -77,7 +77,7 @@ class SubscriptionRepositoryTest {
@Test
fun subscriptionsChangedFlow_hasInitialValue() = runBlocking {
val initialValue = context.subscriptionsChangedFlow().firstWithTimeoutOrNull()
val initialValue = repository.subscriptionsChangedFlow().firstWithTimeoutOrNull()
assertThat(initialValue).isSameInstanceAs(Unit)
}
@@ -85,7 +85,7 @@ class SubscriptionRepositoryTest {
@Test
fun subscriptionsChangedFlow_changed() = runBlocking {
val listDeferred = async {
context.subscriptionsChangedFlow().toListWithTimeout()
repository.subscriptionsChangedFlow().toListWithTimeout()
}
delay(100)
@@ -94,6 +94,17 @@ class SubscriptionRepositoryTest {
assertThat(listDeferred.await()).hasSize(2)
}
@Test
fun activeSubscriptionIdListFlow(): Unit = runBlocking {
mockSubscriptionManager.stub {
on { activeSubscriptionIdList } doReturn intArrayOf(SUB_ID_IN_SLOT_0)
}
val activeSubIds = repository.activeSubscriptionIdListFlow().firstWithTimeoutOrNull()
assertThat(activeSubIds).containsExactly(SUB_ID_IN_SLOT_0)
}
@Test
fun getSelectableSubscriptionInfoList_sortedBySimSlotIndex() {
mockSubscriptionManager.stub {

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