1 /* 2 * Copyright (C) 2011 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.cellbroadcastreceiver; 18 19 import android.content.BroadcastReceiver; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.SharedPreferences; 23 import android.content.pm.PackageManager; 24 import android.os.Bundle; 25 import android.os.RemoteException; 26 import android.os.ServiceManager; 27 import android.os.UserHandle; 28 import android.preference.PreferenceManager; 29 import android.provider.Telephony; 30 import android.telephony.CellBroadcastMessage; 31 import android.telephony.ServiceState; 32 import android.telephony.SubscriptionInfo; 33 import android.telephony.SubscriptionManager; 34 import android.telephony.TelephonyManager; 35 import android.telephony.cdma.CdmaSmsCbProgramData; 36 import android.util.Log; 37 import com.android.internal.telephony.PhoneConstants; 38 39 import com.android.internal.telephony.ITelephony; 40 import com.android.internal.telephony.IccCardConstants; 41 import com.android.internal.telephony.cdma.sms.SmsEnvelope; 42 import com.android.internal.telephony.TelephonyIntents; 43 import com.android.internal.telephony.uicc.IccCardProxy; 44 45 import java.util.List; 46 47 public class CellBroadcastReceiver extends BroadcastReceiver { 48 private static final String TAG = "CellBroadcastReceiver"; 49 static final boolean DBG = false; // STOPSHIP: change to false before ship 50 private static int mServiceState = -1; 51 private static final String GET_LATEST_CB_AREA_INFO_ACTION = 52 "android.cellbroadcastreceiver.GET_LATEST_CB_AREA_INFO"; 53 54 @Override onReceive(Context context, Intent intent)55 public void onReceive(Context context, Intent intent) { 56 onReceiveWithPrivilege(context, intent, false); 57 } 58 onReceiveWithPrivilege(Context context, Intent intent, boolean privileged)59 protected void onReceiveWithPrivilege(Context context, Intent intent, boolean privileged) { 60 if (DBG) log("onReceive " + intent); 61 62 String action = intent.getAction(); 63 64 if (TelephonyIntents.ACTION_SERVICE_STATE_CHANGED.equals(action)) { 65 if (DBG) log("Intent ACTION_SERVICE_STATE_CHANGED"); 66 int subId = intent.getExtras().getInt(PhoneConstants.SUBSCRIPTION_KEY); 67 Log.d(TAG, "subscriptionId = " + subId); 68 if (!SubscriptionManager.isValidSubscriptionId(subId)) { 69 return; 70 } 71 ServiceState serviceState = ServiceState.newFromBundle(intent.getExtras()); 72 int newState = serviceState.getState(); 73 if (newState != mServiceState) { 74 Log.d(TAG, "Service state changed! " + newState + " Full: " + serviceState + 75 " Current state=" + mServiceState); 76 mServiceState = newState; 77 78 if (((newState == ServiceState.STATE_IN_SERVICE) || 79 (newState == ServiceState.STATE_EMERGENCY_ONLY)) && 80 (UserHandle.myUserId() == UserHandle.USER_OWNER)) { 81 startConfigService(context.getApplicationContext(), subId); 82 } 83 } 84 } else if (IccCardProxy.ACTION_INTERNAL_SIM_STATE_CHANGED.equals(action)){ 85 String simStatus = intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE); 86 if (IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(simStatus)) { 87 List<SubscriptionInfo> subscriptionInfoList = SubscriptionManager.from( 88 context).getActiveSubscriptionInfoList(); 89 if (subscriptionInfoList != null) { 90 for (SubscriptionInfo subInfo : subscriptionInfoList) { 91 startConfigService(context, subInfo.getSubscriptionId()); 92 } 93 } 94 } 95 } else if (Telephony.Sms.Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION.equals(action) || 96 Telephony.Sms.Intents.SMS_CB_RECEIVED_ACTION.equals(action)) { 97 // If 'privileged' is false, it means that the intent was delivered to the base 98 // no-permissions receiver class. If we get an SMS_CB_RECEIVED message that way, it 99 // means someone has tried to spoof the message by delivering it outside the normal 100 // permission-checked route, so we just ignore it. 101 if (privileged) { 102 intent.setClass(context, CellBroadcastAlertService.class); 103 context.startService(intent); 104 } else { 105 loge("ignoring unprivileged action received " + action); 106 } 107 } else if (Telephony.Sms.Intents.SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED_ACTION 108 .equals(action)) { 109 if (privileged) { 110 CdmaSmsCbProgramData[] programDataList = (CdmaSmsCbProgramData[]) 111 intent.getParcelableArrayExtra("program_data_list"); 112 if (programDataList != null) { 113 int subId = intent.getExtras().getInt(PhoneConstants.SUBSCRIPTION_KEY); 114 Log.d(TAG, "subscriptionId = " + subId); 115 handleCdmaSmsCbProgramData(context, programDataList, subId); 116 } else { 117 loge("SCPD intent received with no program_data_list"); 118 } 119 } else { 120 loge("ignoring unprivileged action received " + action); 121 } 122 } else if (GET_LATEST_CB_AREA_INFO_ACTION.equals(action)) { 123 if (privileged) { 124 CellBroadcastMessage message = CellBroadcastReceiverApp.getLatestAreaInfo(); 125 if (message != null) { 126 Intent areaInfoIntent = new Intent( 127 CellBroadcastAlertService.CB_AREA_INFO_RECEIVED_ACTION); 128 areaInfoIntent.putExtra("message", message); 129 // Send broadcast twice, once for apps that have PRIVILEGED permission and once 130 // for those that have the runtime one 131 context.sendBroadcastAsUser(areaInfoIntent, UserHandle.ALL, 132 android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE); 133 context.sendBroadcastAsUser(areaInfoIntent, UserHandle.ALL, 134 android.Manifest.permission.READ_PHONE_STATE); 135 } 136 } else { 137 Log.e(TAG, "caller missing READ_PHONE_STATE permission, returning"); 138 } 139 } else { 140 Log.w(TAG, "onReceive() unexpected action " + action); 141 } 142 } 143 144 /** 145 * Handle Service Category Program Data message. 146 * TODO: Send Service Category Program Results response message to sender 147 * 148 * @param context 149 * @param programDataList 150 */ handleCdmaSmsCbProgramData(Context context, CdmaSmsCbProgramData[] programDataList, int subId)151 private void handleCdmaSmsCbProgramData(Context context, 152 CdmaSmsCbProgramData[] programDataList, int subId) { 153 for (CdmaSmsCbProgramData programData : programDataList) { 154 switch (programData.getOperation()) { 155 case CdmaSmsCbProgramData.OPERATION_ADD_CATEGORY: 156 tryCdmaSetCategory(context, programData.getCategory(), true, subId); 157 break; 158 159 case CdmaSmsCbProgramData.OPERATION_DELETE_CATEGORY: 160 tryCdmaSetCategory(context, programData.getCategory(), false, subId); 161 break; 162 163 case CdmaSmsCbProgramData.OPERATION_CLEAR_CATEGORIES: 164 tryCdmaSetCategory(context, 165 SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT, false, subId); 166 tryCdmaSetCategory(context, 167 SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT, false, subId); 168 tryCdmaSetCategory(context, 169 SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY, false, 170 subId); 171 tryCdmaSetCategory(context, 172 SmsEnvelope.SERVICE_CATEGORY_CMAS_TEST_MESSAGE, false, subId); 173 break; 174 175 default: 176 loge("Ignoring unknown SCPD operation " + programData.getOperation()); 177 } 178 } 179 } 180 tryCdmaSetCategory(Context context, int category, boolean enable, int subId)181 private void tryCdmaSetCategory(Context context, int category, boolean enable, int subId) { 182 switch (category) { 183 case SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT: 184 SubscriptionManager.setSubscriptionProperty(subId, 185 SubscriptionManager.CB_EXTREME_THREAT_ALERT, 186 (enable ? "1" : "0")); 187 break; 188 189 case SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT: 190 SubscriptionManager.setSubscriptionProperty(subId, 191 SubscriptionManager.CB_SEVERE_THREAT_ALERT, 192 (enable ? "1" : "0")); 193 break; 194 195 case SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY: 196 SubscriptionManager.setSubscriptionProperty(subId, 197 SubscriptionManager.CB_AMBER_ALERT, 198 (enable ? "1" : "0")); 199 break; 200 201 case SmsEnvelope.SERVICE_CATEGORY_CMAS_TEST_MESSAGE: 202 SubscriptionManager.setSubscriptionProperty(subId, 203 SubscriptionManager.CB_CMAS_TEST_ALERT, 204 (enable ? "1" : "0")); 205 break; 206 207 default: 208 Log.w(TAG, "Ignoring SCPD command to " + (enable ? "enable" : "disable") 209 + " alerts in category " + category); 210 } 211 } 212 213 /** 214 * Tell {@link CellBroadcastConfigService} to enable the CB channels. 215 * @param context the broadcast receiver context 216 */ startConfigService(Context context, int subId)217 static void startConfigService(Context context, int subId) { 218 Intent serviceIntent = new Intent(CellBroadcastConfigService.ACTION_ENABLE_CHANNELS, 219 null, context, CellBroadcastConfigService.class); 220 serviceIntent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId); 221 context.startService(serviceIntent); 222 } 223 224 /** 225 * @return true if the phone is a CDMA phone type 226 */ phoneIsCdma(int subId)227 static boolean phoneIsCdma(int subId) { 228 boolean isCdma = false; 229 try { 230 ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone")); 231 if (phone != null) { 232 isCdma = (phone.getActivePhoneTypeForSubscriber(subId) == 233 TelephonyManager.PHONE_TYPE_CDMA); 234 } 235 } catch (RemoteException e) { 236 Log.w(TAG, "phone.getActivePhoneType() failed", e); 237 } 238 return isCdma; 239 } 240 log(String msg)241 private static void log(String msg) { 242 Log.d(TAG, msg); 243 } 244 loge(String msg)245 private static void loge(String msg) { 246 Log.e(TAG, msg); 247 } 248 } 249