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