1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings;
18 
19 import android.content.Context;
20 import android.telephony.SubscriptionManager;
21 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
22 import android.util.Log;
23 
24 import androidx.annotation.VisibleForTesting;
25 
26 import java.util.concurrent.ExecutorService;
27 import java.util.concurrent.Executors;
28 import java.util.concurrent.atomic.AtomicBoolean;
29 import java.util.stream.IntStream;
30 
31 /**
32  * A Class monitoring the availability of subscription IDs provided within reset request.
33  *
34  * This is to detect the situation when user changing SIM card during the presenting of
35  * confirmation UI.
36  */
37 public class ResetSubscriptionContract implements AutoCloseable {
38     private static final String TAG = "ResetSubscriptionContract";
39 
40     private final Context mContext;
41     private ExecutorService mExecutorService;
42     private final int [] mResetSubscriptionIds;
43     @VisibleForTesting
44     protected OnSubscriptionsChangedListener mSubscriptionsChangedListener;
45     private AtomicBoolean mSubscriptionsUpdateNotify = new AtomicBoolean();
46 
47     /**
48      * Constructor
49      * @param context Context
50      * @param resetRequest the request object for perform network reset operation.
51      */
ResetSubscriptionContract(Context context, ResetNetworkRequest resetRequest)52     public ResetSubscriptionContract(Context context, ResetNetworkRequest resetRequest) {
53         mContext = context;
54         // Only keeps specific subscription ID required to perform reset operation
55         IntStream subIdStream = IntStream.of(
56                 resetRequest.getResetTelephonyAndNetworkPolicyManager(),
57                 resetRequest.getResetApnSubId(), resetRequest.getResetImsSubId());
58         mResetSubscriptionIds = subIdStream.sorted().distinct()
59                 .filter(id -> SubscriptionManager.isUsableSubscriptionId(id))
60                 .toArray();
61 
62         if (mResetSubscriptionIds.length <= 0) {
63             return;
64         }
65 
66         // Monitoring callback through background thread
67         mExecutorService = Executors.newSingleThreadExecutor();
68         startMonitorSubscriptionChange();
69     }
70 
71     /**
72      * A method for detecting if there's any subscription under monitor no longer active.
73      * @return subscription ID which is no longer active.
74      */
getAnyMissingSubscriptionId()75     public Integer getAnyMissingSubscriptionId() {
76         if (mResetSubscriptionIds.length <= 0) {
77             return null;
78         }
79         SubscriptionManager mgr = getSubscriptionManager();
80         if (mgr == null) {
81             Log.w(TAG, "Fail to access subscription manager");
82             return mResetSubscriptionIds[0];
83         }
84         for (int idx = 0; idx < mResetSubscriptionIds.length; idx++) {
85             int subId = mResetSubscriptionIds[idx];
86             if (mgr.getActiveSubscriptionInfo(subId) == null) {
87                 Log.w(TAG, "SubId " + subId + " no longer active.");
88                 return subId;
89             }
90         }
91         return null;
92     }
93 
94     /**
95      * Async callback when detecting if there's any subscription under monitor no longer active.
96      * @param subscriptionId subscription ID which is no longer active.
97      */
onSubscriptionInactive(int subscriptionId)98     public void onSubscriptionInactive(int subscriptionId) {}
99 
100     @VisibleForTesting
getSubscriptionManager()101     protected SubscriptionManager getSubscriptionManager() {
102         return mContext.getSystemService(SubscriptionManager.class);
103     }
104 
105     @VisibleForTesting
getChangeListener()106     protected OnSubscriptionsChangedListener getChangeListener() {
107         return new OnSubscriptionsChangedListener() {
108             @Override
109             public void onSubscriptionsChanged() {
110                 /**
111                  * Reducing the processing time on main UI thread through a flag.
112                  * Once flag get into false, which means latest callback has been
113                  * processed.
114                  */
115                 mSubscriptionsUpdateNotify.set(true);
116 
117                 // Back to main UI thread
118                 mContext.getMainExecutor().execute(() -> {
119                     // Remove notifications and perform checking.
120                     if (mSubscriptionsUpdateNotify.getAndSet(false)) {
121                         Integer subId = getAnyMissingSubscriptionId();
122                         if (subId != null) {
123                             onSubscriptionInactive(subId);
124                         }
125                     }
126                 });
127             }
128         };
129     }
130 
131     private void startMonitorSubscriptionChange() {
132         SubscriptionManager mgr = getSubscriptionManager();
133         if (mgr == null) {
134             return;
135         }
136         // update monitor listener
137         mSubscriptionsChangedListener = getChangeListener();
138 
139         mgr.addOnSubscriptionsChangedListener(
140                 mExecutorService, mSubscriptionsChangedListener);
141     }
142 
143     // Implementation of AutoCloseable
144     public void close() {
145         if (mExecutorService == null) {
146             return;
147         }
148         // Stop monitoring subscription change
149         SubscriptionManager mgr = getSubscriptionManager();
150         if (mgr != null) {
151             mgr.removeOnSubscriptionsChangedListener(mSubscriptionsChangedListener);
152         }
153         // Release Executor
154         mExecutorService.shutdownNow();
155         mExecutorService = null;
156     }
157 }
158