/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings; import android.content.Context; import android.telephony.SubscriptionManager; import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener; import android.util.Log; import androidx.annotation.VisibleForTesting; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.IntStream; /** * A Class monitoring the availability of subscription IDs provided within reset request. * * This is to detect the situation when user changing SIM card during the presenting of * confirmation UI. */ public class ResetSubscriptionContract implements AutoCloseable { private static final String TAG = "ResetSubscriptionContract"; private final Context mContext; private ExecutorService mExecutorService; private final int [] mResetSubscriptionIds; @VisibleForTesting protected OnSubscriptionsChangedListener mSubscriptionsChangedListener; private AtomicBoolean mSubscriptionsUpdateNotify = new AtomicBoolean(); /** * Constructor * @param context Context * @param resetRequest the request object for perform network reset operation. */ public ResetSubscriptionContract(Context context, ResetNetworkRequest resetRequest) { mContext = context; // Only keeps specific subscription ID required to perform reset operation IntStream subIdStream = IntStream.of( resetRequest.getResetTelephonyAndNetworkPolicyManager(), resetRequest.getResetApnSubId(), resetRequest.getResetImsSubId()); mResetSubscriptionIds = subIdStream.sorted().distinct() .filter(id -> SubscriptionManager.isUsableSubscriptionId(id)) .toArray(); if (mResetSubscriptionIds.length <= 0) { return; } // Monitoring callback through background thread mExecutorService = Executors.newSingleThreadExecutor(); startMonitorSubscriptionChange(); } /** * A method for detecting if there's any subscription under monitor no longer active. * @return subscription ID which is no longer active. */ public Integer getAnyMissingSubscriptionId() { if (mResetSubscriptionIds.length <= 0) { return null; } SubscriptionManager mgr = getSubscriptionManager(); if (mgr == null) { Log.w(TAG, "Fail to access subscription manager"); return mResetSubscriptionIds[0]; } for (int idx = 0; idx < mResetSubscriptionIds.length; idx++) { int subId = mResetSubscriptionIds[idx]; if (mgr.getActiveSubscriptionInfo(subId) == null) { Log.w(TAG, "SubId " + subId + " no longer active."); return subId; } } return null; } /** * Async callback when detecting if there's any subscription under monitor no longer active. * @param subscriptionId subscription ID which is no longer active. */ public void onSubscriptionInactive(int subscriptionId) {} @VisibleForTesting protected SubscriptionManager getSubscriptionManager() { return mContext.getSystemService(SubscriptionManager.class); } @VisibleForTesting protected OnSubscriptionsChangedListener getChangeListener() { return new OnSubscriptionsChangedListener() { @Override public void onSubscriptionsChanged() { /** * Reducing the processing time on main UI thread through a flag. * Once flag get into false, which means latest callback has been * processed. */ mSubscriptionsUpdateNotify.set(true); // Back to main UI thread mContext.getMainExecutor().execute(() -> { // Remove notifications and perform checking. if (mSubscriptionsUpdateNotify.getAndSet(false)) { Integer subId = getAnyMissingSubscriptionId(); if (subId != null) { onSubscriptionInactive(subId); } } }); } }; } private void startMonitorSubscriptionChange() { SubscriptionManager mgr = getSubscriptionManager(); if (mgr == null) { return; } // update monitor listener mSubscriptionsChangedListener = getChangeListener(); mgr.addOnSubscriptionsChangedListener( mExecutorService, mSubscriptionsChangedListener); } // Implementation of AutoCloseable public void close() { if (mExecutorService == null) { return; } // Stop monitoring subscription change SubscriptionManager mgr = getSubscriptionManager(); if (mgr != null) { mgr.removeOnSubscriptionsChangedListener(mSubscriptionsChangedListener); } // Release Executor mExecutorService.shutdownNow(); mExecutorService = null; } }