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            return neededNow;
223        }
224        for (SubscriptionInfo info : activeSubscriptionInfoList) {
225            int mcc = info.getMcc();
226            if (mccRequiresEmergencyAffordance(mcc)) {
227                neededNow = true;
228                break;
229            } else if (mcc != 0 && mcc != Integer.MAX_VALUE){
230                // a Sim with a different mcc code was found
231                neededNow = false;
232            }
233            String simOperator  = mTelephonyManager.getSimOperator(info.getSubscriptionId());
234            mcc = 0;
235            if (simOperator != null && simOperator.length() >= 3) {
236                mcc = Integer.parseInt(simOperator.substring(0, 3));
237            }
238            if (mcc != 0) {
239                if (mccRequiresEmergencyAffordance(mcc)) {
240                    neededNow = true;
241                    break;
242                } else {
243                    // a Sim with a different mcc code was found
244                    neededNow = false;
245                }
246            }
247        }
248        if (neededNow != neededBefore) {
249            setSimNeedsEmergencyAffordance(neededNow);
250        }
251        return neededNow;
252    }
253
254    private void setSimNeedsEmergencyAffordance(boolean simNeedsEmergencyAffordance) {
255        mSimNeedsEmergencyAffordance = simNeedsEmergencyAffordance;
256        Settings.Global.putInt(mContext.getContentResolver(),
257                EMERGENCY_SIM_INSERTED_SETTING,
258                simNeedsEmergencyAffordance ? 1 : 0);
259        updateEmergencyAffordanceNeeded();
260    }
261
262    private boolean simNeededAffordanceBefore() {
263        return Settings.Global.getInt(mContext.getContentResolver(),
264                "emergency_sim_inserted_before", 0) != 0;
265    }
266
267    private boolean handleUpdateCellInfo() {
268        List<CellInfo> cellInfos = mTelephonyManager.getAllCellInfo();
269        if (cellInfos == null) {
270            return false;
271        }
272        boolean stopScanningAfterScan = false;
273        for (CellInfo cellInfo : cellInfos) {
274            int mcc = 0;
275            if (cellInfo instanceof CellInfoGsm) {
276                mcc = ((CellInfoGsm) cellInfo).getCellIdentity().getMcc();
277            } else if (cellInfo instanceof CellInfoLte) {
278                mcc = ((CellInfoLte) cellInfo).getCellIdentity().getMcc();
279            } else if (cellInfo instanceof CellInfoWcdma) {
280                mcc = ((CellInfoWcdma) cellInfo).getCellIdentity().getMcc();
281            }
282            if (mccRequiresEmergencyAffordance(mcc)) {
283                setNetworkNeedsEmergencyAffordance(true);
284                return true;
285            } else if (mcc != 0 && mcc != Integer.MAX_VALUE) {
286                // we found an mcc that isn't in the list, abort
287                stopScanningAfterScan = true;
288            }
289        }
290        if (stopScanningAfterScan) {
291            stopScanning();
292        } else {
293            onCellScanFinishedUnsuccessful();
294        }
295        setNetworkNeedsEmergencyAffordance(false);
296        return false;
297    }
298
299    private void setNetworkNeedsEmergencyAffordance(boolean needsAffordance) {
300        synchronized (mLock) {
301            mNetworkNeedsEmergencyAffordance = needsAffordance;
302            updateEmergencyAffordanceNeeded();
303        }
304    }
305
306    private void onCellScanFinishedUnsuccessful() {
307        synchronized (mLock) {
308            mScansCompleted++;
309            if (mScansCompleted >= NUM_SCANS_UNTIL_ABORT) {
310                stopScanning();
311            }
312        }
313    }
314
315    private boolean mccRequiresEmergencyAffordance(int mcc) {
316        return mEmergencyCallMccNumbers.contains(mcc);
317    }
318}
319