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.content.res.Resources; 27 import android.database.ContentObserver; 28 import android.os.Handler; 29 import android.os.Message; 30 import android.os.PersistableBundle; 31 import android.provider.Settings; 32 import android.telephony.CarrierConfigManager; 33 import android.telephony.ServiceState; 34 import android.telephony.SubscriptionManager; 35 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener; 36 37 import com.android.internal.annotations.VisibleForTesting; 38 import com.android.internal.telephony.util.NotificationChannelController; 39 import com.android.telephony.Rlog; 40 41 import java.util.HashMap; 42 import java.util.Map; 43 44 /** 45 * This contains Carrier specific logic based on the states/events 46 * managed in ServiceStateTracker. 47 * {@hide} 48 */ 49 public class CarrierServiceStateTracker extends Handler { 50 private static final String LOG_TAG = "CSST"; 51 protected static final int CARRIER_EVENT_BASE = 100; 52 protected static final int CARRIER_EVENT_VOICE_REGISTRATION = CARRIER_EVENT_BASE + 1; 53 protected static final int CARRIER_EVENT_VOICE_DEREGISTRATION = CARRIER_EVENT_BASE + 2; 54 protected static final int CARRIER_EVENT_DATA_REGISTRATION = CARRIER_EVENT_BASE + 3; 55 protected static final int CARRIER_EVENT_DATA_DEREGISTRATION = CARRIER_EVENT_BASE + 4; 56 protected static final int CARRIER_EVENT_IMS_CAPABILITIES_CHANGED = CARRIER_EVENT_BASE + 5; 57 58 private static final int UNINITIALIZED_DELAY_VALUE = -1; 59 private Phone mPhone; 60 private ServiceStateTracker mSST; 61 private final Map<Integer, NotificationType> mNotificationTypeMap = new HashMap<>(); 62 private int mPreviousSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 63 public static final int NOTIFICATION_PREF_NETWORK = 1000; 64 public static final int NOTIFICATION_EMERGENCY_NETWORK = 1001; 65 66 @VisibleForTesting 67 public static final String EMERGENCY_NOTIFICATION_TAG = "EmergencyNetworkNotification"; 68 69 @VisibleForTesting 70 public static final String PREF_NETWORK_NOTIFICATION_TAG = "PrefNetworkNotification"; 71 CarrierServiceStateTracker(Phone phone, ServiceStateTracker sst)72 public CarrierServiceStateTracker(Phone phone, ServiceStateTracker sst) { 73 this.mPhone = phone; 74 this.mSST = sst; 75 phone.getContext().registerReceiver(mBroadcastReceiver, new IntentFilter( 76 CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)); 77 // Listen for subscriber changes 78 SubscriptionManager.from(mPhone.getContext()).addOnSubscriptionsChangedListener( 79 new OnSubscriptionsChangedListener(this.getLooper()) { 80 @Override 81 public void onSubscriptionsChanged() { 82 int subId = mPhone.getSubId(); 83 if (mPreviousSubId != subId) { 84 mPreviousSubId = subId; 85 registerPrefNetworkModeObserver(); 86 } 87 } 88 }); 89 90 registerNotificationTypes(); 91 registerPrefNetworkModeObserver(); 92 } 93 94 private ContentObserver mPrefNetworkModeObserver = new ContentObserver(this) { 95 @Override 96 public void onChange(boolean selfChange) { 97 handlePrefNetworkModeChanged(); 98 } 99 }; 100 101 /** 102 * Return preferred network mode observer 103 */ 104 @VisibleForTesting getContentObserver()105 public ContentObserver getContentObserver() { 106 return mPrefNetworkModeObserver; 107 } 108 registerPrefNetworkModeObserver()109 private void registerPrefNetworkModeObserver() { 110 int subId = mPhone.getSubId(); 111 unregisterPrefNetworkModeObserver(); 112 if (SubscriptionManager.isValidSubscriptionId(subId)) { 113 mPhone.getContext().getContentResolver().registerContentObserver( 114 Settings.Global.getUriFor(Settings.Global.PREFERRED_NETWORK_MODE + subId), 115 true, 116 mPrefNetworkModeObserver); 117 } 118 } 119 unregisterPrefNetworkModeObserver()120 private void unregisterPrefNetworkModeObserver() { 121 mPhone.getContext().getContentResolver().unregisterContentObserver( 122 mPrefNetworkModeObserver); 123 } 124 125 /** 126 * Returns mNotificationTypeMap 127 */ 128 @VisibleForTesting getNotificationTypeMap()129 public Map<Integer, NotificationType> getNotificationTypeMap() { 130 return mNotificationTypeMap; 131 } 132 registerNotificationTypes()133 private void registerNotificationTypes() { 134 mNotificationTypeMap.put(NOTIFICATION_PREF_NETWORK, 135 new PrefNetworkNotification(NOTIFICATION_PREF_NETWORK)); 136 mNotificationTypeMap.put(NOTIFICATION_EMERGENCY_NETWORK, 137 new EmergencyNetworkNotification(NOTIFICATION_EMERGENCY_NETWORK)); 138 } 139 140 @Override handleMessage(Message msg)141 public void handleMessage(Message msg) { 142 switch (msg.what) { 143 case CARRIER_EVENT_VOICE_REGISTRATION: 144 case CARRIER_EVENT_DATA_REGISTRATION: 145 case CARRIER_EVENT_VOICE_DEREGISTRATION: 146 case CARRIER_EVENT_DATA_DEREGISTRATION: 147 handleConfigChanges(); 148 break; 149 case CARRIER_EVENT_IMS_CAPABILITIES_CHANGED: 150 handleImsCapabilitiesChanged(); 151 break; 152 case NOTIFICATION_EMERGENCY_NETWORK: 153 case NOTIFICATION_PREF_NETWORK: 154 Rlog.d(LOG_TAG, "sending notification after delay: " + msg.what); 155 NotificationType notificationType = mNotificationTypeMap.get(msg.what); 156 if (notificationType != null) { 157 sendNotification(notificationType); 158 } 159 break; 160 } 161 } 162 isPhoneStillRegistered()163 private boolean isPhoneStillRegistered() { 164 if (mSST.mSS == null) { 165 return true; //something has gone wrong, return true and not show the notification. 166 } 167 return (mSST.mSS.getState() == ServiceState.STATE_IN_SERVICE 168 || mSST.mSS.getDataRegistrationState() == ServiceState.STATE_IN_SERVICE); 169 } 170 isPhoneRegisteredForWifiCalling()171 private boolean isPhoneRegisteredForWifiCalling() { 172 Rlog.d(LOG_TAG, "isPhoneRegisteredForWifiCalling: " + mPhone.isWifiCallingEnabled()); 173 return mPhone.isWifiCallingEnabled(); 174 } 175 176 /** 177 * Returns true if the radio is off or in Airplane Mode else returns false. 178 */ 179 @VisibleForTesting isRadioOffOrAirplaneMode()180 public boolean isRadioOffOrAirplaneMode() { 181 Context context = mPhone.getContext(); 182 int airplaneMode = -1; 183 try { 184 airplaneMode = Settings.Global.getInt(context.getContentResolver(), 185 Settings.Global.AIRPLANE_MODE_ON, 0); 186 } catch (Exception e) { 187 Rlog.e(LOG_TAG, "Unable to get AIRPLACE_MODE_ON."); 188 return true; 189 } 190 return (!mSST.isRadioOn() || (airplaneMode != 0)); 191 } 192 193 /** 194 * Returns true if the preferred network is set to 'Global'. 195 */ isGlobalMode()196 private boolean isGlobalMode() { 197 Context context = mPhone.getContext(); 198 int preferredNetworkSetting = -1; 199 try { 200 preferredNetworkSetting = 201 android.provider.Settings.Global.getInt(context.getContentResolver(), 202 android.provider.Settings.Global.PREFERRED_NETWORK_MODE 203 + mPhone.getSubId(), Phone.PREFERRED_NT_MODE); 204 } catch (Exception e) { 205 Rlog.e(LOG_TAG, "Unable to get PREFERRED_NETWORK_MODE."); 206 return true; 207 } 208 return (preferredNetworkSetting == RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA); 209 } 210 handleConfigChanges()211 private void handleConfigChanges() { 212 for (Map.Entry<Integer, NotificationType> entry : mNotificationTypeMap.entrySet()) { 213 NotificationType notificationType = entry.getValue(); 214 evaluateSendingMessageOrCancelNotification(notificationType); 215 } 216 } 217 handlePrefNetworkModeChanged()218 private void handlePrefNetworkModeChanged() { 219 NotificationType notificationType = mNotificationTypeMap.get(NOTIFICATION_PREF_NETWORK); 220 if (notificationType != null) { 221 evaluateSendingMessageOrCancelNotification(notificationType); 222 } 223 } 224 handleImsCapabilitiesChanged()225 private void handleImsCapabilitiesChanged() { 226 NotificationType notificationType = mNotificationTypeMap 227 .get(NOTIFICATION_EMERGENCY_NETWORK); 228 if (notificationType != null) { 229 evaluateSendingMessageOrCancelNotification(notificationType); 230 } 231 } 232 evaluateSendingMessageOrCancelNotification(NotificationType notificationType)233 private void evaluateSendingMessageOrCancelNotification(NotificationType notificationType) { 234 if (evaluateSendingMessage(notificationType)) { 235 Message notificationMsg = obtainMessage(notificationType.getTypeId(), null); 236 Rlog.i(LOG_TAG, "starting timer for notifications." + notificationType.getTypeId()); 237 sendMessageDelayed(notificationMsg, getDelay(notificationType)); 238 } else { 239 cancelNotification(notificationType); 240 Rlog.i(LOG_TAG, "canceling notifications: " + notificationType.getTypeId()); 241 } 242 } 243 244 /** 245 * This method adds a level of indirection, and was created so we can unit the class. 246 **/ 247 @VisibleForTesting evaluateSendingMessage(NotificationType notificationType)248 public boolean evaluateSendingMessage(NotificationType notificationType) { 249 return notificationType.sendMessage(); 250 } 251 252 /** 253 * This method adds a level of indirection, and was created so we can unit the class. 254 **/ 255 @VisibleForTesting getDelay(NotificationType notificationType)256 public int getDelay(NotificationType notificationType) { 257 return notificationType.getDelay(); 258 } 259 260 /** 261 * This method adds a level of indirection, and was created so we can unit the class. 262 **/ 263 @VisibleForTesting getNotificationBuilder(NotificationType notificationType)264 public Notification.Builder getNotificationBuilder(NotificationType notificationType) { 265 return notificationType.getNotificationBuilder(); 266 } 267 268 /** 269 * This method adds a level of indirection, and was created so we can unit the class. 270 **/ 271 @VisibleForTesting getNotificationManager(Context context)272 public NotificationManager getNotificationManager(Context context) { 273 return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); 274 } 275 276 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 277 @Override 278 public void onReceive(Context context, Intent intent) { 279 CarrierConfigManager carrierConfigManager = (CarrierConfigManager) 280 context.getSystemService(Context.CARRIER_CONFIG_SERVICE); 281 PersistableBundle b = carrierConfigManager.getConfigForSubId(mPhone.getSubId()); 282 283 for (Map.Entry<Integer, NotificationType> entry : mNotificationTypeMap.entrySet()) { 284 NotificationType notificationType = entry.getValue(); 285 notificationType.setDelay(b); 286 } 287 handleConfigChanges(); 288 } 289 }; 290 291 /** 292 * Post a notification to the NotificationManager for changing network type. 293 */ 294 @VisibleForTesting sendNotification(NotificationType notificationType)295 public void sendNotification(NotificationType notificationType) { 296 if (!evaluateSendingMessage(notificationType)) { 297 return; 298 } 299 300 Context context = mPhone.getContext(); 301 Notification.Builder builder = getNotificationBuilder(notificationType); 302 // set some common attributes 303 builder.setWhen(System.currentTimeMillis()) 304 .setAutoCancel(true) 305 .setSmallIcon(com.android.internal.R.drawable.stat_sys_warning) 306 .setColor(context.getResources().getColor( 307 com.android.internal.R.color.system_notification_accent_color)); 308 getNotificationManager(context).notify(notificationType.getNotificationTag(), 309 notificationType.getNotificationId(), builder.build()); 310 } 311 312 /** 313 * Cancel notifications if a registration is pending or has been sent. 314 **/ cancelNotification(NotificationType notificationType)315 public void cancelNotification(NotificationType notificationType) { 316 Context context = mPhone.getContext(); 317 removeMessages(notificationType.getTypeId()); 318 getNotificationManager(context).cancel( 319 notificationType.getNotificationTag(), notificationType.getNotificationId()); 320 } 321 322 /** 323 * Dispose the CarrierServiceStateTracker. 324 */ dispose()325 public void dispose() { 326 unregisterPrefNetworkModeObserver(); 327 } 328 329 /** 330 * Class that defines the different types of notifications. 331 */ 332 public interface NotificationType { 333 334 /** 335 * decides if the message should be sent, Returns boolean 336 **/ sendMessage()337 boolean sendMessage(); 338 339 /** 340 * returns the interval by which the message is delayed. 341 **/ getDelay()342 int getDelay(); 343 344 /** sets the interval by which the message is delayed. 345 * @param bundle PersistableBundle 346 **/ setDelay(PersistableBundle bundle)347 void setDelay(PersistableBundle bundle); 348 349 /** 350 * returns notification type id. 351 **/ getTypeId()352 int getTypeId(); 353 354 /** 355 * returns notification id. 356 **/ getNotificationId()357 int getNotificationId(); 358 359 /** 360 * returns notification tag. 361 **/ getNotificationTag()362 String getNotificationTag(); 363 364 /** 365 * returns the notification builder, for the notification to be displayed. 366 **/ getNotificationBuilder()367 Notification.Builder getNotificationBuilder(); 368 } 369 370 /** 371 * Class that defines the network notification, which is shown when the phone cannot camp on 372 * a network, and has 'preferred mode' set to global. 373 */ 374 public class PrefNetworkNotification implements NotificationType { 375 376 private final int mTypeId; 377 private int mDelay = UNINITIALIZED_DELAY_VALUE; 378 PrefNetworkNotification(int typeId)379 PrefNetworkNotification(int typeId) { 380 this.mTypeId = typeId; 381 } 382 383 /** sets the interval by which the message is delayed. 384 * @param bundle PersistableBundle 385 **/ setDelay(PersistableBundle bundle)386 public void setDelay(PersistableBundle bundle) { 387 if (bundle == null) { 388 Rlog.e(LOG_TAG, "bundle is null"); 389 return; 390 } 391 this.mDelay = bundle.getInt( 392 CarrierConfigManager.KEY_PREF_NETWORK_NOTIFICATION_DELAY_INT); 393 Rlog.i(LOG_TAG, "reading time to delay notification pref network: " + mDelay); 394 } 395 getDelay()396 public int getDelay() { 397 return mDelay; 398 } 399 getTypeId()400 public int getTypeId() { 401 return mTypeId; 402 } 403 getNotificationId()404 public int getNotificationId() { 405 return mPhone.getSubId(); 406 } 407 getNotificationTag()408 public String getNotificationTag() { 409 return PREF_NETWORK_NOTIFICATION_TAG; 410 } 411 412 /** 413 * Contains logic on sending notifications. 414 */ sendMessage()415 public boolean sendMessage() { 416 Rlog.i(LOG_TAG, "PrefNetworkNotification: sendMessage() w/values: " 417 + "," + isPhoneStillRegistered() + "," + mDelay + "," + isGlobalMode() 418 + "," + mSST.isRadioOn()); 419 if (mDelay == UNINITIALIZED_DELAY_VALUE || isPhoneStillRegistered() || isGlobalMode() 420 || isRadioOffOrAirplaneMode()) { 421 return false; 422 } 423 return true; 424 } 425 426 /** 427 * Builds a partial notificaiton builder, and returns it. 428 */ getNotificationBuilder()429 public Notification.Builder getNotificationBuilder() { 430 Context context = mPhone.getContext(); 431 Intent notificationIntent = new Intent(Settings.ACTION_DATA_ROAMING_SETTINGS); 432 notificationIntent.putExtra("expandable", true); 433 PendingIntent settingsIntent = PendingIntent.getActivity(context, 0, notificationIntent, 434 PendingIntent.FLAG_ONE_SHOT); 435 Resources res = SubscriptionManager.getResourcesForSubId(context, mPhone.getSubId()); 436 CharSequence title = res.getText( 437 com.android.internal.R.string.NetworkPreferenceSwitchTitle); 438 CharSequence details = res.getText( 439 com.android.internal.R.string.NetworkPreferenceSwitchSummary); 440 return new Notification.Builder(context) 441 .setContentTitle(title) 442 .setStyle(new Notification.BigTextStyle().bigText(details)) 443 .setContentText(details) 444 .setChannelId(NotificationChannelController.CHANNEL_ID_ALERT) 445 .setContentIntent(settingsIntent); 446 } 447 } 448 449 /** 450 * Class that defines the emergency notification, which is shown when Wi-Fi Calling is 451 * available. 452 */ 453 public class EmergencyNetworkNotification implements NotificationType { 454 455 private final int mTypeId; 456 private int mDelay = UNINITIALIZED_DELAY_VALUE; 457 EmergencyNetworkNotification(int typeId)458 EmergencyNetworkNotification(int typeId) { 459 this.mTypeId = typeId; 460 } 461 462 /** sets the interval by which the message is delayed. 463 * @param bundle PersistableBundle 464 **/ setDelay(PersistableBundle bundle)465 public void setDelay(PersistableBundle bundle) { 466 if (bundle == null) { 467 Rlog.e(LOG_TAG, "bundle is null"); 468 return; 469 } 470 this.mDelay = bundle.getInt( 471 CarrierConfigManager.KEY_EMERGENCY_NOTIFICATION_DELAY_INT); 472 Rlog.i(LOG_TAG, "reading time to delay notification emergency: " + mDelay); 473 } 474 getDelay()475 public int getDelay() { 476 return mDelay; 477 } 478 getTypeId()479 public int getTypeId() { 480 return mTypeId; 481 } 482 getNotificationId()483 public int getNotificationId() { 484 return mPhone.getSubId(); 485 } 486 getNotificationTag()487 public String getNotificationTag() { 488 return EMERGENCY_NOTIFICATION_TAG; 489 } 490 491 /** 492 * Contains logic on sending notifications, 493 */ sendMessage()494 public boolean sendMessage() { 495 Rlog.i(LOG_TAG, "EmergencyNetworkNotification: sendMessage() w/values: " 496 + "," + mDelay + "," + isPhoneRegisteredForWifiCalling() + "," 497 + mSST.isRadioOn()); 498 if (mDelay == UNINITIALIZED_DELAY_VALUE || !isPhoneRegisteredForWifiCalling()) { 499 return false; 500 } 501 return true; 502 } 503 504 /** 505 * Builds a partial notificaiton builder, and returns it. 506 */ getNotificationBuilder()507 public Notification.Builder getNotificationBuilder() { 508 Context context = mPhone.getContext(); 509 Resources res = SubscriptionManager.getResourcesForSubId(context, mPhone.getSubId()); 510 CharSequence title = res.getText( 511 com.android.internal.R.string.EmergencyCallWarningTitle); 512 CharSequence details = res.getText( 513 com.android.internal.R.string.EmergencyCallWarningSummary); 514 return new Notification.Builder(context) 515 .setContentTitle(title) 516 .setStyle(new Notification.BigTextStyle().bigText(details)) 517 .setContentText(details) 518 .setFlag(Notification.FLAG_NO_CLEAR, true) 519 .setChannelId(NotificationChannelController.CHANNEL_ID_WFC); 520 } 521 } 522 } 523