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