Snap for 7546365 from 9683efd43f to sc-v2-release
Change-Id: Ie8701fa7211d7d7ad2406a9618c5d54298f42ceb
This commit is contained in:
52
res/drawable-night/accessibility_captions_banner.xml
Normal file
52
res/drawable-night/accessibility_captions_banner.xml
Normal 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 |
52
res/drawable/accessibility_captions_banner.xml
Normal file
52
res/drawable/accessibility_captions_banner.xml
Normal 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>
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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] -->
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
58
src/com/android/settings/network/helper/QueryEsimCardId.java
Normal file
58
src/com/android/settings/network/helper/QueryEsimCardId.java
Normal 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());
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user