blob: 91a4c0a0de19255d4fe3a37f47c2176c93209818 [file] [log] [blame]
/*
* Copyright (C) 2019 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 android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
import android.os.Looper;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import com.android.internal.telephony.TelephonyIntents;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* A listener for active subscription change
*/
public abstract class ActiveSubscriptionsListener
extends SubscriptionManager.OnSubscriptionsChangedListener
implements AutoCloseable {
private static final String TAG = "ActiveSubsciptions";
private static final boolean DEBUG = false;
private Looper mLooper;
private Context mContext;
private static final int STATE_NOT_LISTENING = 0;
private static final int STATE_STOPPING = 1;
private static final int STATE_PREPARING = 2;
private static final int STATE_LISTENING = 3;
private static final int STATE_DATA_CACHED = 4;
private AtomicInteger mCacheState;
private SubscriptionManager mSubscriptionManager;
private IntentFilter mSubscriptionChangeIntentFilter;
private BroadcastReceiver mSubscriptionChangeReceiver;
private static final int MAX_SUBSCRIPTION_UNKNOWN = -1;
private final int mTargetSubscriptionId;
private AtomicInteger mMaxActiveSubscriptionInfos;
private List<SubscriptionInfo> mCachedActiveSubscriptionInfo;
/**
* Constructor
*
* @param looper {@code Looper} of this listener
* @param context {@code Context} of this listener
*/
public ActiveSubscriptionsListener(Looper looper, Context context) {
this(looper, context, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
}
/**
* Constructor
*
* @param looper {@code Looper} of this listener
* @param context {@code Context} of this listener
* @param subscriptionId for subscription on this listener
*/
public ActiveSubscriptionsListener(Looper looper, Context context, int subscriptionId) {
super(looper);
mLooper = looper;
mContext = context;
mTargetSubscriptionId = subscriptionId;
mCacheState = new AtomicInteger(STATE_NOT_LISTENING);
mMaxActiveSubscriptionInfos = new AtomicInteger(MAX_SUBSCRIPTION_UNKNOWN);
mSubscriptionChangeIntentFilter = new IntentFilter();
mSubscriptionChangeIntentFilter.addAction(
CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
mSubscriptionChangeIntentFilter.addAction(
TelephonyIntents.ACTION_RADIO_TECHNOLOGY_CHANGED);
mSubscriptionChangeIntentFilter.addAction(
TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED);
}
@VisibleForTesting
BroadcastReceiver getSubscriptionChangeReceiver() {
return new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (isInitialStickyBroadcast()) {
return;
}
final String action = intent.getAction();
if (TextUtils.isEmpty(action)) {
return;
}
if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(action)) {
final int subId = intent.getIntExtra(
CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
if (!clearCachedSubId(subId)) {
return;
}
if (SubscriptionManager.isValidSubscriptionId(mTargetSubscriptionId)) {
if (SubscriptionManager.isValidSubscriptionId(subId)
&& (mTargetSubscriptionId != subId)) {
return;
}
}
}
onSubscriptionsChanged();
}
};
}
/**
* Active subscriptions got changed
*/
public abstract void onChanged();
@Override
public void onSubscriptionsChanged() {
// clear value in cache
clearCache();
listenerNotify();
}
/**
* Start listening subscriptions change
*/
public void start() {
monitorSubscriptionsChange(true);
}
/**
* Stop listening subscriptions change
*/
public void stop() {
monitorSubscriptionsChange(false);
}
/**
* Implementation of {@code AutoCloseable}
*/
public void close() {
stop();
}
/**
* Get SubscriptionManager
*
* @return a SubscriptionManager
*/
public SubscriptionManager getSubscriptionManager() {
if (mSubscriptionManager == null) {
mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class);
}
return mSubscriptionManager;
}
/**
* Get current max. number active subscription info(s) been setup within device
*
* @return max. number of active subscription info(s)
*/
public int getActiveSubscriptionInfoCountMax() {
int cacheState = mCacheState.get();
if (cacheState < STATE_LISTENING) {
return getSubscriptionManager().getActiveSubscriptionInfoCountMax();
}
mMaxActiveSubscriptionInfos.compareAndSet(MAX_SUBSCRIPTION_UNKNOWN,
getSubscriptionManager().getActiveSubscriptionInfoCountMax());
return mMaxActiveSubscriptionInfos.get();
}
/**
* Get a list of active subscription info
*
* @return A list of active subscription info
*/
public List<SubscriptionInfo> getActiveSubscriptionsInfo() {
if (mCacheState.get() >= STATE_DATA_CACHED) {
return mCachedActiveSubscriptionInfo;
}
mCachedActiveSubscriptionInfo = getSubscriptionManager().getActiveSubscriptionInfoList();
mCacheState.compareAndSet(STATE_LISTENING, STATE_DATA_CACHED);
if (DEBUG) {
if ((mCachedActiveSubscriptionInfo == null)
|| (mCachedActiveSubscriptionInfo.size() <= 0)) {
Log.d(TAG, "active subscriptions: " + mCachedActiveSubscriptionInfo);
} else {
final StringBuilder logString = new StringBuilder("active subscriptions:");
for (SubscriptionInfo subInfo : mCachedActiveSubscriptionInfo) {
logString.append(" " + subInfo.getSubscriptionId());
}
Log.d(TAG, logString.toString());
}
}
return mCachedActiveSubscriptionInfo;
}
/**
* Get an active subscription info with given subscription ID
*
* @param subId target subscription ID
* @return A subscription info which is active list
*/
public SubscriptionInfo getActiveSubscriptionInfo(int subId) {
final List<SubscriptionInfo> subInfoList = getActiveSubscriptionsInfo();
if (subInfoList == null) {
return null;
}
for (SubscriptionInfo subInfo : subInfoList) {
if (subInfo.getSubscriptionId() == subId) {
return subInfo;
}
}
return null;
}
/**
* Get a list of all subscription info which accessible by Settings app
*
* @return A list of accessible subscription info
*/
public List<SubscriptionInfo> getAccessibleSubscriptionsInfo() {
return getSubscriptionManager().getAvailableSubscriptionInfoList();
}
/**
* Get an accessible subscription info with given subscription ID
*
* @param subId target subscription ID
* @return A subscription info which is accessible list
*/
public SubscriptionInfo getAccessibleSubscriptionInfo(int subId) {
// Always check if subId is part of activeSubscriptions
// since there's cache design within SubscriptionManager.
// That give us a chance to avoid from querying ContentProvider.
final SubscriptionInfo activeSubInfo = getActiveSubscriptionInfo(subId);
if (activeSubInfo != null) {
return activeSubInfo;
}
final List<SubscriptionInfo> subInfoList = getAccessibleSubscriptionsInfo();
if (subInfoList == null) {
return null;
}
for (SubscriptionInfo subInfo : subInfoList) {
if (subInfo.getSubscriptionId() == subId) {
return subInfo;
}
}
return null;
}
/**
* Clear data cached within listener
*/
public void clearCache() {
mMaxActiveSubscriptionInfos.set(MAX_SUBSCRIPTION_UNKNOWN);
mCacheState.compareAndSet(STATE_DATA_CACHED, STATE_LISTENING);
mCachedActiveSubscriptionInfo = null;
}
@VisibleForTesting
void registerForSubscriptionsChange() {
getSubscriptionManager().addOnSubscriptionsChangedListener(
mContext.getMainExecutor(), this);
}
private void monitorSubscriptionsChange(boolean on) {
if (on) {
if (!mCacheState.compareAndSet(STATE_NOT_LISTENING, STATE_PREPARING)) {
return;
}
if (mSubscriptionChangeReceiver == null) {
mSubscriptionChangeReceiver = getSubscriptionChangeReceiver();
}
mContext.registerReceiver(mSubscriptionChangeReceiver,
mSubscriptionChangeIntentFilter, null, new Handler(mLooper));
registerForSubscriptionsChange();
mCacheState.compareAndSet(STATE_PREPARING, STATE_LISTENING);
return;
}
final int currentState = mCacheState.getAndSet(STATE_STOPPING);
if (currentState <= STATE_STOPPING) {
mCacheState.compareAndSet(STATE_STOPPING, currentState);
return;
}
if (mSubscriptionChangeReceiver != null) {
mContext.unregisterReceiver(mSubscriptionChangeReceiver);
}
getSubscriptionManager().removeOnSubscriptionsChangedListener(this);
clearCache();
mCacheState.compareAndSet(STATE_STOPPING, STATE_NOT_LISTENING);
}
private void listenerNotify() {
if (mCacheState.get() < STATE_LISTENING) {
return;
}
onChanged();
}
private boolean clearCachedSubId(int subId) {
if (mCacheState.get() < STATE_DATA_CACHED) {
return false;
}
if (mCachedActiveSubscriptionInfo == null) {
return false;
}
for (SubscriptionInfo subInfo : mCachedActiveSubscriptionInfo) {
if (subInfo.getSubscriptionId() == subId) {
clearCache();
return true;
}
}
return false;
}
}