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.internal.telephony;
18
19import android.app.Notification;
20import android.app.NotificationManager;
21import android.app.PendingIntent;
22import android.content.BroadcastReceiver;
23import android.content.Context;
24import android.content.Intent;
25import android.content.IntentFilter;
26import android.os.Handler;
27import android.os.Message;
28import android.os.PersistableBundle;
29import android.provider.Settings;
30import android.telephony.CarrierConfigManager;
31import android.telephony.Rlog;
32import android.telephony.ServiceState;
33
34import com.android.internal.annotations.VisibleForTesting;
35import com.android.internal.telephony.util.NotificationChannelController;
36
37import java.util.HashMap;
38import java.util.Map;
39
40
41/**
42 * This contains Carrier specific logic based on the states/events
43 * managed in ServiceStateTracker.
44 * {@hide}
45 */
46public class CarrierServiceStateTracker extends Handler {
47    private static final String LOG_TAG = "CSST";
48    protected static final int CARRIER_EVENT_BASE = 100;
49    protected static final int CARRIER_EVENT_VOICE_REGISTRATION = CARRIER_EVENT_BASE + 1;
50    protected static final int CARRIER_EVENT_VOICE_DEREGISTRATION = CARRIER_EVENT_BASE + 2;
51    protected static final int CARRIER_EVENT_DATA_REGISTRATION = CARRIER_EVENT_BASE + 3;
52    protected static final int CARRIER_EVENT_DATA_DEREGISTRATION = CARRIER_EVENT_BASE + 4;
53    private static final int UNINITIALIZED_DELAY_VALUE = -1;
54    private Phone mPhone;
55    private ServiceStateTracker mSST;
56
57    public static final int NOTIFICATION_PREF_NETWORK = 1000;
58    public static final int NOTIFICATION_EMERGENCY_NETWORK = 1001;
59
60    private final Map<Integer, NotificationType> mNotificationTypeMap = new HashMap<>();
61
62    public CarrierServiceStateTracker(Phone phone, ServiceStateTracker sst) {
63        this.mPhone = phone;
64        this.mSST = sst;
65        phone.getContext().registerReceiver(mBroadcastReceiver, new IntentFilter(
66                CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
67        registerNotificationTypes();
68    }
69
70    private void registerNotificationTypes() {
71        mNotificationTypeMap.put(NOTIFICATION_PREF_NETWORK,
72                new PrefNetworkNotification(NOTIFICATION_PREF_NETWORK));
73        mNotificationTypeMap.put(NOTIFICATION_EMERGENCY_NETWORK,
74                new EmergencyNetworkNotification(NOTIFICATION_EMERGENCY_NETWORK));
75    }
76
77    @Override
78    public void handleMessage(Message msg) {
79        switch (msg.what) {
80            case CARRIER_EVENT_VOICE_REGISTRATION:
81            case CARRIER_EVENT_DATA_REGISTRATION:
82            case CARRIER_EVENT_VOICE_DEREGISTRATION:
83            case CARRIER_EVENT_DATA_DEREGISTRATION:
84                handleConfigChanges();
85                break;
86            case NOTIFICATION_EMERGENCY_NETWORK:
87            case NOTIFICATION_PREF_NETWORK:
88                Rlog.d(LOG_TAG, "sending notification after delay: " + msg.what);
89                NotificationType notificationType = mNotificationTypeMap.get(msg.what);
90                if (notificationType != null) {
91                    sendNotification(notificationType);
92                }
93                break;
94        }
95    }
96
97    private boolean isPhoneStillRegistered() {
98        if (mSST.mSS == null) {
99            return true; //something has gone wrong, return true and not show the notification.
100        }
101        return (mSST.mSS.getVoiceRegState() == ServiceState.STATE_IN_SERVICE
102                || mSST.mSS.getDataRegState() == ServiceState.STATE_IN_SERVICE);
103    }
104
105    private boolean isPhoneVoiceRegistered() {
106        if (mSST.mSS == null) {
107            return true; //something has gone wrong, return true and not show the notification.
108        }
109        return (mSST.mSS.getVoiceRegState() == ServiceState.STATE_IN_SERVICE);
110    }
111
112    private boolean isPhoneRegisteredForWifiCalling() {
113        Rlog.d(LOG_TAG, "isPhoneRegisteredForWifiCalling: " + mPhone.isWifiCallingEnabled());
114        return mPhone.isWifiCallingEnabled();
115    }
116
117    /**
118     * Returns true if the radio is off or in Airplane Mode else returns false.
119     */
120    @VisibleForTesting
121    public boolean isRadioOffOrAirplaneMode() {
122        Context context = mPhone.getContext();
123        int airplaneMode = -1;
124        try {
125            airplaneMode = Settings.Global.getInt(context.getContentResolver(),
126                    Settings.Global.AIRPLANE_MODE_ON, 0);
127        } catch (Exception e) {
128            Rlog.e(LOG_TAG, "Unable to get AIRPLACE_MODE_ON.");
129            return true;
130        }
131        return (!mSST.isRadioOn() || (airplaneMode != 0));
132    }
133
134    /**
135     * Returns true if the preferred network is set to 'Global'.
136     */
137    private boolean isGlobalMode() {
138        Context context = mPhone.getContext();
139        int preferredNetworkSetting = -1;
140        try {
141            preferredNetworkSetting =
142                    android.provider.Settings.Global.getInt(context.getContentResolver(),
143                            android.provider.Settings.Global.PREFERRED_NETWORK_MODE
144                                    + mPhone.getSubId(), Phone.PREFERRED_NT_MODE);
145        } catch (Exception e) {
146            Rlog.e(LOG_TAG, "Unable to get PREFERRED_NETWORK_MODE.");
147            return true;
148        }
149        return (preferredNetworkSetting == RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA);
150    }
151
152    private void handleConfigChanges() {
153        for (Map.Entry<Integer, NotificationType> entry : mNotificationTypeMap.entrySet()) {
154            NotificationType notificationType = entry.getValue();
155            if (evaluateSendingMessage(notificationType)) {
156                Message notificationMsg = obtainMessage(notificationType.getTypeId(), null);
157                Rlog.i(LOG_TAG, "starting timer for notifications." + notificationType.getTypeId());
158                sendMessageDelayed(notificationMsg, getDelay(notificationType));
159            } else {
160                cancelNotification(notificationType.getTypeId());
161                Rlog.i(LOG_TAG, "canceling notifications: " + notificationType.getTypeId());
162            }
163        }
164    }
165
166    /**
167     * This method adds a level of indirection, and was created so we can unit the class.
168     **/
169    @VisibleForTesting
170    public boolean evaluateSendingMessage(NotificationType notificationType) {
171        return notificationType.sendMessage();
172    }
173
174    /**
175     * This method adds a level of indirection, and was created so we can unit the class.
176     **/
177    @VisibleForTesting
178    public int getDelay(NotificationType notificationType) {
179        return notificationType.getDelay();
180    }
181
182    /**
183     * This method adds a level of indirection, and was created so we can unit the class.
184     **/
185    @VisibleForTesting
186    public Notification.Builder getNotificationBuilder(NotificationType notificationType) {
187        return notificationType.getNotificationBuilder();
188    }
189
190    /**
191     * This method adds a level of indirection, and was created so we can unit the class.
192     **/
193    @VisibleForTesting
194    public NotificationManager getNotificationManager(Context context) {
195        return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
196    }
197
198    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
199        @Override
200        public void onReceive(Context context, Intent intent) {
201            CarrierConfigManager carrierConfigManager = (CarrierConfigManager)
202                    context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
203            PersistableBundle b = carrierConfigManager.getConfigForSubId(mPhone.getSubId());
204
205            for (Map.Entry<Integer, NotificationType> entry : mNotificationTypeMap.entrySet()) {
206                NotificationType notificationType = entry.getValue();
207                notificationType.setDelay(b);
208            }
209            handleConfigChanges();
210        }
211    };
212
213    /**
214     * Post a notification to the NotificationManager for changing network type.
215     */
216    @VisibleForTesting
217    public void sendNotification(NotificationType notificationType) {
218        if (!evaluateSendingMessage(notificationType)) {
219            return;
220        }
221
222        Context context = mPhone.getContext();
223        Notification.Builder builder = getNotificationBuilder(notificationType);
224        // set some common attributes
225        builder.setWhen(System.currentTimeMillis())
226                .setAutoCancel(true)
227                .setSmallIcon(com.android.internal.R.drawable.stat_sys_warning)
228                .setColor(context.getResources().getColor(
229                       com.android.internal.R.color.system_notification_accent_color));
230
231        getNotificationManager(context).notify(notificationType.getTypeId(), builder.build());
232    }
233
234    /**
235     * Cancel notifications if a registration is pending or has been sent.
236     **/
237    public void cancelNotification(int notificationId) {
238        Context context = mPhone.getContext();
239        removeMessages(notificationId);
240        getNotificationManager(context).cancel(notificationId);
241    }
242
243    /**
244     * Class that defines the different types of notifications.
245     */
246    public interface NotificationType {
247
248        /**
249         * decides if the message should be sent, Returns boolean
250         **/
251        boolean sendMessage();
252
253        /**
254         * returns the interval by which the message is delayed.
255         **/
256        int getDelay();
257
258        /** sets the interval by which the message is delayed.
259         * @param bundle PersistableBundle
260        **/
261        void setDelay(PersistableBundle bundle);
262
263        /**
264         * returns notification type id.
265         **/
266        int getTypeId();
267
268        /**
269         * returns the notification builder, for the notification to be displayed.
270         **/
271        Notification.Builder getNotificationBuilder();
272    }
273
274    /**
275     * Class that defines the network notification, which is shown when the phone cannot camp on
276     * a network, and has 'preferred mode' set to global.
277     */
278    public class PrefNetworkNotification implements NotificationType {
279
280        private final int mTypeId;
281        private int mDelay = UNINITIALIZED_DELAY_VALUE;
282
283        PrefNetworkNotification(int typeId) {
284            this.mTypeId = typeId;
285        }
286
287        /** sets the interval by which the message is delayed.
288         * @param bundle PersistableBundle
289         **/
290        public void setDelay(PersistableBundle bundle) {
291            if (bundle == null) {
292                Rlog.e(LOG_TAG, "bundle is null");
293                return;
294            }
295            this.mDelay = bundle.getInt(
296                    CarrierConfigManager.KEY_PREF_NETWORK_NOTIFICATION_DELAY_INT);
297            Rlog.i(LOG_TAG, "reading time to delay notification emergency: " + mDelay);
298        }
299
300        public int getDelay() {
301            return mDelay;
302        }
303
304        public int getTypeId() {
305            return mTypeId;
306        }
307
308        /**
309         * Contains logic on sending notifications.
310         */
311        public boolean sendMessage() {
312            Rlog.i(LOG_TAG, "PrefNetworkNotification: sendMessage() w/values: "
313                    + "," + isPhoneStillRegistered() + "," + mDelay + "," + isGlobalMode()
314                    + "," + mSST.isRadioOn());
315            if (mDelay == UNINITIALIZED_DELAY_VALUE ||  isPhoneStillRegistered() || isGlobalMode()
316                    || isRadioOffOrAirplaneMode()) {
317                return false;
318            }
319            return true;
320        }
321
322        /**
323         * Builds a partial notificaiton builder, and returns it.
324         */
325        public Notification.Builder getNotificationBuilder() {
326            Context context = mPhone.getContext();
327            Intent notificationIntent = new Intent(Settings.ACTION_DATA_ROAMING_SETTINGS);
328            PendingIntent settingsIntent = PendingIntent.getActivity(context, 0, notificationIntent,
329                    PendingIntent.FLAG_ONE_SHOT);
330            CharSequence title = context.getText(
331                    com.android.internal.R.string.NetworkPreferenceSwitchTitle);
332            CharSequence details = context.getText(
333                    com.android.internal.R.string.NetworkPreferenceSwitchSummary);
334            return new Notification.Builder(context)
335                    .setContentTitle(title)
336                    .setStyle(new Notification.BigTextStyle().bigText(details))
337                    .setContentText(details)
338                    .setChannel(NotificationChannelController.CHANNEL_ID_ALERT)
339                    .setContentIntent(settingsIntent);
340        }
341    }
342
343    /**
344     * Class that defines the emergency notification, which is shown when the user is out of cell
345     * connectivity, but has wifi enabled.
346     */
347    public class EmergencyNetworkNotification implements NotificationType {
348
349        private final int mTypeId;
350        private int mDelay = UNINITIALIZED_DELAY_VALUE;
351
352        EmergencyNetworkNotification(int typeId) {
353            this.mTypeId = typeId;
354        }
355
356        /** sets the interval by which the message is delayed.
357         * @param bundle PersistableBundle
358         **/
359        public void setDelay(PersistableBundle bundle) {
360            if (bundle == null) {
361                Rlog.e(LOG_TAG, "bundle is null");
362                return;
363            }
364            this.mDelay = bundle.getInt(
365                    CarrierConfigManager.KEY_EMERGENCY_NOTIFICATION_DELAY_INT);
366            Rlog.i(LOG_TAG, "reading time to delay notification emergency: " + mDelay);
367        }
368
369        public int getDelay() {
370            return mDelay;
371        }
372
373        public int getTypeId() {
374            return mTypeId;
375        }
376
377        /**
378         * Contains logic on sending notifications,
379         */
380        public boolean sendMessage() {
381            Rlog.i(LOG_TAG, "EmergencyNetworkNotification: sendMessage() w/values: "
382                    + "," + isPhoneVoiceRegistered() + "," + mDelay + ","
383                    + isPhoneRegisteredForWifiCalling() + "," + mSST.isRadioOn());
384            if (mDelay == UNINITIALIZED_DELAY_VALUE || isPhoneVoiceRegistered()
385                    || !isPhoneRegisteredForWifiCalling()) {
386                return false;
387            }
388            return true;
389        }
390
391        /**
392         * Builds a partial notificaiton builder, and returns it.
393         */
394        public Notification.Builder getNotificationBuilder() {
395            Context context = mPhone.getContext();
396            CharSequence title = context.getText(
397                    com.android.internal.R.string.EmergencyCallWarningTitle);
398            CharSequence details = context.getText(
399                    com.android.internal.R.string.EmergencyCallWarningSummary);
400            return new Notification.Builder(context)
401                    .setContentTitle(title)
402                    .setStyle(new Notification.BigTextStyle().bigText(details))
403                    .setContentText(details)
404                    .setChannel(NotificationChannelController.CHANNEL_ID_WFC);
405        }
406    }
407}
408