Snap for 7546365 from 9683efd43f to sc-v2-release

Change-Id: Ie8701fa7211d7d7ad2406a9618c5d54298f42ceb
This commit is contained in:
Android Build Coastguard Worker
2021-07-14 01:09:21 +00:00
23 changed files with 1282 additions and 72 deletions

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2021 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="412dp"
android:height="300dp"
android:viewportWidth="412"
android:viewportHeight="300">
<path
android:fillColor="#FF000000"
android:pathData="M383.9,300H28.1C12.6,300 0,287.4 0,271.9V28.1C0,12.6 12.6,0 28.1,0h355.8C399.4,0 412,12.6 412,28.1v243.8C412,287.4 399.4,300 383.9,300z"/>
<path
android:pathData="M79.2,179.6h53.6v8.5h-53.6z"
android:fillColor="#669DF6"/>
<path
android:pathData="M142.5,179.6h30.4v8.5h-30.4z"
android:fillColor="#669DF6"/>
<path
android:pathData="M79.2,195.5h79.2v8.5h-79.2z"
android:fillColor="#669DF6"/>
<path
android:pathData="M168.1,195.5h34.1v8.5h-34.1z"
android:fillColor="#669DF6"/>
<path
android:pathData="M211.9,195.5h34.1v8.5h-34.1z"
android:fillColor="#669DF6"/>
<path
android:pathData="M182.7,179.6h73.1v8.5h-73.1z"
android:fillColor="#669DF6"/>
<path
android:pathData="M265.5,179.6h26.8v8.5h-26.8z"
android:fillColor="#669DF6"/>
<path
android:pathData="M302.1,179.6h26.8v8.5h-26.8z"
android:fillColor="#669DF6"/>
<path
android:pathData="M142.7,67.9h-11.5c-1.6,0 -2.9,1.3 -2.9,2.9H67.8c-7.9,0 -14.4,6.5 -14.4,14.4v132.4c0,7.9 6.5,14.4 14.4,14.4h276.4c7.9,0 14.4,-6.5 14.4,-14.4V85.2c0,-7.9 -6.5,-14.4 -14.4,-14.4H203.1c0,-1.6 -1.3,-2.9 -2.9,-2.9h-28.8c-1.6,0 -2.9,1.3 -2.9,2.9h-23C145.5,69.2 144.3,67.9 142.7,67.9zM344.2,73.7c6.4,0 11.5,5.2 11.5,11.5v132.4c0,6.3 -5.2,11.5 -11.5,11.5H67.8c-6.4,0 -11.5,-5.2 -11.5,-11.5V85.2c0,-6.3 5.2,-11.5 11.5,-11.5H344.2z"
android:fillColor="#80868B"/>
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2021 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="412dp"
android:height="300dp"
android:viewportWidth="412"
android:viewportHeight="300">
<path
android:pathData="M383.9,300H28.1C12.6,300 0,287.4 0,271.9V28.1C0,12.6 12.6,0 28.1,0h355.8C399.4,0 412,12.6 412,28.1v243.8C412,287.4 399.4,300 383.9,300z"
android:fillColor="#FFFFFF"/>
<path
android:pathData="M79.2,179.6h53.6v8.5h-53.6z"
android:fillColor="#1A73E8"/>
<path
android:pathData="M142.5,179.6h30.4v8.5h-30.4z"
android:fillColor="#1A73E8"/>
<path
android:pathData="M79.2,195.5h79.2v8.5h-79.2z"
android:fillColor="#1A73E8"/>
<path
android:pathData="M168.1,195.5h34.1v8.5h-34.1z"
android:fillColor="#1A73E8"/>
<path
android:pathData="M211.9,195.5h34.1v8.5h-34.1z"
android:fillColor="#1A73E8"/>
<path
android:pathData="M182.7,179.6h73.1v8.5h-73.1z"
android:fillColor="#1A73E8"/>
<path
android:pathData="M265.5,179.6h26.8v8.5h-26.8z"
android:fillColor="#1A73E8"/>
<path
android:pathData="M302.1,179.6h26.8v8.5h-26.8z"
android:fillColor="#1A73E8"/>
<path
android:pathData="M142.7,67.9h-11.5c-1.6,0 -2.9,1.3 -2.9,2.9H67.8c-7.9,0 -14.4,6.5 -14.4,14.4v132.4c0,7.9 6.5,14.4 14.4,14.4h276.4c7.9,0 14.4,-6.5 14.4,-14.4V85.2c0,-7.9 -6.5,-14.4 -14.4,-14.4H203.1c0,-1.6 -1.3,-2.9 -2.9,-2.9h-28.8c-1.6,0 -2.9,1.3 -2.9,2.9h-23C145.5,69.2 144.3,67.9 142.7,67.9zM344.2,73.7c6.4,0 11.5,5.2 11.5,11.5v132.4c0,6.3 -5.2,11.5 -11.5,11.5H67.8c-6.4,0 -11.5,-5.2 -11.5,-11.5V85.2c0,-6.3 5.2,-11.5 11.5,-11.5H344.2z"
android:fillColor="#DADCE0"/>
</vector>

View File

@@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2020 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.
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:layout_width="match_parent"
android:layout_height="@dimen/captioning_preview_height"
android:contentDescription="@null"
android:scaleType="fitCenter"
android:src="@drawable/accessibility_captions" />
</FrameLayout>

View File

@@ -107,6 +107,28 @@
style="@style/BiometricEnrollIntroMessage" />
</LinearLayout>
<LinearLayout
android:id="@+id/info_row_require_eyes"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:visibility="gone">
<ImageView
android:id="@+id/icon_require_eyes"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/ic_settings_24dp"/>
<Space
android:layout_width="16dp"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/info_message_require_eyes"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/BiometricEnrollIntroMessage" />
</LinearLayout>
<!-- How it works -->
<TextView
android:layout_width="match_parent"

View File

@@ -269,6 +269,9 @@
<!-- ComponentName to launch a vendor-specific enrollment activity if available -->
<string name="config_face_enroll" translatable="false"></string>
<!-- Whether to show the "require eyes" info section on the face enroll intro screen -->
<bool name="config_face_intro_show_require_eyes">true</bool>
<!-- Whether to use the Lottie animation for the face education enrollment screen -->
<bool name="config_face_education_use_lottie">false</bool>

