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