1 /* 2 * Copyright (C) 2016 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.internal.telephony; 18 19 import android.app.Notification; 20 import android.app.NotificationManager; 21 import android.app.PendingIntent; 22 import android.content.BroadcastReceiver; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.IntentFilter; 26 import android.os.Handler; 27 import android.os.Message; 28 import android.os.PersistableBundle; 29 import android.provider.Settings; 30 import android.telephony.CarrierConfigManager; 31 import android.telephony.Rlog; 32 import android.telephony.ServiceState; 33 34 import com.android.internal.annotations.VisibleForTesting; 35 import com.android.internal.telephony.util.NotificationChannelController; 36 37 import java.util.HashMap; 38 import java.util.Map; 39 40 41 /** 42 * This contains Carrier specific logic based on the states/events 43 * managed in ServiceStateTracker. 44 * {@hide} 45 */ 46 public class CarrierServiceStateTracker extends Handler { 47 private static final String LOG_TAG = "CSST"; 48 protected static final int CARRIER_EVENT_BASE = 100; 49 protected static final int CARRIER_EVENT_VOICE_REGISTRATION = CARRIER_EVENT_BASE + 1; 50 protected static final int CARRIER_EVENT_VOICE_DEREGISTRATION = CARRIER_EVENT_BASE + 2; 51 protected static final int CARRIER_EVENT_DATA_REGISTRATION = CARRIER_EVENT_BASE + 3; 52 protected static final int CARRIER_EVENT_DATA_DEREGISTRATION = CARRIER_EVENT_BASE + 4; 53 private static final int UNINITIALIZED_DELAY_VALUE = -1; 54 private Phone mPhone; 55 private ServiceStateTracker mSST; 56 57 public static final int NOTIFICATION_PREF_NETWORK = 1000; 58 public static final int NOTIFICATION_EMERGENCY_NETWORK = 1001; 59 60 private final Map<Integer, NotificationType> mNotificationTypeMap = new HashMap<>(); 61 CarrierServiceStateTracker(Phone phone, ServiceStateTracker sst)62 public CarrierServiceStateTracker(Phone phone, ServiceStateTracker sst) { 63 this.mPhone = phone; 64 this.mSST = sst; 65 phone.getContext().registerReceiver(mBroadcastReceiver, new IntentFilter( 66 CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)); 67 registerNotificationTypes(); 68 } 69 registerNotificationTypes()70 private void registerNotificationTypes() { 71 mNotificationTypeMap.put(NOTIFICATION_PREF_NETWORK, 72 new PrefNetworkNotification(NOTIFICATION_PREF_NETWORK)); 73 mNotificationTypeMap.put(NOTIFICATION_EMERGENCY_NETWORK, 74 new EmergencyNetworkNotification(NOTIFICATION_EMERGENCY_NETWORK)); 75 } 76 77 @Override handleMessage(Message msg)78 public void handleMessage(Message msg) { 79 switch (msg.what) { 80 case CARRIER_EVENT_VOICE_REGISTRATION: 81 case CARRIER_EVENT_DATA_REGISTRATION: 82 case CARRIER_EVENT_VOICE_DEREGISTRATION: 83 case CARRIER_EVENT_DATA_DEREGISTRATION: 84 handleConfigChanges(); 85 break; 86 case NOTIFICATION_EMERGENCY_NETWORK: 87 case NOTIFICATION_PREF_NETWORK: 88 Rlog.d(LOG_TAG, "sending notification after delay: " + msg.what); 89 NotificationType notificationType = mNotificationTypeMap.get(msg.what); 90 if (notificationType != null) { 91 sendNotification(notificationType); 92 } 93 break; 94 } 95 } 96 isPhoneStillRegistered()97 private boolean isPhoneStillRegistered() { 98 if (mSST.mSS == null) { 99 return true; //something has gone wrong, return true and not show the notification. 100 } 101 return (mSST.mSS.getVoiceRegState() == ServiceState.STATE_IN_SERVICE 102 || mSST.mSS.getDataRegState() == ServiceState.STATE_IN_SERVICE); 103 } 104 isPhoneVoiceRegistered()105 private boolean isPhoneVoiceRegistered() { 106 if (mSST.mSS == null) { 107 return true; //something has gone wrong, return true and not show the notification. 108 } 109 return (mSST.mSS.getVoiceRegState() == ServiceState.STATE_IN_SERVICE); 110 } 111 isPhoneRegisteredForWifiCalling()112 private boolean isPhoneRegisteredForWifiCalling() { 113 Rlog.d(LOG_TAG, "isPhoneRegisteredForWifiCalling: " + mPhone.isWifiCallingEnabled()); 114 return mPhone.isWifiCallingEnabled(); 115 } 116 117 /** 118 * Returns true if the radio is off or in Airplane Mode else returns false. 119 */ 120 @VisibleForTesting isRadioOffOrAirplaneMode()121 public boolean isRadioOffOrAirplaneMode() { 122 Context context = mPhone.getContext(); 123 int airplaneMode = -1; 124 try { 125 airplaneMode = Settings.Global.getInt(context.getContentResolver(), 126 Settings.Global.AIRPLANE_MODE_ON, 0); 127 } catch (Exception e) { 128 Rlog.e(LOG_TAG, "Unable to get AIRPLACE_MODE_ON."); 129 return true; 130 } 131 return (!mSST.isRadioOn() || (airplaneMode != 0)); 132 } 133 134 /** 135 * Returns true if the preferred network is set to 'Global'. 136 */ isGlobalMode()137 private boolean isGlobalMode() { 138 Context context = mPhone.getContext(); 139 int preferredNetworkSetting = -1; 140 try { 141 preferredNetworkSetting = 142 android.provider.Settings.Global.getInt(context.getContentResolver(), 143 android.provider.Settings.Global.PREFERRED_NETWORK_MODE 144 + mPhone.getSubId(), Phone.PREFERRED_NT_MODE); 145 } catch (Exception e) { 146 Rlog.e(LOG_TAG, "Unable to get PREFERRED_NETWORK_MODE."); 147 return true; 148 } 149 return (preferredNetworkSetting == RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA); 150 } 151 handleConfigChanges()152 private void handleConfigChanges() { 153 for (Map.Entry<Integer, NotificationType> entry : mNotificationTypeMap.entrySet()) { 154 NotificationType notificationType = entry.getValue(); 155 if (evaluateSendingMessage(notificationType)) { 156 Message notificationMsg = obtainMessage(notificationType.getTypeId(), null); 157 Rlog.i(LOG_TAG, "starting timer for notifications." + notificationType.getTypeId()); 158 sendMessageDelayed(notificationMsg, getDelay(notificationType)); 159 } else { 160 cancelNotification(notificationType.getTypeId()); 161 Rlog.i(LOG_TAG, "canceling notifications: " + notificationType.getTypeId()); 162 } 163 } 164 } 165 166 /** 167 * This method adds a level of indirection, and was created so we can unit the class. 168 **/ 169 @VisibleForTesting evaluateSendingMessage(NotificationType notificationType)170 public boolean evaluateSendingMessage(NotificationType notificationType) { 171 return notificationType.sendMessage(); 172 } 173 174 /** 175 * This method adds a level of indirection, and was created so we can unit the class. 176 **/ 177 @VisibleForTesting getDelay(NotificationType notificationType)178 public int getDelay(NotificationType notificationType) { 179 return notificationType.getDelay(); 180 } 181 182 /** 183 * This method adds a level of indirection, and was created so we can unit the class. 184 **/ 185 @VisibleForTesting getNotificationBuilder(NotificationType notificationType)186 public Notification.Builder getNotificationBuilder(NotificationType notificationType) { 187 return notificationType.getNotificationBuilder(); 188 } 189 190 /** 191 * This method adds a level of indirection, and was created so we can unit the class. 192 **/ 193 @VisibleForTesting getNotificationManager(Context context)194 public NotificationManager getNotificationManager(Context context) { 195 return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); 196 } 197 198 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 199 @Override 200 public void onReceive(Context context, Intent intent) { 201 CarrierConfigManager carrierConfigManager = (CarrierConfigManager) 202 context.getSystemService(Context.CARRIER_CONFIG_SERVICE); 203 PersistableBundle b = carrierConfigManager.getConfigForSubId(mPhone.getSubId()); 204 205 for (Map.Entry<Integer, NotificationType> entry : mNotificationTypeMap.entrySet()) { 206 NotificationType notificationType = entry.getValue(); 207 notificationType.setDelay(b); 208 } 209 handleConfigChanges(); 210 } 211 }; 212 213 /** 214 * Post a notification to the NotificationManager for changing network type. 215 */ 216 @VisibleForTesting sendNotification(NotificationType notificationType)217 public void sendNotification(NotificationType notificationType) { 218 if (!evaluateSendingMessage(notificationType)) { 219 return; 220 } 221 222 Context context = mPhone.getContext(); 223 Notification.Builder builder = getNotificationBuilder(notificationType); 224 // set some common attributes 225 builder.setWhen(System.currentTimeMillis()) 226 .setAutoCancel(true) 227 .setSmallIcon(com.android.internal.R.drawable.stat_sys_warning) 228 .setColor(context.getResources().getColor( 229 com.android.internal.R.color.system_notification_accent_color)); 230 231 getNotificationManager(context).notify(notificationType.getTypeId(), builder.build()); 232 } 233 234 /** 235 * Cancel notifications if a registration is pending or has been sent. 236 **/ cancelNotification(int notificationId)237 public void cancelNotification(int notificationId) { 238 Context context = mPhone.getContext(); 239 removeMessages(notificationId); 240 getNotificationManager(context).cancel(notificationId); 241 } 242 243 /** 244 * Class that defines the different types of notifications. 245 */ 246 public interface NotificationType { 247 248 /** 249 * decides if the message should be sent, Returns boolean 250 **/ sendMessage()251 boolean sendMessage(); 252 253 /** 254 * returns the interval by which the message is delayed. 255 **/ getDelay()256 int getDelay(); 257 258 /** sets the interval by which the message is delayed. 259 * @param bundle PersistableBundle 260 **/ setDelay(PersistableBundle bundle)261 void setDelay(PersistableBundle bundle); 262 263 /** 264 * returns notification type id. 265 **/ getTypeId()266 int getTypeId(); 267 268 /** 269 * returns the notification builder, for the notification to be displayed. 270 **/ getNotificationBuilder()271 Notification.Builder getNotificationBuilder(); 272 } 273 274 /** 275 * Class that defines the network notification, which is shown when the phone cannot camp on 276 * a network, and has 'preferred mode' set to global. 277 */ 278 public class PrefNetworkNotification implements NotificationType { 279 280 private final int mTypeId; 281 private int mDelay = UNINITIALIZED_DELAY_VALUE; 282 PrefNetworkNotification(int typeId)283 PrefNetworkNotification(int typeId) { 284 this.mTypeId = typeId; 285 } 286 287 /** sets the interval by which the message is delayed. 288 * @param bundle PersistableBundle 289 **/ setDelay(PersistableBundle bundle)290 public void setDelay(PersistableBundle bundle) { 291 if (bundle == null) { 292 Rlog.e(LOG_TAG, "bundle is null"); 293 return; 294 } 295 this.mDelay = bundle.getInt( 296 CarrierConfigManager.KEY_PREF_NETWORK_NOTIFICATION_DELAY_INT); 297 Rlog.i(LOG_TAG, "reading time to delay notification emergency: " + mDelay); 298 } 299 getDelay()300 public int getDelay() { 301 return mDelay; 302 } 303 getTypeId()304 public int getTypeId() { 305 return mTypeId; 306 } 307 308 /** 309 * Contains logic on sending notifications. 310 */ sendMessage()311 public boolean sendMessage() { 312 Rlog.i(LOG_TAG, "PrefNetworkNotification: sendMessage() w/values: " 313 + "," + isPhoneStillRegistered() + "," + mDelay + "," + isGlobalMode() 314 + "," + mSST.isRadioOn()); 315 if (mDelay == UNINITIALIZED_DELAY_VALUE || isPhoneStillRegistered() || isGlobalMode() 316 || isRadioOffOrAirplaneMode()) { 317 return false; 318 } 319 return true; 320 } 321 322 /** 323 * Builds a partial notificaiton builder, and returns it. 324 */ getNotificationBuilder()325 public Notification.Builder getNotificationBuilder() { 326 Context context = mPhone.getContext(); 327 Intent notificationIntent = new Intent(Settings.ACTION_DATA_ROAMING_SETTINGS); 328 PendingIntent settingsIntent = PendingIntent.getActivity(context, 0, notificationIntent, 329 PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE); 330 CharSequence title = context.getText( 331 com.android.internal.R.string.NetworkPreferenceSwitchTitle); 332 CharSequence details = context.getText( 333 com.android.internal.R.string.NetworkPreferenceSwitchSummary); 334 return new Notification.Builder(context) 335 .setContentTitle(title) 336 .setStyle(new Notification.BigTextStyle().bigText(details)) 337 .setContentText(details) 338 .setChannel(NotificationChannelController.CHANNEL_ID_ALERT) 339 .setContentIntent(settingsIntent); 340 } 341 } 342 343 /** 344 * Class that defines the emergency notification, which is shown when the user is out of cell 345 * connectivity, but has wifi enabled. 346 */ 347 public class EmergencyNetworkNotification implements NotificationType { 348 349 private final int mTypeId; 350 private int mDelay = UNINITIALIZED_DELAY_VALUE; 351 EmergencyNetworkNotification(int typeId)352 EmergencyNetworkNotification(int typeId) { 353 this.mTypeId = typeId; 354 } 355 356 /** sets the interval by which the message is delayed. 357 * @param bundle PersistableBundle 358 **/ setDelay(PersistableBundle bundle)359 public void setDelay(PersistableBundle bundle) { 360 if (bundle == null) { 361 Rlog.e(LOG_TAG, "bundle is null"); 362 return; 363 } 364 this.mDelay = bundle.getInt( 365 CarrierConfigManager.KEY_EMERGENCY_NOTIFICATION_DELAY_INT); 366 Rlog.i(LOG_TAG, "reading time to delay notification emergency: " + mDelay); 367 } 368 getDelay()369 public int getDelay() { 370 return mDelay; 371 } 372 getTypeId()373 public int getTypeId() { 374 return mTypeId; 375 } 376 377 /** 378 * Contains logic on sending notifications, 379 */ sendMessage()380 public boolean sendMessage() { 381 Rlog.i(LOG_TAG, "EmergencyNetworkNotification: sendMessage() w/values: " 382 + "," + isPhoneVoiceRegistered() + "," + mDelay + "," 383 + isPhoneRegisteredForWifiCalling() + "," + mSST.isRadioOn()); 384 if (mDelay == UNINITIALIZED_DELAY_VALUE || isPhoneVoiceRegistered() 385 || !isPhoneRegisteredForWifiCalling()) { 386 return false; 387 } 388 return true; 389 } 390 391 /** 392 * Builds a partial notificaiton builder, and returns it. 393 */ getNotificationBuilder()394 public Notification.Builder getNotificationBuilder() { 395 Context context = mPhone.getContext(); 396 CharSequence title = context.getText( 397 com.android.internal.R.string.EmergencyCallWarningTitle); 398 CharSequence details = context.getText( 399 com.android.internal.R.string.EmergencyCallWarningSummary); 400 return new Notification.Builder(context) 401 .setContentTitle(title) 402 .setStyle(new Notification.BigTextStyle().bigText(details)) 403 .setContentText(details) 404 .setChannel(NotificationChannelController.CHANNEL_ID_WFC); 405 } 406 } 407 } 408