/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.cellbroadcastreceiver; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; import android.preference.PreferenceManager; import android.provider.Telephony; import android.telephony.CellBroadcastMessage; import android.telephony.PhoneStateListener; import android.telephony.ServiceState; import android.telephony.TelephonyManager; import android.telephony.cdma.CdmaSmsCbProgramData; import android.util.Log; import com.android.internal.telephony.ITelephony; import com.android.internal.telephony.cdma.sms.SmsEnvelope; public class CellBroadcastReceiver extends BroadcastReceiver { private static final String TAG = "CellBroadcastReceiver"; static final boolean DBG = true; // STOPSHIP: change to false before ship private static final String GET_LATEST_CB_AREA_INFO_ACTION = "android.cellbroadcastreceiver.GET_LATEST_CB_AREA_INFO"; @Override public void onReceive(Context context, Intent intent) { onReceiveWithPrivilege(context, intent, false); } protected void onReceiveWithPrivilege(Context context, Intent intent, boolean privileged) { if (DBG) log("onReceive " + intent); String action = intent.getAction(); if (Intent.ACTION_BOOT_COMPLETED.equals(action)) { if (DBG) log("Registering for ServiceState updates"); TelephonyManager tm = (TelephonyManager) context.getSystemService( Context.TELEPHONY_SERVICE); tm.listen(new ServiceStateListener(context.getApplicationContext()), PhoneStateListener.LISTEN_SERVICE_STATE); } else if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(action)) { boolean airplaneModeOn = intent.getBooleanExtra("state", false); if (!airplaneModeOn) { startConfigService(context); } } else if (Telephony.Sms.Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION.equals(action) || Telephony.Sms.Intents.SMS_CB_RECEIVED_ACTION.equals(action)) { // If 'privileged' is false, it means that the intent was delivered to the base // no-permissions receiver class. If we get an SMS_CB_RECEIVED message that way, it // means someone has tried to spoof the message by delivering it outside the normal // permission-checked route, so we just ignore it. if (privileged) { intent.setClass(context, CellBroadcastAlertService.class); context.startService(intent); } else { Log.e(TAG, "ignoring unprivileged action received " + action); } } else if (Telephony.Sms.Intents.SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED_ACTION .equals(action)) { if (privileged) { CdmaSmsCbProgramData[] programDataList = (CdmaSmsCbProgramData[]) intent.getParcelableArrayExtra("program_data_list"); if (programDataList != null) { handleCdmaSmsCbProgramData(context, programDataList); } else { Log.e(TAG, "SCPD intent received with no program_data_list"); } } else { Log.e(TAG, "ignoring unprivileged action received " + action); } } else if (GET_LATEST_CB_AREA_INFO_ACTION.equals(action)) { if (privileged) { CellBroadcastMessage message = CellBroadcastReceiverApp.getLatestAreaInfo(); if (message != null) { Intent areaInfoIntent = new Intent( CellBroadcastAlertService.CB_AREA_INFO_RECEIVED_ACTION); areaInfoIntent.putExtra("message", message); context.sendBroadcastAsUser(areaInfoIntent, UserHandle.ALL, android.Manifest.permission.READ_PHONE_STATE); } } else { Log.e(TAG, "caller missing READ_PHONE_STATE permission, returning"); } } else { Log.w(TAG, "onReceive() unexpected action " + action); } } /** * Handle Service Category Program Data message. * TODO: Send Service Category Program Results response message to sender * * @param context * @param programDataList */ private void handleCdmaSmsCbProgramData(Context context, CdmaSmsCbProgramData[] programDataList) { for (CdmaSmsCbProgramData programData : programDataList) { switch (programData.getOperation()) { case CdmaSmsCbProgramData.OPERATION_ADD_CATEGORY: tryCdmaSetCategory(context, programData.getCategory(), true); break; case CdmaSmsCbProgramData.OPERATION_DELETE_CATEGORY: tryCdmaSetCategory(context, programData.getCategory(), false); break; case CdmaSmsCbProgramData.OPERATION_CLEAR_CATEGORIES: tryCdmaSetCategory(context, SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT, false); tryCdmaSetCategory(context, SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT, false); tryCdmaSetCategory(context, SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY, false); tryCdmaSetCategory(context, SmsEnvelope.SERVICE_CATEGORY_CMAS_TEST_MESSAGE, false); break; default: Log.e(TAG, "Ignoring unknown SCPD operation " + programData.getOperation()); } } } private void tryCdmaSetCategory(Context context, int category, boolean enable) { SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context); switch (category) { case SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT: sharedPrefs.edit().putBoolean( CellBroadcastSettings.KEY_ENABLE_CMAS_EXTREME_THREAT_ALERTS, enable) .apply(); break; case SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT: sharedPrefs.edit().putBoolean( CellBroadcastSettings.KEY_ENABLE_CMAS_SEVERE_THREAT_ALERTS, enable) .apply(); break; case SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY: sharedPrefs.edit().putBoolean( CellBroadcastSettings.KEY_ENABLE_CMAS_AMBER_ALERTS, enable).apply(); break; case SmsEnvelope.SERVICE_CATEGORY_CMAS_TEST_MESSAGE: sharedPrefs.edit().putBoolean( CellBroadcastSettings.KEY_ENABLE_CMAS_TEST_ALERTS, enable).apply(); break; default: Log.w(TAG, "Ignoring SCPD command to " + (enable ? "enable" : "disable") + " alerts in category " + category); } } /** * Tell {@link CellBroadcastConfigService} to enable the CB channels. * @param context the broadcast receiver context */ static void startConfigService(Context context) { if (phoneIsCdma()) { if (DBG) log("CDMA phone detected; doing nothing"); } else { Intent serviceIntent = new Intent(CellBroadcastConfigService.ACTION_ENABLE_CHANNELS, null, context, CellBroadcastConfigService.class); context.startService(serviceIntent); } } /** * @return true if the phone is a CDMA phone type */ private static boolean phoneIsCdma() { boolean isCdma = false; try { ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone")); if (phone != null) { isCdma = (phone.getActivePhoneType() == TelephonyManager.PHONE_TYPE_CDMA); } } catch (RemoteException e) { Log.w(TAG, "phone.getActivePhoneType() failed", e); } return isCdma; } private static class ServiceStateListener extends PhoneStateListener { private final Context mContext; private int mServiceState = -1; ServiceStateListener(Context context) { mContext = context; } @Override public void onServiceStateChanged(ServiceState ss) { int newState = ss.getState(); if (newState != mServiceState) { Log.d(TAG, "Service state changed! " + newState + " Full: " + ss); mServiceState = newState; if (newState == ServiceState.STATE_IN_SERVICE || newState == ServiceState.STATE_EMERGENCY_ONLY) { startConfigService(mContext); } } } } private static void log(String msg) { Log.d(TAG, msg); } }