blob: 7f49f288f0b6b5e625715d3b6a1d5e3bac6782f7 [file] [log] [blame]
/*
* Copyright (C) 2016 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.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.ContentObserver;
import android.os.Handler;
import android.os.Message;
import android.os.PersistableBundle;
import android.provider.Settings;
import android.telephony.CarrierConfigManager;
import android.telephony.Rlog;
import android.telephony.ServiceState;
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.util.NotificationChannelController;
import java.util.HashMap;
import java.util.Map;
/**
* This contains Carrier specific logic based on the states/events
* managed in ServiceStateTracker.
* {@hide}
*/
public class CarrierServiceStateTracker extends Handler {
private static final String LOG_TAG = "CSST";
protected static final int CARRIER_EVENT_BASE = 100;
protected static final int CARRIER_EVENT_VOICE_REGISTRATION = CARRIER_EVENT_BASE + 1;
protected static final int CARRIER_EVENT_VOICE_DEREGISTRATION = CARRIER_EVENT_BASE + 2;
protected static final int CARRIER_EVENT_DATA_REGISTRATION = CARRIER_EVENT_BASE + 3;
protected static final int CARRIER_EVENT_DATA_DEREGISTRATION = CARRIER_EVENT_BASE + 4;
private static final int UNINITIALIZED_DELAY_VALUE = -1;
private Phone mPhone;
private ServiceStateTracker mSST;
private final Map<Integer, NotificationType> mNotificationTypeMap = new HashMap<>();
private int mPreviousSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
public static final int NOTIFICATION_PREF_NETWORK = 1000;
public static final int NOTIFICATION_EMERGENCY_NETWORK = 1001;
public CarrierServiceStateTracker(Phone phone, ServiceStateTracker sst) {
this.mPhone = phone;
this.mSST = sst;
phone.getContext().registerReceiver(mBroadcastReceiver, new IntentFilter(
CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
// Listen for subscriber changes
SubscriptionManager.from(mPhone.getContext()).addOnSubscriptionsChangedListener(
new OnSubscriptionsChangedListener(this.getLooper()) {
@Override
public void onSubscriptionsChanged() {
int subId = mPhone.getSubId();
if (mPreviousSubId != subId) {
mPreviousSubId = subId;
registerPrefNetworkModeObserver();
}
}
});
registerNotificationTypes();
registerPrefNetworkModeObserver();
}
private ContentObserver mPrefNetworkModeObserver = new ContentObserver(this) {
@Override
public void onChange(boolean selfChange) {
handlePrefNetworkModeChanged();
}
};
/**
* Return preferred network mode observer
*/
@VisibleForTesting
public ContentObserver getContentObserver() {
return mPrefNetworkModeObserver;
}
private void registerPrefNetworkModeObserver() {
int subId = mPhone.getSubId();
unregisterPrefNetworkModeObserver();
if (SubscriptionManager.isValidSubscriptionId(subId)) {
mPhone.getContext().getContentResolver().registerContentObserver(
Settings.Global.getUriFor(Settings.Global.PREFERRED_NETWORK_MODE + subId),
true,
mPrefNetworkModeObserver);
}
}
private void unregisterPrefNetworkModeObserver() {
mPhone.getContext().getContentResolver().unregisterContentObserver(
mPrefNetworkModeObserver);
}
/**
* Returns mNotificationTypeMap
*/
@VisibleForTesting
public Map<Integer, NotificationType> getNotificationTypeMap() {
return mNotificationTypeMap;
}
private void registerNotificationTypes() {
mNotificationTypeMap.put(NOTIFICATION_PREF_NETWORK,
new PrefNetworkNotification(NOTIFICATION_PREF_NETWORK));
mNotificationTypeMap.put(NOTIFICATION_EMERGENCY_NETWORK,
new EmergencyNetworkNotification(NOTIFICATION_EMERGENCY_NETWORK));
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case CARRIER_EVENT_VOICE_REGISTRATION:
case CARRIER_EVENT_DATA_REGISTRATION:
case CARRIER_EVENT_VOICE_DEREGISTRATION:
case CARRIER_EVENT_DATA_DEREGISTRATION:
handleConfigChanges();
break;
case NOTIFICATION_EMERGENCY_NETWORK:
case NOTIFICATION_PREF_NETWORK:
Rlog.d(LOG_TAG, "sending notification after delay: " + msg.what);
NotificationType notificationType = mNotificationTypeMap.get(msg.what);
if (notificationType != null) {
sendNotification(notificationType);
}
break;
}
}
private boolean isPhoneStillRegistered() {
if (mSST.mSS == null) {
return true; //something has gone wrong, return true and not show the notification.
}
return (mSST.mSS.getVoiceRegState() == ServiceState.STATE_IN_SERVICE
|| mSST.mSS.getDataRegState() == ServiceState.STATE_IN_SERVICE);
}
private boolean isPhoneVoiceRegistered() {
if (mSST.mSS == null) {
return true; //something has gone wrong, return true and not show the notification.
}
return (mSST.mSS.getVoiceRegState() == ServiceState.STATE_IN_SERVICE);
}
private boolean isPhoneRegisteredForWifiCalling() {
Rlog.d(LOG_TAG, "isPhoneRegisteredForWifiCalling: " + mPhone.isWifiCallingEnabled());
return mPhone.isWifiCallingEnabled();
}
/**
* Returns true if the radio is off or in Airplane Mode else returns false.
*/
@VisibleForTesting
public boolean isRadioOffOrAirplaneMode() {
Context context = mPhone.getContext();
int airplaneMode = -1;
try {
airplaneMode = Settings.Global.getInt(context.getContentResolver(),
Settings.Global.AIRPLANE_MODE_ON, 0);
} catch (Exception e) {
Rlog.e(LOG_TAG, "Unable to get AIRPLACE_MODE_ON.");
return true;
}
return (!mSST.isRadioOn() || (airplaneMode != 0));
}
/**
* Returns true if the preferred network is set to 'Global'.
*/
private boolean isGlobalMode() {
Context context = mPhone.getContext();
int preferredNetworkSetting = -1;
try {
preferredNetworkSetting =
android.provider.Settings.Global.getInt(context.getContentResolver(),
android.provider.Settings.Global.PREFERRED_NETWORK_MODE
+ mPhone.getSubId(), Phone.PREFERRED_NT_MODE);
} catch (Exception e) {
Rlog.e(LOG_TAG, "Unable to get PREFERRED_NETWORK_MODE.");
return true;
}
return (preferredNetworkSetting == RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA);
}
private void handleConfigChanges() {
for (Map.Entry<Integer, NotificationType> entry : mNotificationTypeMap.entrySet()) {
NotificationType notificationType = entry.getValue();
evaluateSendingMessageOrCancelNotification(notificationType);
}
}
private void handlePrefNetworkModeChanged() {
NotificationType notificationType = mNotificationTypeMap.get(NOTIFICATION_PREF_NETWORK);
if (notificationType != null) {
evaluateSendingMessageOrCancelNotification(notificationType);
}
}
private void evaluateSendingMessageOrCancelNotification(NotificationType notificationType) {
if (evaluateSendingMessage(notificationType)) {
Message notificationMsg = obtainMessage(notificationType.getTypeId(), null);
Rlog.i(LOG_TAG, "starting timer for notifications." + notificationType.getTypeId());
sendMessageDelayed(notificationMsg, getDelay(notificationType));
} else {
cancelNotification(notificationType.getTypeId());
Rlog.i(LOG_TAG, "canceling notifications: " + notificationType.getTypeId());
}
}
/**
* This method adds a level of indirection, and was created so we can unit the class.
**/
@VisibleForTesting
public boolean evaluateSendingMessage(NotificationType notificationType) {
return notificationType.sendMessage();
}
/**
* This method adds a level of indirection, and was created so we can unit the class.
**/
@VisibleForTesting
public int getDelay(NotificationType notificationType) {
return notificationType.getDelay();
}
/**
* This method adds a level of indirection, and was created so we can unit the class.
**/
@VisibleForTesting
public Notification.Builder getNotificationBuilder(NotificationType notificationType) {
return notificationType.getNotificationBuilder();
}
/**
* This method adds a level of indirection, and was created so we can unit the class.
**/
@VisibleForTesting
public NotificationManager getNotificationManager(Context context) {
return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
}
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
CarrierConfigManager carrierConfigManager = (CarrierConfigManager)
context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
PersistableBundle b = carrierConfigManager.getConfigForSubId(mPhone.getSubId());
for (Map.Entry<Integer, NotificationType> entry : mNotificationTypeMap.entrySet()) {
NotificationType notificationType = entry.getValue();
notificationType.setDelay(b);
}
handleConfigChanges();
}
};
/**
* Post a notification to the NotificationManager for changing network type.
*/
@VisibleForTesting
public void sendNotification(NotificationType notificationType) {
if (!evaluateSendingMessage(notificationType)) {
return;
}
Context context = mPhone.getContext();
Notification.Builder builder = getNotificationBuilder(notificationType);
// set some common attributes
builder.setWhen(System.currentTimeMillis())
.setAutoCancel(true)
.setSmallIcon(com.android.internal.R.drawable.stat_sys_warning)
.setColor(context.getResources().getColor(
com.android.internal.R.color.system_notification_accent_color));
getNotificationManager(context).notify(notificationType.getTypeId(), builder.build());
}
/**
* Cancel notifications if a registration is pending or has been sent.
**/
public void cancelNotification(int notificationId) {
Context context = mPhone.getContext();
removeMessages(notificationId);
getNotificationManager(context).cancel(notificationId);
}
/**
* Dispose the CarrierServiceStateTracker.
*/
public void dispose() {
unregisterPrefNetworkModeObserver();
}
/**
* Class that defines the different types of notifications.
*/
public interface NotificationType {
/**
* decides if the message should be sent, Returns boolean
**/
boolean sendMessage();
/**
* returns the interval by which the message is delayed.
**/
int getDelay();
/** sets the interval by which the message is delayed.
* @param bundle PersistableBundle
**/
void setDelay(PersistableBundle bundle);
/**
* returns notification type id.
**/
int getTypeId();
/**
* returns the notification builder, for the notification to be displayed.
**/
Notification.Builder getNotificationBuilder();
}
/**
* Class that defines the network notification, which is shown when the phone cannot camp on
* a network, and has 'preferred mode' set to global.
*/
public class PrefNetworkNotification implements NotificationType {
private final int mTypeId;
private int mDelay = UNINITIALIZED_DELAY_VALUE;
PrefNetworkNotification(int typeId) {
this.mTypeId = typeId;
}
/** sets the interval by which the message is delayed.
* @param bundle PersistableBundle
**/
public void setDelay(PersistableBundle bundle) {
if (bundle == null) {
Rlog.e(LOG_TAG, "bundle is null");
return;
}
this.mDelay = bundle.getInt(
CarrierConfigManager.KEY_PREF_NETWORK_NOTIFICATION_DELAY_INT);
Rlog.i(LOG_TAG, "reading time to delay notification pref network: " + mDelay);
}
public int getDelay() {
return mDelay;
}
public int getTypeId() {
return mTypeId;
}
/**
* Contains logic on sending notifications.
*/
public boolean sendMessage() {
Rlog.i(LOG_TAG, "PrefNetworkNotification: sendMessage() w/values: "
+ "," + isPhoneStillRegistered() + "," + mDelay + "," + isGlobalMode()
+ "," + mSST.isRadioOn());
if (mDelay == UNINITIALIZED_DELAY_VALUE || isPhoneStillRegistered() || isGlobalMode()
|| isRadioOffOrAirplaneMode()) {
return false;
}
return true;
}
/**
* Builds a partial notificaiton builder, and returns it.
*/
public Notification.Builder getNotificationBuilder() {
Context context = mPhone.getContext();
Intent notificationIntent = new Intent(Settings.ACTION_DATA_ROAMING_SETTINGS);
notificationIntent.putExtra("expandable", true);
PendingIntent settingsIntent = PendingIntent.getActivity(context, 0, notificationIntent,
PendingIntent.FLAG_ONE_SHOT);
CharSequence title = context.getText(
com.android.internal.R.string.NetworkPreferenceSwitchTitle);
CharSequence details = context.getText(
com.android.internal.R.string.NetworkPreferenceSwitchSummary);
return new Notification.Builder(context)
.setContentTitle(title)
.setStyle(new Notification.BigTextStyle().bigText(details))
.setContentText(details)
.setChannel(NotificationChannelController.CHANNEL_ID_ALERT)
.setContentIntent(settingsIntent);
}
}
/**
* Class that defines the emergency notification, which is shown when the user is out of cell
* connectivity, but has wifi enabled.
*/
public class EmergencyNetworkNotification implements NotificationType {
private final int mTypeId;
private int mDelay = UNINITIALIZED_DELAY_VALUE;
EmergencyNetworkNotification(int typeId) {
this.mTypeId = typeId;
}
/** sets the interval by which the message is delayed.
* @param bundle PersistableBundle
**/
public void setDelay(PersistableBundle bundle) {
if (bundle == null) {
Rlog.e(LOG_TAG, "bundle is null");
return;
}
this.mDelay = bundle.getInt(
CarrierConfigManager.KEY_EMERGENCY_NOTIFICATION_DELAY_INT);
Rlog.i(LOG_TAG, "reading time to delay notification emergency: " + mDelay);
}
public int getDelay() {
return mDelay;
}
public int getTypeId() {
return mTypeId;
}
/**
* Contains logic on sending notifications,
*/
public boolean sendMessage() {
Rlog.i(LOG_TAG, "EmergencyNetworkNotification: sendMessage() w/values: "
+ "," + isPhoneVoiceRegistered() + "," + mDelay + ","
+ isPhoneRegisteredForWifiCalling() + "," + mSST.isRadioOn());
if (mDelay == UNINITIALIZED_DELAY_VALUE || isPhoneVoiceRegistered()
|| !isPhoneRegisteredForWifiCalling()) {
return false;
}
return true;
}
/**
* Builds a partial notificaiton builder, and returns it.
*/
public Notification.Builder getNotificationBuilder() {
Context context = mPhone.getContext();
CharSequence title = context.getText(
com.android.internal.R.string.EmergencyCallWarningTitle);
CharSequence details = context.getText(
com.android.internal.R.string.EmergencyCallWarningSummary);
return new Notification.Builder(context)
.setContentTitle(title)
.setStyle(new Notification.BigTextStyle().bigText(details))
.setContentText(details)
.setChannel(NotificationChannelController.CHANNEL_ID_WFC);
}
}
}