View File

@@ -796,6 +796,11 @@
<string name="security_settings_face_enroll_introduction_info_looking"></string>
<!-- Message on the face enrollment introduction page that provides information about what could cause the phone to unlock when asking for parental consent. [CHAR LIMIT=NONE] -->
<string name="security_settings_face_enroll_introduction_info_consent_looking"></string>
<!-- Message on the face enrollment introduction page that provides information about how to require eyes to be open for Face Unlock. [CHAR LIMIT=NONE] -->
<string name="security_settings_face_enroll_introduction_info_gaze"></string>
<!-- Message on the face enrollment introduction page that provides information about how to require eyes to be open for Face Unlock when asking for parental consent. [CHAR LIMIT=NONE] -->
<string name="security_settings_face_enroll_introduction_info_consent_gaze"></string>
<!-- Title of a section on the face enrollment introduction page that explains how face unlock works. [CHAR LIMIT=40] -->
<string name="security_settings_face_enroll_introduction_how_title"></string>
<!-- Message on the face enrollment introduction page that explains how face unlock works. [CHAR LIMIT=NONE] -->

View File

@@ -21,12 +21,11 @@
android:persistent="false"
android:title="@string/accessibility_captioning_title">
<com.android.settingslib.widget.LayoutPreference
<com.android.settingslib.widget.IllustrationPreference
android:key="captions_preview"
android:layout="@layout/accessibility_captions_preview"
android:persistent="false"
android:selectable="false"
android:title="@string/summary_placeholder"
settings:lottie_rawRes="@drawable/accessibility_captions_banner"
settings:searchable="false" />
<com.android.settings.widget.SettingsMainSwitchPreference

View File

