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