script-astra/Android/Sdk/sources/android-35/com/android/internal/telephony/CellBroadcastConfigTracker.java
localadmin 4380f00a78 init
2025-01-20 18:15:20 +03:00

575 lines
23 KiB
Java

/*
* Copyright 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.internal.telephony;
import android.annotation.NonNull;
import android.os.AsyncResult;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Message;
import android.telephony.CellBroadcastIdRange;
import android.telephony.SmsCbMessage;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.IndentingPrintWriter;
import android.util.LocalLog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Consumer;
/**
* This class is to track the state to set cell broadcast config
*/
public final class CellBroadcastConfigTracker extends StateMachine {
private static final boolean DBG = Build.IS_DEBUGGABLE;
private static final int EVENT_REQUEST = 1;
private static final int EVENT_CONFIGURATION_DONE = 2;
private static final int EVENT_ACTIVATION_DONE = 3;
private static final int EVENT_RADIO_OFF = 4;
private static final int EVENT_SUBSCRIPTION_CHANGED = 5;
@VisibleForTesting
public static final int EVENT_RADIO_RESET = 6;
private static final int SMS_CB_CODE_SCHEME_MIN = 0;
private static final int SMS_CB_CODE_SCHEME_MAX = 255;
// Cache of current cell broadcast id ranges of 3gpp
private List<CellBroadcastIdRange> mCbRanges3gpp = new CopyOnWriteArrayList<>();
// Cache of current cell broadcast id ranges of 3gpp2
private List<CellBroadcastIdRange> mCbRanges3gpp2 = new CopyOnWriteArrayList<>();
private Phone mPhone;
private final LocalLog mLocalLog = new LocalLog(128);
@VisibleForTesting
public int mSubId;
@VisibleForTesting
public final SubscriptionManager.OnSubscriptionsChangedListener mSubChangedListener =
new SubscriptionManager.OnSubscriptionsChangedListener() {
@Override
public void onSubscriptionsChanged() {
sendMessage(EVENT_SUBSCRIPTION_CHANGED);
}
};
/**
* The class is to present the request to set cell broadcast id ranges
*/
private static class Request {
private final List<CellBroadcastIdRange> mCbRangesRequest3gpp =
new CopyOnWriteArrayList<>();
private final List<CellBroadcastIdRange> mCbRangesRequest3gpp2 =
new CopyOnWriteArrayList<>();
Consumer<Integer> mCallback;
Request(@NonNull List<CellBroadcastIdRange> ranges, @NonNull Consumer<Integer> callback) {
ranges.forEach(r -> {
if (r.getType() == SmsCbMessage.MESSAGE_FORMAT_3GPP) {
mCbRangesRequest3gpp.add(r);
} else {
mCbRangesRequest3gpp2.add(r);
}
});
mCallback = callback;
}
List<CellBroadcastIdRange> get3gppRanges() {
return mCbRangesRequest3gpp;
}
List<CellBroadcastIdRange> get3gpp2Ranges() {
return mCbRangesRequest3gpp2;
}
Consumer<Integer> getCallback() {
return mCallback;
}
@Override
public String toString() {
return "Request[mCbRangesRequest3gpp = " + mCbRangesRequest3gpp + ", "
+ "mCbRangesRequest3gpp2 = " + mCbRangesRequest3gpp2 + "]";
}
}
/**
* The default state.
*/
private class DefaultState extends State {
@Override
public void enter() {
mPhone.registerForRadioOffOrNotAvailable(getHandler(), EVENT_RADIO_OFF, null);
mPhone.mCi.registerForModemReset(getHandler(), EVENT_RADIO_RESET, null);
mPhone.getContext().getSystemService(SubscriptionManager.class)
.addOnSubscriptionsChangedListener(new HandlerExecutor(getHandler()),
mSubChangedListener);
}
@Override
public void exit() {
mPhone.unregisterForRadioOffOrNotAvailable(getHandler());
mPhone.mCi.unregisterForModemReset(getHandler());
mPhone.getContext().getSystemService(SubscriptionManager.class)
.removeOnSubscriptionsChangedListener(mSubChangedListener);
}
@Override
public boolean processMessage(Message msg) {
boolean retVal = HANDLED;
if (DBG) {
logd("DefaultState message:" + msg.what);
}
switch (msg.what) {
case EVENT_RADIO_OFF:
case EVENT_RADIO_RESET:
resetConfig();
break;
case EVENT_SUBSCRIPTION_CHANGED:
int subId = mPhone.getSubId();
if (mSubId != subId) {
log("SubId changed from " + mSubId + " to " + subId);
mSubId = subId;
resetConfig();
}
break;
default:
log("unexpected message!");
break;
}
return retVal;
}
}
private DefaultState mDefaultState = new DefaultState();
/*
* The idle state which does not have ongoing radio request.
*/
private class IdleState extends State {
@Override
public boolean processMessage(Message msg) {
boolean retVal = NOT_HANDLED;
if (DBG) {
logd("IdleState message:" + msg.what);
}
switch (msg.what) {
case EVENT_REQUEST:
Request request = (Request) msg.obj;
if (DBG) {
logd("IdleState handle EVENT_REQUEST with request:" + request);
mLocalLog.log("IdleState handle EVENT_REQUEST with request:" + request
+ ", mCbRanges3gpp:" + mCbRanges3gpp
+ ", mCbRanges3gpp2:" + mCbRanges3gpp2);
}
if (!mCbRanges3gpp.equals(request.get3gppRanges())) {
// set gsm config if the config is changed
setGsmConfig(request.get3gppRanges(), request);
transitionTo(mGsmConfiguringState);
} else if (!mCbRanges3gpp2.equals(request.get3gpp2Ranges())) {
// set cdma config directly if no gsm config change but cdma config is
// changed
setCdmaConfig(request.get3gpp2Ranges(), request);
transitionTo(mCdmaConfiguringState);
} else {
logd("Do nothing as the requested ranges are same as now");
request.getCallback().accept(
TelephonyManager.CELL_BROADCAST_RESULT_SUCCESS);
}
retVal = HANDLED;
break;
default:
break;
}
return retVal;
}
}
private IdleState mIdleState = new IdleState();
/*
* The state waiting for the result to set gsm config.
*/
private class GsmConfiguringState extends State {
@Override
public boolean processMessage(Message msg) {
boolean retVal = NOT_HANDLED;
if (DBG) {
logd("GsmConfiguringState message:" + msg.what);
}
switch (msg.what) {
case EVENT_REQUEST:
deferMessage(msg);
retVal = HANDLED;
break;
case EVENT_CONFIGURATION_DONE:
AsyncResult ar = (AsyncResult) msg.obj;
Request request = (Request) ar.userObj;
if (DBG) {
logd("GsmConfiguringState handle EVENT_CONFIGURATION_DONE with request:"
+ request);
}
if (ar.exception == null) {
// set gsm activation and transit to gsm activating state
setActivation(SmsCbMessage.MESSAGE_FORMAT_3GPP,
!request.get3gppRanges().isEmpty(), request);
transitionTo(mGsmActivatingState);
} else {
logd("Failed to set gsm config");
mLocalLog.log("GsmConfiguringState Failed to set gsm config:" + request);
request.getCallback().accept(
TelephonyManager.CELL_BROADCAST_RESULT_FAIL_CONFIG);
// transit to idle state on the failure case
transitionTo(mIdleState);
}
retVal = HANDLED;
break;
default:
break;
}
return retVal;
}
}
private GsmConfiguringState mGsmConfiguringState = new GsmConfiguringState();
/*
* The state waiting for the result to set gsm activation.
*/
private class GsmActivatingState extends State {
@Override
public boolean processMessage(Message msg) {
boolean retVal = NOT_HANDLED;
if (DBG) {
logd("GsmActivatingState message:" + msg.what);
}
switch (msg.what) {
case EVENT_REQUEST:
deferMessage(msg);
retVal = HANDLED;
break;
case EVENT_ACTIVATION_DONE:
AsyncResult ar = (AsyncResult) msg.obj;
Request request = (Request) ar.userObj;
if (DBG) {
logd("GsmActivatingState handle EVENT_ACTIVATION_DONE with request:"
+ request);
mLocalLog.log("GsmActivatingState EVENT_ACTIVATION_DONE, exception:"
+ ar.exception + ", request:" + request);
}
if (ar.exception == null) {
mCbRanges3gpp = request.get3gppRanges();
if (!mCbRanges3gpp2.equals(request.get3gpp2Ranges())) {
// set cdma config and transit to cdma configuring state if the config
// is changed.
setCdmaConfig(request.get3gpp2Ranges(), request);
transitionTo(mCdmaConfiguringState);
} else {
logd("Done as no need to update ranges for 3gpp2");
request.getCallback().accept(
TelephonyManager.CELL_BROADCAST_RESULT_SUCCESS);
// transit to idle state if there is no cdma config change
transitionTo(mIdleState);
}
} else {
logd("Failed to set gsm activation");
request.getCallback().accept(
TelephonyManager.CELL_BROADCAST_RESULT_FAIL_ACTIVATION);
// transit to idle state on the failure case
transitionTo(mIdleState);
}
retVal = HANDLED;
break;
default:
break;
}
return retVal;
}
}
private GsmActivatingState mGsmActivatingState = new GsmActivatingState();
/*
* The state waiting for the result to set cdma config.
*/
private class CdmaConfiguringState extends State {
@Override
public boolean processMessage(Message msg) {
boolean retVal = NOT_HANDLED;
if (DBG) {
logd("CdmaConfiguringState message:" + msg.what);
}
switch (msg.what) {
case EVENT_REQUEST:
deferMessage(msg);
retVal = HANDLED;
break;
case EVENT_CONFIGURATION_DONE:
AsyncResult ar = (AsyncResult) msg.obj;
Request request = (Request) ar.userObj;
if (DBG) {
logd("CdmaConfiguringState handle EVENT_ACTIVATION_DONE with request:"
+ request);
}
if (ar.exception == null) {
// set cdma activation and transit to cdma activating state
setActivation(SmsCbMessage.MESSAGE_FORMAT_3GPP2,
!request.get3gpp2Ranges().isEmpty(), request);
transitionTo(mCdmaActivatingState);
} else {
logd("Failed to set cdma config");
mLocalLog.log("CdmaConfiguringState Failed to set cdma config:" + request);
request.getCallback().accept(
TelephonyManager.CELL_BROADCAST_RESULT_FAIL_CONFIG);
// transit to idle state on the failure case
transitionTo(mIdleState);
}
retVal = HANDLED;
break;
default:
break;
}
return retVal;
}
}
private CdmaConfiguringState mCdmaConfiguringState = new CdmaConfiguringState();
/*
* The state waiting for the result to set cdma activation.
*/
private class CdmaActivatingState extends State {
@Override
public boolean processMessage(Message msg) {
boolean retVal = NOT_HANDLED;
if (DBG) {
logd("CdmaActivatingState message:" + msg.what);
}
switch (msg.what) {
case EVENT_REQUEST:
deferMessage(msg);
retVal = HANDLED;
break;
case EVENT_ACTIVATION_DONE:
AsyncResult ar = (AsyncResult) msg.obj;
Request request = (Request) ar.userObj;
if (DBG) {
logd("CdmaActivatingState handle EVENT_ACTIVATION_DONE with request:"
+ request);
mLocalLog.log("CdmaActivatingState EVENT_ACTIVATION_DONE, exception:"
+ ar.exception + ", request:" + request);
}
if (ar.exception == null) {
mCbRanges3gpp2 = request.get3gpp2Ranges();
request.getCallback().accept(
TelephonyManager.CELL_BROADCAST_RESULT_SUCCESS);
} else {
logd("Failed to set cdma activation");
request.getCallback().accept(
TelephonyManager.CELL_BROADCAST_RESULT_FAIL_ACTIVATION);
}
// transit to idle state anyway
transitionTo(mIdleState);
retVal = HANDLED;
break;
default:
break;
}
return retVal;
}
}
private CdmaActivatingState mCdmaActivatingState = new CdmaActivatingState();
private CellBroadcastConfigTracker(Phone phone) {
super("CellBroadcastConfigTracker-" + phone.getPhoneId());
init(phone);
}
private CellBroadcastConfigTracker(Phone phone, Handler handler) {
super("CellBroadcastConfigTracker-" + phone.getPhoneId(), handler);
init(phone);
}
private void init(Phone phone) {
logd("init");
mPhone = phone;
mSubId = mPhone.getSubId();
addState(mDefaultState);
addState(mIdleState, mDefaultState);
addState(mGsmConfiguringState, mDefaultState);
addState(mGsmActivatingState, mDefaultState);
addState(mCdmaConfiguringState, mDefaultState);
addState(mCdmaActivatingState, mDefaultState);
setInitialState(mIdleState);
}
/**
* create a CellBroadcastConfigTracker instance for the phone
*/
public static CellBroadcastConfigTracker make(Phone phone, Handler handler,
boolean shouldStart) {
CellBroadcastConfigTracker tracker = handler == null
? new CellBroadcastConfigTracker(phone)
: new CellBroadcastConfigTracker(phone, handler);
if (shouldStart) {
tracker.start();
}
return tracker;
}
/**
* Return current cell broadcast ranges.
*/
@NonNull public List<CellBroadcastIdRange> getCellBroadcastIdRanges() {
List<CellBroadcastIdRange> ranges = new ArrayList<>();
ranges.addAll(mCbRanges3gpp);
ranges.addAll(mCbRanges3gpp2);
return ranges;
}
/**
* Set reception of cell broadcast messages with the list of the given ranges.
*/
public void setCellBroadcastIdRanges(
@NonNull List<CellBroadcastIdRange> ranges, @NonNull Consumer<Integer> callback) {
if (DBG) {
logd("setCellBroadcastIdRanges with ranges:" + ranges);
}
ranges = mergeRangesAsNeeded(ranges);
sendMessage(EVENT_REQUEST, new Request(ranges, callback));
}
/**
* Merge the overlapped CellBroadcastIdRanges in the list as needed
* @param ranges the list of CellBroadcastIdRanges
* @return the list of CellBroadcastIdRanges without overlapping
*
* @throws IllegalArgumentException if there is conflict of the ranges. For instance,
* the channel is enabled in some range, but disable in others.
*/
@VisibleForTesting
public static @NonNull List<CellBroadcastIdRange> mergeRangesAsNeeded(
@NonNull List<CellBroadcastIdRange> ranges) throws IllegalArgumentException {
ranges.sort((r1, r2) -> r1.getType() != r2.getType() ? r1.getType() - r2.getType()
: (r1.getStartId() != r2.getStartId() ? r1.getStartId() - r2.getStartId()
: r2.getEndId() - r1.getEndId()));
final List<CellBroadcastIdRange> newRanges = new ArrayList<>();
ranges.forEach(r -> {
if (newRanges.isEmpty() || newRanges.get(newRanges.size() - 1).getType() != r.getType()
|| newRanges.get(newRanges.size() - 1).getEndId() + 1 < r.getStartId()
|| (newRanges.get(newRanges.size() - 1).getEndId() + 1 == r.getStartId()
&& newRanges.get(newRanges.size() - 1).isEnabled() != r.isEnabled())) {
newRanges.add(new CellBroadcastIdRange(r.getStartId(), r.getEndId(),
r.getType(), r.isEnabled()));
} else {
if (newRanges.get(newRanges.size() - 1).isEnabled() != r.isEnabled()) {
throw new IllegalArgumentException("range conflict " + r);
}
if (r.getEndId() > newRanges.get(newRanges.size() - 1).getEndId()) {
CellBroadcastIdRange range = newRanges.get(newRanges.size() - 1);
newRanges.set(newRanges.size() - 1, new CellBroadcastIdRange(
range.getStartId(), r.getEndId(), range.getType(), range.isEnabled()));
}
}
});
return newRanges;
}
private void resetConfig() {
mCbRanges3gpp.clear();
mCbRanges3gpp2.clear();
}
private void setGsmConfig(List<CellBroadcastIdRange> ranges, Request request) {
if (DBG) {
logd("setGsmConfig with " + ranges);
}
SmsBroadcastConfigInfo[] configs = new SmsBroadcastConfigInfo[ranges.size()];
for (int i = 0; i < configs.length; i++) {
CellBroadcastIdRange r = ranges.get(i);
configs[i] = new SmsBroadcastConfigInfo(r.getStartId(), r.getEndId(),
SMS_CB_CODE_SCHEME_MIN, SMS_CB_CODE_SCHEME_MAX, r.isEnabled());
}
Message response = obtainMessage(EVENT_CONFIGURATION_DONE, request);
mPhone.mCi.setGsmBroadcastConfig(configs, response);
}
private void setCdmaConfig(List<CellBroadcastIdRange> ranges, Request request) {
if (DBG) {
logd("setCdmaConfig with " + ranges);
}
CdmaSmsBroadcastConfigInfo[] configs =
new CdmaSmsBroadcastConfigInfo[ranges.size()];
for (int i = 0; i < configs.length; i++) {
CellBroadcastIdRange r = ranges.get(i);
configs[i] = new CdmaSmsBroadcastConfigInfo(
r.getStartId(), r.getEndId(), 1, r.isEnabled());
}
Message response = obtainMessage(EVENT_CONFIGURATION_DONE, request);
mPhone.mCi.setCdmaBroadcastConfig(configs, response);
}
private void setActivation(int type, boolean activate, Request request) {
if (DBG) {
logd("setActivation(" + type + "." + activate + ')');
}
Message response = obtainMessage(EVENT_ACTIVATION_DONE, request);
if (type == SmsCbMessage.MESSAGE_FORMAT_3GPP) {
mPhone.mCi.setGsmBroadcastActivation(activate, response);
} else if (type == SmsCbMessage.MESSAGE_FORMAT_3GPP2) {
mPhone.mCi.setCdmaBroadcastActivation(activate, response);
}
}
/**
* Dump the state of CellBroadcastConfigTracker
*
* @param fd File descriptor
* @param printWriter Print writer
* @param args Arguments
*/
public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " ");
pw.println(CellBroadcastConfigTracker.class.getSimpleName()
+ "-" + mPhone.getPhoneId() + ":");
pw.increaseIndent();
pw.println("Current mCbRanges3gpp:" + mCbRanges3gpp);
pw.println("Current mCbRanges3gpp2:" + mCbRanges3gpp2);
pw.decreaseIndent();
pw.println("Local logs:");
pw.increaseIndent();
mLocalLog.dump(fd, pw, args);
pw.decreaseIndent();
}
}