@@ -182,7 +182,7 @@ public final class HibernatedAppsPreferenceController extends BasePreferenceCont
private static boolean isHibernationEnabled() {
return DeviceConfig.getBoolean(
NAMESPACE_APP_HIBERNATION, PROPERTY_APP_HIBERNATION_ENABLED, false);
NAMESPACE_APP_HIBERNATION, PROPERTY_APP_HIBERNATION_ENABLED, true);
}
/**

View File

@@ -144,7 +144,7 @@ public final class HibernationSwitchPreferenceController extends AppInfoPreferen
private static boolean isHibernationEnabled() {
return DeviceConfig.getBoolean(
NAMESPACE_APP_HIBERNATION, PROPERTY_APP_HIBERNATION_ENABLED, false);
NAMESPACE_APP_HIBERNATION, PROPERTY_APP_HIBERNATION_ENABLED, true);
}
private static boolean hibernationTargetsPreSApps() {

View File

@@ -24,6 +24,7 @@ import android.hardware.face.FaceSensorPropertiesInternal;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
@@ -74,11 +75,13 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Apply extracted theme color to icons.
final ImageView iconGlasses = findViewById(R.id.icon_glasses);
final ImageView iconLooking = findViewById(R.id.icon_looking);
iconGlasses.getBackground().setColorFilter(getIconColorFilter());
iconLooking.getBackground().setColorFilter(getIconColorFilter());
// Set text for views with multiple variations.
final TextView infoMessageGlasses = findViewById(R.id.info_message_glasses);
final TextView infoMessageLooking = findViewById(R.id.info_message_looking);
final TextView howMessage = findViewById(R.id.how_message);
@@ -86,10 +89,20 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
final TextView inControlMessage = findViewById(R.id.message_in_control);
infoMessageGlasses.setText(getInfoMessageGlasses());
infoMessageLooking.setText(getInfoMessageLooking());
howMessage.setText(getHowMessage());
inControlTitle.setText(getInControlTitle());
howMessage.setText(getHowMessage());
inControlMessage.setText(getInControlMessage());
// Set up and show the "require eyes" info section if necessary.
if (getResources().getBoolean(R.bool.config_face_intro_show_require_eyes)) {
final LinearLayout infoRowRequireEyes = findViewById(R.id.info_row_require_eyes);
final ImageView iconRequireEyes = findViewById(R.id.icon_require_eyes);
final TextView infoMessageRequireEyes = findViewById(R.id.info_message_require_eyes);
infoRowRequireEyes.setVisibility(View.VISIBLE);
iconRequireEyes.getBackground().setColorFilter(getIconColorFilter());
infoMessageRequireEyes.setText(getInfoMessageRequireEyes());
}
mFaceManager = Utils.getFaceManagerOrNull(this);
mFaceFeatureProvider = FeatureFactory.getFactory(getApplicationContext())
.getFaceFeatureProvider();
@@ -126,6 +139,11 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
return R.string.security_settings_face_enroll_introduction_info_looking;
}
@StringRes
protected int getInfoMessageRequireEyes() {
return R.string.security_settings_face_enroll_introduction_info_gaze;
}
@StringRes
protected int getHowMessage() {
return R.string.security_settings_face_enroll_introduction_how_message;

View File

@@ -80,6 +80,12 @@ public class FaceEnrollParentalConsent extends FaceEnrollIntroduction {
return R.string.security_settings_face_enroll_introduction_info_consent_looking;
}
@Override
@StringRes
protected int getInfoMessageRequireEyes() {
return R.string.security_settings_face_enroll_introduction_info_consent_gaze;
}
@Override
@StringRes
protected int getHowMessage() {

View File

@@ -25,19 +25,30 @@ import android.os.Looper;
import android.provider.Settings;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.util.Log;
import androidx.annotation.Keep;
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* A proxy to the subscription manager
*/
public class ProxySubscriptionManager implements LifecycleObserver {
private static final String LOG_TAG = "ProxySubscriptionManager";
private static final int LISTENER_END_OF_LIFE = -1;
private static final int LISTENER_IS_INACTIVE = 0;
private static final int LISTENER_IS_ACTIVE = 1;
/**
* Interface for monitor active subscriptions list changing
*/
@@ -74,21 +85,35 @@ public class ProxySubscriptionManager implements LifecycleObserver {
private ProxySubscriptionManager(Context context) {
final Looper looper = context.getMainLooper();
ActiveSubscriptionsListener subscriptionMonitor = new ActiveSubscriptionsListener(
looper, context) {
public void onChanged() {
notifySubscriptionInfoMightChanged();
}
};
GlobalSettingsChangeListener airplaneModeMonitor = new GlobalSettingsChangeListener(
looper, context, Settings.Global.AIRPLANE_MODE_ON) {
public void onChanged(String field) {
subscriptionMonitor.clearCache();
notifySubscriptionInfoMightChanged();
}
};
init(context, subscriptionMonitor, airplaneModeMonitor);
}
@Keep
@VisibleForTesting
protected void init(Context context, ActiveSubscriptionsListener activeSubscriptionsListener,
GlobalSettingsChangeListener airplaneModeOnSettingsChangeListener) {
mActiveSubscriptionsListeners =
new ArrayList<OnActiveSubscriptionChangedListener>();
mPendingNotifyListeners =
new ArrayList<OnActiveSubscriptionChangedListener>();
mSubscriptionMonitor = new ActiveSubscriptionsListener(looper, context) {
public void onChanged() {
notifyAllListeners();
}
};
mAirplaneModeMonitor = new GlobalSettingsChangeListener(looper,
context, Settings.Global.AIRPLANE_MODE_ON) {
public void onChanged(String field) {
mSubscriptionMonitor.clearCache();
notifyAllListeners();
}
};
mSubscriptionMonitor = activeSubscriptionsListener;
mAirplaneModeMonitor = airplaneModeOnSettingsChangeListener;
mSubscriptionMonitor.start();
}
@@ -98,15 +123,19 @@ public class ProxySubscriptionManager implements LifecycleObserver {
private GlobalSettingsChangeListener mAirplaneModeMonitor;
private List<OnActiveSubscriptionChangedListener> mActiveSubscriptionsListeners;
private List<OnActiveSubscriptionChangedListener> mPendingNotifyListeners;
private void notifyAllListeners() {
for (OnActiveSubscriptionChangedListener listener : mActiveSubscriptionsListeners) {
final Lifecycle lifecycle = listener.getLifecycle();
if ((lifecycle == null)
|| (lifecycle.getCurrentState().isAtLeast(Lifecycle.State.STARTED))) {
listener.onChanged();
}
}
@Keep
@VisibleForTesting
protected void notifySubscriptionInfoMightChanged() {
// create a merged list for processing all listeners
List<OnActiveSubscriptionChangedListener> listeners =
new ArrayList<OnActiveSubscriptionChangedListener>(mPendingNotifyListeners);
listeners.addAll(mActiveSubscriptionsListeners);
mActiveSubscriptionsListeners.clear();
mPendingNotifyListeners.clear();
processStatusChangeOnListeners(listeners);
}
/**
@@ -131,6 +160,11 @@ public class ProxySubscriptionManager implements LifecycleObserver {
@OnLifecycleEvent(ON_START)
void onStart() {
mSubscriptionMonitor.start();
// callback notify those listener(s) which back to active state
List<OnActiveSubscriptionChangedListener> listeners = mPendingNotifyListeners;
mPendingNotifyListeners = new ArrayList<OnActiveSubscriptionChangedListener>();
processStatusChangeOnListeners(listeners);
}
@OnLifecycleEvent(ON_STOP)
@@ -215,12 +249,17 @@ public class ProxySubscriptionManager implements LifecycleObserver {
}
/**
* Add listener to active subscriptions monitor list
* Add listener to active subscriptions monitor list.
* Note: listener only take place when change happens.
* No immediate callback performed after the invoke of this method.
*
* @param listener listener to active subscriptions change
*/
@Keep
public void addActiveSubscriptionsListener(OnActiveSubscriptionChangedListener listener) {
if (mActiveSubscriptionsListeners.contains(listener)) {
removeSpecificListenerAndCleanList(listener, mPendingNotifyListeners);
removeSpecificListenerAndCleanList(listener, mActiveSubscriptionsListeners);
if ((listener == null) || (getListenerState(listener) == LISTENER_END_OF_LIFE)) {
return;
}
mActiveSubscriptionsListeners.add(listener);
@@ -231,7 +270,51 @@ public class ProxySubscriptionManager implements LifecycleObserver {
*
* @param listener listener to active subscriptions change
*/
@Keep
public void removeActiveSubscriptionsListener(OnActiveSubscriptionChangedListener listener) {
mActiveSubscriptionsListeners.remove(listener);
removeSpecificListenerAndCleanList(listener, mPendingNotifyListeners);
removeSpecificListenerAndCleanList(listener, mActiveSubscriptionsListeners);
}
private int getListenerState(OnActiveSubscriptionChangedListener listener) {
Lifecycle lifecycle = listener.getLifecycle();
if (lifecycle == null) {
return LISTENER_IS_ACTIVE;
}
Lifecycle.State lifecycleState = lifecycle.getCurrentState();
if (lifecycleState == Lifecycle.State.DESTROYED) {
Log.d(LOG_TAG, "Listener dead detected - " + listener);
return LISTENER_END_OF_LIFE;
}
return lifecycleState.isAtLeast(Lifecycle.State.STARTED) ?
LISTENER_IS_ACTIVE : LISTENER_IS_INACTIVE;
}
private void removeSpecificListenerAndCleanList(OnActiveSubscriptionChangedListener listener,
List<OnActiveSubscriptionChangedListener> list) {
// also drop listener(s) which is end of life
list.removeIf(it -> (it == listener) || (getListenerState(it) == LISTENER_END_OF_LIFE));
}
private void processStatusChangeOnListeners(
List<OnActiveSubscriptionChangedListener> listeners) {
// categorize listener(s), and end of life listener(s) been ignored
Map<Integer, List<OnActiveSubscriptionChangedListener>> categorizedListeners =
listeners.stream()
.collect(Collectors.groupingBy(it -> getListenerState(it)));
// have inactive listener(s) in pending list
categorizedListeners.computeIfPresent(LISTENER_IS_INACTIVE, (category, list) -> {
mPendingNotifyListeners.addAll(list);
return list;
});
// get active listener(s)
categorizedListeners.computeIfPresent(LISTENER_IS_ACTIVE, (category, list) -> {
mActiveSubscriptionsListeners.addAll(list);
// notify each one of them
list.stream().forEach(it -> it.onChanged());
return list;
});
}
}

View File

@@ -486,7 +486,7 @@ public class SubscriptionUtil {
* @param info the subscriptionInfo to check against.
* @return true if this subscription should be visible to the API caller.
*/
private static boolean isSubscriptionVisible(
public static boolean isSubscriptionVisible(
SubscriptionManager subscriptionManager, Context context, SubscriptionInfo info) {
if (info == null) return false;
// If subscription is NOT grouped opportunistic subscription, it's visible.

View File

@@ -0,0 +1,58 @@
/*
* Copyright (C) 2021 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.network.helper;
import android.telephony.TelephonyManager;
import android.telephony.UiccCardInfo;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicIntegerArray;
/**
* This is a Callable class which queries valid card ID for eSIM
*/
public class QueryEsimCardId implements Callable<AtomicIntegerArray> {
private static final String TAG = "QueryEsimCardId";
private TelephonyManager mTelephonyManager;
/**
* Constructor of class
* @param TelephonyManager
*/
public QueryEsimCardId(TelephonyManager telephonyManager) {
mTelephonyManager = telephonyManager;
}
/**
* Implementation of Callable
* @return card ID(s) in AtomicIntegerArray
*/
public AtomicIntegerArray call() {
List<UiccCardInfo> cardInfos = mTelephonyManager.getUiccCardsInfo();
if (cardInfos == null) {
return new AtomicIntegerArray(0);
}
return new AtomicIntegerArray(cardInfos.stream()
.filter(Objects::nonNull)
.filter(cardInfo -> (!cardInfo.isRemovable()
&& (cardInfo.getCardId() != TelephonyManager.UNSUPPORTED_CARD_ID)))
.mapToInt(UiccCardInfo::getCardId)
.toArray());
}
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright (C) 2021 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.network.helper;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.UiccSlotInfo;
import java.util.Arrays;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicIntegerArray;
/**
* This is a Callable class which query slot index within device
*/
public class QuerySimSlotIndex implements Callable<AtomicIntegerArray> {
private static final String TAG = "QuerySimSlotIndex";
private TelephonyManager mTelephonyManager;
private boolean mDisabledSlotsIncluded;
private boolean mOnlySlotWithSim;
/**
* Constructor of class
* @param TelephonyManager
* @param disabledSlotsIncluded query both active and inactive slots when true,
* only query active slot when false.
* @param onlySlotWithSim query slot index with SIM available when true,
* include absent ones when false.
*/
public QuerySimSlotIndex(TelephonyManager telephonyManager,
boolean disabledSlotsIncluded, boolean onlySlotWithSim) {
mTelephonyManager = telephonyManager;
mDisabledSlotsIncluded = disabledSlotsIncluded;
mOnlySlotWithSim = onlySlotWithSim;
}
/**
* Implementation of Callable
* @return slot index in AtomicIntegerArray
*/
public AtomicIntegerArray call() {
UiccSlotInfo [] slotInfo = mTelephonyManager.getUiccSlotsInfo();
if (slotInfo == null) {
return new AtomicIntegerArray(0);
}
int slotIndexFilter = mOnlySlotWithSim ? 0 : SubscriptionManager.INVALID_SIM_SLOT_INDEX;
return new AtomicIntegerArray(Arrays.stream(slotInfo)
.filter(slot -> filterSlot(slot))
.mapToInt(slot -> mapToSlotIndex(slot))
.filter(slotIndex -> (slotIndex >= slotIndexFilter))
.toArray());
}
protected boolean filterSlot(UiccSlotInfo slotInfo) {
if (mDisabledSlotsIncluded) {
return true;
}
if (slotInfo == null) {
return false;
}
return slotInfo.getIsActive();
}
protected int mapToSlotIndex(UiccSlotInfo slotInfo) {
if (slotInfo == null) {
return SubscriptionManager.INVALID_SIM_SLOT_INDEX;
}
if (slotInfo.getCardStateInfo() == UiccSlotInfo.CARD_STATE_INFO_ABSENT) {
return SubscriptionManager.INVALID_SIM_SLOT_INDEX;
}
return slotInfo.getLogicalSlotIdx();
}
}

View File

@@ -0,0 +1,160 @@
/*
* Copyright (C) 2021 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.network.helper;
import android.content.Context;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.Log;
import androidx.annotation.Keep;
import androidx.annotation.VisibleForTesting;
import com.android.settings.network.helper.SubscriptionAnnotation;
import com.android.settingslib.utils.ThreadUtils;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicIntegerArray;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* This is a Callable class to query user selectable subscription list.
*
* Here's example of creating a Callable for retrieving a list of SubscriptionAnnotation
* for active Subscriptions:
*
* List<SubscriptionAnnotation> result = (new SelectableSubscriptions(context, false)).call();
*
* Another example for retrieving a list of SubscriptionAnnotation for all subscriptions
* accessible in another thread.
*
* List<SubscriptionAnnotation> result = ExecutorService.submit(
* new SelectableSubscriptions(context, true)).get()
*/
public class SelectableSubscriptions implements Callable<List<SubscriptionAnnotation>> {
private static final String TAG = "SelectableSubscriptions";
private Context mContext;
private Supplier<List<SubscriptionInfo>> mSubscriptions;
private Predicate<SubscriptionAnnotation> mFilter;
private Function<List<SubscriptionAnnotation>, List<SubscriptionAnnotation>> mFinisher;
/**
* Constructor of class
* @param context
* @param disabledSlotsIncluded query both active and inactive slots when true,
* only query active slot when false.
*/
public SelectableSubscriptions(Context context, boolean disabledSlotsIncluded) {
mContext = context;
mSubscriptions = disabledSlotsIncluded ? (() -> getAvailableSubInfoList(context)) :
(() -> getActiveSubInfoList(context));
mFilter = disabledSlotsIncluded ? (subAnno -> subAnno.isExisted()) :
(subAnno -> subAnno.isActive());
mFinisher = annoList -> annoList;
}
/**
* Add UnaryOperator to be applied to the final result.
* @param finisher a function to be applied to the final result.
*/
public SelectableSubscriptions addFinisher(
UnaryOperator<List<SubscriptionAnnotation>> finisher) {
mFinisher = mFinisher.andThen(finisher);
return this;
}
/**
* Implementation of Callable
* @return a list of SubscriptionAnnotation which is user selectable
*/
public List<SubscriptionAnnotation> call() {
TelephonyManager telMgr = mContext.getSystemService(TelephonyManager.class);
try {
// query in background thread
Future<AtomicIntegerArray> eSimCardId =
ThreadUtils.postOnBackgroundThread(new QueryEsimCardId(telMgr));
// query in background thread
Future<AtomicIntegerArray> simSlotIndex =
ThreadUtils.postOnBackgroundThread(
new QuerySimSlotIndex(telMgr, true, true));
// query in background thread
Future<AtomicIntegerArray> activeSimSlotIndex =
ThreadUtils.postOnBackgroundThread(
new QuerySimSlotIndex(telMgr, false, true));
List<SubscriptionInfo> subInfoList = mSubscriptions.get();
// wait for result from background thread
List<Integer> eSimCardIdList = atomicToList(eSimCardId.get());
List<Integer> simSlotIndexList = atomicToList(simSlotIndex.get());
List<Integer> activeSimSlotIndexList = atomicToList(activeSimSlotIndex.get());
// build a list of SubscriptionAnnotation
return IntStream.range(0, subInfoList.size())
.mapToObj(subInfoIndex ->
new SubscriptionAnnotation.Builder(subInfoList, subInfoIndex))
.map(annoBdr -> annoBdr.build(mContext,
eSimCardIdList, simSlotIndexList, activeSimSlotIndexList))
.filter(mFilter)
.collect(Collectors.collectingAndThen(Collectors.toList(), mFinisher));
} catch (Exception exception) {
Log.w(TAG, "Fail to request subIdList", exception);
}
return Collections.emptyList();
}
protected List<SubscriptionInfo> getSubInfoList(Context context,
Function<SubscriptionManager, List<SubscriptionInfo>> convertor) {
SubscriptionManager subManager = getSubscriptionManager(context);
return (subManager == null) ? Collections.emptyList() : convertor.apply(subManager);
}
protected SubscriptionManager getSubscriptionManager(Context context) {
return context.getSystemService(SubscriptionManager.class);
}
protected List<SubscriptionInfo> getAvailableSubInfoList(Context context) {
return getSubInfoList(context, SubscriptionManager::getAvailableSubscriptionInfoList);
}
protected List<SubscriptionInfo> getActiveSubInfoList(Context context) {
return getSubInfoList(context, SubscriptionManager::getActiveSubscriptionInfoList);
}
@Keep
@VisibleForTesting
protected static List<Integer> atomicToList(AtomicIntegerArray atomicIntArray) {
if (atomicIntArray == null) {
return Collections.emptyList();
}
return IntStream.range(0, atomicIntArray.length())
.map(idx -> atomicIntArray.get(idx)).boxed()
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,163 @@
/*
* Copyright (C) 2021 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.network.helper;
import android.content.Context;
import android.os.ParcelUuid;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import androidx.annotation.Keep;
import androidx.annotation.VisibleForTesting;
import com.android.settings.network.SubscriptionUtil;
import java.util.List;
/**
* This is a class helps providing additional info required by UI
* based on SubscriptionInfo.
*/
public class SubscriptionAnnotation {
private static final String TAG = "SubscriptionAnnotation";
private SubscriptionInfo mSubInfo;
private int mOrderWithinList;
private int mType = TYPE_UNKNOWN;
private boolean mIsExisted;
private boolean mIsActive;
private boolean mIsAllowToDisplay;
public static final ParcelUuid EMPTY_UUID = ParcelUuid.fromString("0-0-0-0-0");
public static final int TYPE_UNKNOWN = 0x0;
public static final int TYPE_PSIM = 0x1;
public static final int TYPE_ESIM = 0x2;
/**
* Builder class for SubscriptionAnnotation
*/
public static class Builder {
private List<SubscriptionInfo> mSubInfoList;
private int mIndexWithinList;
/**
* Constructor of builder
* @param subInfoList list of subscription info
* @param indexWithinList target index within list provided
*/
public Builder(List<SubscriptionInfo> subInfoList, int indexWithinList) {
mSubInfoList = subInfoList;
mIndexWithinList = indexWithinList;
}
public SubscriptionAnnotation build(Context context, List<Integer> eSimCardId,
List<Integer> simSlotIndex, List<Integer> activeSimSlotIndex) {
return new SubscriptionAnnotation(mSubInfoList, mIndexWithinList, context,
eSimCardId, simSlotIndex, activeSimSlotIndex);
}
}
/**
* Constructor of class
*/
@Keep
@VisibleForTesting
protected SubscriptionAnnotation(List<SubscriptionInfo> subInfoList, int subInfoIndex,
Context context, List<Integer> eSimCardId,
List<Integer> simSlotIndex, List<Integer> activeSimSlotIndexList) {
if ((subInfoIndex < 0) || (subInfoIndex >= subInfoList.size())) {
return;
}
mSubInfo = subInfoList.get(subInfoIndex);
if (mSubInfo == null) {
return;
}
mOrderWithinList = subInfoIndex;
mType = mSubInfo.isEmbedded() ? TYPE_ESIM : TYPE_PSIM;
if (mType == TYPE_ESIM) {
int cardId = mSubInfo.getCardId();
mIsExisted = eSimCardId.contains(cardId);
if (mIsExisted) {
mIsActive = activeSimSlotIndexList.contains(mSubInfo.getSimSlotIndex());
mIsAllowToDisplay = isDisplayAllowed(context);
}
return;
}
mIsExisted = simSlotIndex.contains(mSubInfo.getSimSlotIndex());
mIsActive = activeSimSlotIndexList.contains(mSubInfo.getSimSlotIndex());
if (mIsExisted) {
mIsAllowToDisplay = isDisplayAllowed(context);
}
}
// the index provided during construction of Builder
@Keep
public int getOrderingInList() {
return mOrderWithinList;
}
// type of subscription
@Keep
public int getType() {
return mType;
}
// if a subscription is existed within device
@Keep
public boolean isExisted() {
return mIsExisted;
}
// if a subscription is currently ON
@Keep
public boolean isActive() {
return mIsActive;
}
// if display of subscription is allowed
@Keep
public boolean isDisplayAllowed() {
return mIsAllowToDisplay;
}
// the subscription ID
@Keep
public int getSubscriptionId() {
return (mSubInfo == null) ? SubscriptionManager.INVALID_SUBSCRIPTION_ID :
mSubInfo.getSubscriptionId();
}
// the grouping UUID
@Keep
public ParcelUuid getGroupUuid() {
return (mSubInfo == null) ? null : mSubInfo.getGroupUuid();
}
// the SubscriptionInfo
@Keep
public SubscriptionInfo getSubInfo() {
return mSubInfo;
}
private boolean isDisplayAllowed(Context context) {
return SubscriptionUtil.isSubscriptionVisible(
context.getSystemService(SubscriptionManager.class), context, mSubInfo);
}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright (C) 2021 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.network.helper;
import android.os.ParcelUuid;
import androidx.annotation.Keep;
import androidx.annotation.VisibleForTesting;
import com.android.settings.network.helper.SubscriptionAnnotation;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
/**
* A UnaryOperator for converting a list of SubscriptionAnnotation into
* another list of SubscriptionAnnotation based on group UUID.
* Only one SubscriptionAnnotation with entries with same (valid) group UUID would be kept.
*
* Here's an example when applying this operation as a finisher of SelectableSubscriptions:
*
* Callable<SubscriptionAnnotation> callable = (new SelectableSubscriptions(context, true))
* .addFinisher(new SubscriptionGrouping());
*
* List<SubscriptionAnnotation> result = ExecutorService.submit(callable).get()
*/
public class SubscriptionGrouping
implements UnaryOperator<List<SubscriptionAnnotation>> {
// implementation of UnaryOperator
public List<SubscriptionAnnotation> apply(List<SubscriptionAnnotation> listOfSubscriptions) {
// group by GUID
Map<ParcelUuid, List<SubscriptionAnnotation>> groupedSubInfoList =
listOfSubscriptions.stream()
.filter(Objects::nonNull)
.collect(Collectors.groupingBy(subAnno -> getGroupUuid(subAnno)));
// select best one from subscription(s) within the same group
groupedSubInfoList.replaceAll((uuid, annoList) -> {
if ((uuid == SubscriptionAnnotation.EMPTY_UUID) || (annoList.size() <= 1)) {
return annoList;
}
return Collections.singletonList(selectBestFromList(annoList));
});
// build a stream of subscriptions
return groupedSubInfoList.values()
.stream().flatMap(List::stream).collect(Collectors.toList());
}
@Keep
@VisibleForTesting
protected ParcelUuid getGroupUuid(SubscriptionAnnotation subAnno) {
ParcelUuid groupUuid = subAnno.getGroupUuid();
return (groupUuid == null) ? SubscriptionAnnotation.EMPTY_UUID : groupUuid;
}
protected SubscriptionAnnotation selectBestFromList(List<SubscriptionAnnotation> annoList) {
Comparator<SubscriptionAnnotation> annoSelector = (anno1, anno2) -> {
if (anno1.isDisplayAllowed() != anno2.isDisplayAllowed()) {
return anno1.isDisplayAllowed() ? -1 : 1;
}
if (anno1.isActive() != anno2.isActive()) {
return anno1.isActive() ? -1 : 1;
}
if (anno1.isExisted() != anno2.isExisted()) {
return anno1.isExisted() ? -1 : 1;
}
return 0;
};
annoSelector = annoSelector
// eSIM in front of pSIM
.thenComparingInt(anno -> -anno.getType())
// subscription ID in reverse order
.thenComparingInt(anno -> -anno.getSubscriptionId());
return annoList.stream().sorted(annoSelector).findFirst().orElse(null);
}
}

View File

@@ -19,6 +19,7 @@ package com.android.settings.network.telephony;
import static com.android.settings.SettingsActivity.EXTRA_FRAGMENT_ARG_KEY;
import android.app.ActionBar;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.UserManager;
@@ -43,6 +44,8 @@ import com.android.settings.R;
import com.android.settings.core.SettingsBaseActivity;
import com.android.settings.network.ProxySubscriptionManager;
import com.android.settings.network.SubscriptionUtil;
import com.android.settings.network.helper.SelectableSubscriptions;
import com.android.settings.network.helper.SubscriptionAnnotation;
import java.util.List;
@@ -129,15 +132,13 @@ public class MobileNetworkActivity extends SettingsBaseActivity
: ((startIntent != null)
? startIntent.getIntExtra(Settings.EXTRA_SUB_ID, SUB_ID_NULL)
: SUB_ID_NULL);
// perform registration after mCurSubscriptionId been configured.
registerActiveSubscriptionsListener();
final SubscriptionInfo subscription = getSubscription();
maybeShowContactDiscoveryDialog(subscription);
// Since onChanged() will take place immediately when addActiveSubscriptionsListener(),
// perform registration after mCurSubscriptionId been configured.
registerActiveSubscriptionsListener();
updateSubscriptions(subscription, savedInstanceState);
updateSubscriptions(subscription, null);
}
@VisibleForTesting
@@ -244,15 +245,21 @@ public class MobileNetworkActivity extends SettingsBaseActivity
*/
@VisibleForTesting
SubscriptionInfo getSubscription() {
List<SubscriptionAnnotation> subList =
(new SelectableSubscriptions(this, true)).call();
SubscriptionAnnotation currentSubInfo = null;
if (mCurSubscriptionId != SUB_ID_NULL) {
return getSubscriptionForSubId(mCurSubscriptionId);
currentSubInfo = subList.stream()
.filter(SubscriptionAnnotation::isDisplayAllowed)
.filter(subAnno -> (subAnno.getSubscriptionId() == mCurSubscriptionId))
.findFirst().orElse(null);
}
final List<SubscriptionInfo> subInfos = getProxySubscriptionManager()
.getActiveSubscriptionsInfo();
if (CollectionUtils.isEmpty(subInfos)) {
return null;
if (currentSubInfo == null) {
currentSubInfo = subList.stream()
.filter(SubscriptionAnnotation::isDisplayAllowed)
.findFirst().orElse(null);
}
return subInfos.get(0);
return (currentSubInfo == null) ? null : currentSubInfo.getSubInfo();
}
@VisibleForTesting
@@ -287,7 +294,7 @@ public class MobileNetworkActivity extends SettingsBaseActivity
final Fragment fragment = new MobileNetworkSettings();
fragment.setArguments(bundle);
fragmentTransaction.replace(R.id.content_frame, fragment, fragmentTag);
fragmentTransaction.commit();
fragmentTransaction.commitAllowingStateLoss();
}
private void removeContactDiscoveryDialog(int subId) {

View File

@@ -0,0 +1,228 @@
/*
* Copyright (C) 2021 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.network;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.content.Context;
import androidx.lifecycle.Lifecycle;
import androidx.test.annotation.UiThreadTest;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
public class ProxySubscriptionManagerTest {
private Context mContext;
@Mock
private ActiveSubscriptionsListener mActiveSubscriptionsListener;
@Mock
private GlobalSettingsChangeListener mAirplaneModeOnSettingsChangeListener;
@Mock
private Lifecycle mLifecycle_ON_PAUSE;
@Mock
private Lifecycle mLifecycle_ON_RESUME;
@Mock
private Lifecycle mLifecycle_ON_DESTROY;
private Client mClient1;
private Client mClient2;
@Before
@UiThreadTest
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = spy(ApplicationProvider.getApplicationContext());
doReturn(Lifecycle.State.CREATED).when(mLifecycle_ON_PAUSE).getCurrentState();
doReturn(Lifecycle.State.STARTED).when(mLifecycle_ON_RESUME).getCurrentState();
doReturn(Lifecycle.State.DESTROYED).when(mLifecycle_ON_DESTROY).getCurrentState();
mClient1 = new Client();
mClient1.setLifecycle(mLifecycle_ON_RESUME);
mClient2 = new Client();
mClient2.setLifecycle(mLifecycle_ON_RESUME);
}
private ProxySubscriptionManager getInstance(Context context) {
ProxySubscriptionManager proxy =
Mockito.mock(ProxySubscriptionManager.class, Mockito.CALLS_REAL_METHODS);
proxy.init(context, mActiveSubscriptionsListener, mAirplaneModeOnSettingsChangeListener);
proxy.notifySubscriptionInfoMightChanged();
return proxy;
}
public class Client implements ProxySubscriptionManager.OnActiveSubscriptionChangedListener {
private Lifecycle lifeCycle;
private int numberOfCallback;
public void onChanged() {
numberOfCallback++;
}
public Lifecycle getLifecycle() {
return lifeCycle;
}
public int getCallbackCount() {
return numberOfCallback;
}
public void setLifecycle(Lifecycle lifecycle) {
lifeCycle = lifecycle;
}
}
@Test
@UiThreadTest
public void addActiveSubscriptionsListener_addOneClient_getNoCallback() {
ProxySubscriptionManager proxy = getInstance(mContext);
proxy.addActiveSubscriptionsListener(mClient1);
assertThat(mClient1.getCallbackCount()).isEqualTo(0);
}
@Test
@UiThreadTest
public void addActiveSubscriptionsListener_addOneClient_changeOnSimGetCallback() {
ProxySubscriptionManager proxy = getInstance(mContext);
proxy.addActiveSubscriptionsListener(mClient1);
assertThat(mClient1.getCallbackCount()).isEqualTo(0);
proxy.notifySubscriptionInfoMightChanged();
assertThat(mClient1.getCallbackCount()).isEqualTo(1);
}
@Test
@UiThreadTest
public void addActiveSubscriptionsListener_addOneClient_noCallbackUntilUiResume() {
ProxySubscriptionManager proxy = getInstance(mContext);
mClient1.setLifecycle(mLifecycle_ON_PAUSE);
proxy.addActiveSubscriptionsListener(mClient1);
assertThat(mClient1.getCallbackCount()).isEqualTo(0);
proxy.notifySubscriptionInfoMightChanged();
assertThat(mClient1.getCallbackCount()).isEqualTo(0);
mClient1.setLifecycle(mLifecycle_ON_RESUME);
proxy.onStart();
Assert.assertTrue(mClient1.getCallbackCount() > 0);
mClient1.setLifecycle(mLifecycle_ON_PAUSE);
proxy.onStop();
int latestCallbackCount = mClient1.getCallbackCount();
proxy.notifySubscriptionInfoMightChanged();
assertThat(mClient1.getCallbackCount()).isEqualTo(latestCallbackCount);
}
@Test
@UiThreadTest
public void addActiveSubscriptionsListener_addTwoClient_eachClientGetNoCallback() {
ProxySubscriptionManager proxy = getInstance(mContext);
proxy.addActiveSubscriptionsListener(mClient1);
assertThat(mClient1.getCallbackCount()).isEqualTo(0);
proxy.addActiveSubscriptionsListener(mClient2);
assertThat(mClient1.getCallbackCount()).isEqualTo(0);
assertThat(mClient2.getCallbackCount()).isEqualTo(0);
}
@Test
@UiThreadTest
public void addActiveSubscriptionsListener_addTwoClient_callbackOnlyWhenResume() {
ProxySubscriptionManager proxy = getInstance(mContext);
proxy.addActiveSubscriptionsListener(mClient1);
assertThat(mClient1.getCallbackCount()).isEqualTo(0);
proxy.addActiveSubscriptionsListener(mClient2);
assertThat(mClient1.getCallbackCount()).isEqualTo(0);
assertThat(mClient2.getCallbackCount()).isEqualTo(0);
mClient1.setLifecycle(mLifecycle_ON_PAUSE);
proxy.onStop();
assertThat(mClient1.getCallbackCount()).isEqualTo(0);
assertThat(mClient2.getCallbackCount()).isEqualTo(0);
proxy.notifySubscriptionInfoMightChanged();
assertThat(mClient1.getCallbackCount()).isEqualTo(0);
assertThat(mClient2.getCallbackCount()).isEqualTo(1);
mClient1.setLifecycle(mLifecycle_ON_RESUME);
proxy.onStart();
Assert.assertTrue(mClient1.getCallbackCount() > 0);
assertThat(mClient2.getCallbackCount()).isEqualTo(1);
}
@Test
@UiThreadTest
public void removeActiveSubscriptionsListener_removedClient_noCallback() {
ProxySubscriptionManager proxy = getInstance(mContext);
proxy.addActiveSubscriptionsListener(mClient1);
assertThat(mClient1.getCallbackCount()).isEqualTo(0);
proxy.notifySubscriptionInfoMightChanged();
assertThat(mClient1.getCallbackCount()).isEqualTo(1);
proxy.removeActiveSubscriptionsListener(mClient1);
assertThat(mClient1.getCallbackCount()).isEqualTo(1);
proxy.notifySubscriptionInfoMightChanged();
assertThat(mClient1.getCallbackCount()).isEqualTo(1);
}
@Test
@UiThreadTest
public void notifySubscriptionInfoMightChanged_destroyedClient_autoRemove() {
ProxySubscriptionManager proxy = getInstance(mContext);
proxy.addActiveSubscriptionsListener(mClient1);
assertThat(mClient1.getCallbackCount()).isEqualTo(0);
proxy.notifySubscriptionInfoMightChanged();
assertThat(mClient1.getCallbackCount()).isEqualTo(1);
mClient1.setLifecycle(mLifecycle_ON_DESTROY);
proxy.notifySubscriptionInfoMightChanged();
assertThat(mClient1.getCallbackCount()).isEqualTo(1);
mClient1.setLifecycle(mLifecycle_ON_RESUME);
proxy.notifySubscriptionInfoMightChanged();
assertThat(mClient1.getCallbackCount()).isEqualTo(1);
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright (C) 2021 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.network.helper;
import static com.google.common.truth.Truth.assertThat;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
import java.util.concurrent.atomic.AtomicIntegerArray;
@RunWith(AndroidJUnit4.class)
public class SelectableSubscriptionsTest {
@Before
public void setUp() {
}
@Test
public void atomicToList_nullInput_getNoneNullEmptyList() {
List<Integer> result = SelectableSubscriptions.atomicToList(null);
assertThat(result.size()).isEqualTo(0);
}
@Test
public void atomicToList_zeroLengthInput_getEmptyList() {
List<Integer> result = SelectableSubscriptions.atomicToList(new AtomicIntegerArray(0));
assertThat(result.size()).isEqualTo(0);
}
@Test
public void atomicToList_subIdInArray_getList() {
AtomicIntegerArray array = new AtomicIntegerArray(3);
array.set(0, 3);
array.set(1, 7);
array.set(2, 4);
List<Integer> result = SelectableSubscriptions.atomicToList(array);
assertThat(result.size()).isEqualTo(3);
assertThat(result.get(0)).isEqualTo(3);
assertThat(result.get(1)).isEqualTo(7);
assertThat(result.get(2)).isEqualTo(4);
}
}

View File

@@ -0,0 +1,137 @@
/*
* Copyright (C) 2021 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.network.helper;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.os.ParcelUuid;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.settings.network.helper.SubscriptionAnnotation;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations;
import java.util.Arrays;
import java.util.List;
@RunWith(AndroidJUnit4.class)
public class SubscriptionGroupingTest {
private ParcelUuid mMockUuid;
private Context mContext;
private SubscriptionGrouping mTarget;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = spy(ApplicationProvider.getApplicationContext());
mMockUuid = ParcelUuid.fromString("1-1-1-1-1");
mTarget = spy(new SubscriptionGrouping());
}
@Test
public void apply_multipleEntriesWithSameGroupUuid_onlyOneLeft() {
SubscriptionAnnotation subAnno1 = new TestSubAnnotation(1,
SubscriptionAnnotation.TYPE_ESIM, true, true, mMockUuid);
SubscriptionAnnotation subAnno2 = new TestSubAnnotation(2,
SubscriptionAnnotation.TYPE_PSIM, true, true, mMockUuid);
SubscriptionAnnotation subAnno3 = new TestSubAnnotation(3,
SubscriptionAnnotation.TYPE_ESIM, true, true, mMockUuid);
doReturn(mMockUuid).when(mTarget).getGroupUuid(any());
List<SubscriptionAnnotation> result = mTarget
.apply(Arrays.asList(subAnno2, subAnno1, subAnno3));
assertThat(result.size()).isEqualTo(1);
assertThat(result.get(0)).isEqualTo(subAnno3);
}
@Test
public void apply_multipleEntriesWithSameGroupUuid_disabledOneIsAvoided() {
SubscriptionAnnotation subAnno1 = new TestSubAnnotation(1,
SubscriptionAnnotation.TYPE_ESIM, true, true, mMockUuid);
SubscriptionAnnotation subAnno2 = new TestSubAnnotation(2,
SubscriptionAnnotation.TYPE_PSIM, true, true, mMockUuid);
SubscriptionAnnotation subAnno3 = new TestSubAnnotation(3,
SubscriptionAnnotation.TYPE_ESIM, false, false, mMockUuid);
doReturn(mMockUuid).when(mTarget).getGroupUuid(any());
List<SubscriptionAnnotation> result = mTarget
.apply(Arrays.asList(subAnno2, subAnno1, subAnno3));
assertThat(result.size()).isEqualTo(1);
assertThat(result.get(0)).isEqualTo(subAnno1);
}
private class TestSubAnnotation extends SubscriptionAnnotation {
private int mSubId;
private int mSimType;
private boolean mIsActive;
private boolean mIsDisplayAllowed;
private ParcelUuid mUuid;
private TestSubAnnotation(int subId, int simType,
boolean isActive, boolean isDisplayAllowed, ParcelUuid guuid) {
super(null, -1, null, null, null, null);
mSubId = subId;
mSimType = simType;
mIsActive = isActive;
mIsDisplayAllowed = isDisplayAllowed;
mUuid = guuid;
}
@Override
public int getSubscriptionId() {
return mSubId;
}
@Override
public int getType() {
return mSimType;
}
@Override
public boolean isExisted() {
return true;
}
@Override
public boolean isActive() {
return mIsActive;
}
@Override
public boolean isDisplayAllowed() {
return mIsDisplayAllowed;
}
@Override
public ParcelUuid getGroupUuid() {
return mUuid;
}
}
}