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
17package com.android.server.emergency;
18
19import android.content.BroadcastReceiver;
20import android.content.Context;
21import android.content.Intent;
22import android.content.IntentFilter;
23import android.os.Handler;
24import android.os.HandlerThread;
25import android.os.Looper;
26import android.os.Message;
27import android.provider.Settings;
28import android.telephony.CellInfo;
29import android.telephony.CellInfoGsm;
30import android.telephony.CellInfoLte;
31import android.telephony.CellInfoWcdma;
32import android.telephony.CellLocation;
33import android.telephony.PhoneStateListener;
34import android.telephony.SubscriptionInfo;
35import android.telephony.SubscriptionManager;
36import android.telephony.TelephonyManager;
37
38import com.android.server.SystemService;
39
40import java.util.ArrayList;
41import java.util.Arrays;
42import java.util.List;
43
44/**
45 * A service that listens to connectivity and SIM card changes and determines if the emergency mode
46 * should be enabled
47 */
48public class EmergencyAffordanceService extends SystemService {
49
50    private static final String TAG = "EmergencyAffordanceService";
51
52    private static final int NUM_SCANS_UNTIL_ABORT = 4;
53
54    private static final int INITIALIZE_STATE = 1;
55    private static final int CELL_INFO_STATE_CHANGED = 2;
56    private static final int SUBSCRIPTION_CHANGED = 3;
57
58    /**
59     * Global setting, whether the last scan of the sim cards reveal that a sim was inserted that
60     * requires the emergency affordance. The value is a boolean (1 or 0).
61     * @hide
62     */
63    private static final String EMERGENCY_SIM_INSERTED_SETTING = "emergency_sim_inserted_before";
64
65    private final Context mContext;
66    private final ArrayList<Integer> mEmergencyCallMccNumbers;
67
68    private final Object mLock = new Object();
69
70    private TelephonyManager mTelephonyManager;
71    private SubscriptionManager mSubscriptionManager;
72    private boolean mEmergencyAffordanceNeeded;
73    private MyHandler mHandler;
74    private int mScansCompleted;
75    private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
76        @Override
77        public void onCellInfoChanged(List<CellInfo> cellInfo) {
78            if (!isEmergencyAffordanceNeeded()) {
79                requestCellScan();
80            }
81        }
82
83        @Override
84        public void onCellLocationChanged(CellLocation location) {
85            if (!isEmergencyAffordanceNeeded()) {
86                requestCellScan();
87            }
88        }
89    };
90    private BroadcastReceiver mAirplaneModeReceiver = new BroadcastReceiver() {
91        @Override
92        public void onReceive(Context context, Intent intent) {
93            if (Settings.Global.getInt(context.getContentResolver(),
94                    Settings.Global.AIRPLANE_MODE_ON, 0) == 0) {
95                startScanning();
96                requestCellScan();
97            }
98        }
99    };
100    private boolean mSimNeedsEmergencyAffordance;
101    private boolean mNetworkNeedsEmergencyAffordance;
102    private boolean mVoiceCapable;
103
104    private void requestCellScan() {
105        mHandler.obtainMessage(CELL_INFO_STATE_CHANGED).sendToTarget();
106    }
107
108    private SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionChangedListener
109            = new SubscriptionManager.OnSubscriptionsChangedListener() {
110        @Override
111        public void onSubscriptionsChanged() {
112            mHandler.obtainMessage(SUBSCRIPTION_CHANGED).sendToTarget();
113        }
114    };
115
116    public EmergencyAffordanceService(Context context) {
117        super(context);
118        mContext = context;
119        int[] numbers = context.getResources().getIntArray(
120                com.android.internal.R.array.config_emergency_mcc_codes);
121        mEmergencyCallMccNumbers = new ArrayList<>(numbers.length);
122        for (int i = 0; i < numbers.length; i++) {
123            mEmergencyCallMccNumbers.add(numbers[i]);
124        }
125    }
126
127    private void updateEmergencyAffordanceNeeded() {
128        synchronized (mLock) {
129            mEmergencyAffordanceNeeded = mVoiceCapable && (mSimNeedsEmergencyAffordance ||
130                    mNetworkNeedsEmergencyAffordance);
131            Settings.Global.putInt(mContext.getContentResolver(),
132                    Settings.Global.EMERGENCY_AFFORDANCE_NEEDED,
133                    mEmergencyAffordanceNeeded ? 1 : 0);
134            if (mEmergencyAffordanceNeeded) {
135                stopScanning();
136            }
137        }
138    }
139
140    private void stopScanning() {
141        synchronized (mLock) {
142            mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
143            mScansCompleted = 0;
144        }
145    }
146
147    private boolean isEmergencyAffordanceNeeded() {
148        synchronized (mLock) {
149            return mEmergencyAffordanceNeeded;
150        }
151    }
152
153    @Override
154    public void onStart() {
155    }
156
157    @Override
158    public void onBootPhase(int phase) {
159        if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
160            mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
161            mVoiceCapable = mTelephonyManager.isVoiceCapable();
162            if (!mVoiceCapable) {
163                updateEmergencyAffordanceNeeded();
164                return;
165            }
166            mSubscriptionManager = SubscriptionManager.from(mContext);
167            HandlerThread thread = new HandlerThread(TAG);
168            thread.start();
169            mHandler = new MyHandler(thread.getLooper());
170            mHandler.obtainMessage(INITIALIZE_STATE).sendToTarget();
171            startScanning();
172            IntentFilter filter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);
173            mContext.registerReceiver(mAirplaneModeReceiver, filter);
174            mSubscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionChangedListener);
175        }
176    }
177
178    private void startScanning() {
179        mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CELL_INFO
180                | PhoneStateListener.LISTEN_CELL_LOCATION);
181    }
182
183    /** Handler to do the heavier work on */
184    private class MyHandler extends Handler {
185
186        public MyHandler(Looper l) {
187            super(l);
188        }
189
190        @Override
191        public void handleMessage(Message msg) {
192            switch (msg.what) {
193                case INITIALIZE_STATE:
194                    handleInitializeState();
195                    break;
196                case CELL_INFO_STATE_CHANGED:
197                    handleUpdateCellInfo();
198                    break;
199                case SUBSCRIPTION_CHANGED:
200                    handleUpdateSimSubscriptionInfo();
201                    break;
202            }
203        }
204    }
205
206    private void handleInitializeState() {
207        if (handleUpdateSimSubscriptionInfo()) {
208            return;
209        }
210        if (handleUpdateCellInfo()) {
211            return;
212        }
213        updateEmergencyAffordanceNeeded();
214    }
215
216    private boolean handleUpdateSimSubscriptionInfo() {
217        boolean neededBefore = simNeededAffordanceBefore();
218        boolean neededNow = neededBefore;
219        List<SubscriptionInfo> activeSubscriptionInfoList =
220                mSubscriptionManager.getActiveSubscriptionInfoList();
221        if (activeSubscriptionInfoList == null) {
222            setSimNeedsEmergencyAffordance(neededNow);
223            return neededNow;
224        }
225        for (SubscriptionInfo info : activeSubscriptionInfoList) {
226            int mcc = info.getMcc();
227            if (mccRequiresEmergencyAffordance(mcc)) {
228                neededNow = true;
229                break;
230            } else if (mcc != 0 && mcc != Integer.MAX_VALUE){
231                // a Sim with a different mcc code was found
232                neededNow = false;
233            }
234            String simOperator  = mTelephonyManager.getSimOperator(info.getSubscriptionId());
235            mcc = 0;
236            if (simOperator != null && simOperator.length() >= 3) {
237                mcc = Integer.parseInt(simOperator.substring(0, 3));
238            }
239            if (mcc != 0) {
240                if (mccRequiresEmergencyAffordance(mcc)) {
241                    neededNow = true;
242                    break;
243                } else {
244                    // a Sim with a different mcc code was found
245                    neededNow = false;
246                }
247            }
248        }
249        setSimNeedsEmergencyAffordance(neededNow);
250        return neededNow;
251    }
252
253    private void setSimNeedsEmergencyAffordance(boolean simNeedsEmergencyAffordance) {
254        if (simNeededAffordanceBefore() != simNeedsEmergencyAffordance) {
255            Settings.Global.putInt(mContext.getContentResolver(),
256                    EMERGENCY_SIM_INSERTED_SETTING,
257                    simNeedsEmergencyAffordance ? 1 : 0);
258        }
259        if (simNeedsEmergencyAffordance != mSimNeedsEmergencyAffordance) {
260            mSimNeedsEmergencyAffordance = simNeedsEmergencyAffordance;
261            updateEmergencyAffordanceNeeded();
262        }
263    }
264
265    private boolean simNeededAffordanceBefore() {
266        return Settings.Global.getInt(mContext.getContentResolver(),
267                EMERGENCY_SIM_INSERTED_SETTING, 0) != 0;
268    }
269
270    private boolean handleUpdateCellInfo() {
271        List<CellInfo> cellInfos = mTelephonyManager.getAllCellInfo();
272        if (cellInfos == null) {
273            return false;
274        }
275        boolean stopScanningAfterScan = false;
276        for (CellInfo cellInfo : cellInfos) {
277            int mcc = 0;
278            if (cellInfo instanceof CellInfoGsm) {
279                mcc = ((CellInfoGsm) cellInfo).getCellIdentity().getMcc();
280            } else if (cellInfo instanceof CellInfoLte) {
281                mcc = ((CellInfoLte) cellInfo).getCellIdentity().getMcc();
282            } else if (cellInfo instanceof CellInfoWcdma) {
283                mcc = ((CellInfoWcdma) cellInfo).getCellIdentity().getMcc();
284            }
285            if (mccRequiresEmergencyAffordance(mcc)) {
286                setNetworkNeedsEmergencyAffordance(true);
287                return true;
288            } else if (mcc != 0 && mcc != Integer.MAX_VALUE) {
289                // we found an mcc that isn't in the list, abort
290                stopScanningAfterScan = true;
291            }
292        }
293        if (stopScanningAfterScan) {
294            stopScanning();
295        } else {
296            onCellScanFinishedUnsuccessful();
297        }
298        setNetworkNeedsEmergencyAffordance(false);
299        return false;
300    }
301
302    private void setNetworkNeedsEmergencyAffordance(boolean needsAffordance) {
303        synchronized (mLock) {
304            mNetworkNeedsEmergencyAffordance = needsAffordance;
305            updateEmergencyAffordanceNeeded();
306        }
307    }
308
309    private void onCellScanFinishedUnsuccessful() {
310        synchronized (mLock) {
311            mScansCompleted++;
312            if (mScansCompleted >= NUM_SCANS_UNTIL_ABORT) {
313                stopScanning();
314            }
315        }
316    }
317
318    private boolean mccRequiresEmergencyAffordance(int mcc) {
319        return mEmergencyCallMccNumbers.contains(mcc);
320    }
321}
322