1 /*
2  * Copyright (C) 2009 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 
17 package com.android.phone;
18 
19 import android.app.Notification;
20 import android.app.NotificationManager;
21 import android.app.PendingIntent;
22 import android.app.Service;
23 import android.content.BroadcastReceiver;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.os.AsyncResult;
28 import android.os.Binder;
29 import android.os.CountDownTimer;
30 import android.os.Handler;
31 import android.os.IBinder;
32 import android.os.Message;
33 import android.os.UserHandle;
34 import android.sysprop.TelephonyProperties;
35 import android.telephony.TelephonyManager;
36 import android.util.Log;
37 
38 import com.android.internal.telephony.Phone;
39 import com.android.internal.telephony.PhoneConstants;
40 import com.android.internal.telephony.TelephonyIntents;
41 import com.android.internal.telephony.util.NotificationChannelController;
42 
43 import java.text.SimpleDateFormat;
44 
45 /**
46  * Application service that inserts/removes Emergency Callback Mode notification and
47  * updates Emergency Callback Mode countdown clock in the notification
48  *
49  * @see EmergencyCallbackModeExitDialog
50  */
51 public class EmergencyCallbackModeService extends Service {
52 
53     // Default Emergency Callback Mode timeout value
54     private static final long DEFAULT_ECM_EXIT_TIMER_VALUE = 300000L;
55     private static final String LOG_TAG = "EmergencyCallbackModeService";
56 
57     private NotificationManager mNotificationManager = null;
58     private CountDownTimer mTimer = null;
59     private long mTimeLeft = 0;
60     private Phone mPhone = null;
61     private boolean mInEmergencyCall = false;
62 
63     private static final int ECM_TIMER_RESET = 1;
64 
65     private Handler mHandler = new Handler () {
66         public void handleMessage(Message msg) {
67             switch (msg.what) {
68                 case ECM_TIMER_RESET:
69                     resetEcmTimer((AsyncResult) msg.obj);
70                     break;
71             }
72         }
73     };
74 
75     @Override
onCreate()76     public void onCreate() {
77          Phone phoneInEcm = PhoneGlobals.getInstance().getPhoneInEcm();
78         // Check if it is CDMA phone
79         if (phoneInEcm == null || ((phoneInEcm.getPhoneType() != PhoneConstants.PHONE_TYPE_CDMA)
80                 && (phoneInEcm.getImsPhone() == null))) {
81             Log.e(LOG_TAG, "Error! Emergency Callback Mode not supported for " + phoneInEcm);
82             stopSelf();
83             return;
84         }
85 
86         // Register receiver for intents
87         IntentFilter filter = new IntentFilter();
88         filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
89         filter.addAction(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS);
90         registerReceiver(mEcmReceiver, filter);
91 
92         mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
93 
94         // Register ECM timer reset notfication
95         mPhone = phoneInEcm;
96         mPhone.registerForEcmTimerReset(mHandler, ECM_TIMER_RESET, null);
97 
98         startTimerNotification();
99     }
100 
101     @Override
onDestroy()102     public void onDestroy() {
103         if (mPhone != null) {
104             // Unregister receiver
105             unregisterReceiver(mEcmReceiver);
106             // Unregister ECM timer reset notification
107             mPhone.unregisterForEcmTimerReset(mHandler);
108 
109             // Cancel the notification and timer
110             mNotificationManager.cancelAsUser(null, R.string.phone_in_ecm_notification_title,
111                     UserHandle.ALL);
112             mTimer.cancel();
113         }
114     }
115 
116     /**
117      * Listens for Emergency Callback Mode intents
118      */
119     private BroadcastReceiver mEcmReceiver = new BroadcastReceiver() {
120         @Override
121         public void onReceive(Context context, Intent intent) {
122             // Stop the service when phone exits Emergency Callback Mode
123             if (intent.getAction().equals(
124                     TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED)) {
125                 if (!intent.getBooleanExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, false)) {
126                     stopSelf();
127                 }
128             }
129             // Show dialog box
130             else if (intent.getAction().equals(
131                     TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS)) {
132                     context.startActivity(
133                             new Intent(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS)
134                     .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
135             }
136         }
137     };
138 
139     /**
140      * Start timer notification for Emergency Callback Mode
141      */
startTimerNotification()142     private void startTimerNotification() {
143         // Get Emergency Callback Mode timeout value
144         long ecmTimeout = TelephonyProperties.ecm_exit_timer().orElse(DEFAULT_ECM_EXIT_TIMER_VALUE);
145 
146         // Show the notification
147         showNotification(ecmTimeout);
148 
149         // Start countdown timer for the notification updates
150         if (mTimer != null) {
151             mTimer.cancel();
152         } else {
153             mTimer = new CountDownTimer(ecmTimeout, 1000) {
154 
155                 @Override
156                 public void onTick(long millisUntilFinished) {
157                     mTimeLeft = millisUntilFinished;
158                 }
159 
160                 @Override
161                 public void onFinish() {
162                     //Do nothing
163                 }
164 
165             };
166         }
167         mTimer.start();
168     }
169 
170     /**
171      * Shows notification for Emergency Callback Mode
172      */
showNotification(long millisUntilFinished)173     private void showNotification(long millisUntilFinished) {
174         Phone imsPhone = mPhone.getImsPhone();
175         boolean isInEcm = mPhone.isInEcm() || (imsPhone != null && imsPhone.isInEcm());
176         if (!isInEcm) {
177             Log.i(LOG_TAG, "Asked to show notification but not in ECM mode");
178             if (mTimer != null) {
179                 mTimer.cancel();
180             }
181             return;
182         }
183         final Notification.Builder builder = new Notification.Builder(getApplicationContext());
184         builder.setOngoing(true);
185         builder.setPriority(Notification.PRIORITY_HIGH);
186         builder.setSmallIcon(R.drawable.ic_emergency_callback_mode);
187         builder.setTicker(getText(R.string.phone_entered_ecm_text));
188         builder.setContentTitle(getText(R.string.phone_in_ecm_notification_title));
189         builder.setColor(getResources().getColor(R.color.dialer_theme_color));
190 
191         // PendingIntent to launch Emergency Callback Mode Exit activity if the user selects
192         // this notification
193         PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
194                 new Intent(EmergencyCallbackModeExitDialog.ACTION_SHOW_ECM_EXIT_DIALOG), 0);
195         builder.setContentIntent(contentIntent);
196 
197         // Format notification string
198         String text = null;
199         if(mInEmergencyCall) {
200             text = getText(
201                     // During IMS ECM, data restriction hint should be removed.
202                     (imsPhone != null && imsPhone.isInImsEcm())
203                     ? R.string.phone_in_ecm_call_notification_text_without_data_restriction_hint
204                     : R.string.phone_in_ecm_call_notification_text).toString();
205         } else {
206             // Calculate the time in ms when the notification will be finished.
207             long finishedCountMs = millisUntilFinished + System.currentTimeMillis();
208             builder.setShowWhen(true);
209             builder.setChronometerCountDown(true);
210             builder.setUsesChronometer(true);
211             builder.setWhen(finishedCountMs);
212 
213             String completeTime = SimpleDateFormat.getTimeInstance(SimpleDateFormat.SHORT).format(
214                     finishedCountMs);
215             text = getResources().getString(
216                     // During IMS ECM, data restriction hint should be removed.
217                     (imsPhone != null && imsPhone.isInImsEcm())
218                     ? R.string.phone_in_ecm_notification_complete_time_without_data_restriction_hint
219                     : R.string.phone_in_ecm_notification_complete_time,
220                     completeTime);
221         }
222         builder.setContentText(text);
223         builder.setChannelId(NotificationChannelController.CHANNEL_ID_ALERT);
224 
225         // Show notification
226         mNotificationManager.notifyAsUser(null, R.string.phone_in_ecm_notification_title,
227                 builder.build(), UserHandle.ALL);
228     }
229 
230     /**
231      * Handle ECM_TIMER_RESET notification
232      */
resetEcmTimer(AsyncResult r)233     private void resetEcmTimer(AsyncResult r) {
234         boolean isTimerCanceled = ((Boolean)r.result).booleanValue();
235 
236         if (isTimerCanceled) {
237             mInEmergencyCall = true;
238             mTimer.cancel();
239             showNotification(0);
240         } else {
241             mInEmergencyCall = false;
242             startTimerNotification();
243         }
244     }
245 
246     @Override
onBind(Intent intent)247     public IBinder onBind(Intent intent) {
248         return mBinder;
249     }
250 
251     // This is the object that receives interactions from clients.
252     private final IBinder mBinder = new LocalBinder();
253 
254     /**
255      * Class for clients to access
256      */
257     public class LocalBinder extends Binder {
getService()258         EmergencyCallbackModeService getService() {
259             return EmergencyCallbackModeService.this;
260         }
261     }
262 
263     /**
264      * Returns Emergency Callback Mode timeout value
265      */
getEmergencyCallbackModeTimeout()266     public long getEmergencyCallbackModeTimeout() {
267         return mTimeLeft;
268     }
269 
270     /**
271      * Returns Emergency Callback Mode call state
272      */
getEmergencyCallbackModeCallState()273     public boolean getEmergencyCallbackModeCallState() {
274         return mInEmergencyCall;
275     }
276 }
277