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