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.Activity;
20 import android.app.AlertDialog;
21 import android.app.Dialog;
22 import android.app.ProgressDialog;
23 import android.content.BroadcastReceiver;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.DialogInterface;
27 import android.content.DialogInterface.OnCancelListener;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.content.ServiceConnection;
31 import android.os.AsyncResult;
32 import android.os.Bundle;
33 import android.os.CountDownTimer;
34 import android.os.Handler;
35 import android.os.IBinder;
36 import android.os.Looper;
37 import android.os.Message;
38 import android.telephony.TelephonyManager;
39 import android.util.Log;
40 
41 import com.android.internal.telephony.Phone;
42 import com.android.internal.telephony.TelephonyIntents;
43 
44 /**
45  * Displays dialog that enables users to exit Emergency Callback Mode
46  *
47  * @see EmergencyCallbackModeService
48  */
49 public class EmergencyCallbackModeExitDialog extends Activity implements OnCancelListener {
50 
51     private static final String TAG = "EmergencyCallbackMode";
52 
53     /** Intent to trigger the Emergency Callback Mode exit dialog */
54     static final String ACTION_SHOW_ECM_EXIT_DIALOG =
55             "com.android.phone.action.ACTION_SHOW_ECM_EXIT_DIALOG";
56 
57     public static final int EXIT_ECM_BLOCK_OTHERS = 1;
58     public static final int EXIT_ECM_DIALOG = 2;
59     public static final int EXIT_ECM_PROGRESS_DIALOG = 3;
60     public static final int EXIT_ECM_IN_EMERGENCY_CALL_DIALOG = 4;
61 
62     AlertDialog mAlertDialog = null;
63     ProgressDialog mProgressDialog = null;
64     CountDownTimer mTimer = null;
65     EmergencyCallbackModeService mService = null;
66     Handler mHandler = null;
67     int mDialogType = 0;
68     long mEcmTimeout = 0;
69     private boolean mInEmergencyCall = false;
70     private static final int ECM_TIMER_RESET = 1;
71     private Phone mPhone = null;
72     private boolean mIsResumed = false;
73 
74     @Override
onCreate(Bundle savedInstanceState)75     public void onCreate(Bundle savedInstanceState) {
76         super.onCreate(savedInstanceState);
77 
78         mPhone = PhoneGlobals.getInstance().getPhoneInEcm();
79         // Check if phone is in Emergency Callback Mode. If not, exit.
80         if (mPhone == null || !mPhone.isInEcm()) {
81             Log.i(TAG, "ECMModeExitDialog launched - isInEcm: false" + " phone:" + mPhone);
82             finish();
83             return;
84         }
85         Log.i(TAG, "ECMModeExitDialog launched - isInEcm: true" + " phone:" + mPhone);
86 
87         mHandler = new Handler();
88 
89         // Start thread that will wait for the connection completion so that it can get
90         // timeout value from the service
91         Thread waitForConnectionCompleteThread = new Thread(null, mTask,
92                 "EcmExitDialogWaitThread");
93         waitForConnectionCompleteThread.start();
94 
95         // Register ECM timer reset notfication
96         mPhone.registerForEcmTimerReset(mTimerResetHandler, ECM_TIMER_RESET, null);
97 
98         // Register receiver for intent closing the dialog
99         IntentFilter filter = new IntentFilter();
100         filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
101         registerReceiver(mEcmExitReceiver, filter);
102     }
103 
104     @Override
onResume()105     public void onResume() {
106         super.onResume();
107         mIsResumed = true;
108     }
109 
110     @Override
onPause()111     public void onPause() {
112         super.onPause();
113         mIsResumed = false;
114     }
115 
116     @Override
onDestroy()117     public void onDestroy() {
118         super.onDestroy();
119         try {
120             unregisterReceiver(mEcmExitReceiver);
121         } catch (IllegalArgumentException e) {
122             // Receiver was never registered - silently ignore.
123         }
124         // Unregister ECM timer reset notification
125         if (mPhone != null) {
126             mPhone.unregisterForEcmTimerReset(mHandler);
127         }
128     }
129 
130     @Override
onRestoreInstanceState(Bundle savedInstanceState)131     protected void onRestoreInstanceState(Bundle savedInstanceState) {
132         super.onRestoreInstanceState(savedInstanceState);
133         mDialogType = savedInstanceState.getInt("DIALOG_TYPE");
134     }
135 
136     @Override
onSaveInstanceState(Bundle outState)137     protected void onSaveInstanceState(Bundle outState) {
138         super.onSaveInstanceState(outState);
139         outState.putInt("DIALOG_TYPE", mDialogType);
140     }
141 
142     /**
143      * Waits until bind to the service completes
144      */
145     private Runnable mTask = new Runnable() {
146         public void run() {
147             Looper.prepare();
148 
149             // Bind to the remote service
150             bindService(new Intent(EmergencyCallbackModeExitDialog.this,
151                     EmergencyCallbackModeService.class), mConnection, Context.BIND_AUTO_CREATE);
152 
153             // Wait for bind to finish
154             synchronized (EmergencyCallbackModeExitDialog.this) {
155                 try {
156                     if (mService == null) {
157                         EmergencyCallbackModeExitDialog.this.wait();
158                     }
159                 } catch (InterruptedException e) {
160                     Log.d("ECM", "EmergencyCallbackModeExitDialog InterruptedException: "
161                             + e.getMessage());
162                     e.printStackTrace();
163                 }
164             }
165 
166             // Get timeout value and call state from the service
167             if (mService != null) {
168                 mEcmTimeout = mService.getEmergencyCallbackModeTimeout();
169                 mInEmergencyCall = mService.getEmergencyCallbackModeCallState();
170                 try {
171                     // Unbind from remote service
172                     unbindService(mConnection);
173                 } catch (IllegalArgumentException e) {
174                     // Failed to unbind from service. Don't crash as this brings down the entire
175                     // radio.
176                     Log.w(TAG, "Failed to unbind from EmergencyCallbackModeService");
177                 }
178             }
179 
180             // Show dialog
181             mHandler.post(new Runnable() {
182                 public void run() {
183                     showEmergencyCallbackModeExitDialog();
184                 }
185             });
186         }
187     };
188 
189     /**
190      * Shows Emergency Callback Mode dialog and starts countdown timer
191      */
showEmergencyCallbackModeExitDialog()192     private void showEmergencyCallbackModeExitDialog() {
193         if (isDestroyed()) {
194             Log.w(TAG, "Tried to show dialog, but activity was already finished");
195             return;
196         }
197         if(mInEmergencyCall) {
198             mDialogType = EXIT_ECM_IN_EMERGENCY_CALL_DIALOG;
199             showDialog(EXIT_ECM_IN_EMERGENCY_CALL_DIALOG);
200         } else {
201             if (getIntent().getAction().equals(
202                     TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS)) {
203                 mDialogType = EXIT_ECM_BLOCK_OTHERS;
204                 showDialog(EXIT_ECM_BLOCK_OTHERS);
205             } else if (getIntent().getAction().equals(ACTION_SHOW_ECM_EXIT_DIALOG)) {
206                 mDialogType = EXIT_ECM_DIALOG;
207                 showDialog(EXIT_ECM_DIALOG);
208             }
209 
210             mTimer = new CountDownTimer(mEcmTimeout, 1000) {
211                 @Override
212                 public void onTick(long millisUntilFinished) {
213                     CharSequence text = getDialogText(millisUntilFinished);
214                     mAlertDialog.setMessage(text);
215                 }
216 
217                 @Override
218                 public void onFinish() {
219                     //Do nothing
220                 }
221             }.start();
222         }
223     }
224 
225     /**
226      * Creates dialog that enables users to exit Emergency Callback Mode
227      */
228     @Override
onCreateDialog(int id)229     protected Dialog onCreateDialog(int id) {
230         switch (id) {
231         case EXIT_ECM_BLOCK_OTHERS:
232         case EXIT_ECM_DIALOG:
233             CharSequence text = getDialogText(mEcmTimeout);
234             mAlertDialog = new AlertDialog.Builder(EmergencyCallbackModeExitDialog.this,
235                     android.R.style.Theme_DeviceDefault_Dialog_Alert)
236                     .setIcon(R.drawable.ic_emergency_callback_mode)
237                     .setTitle(R.string.phone_in_ecm_notification_title)
238                     .setMessage(text)
239                     .setPositiveButton(R.string.alert_dialog_yes,
240                             new DialogInterface.OnClickListener() {
241                                 public void onClick(DialogInterface dialog,int whichButton) {
242                                     // User clicked Yes. Exit Emergency Callback Mode.
243                                     mPhone.exitEmergencyCallbackMode();
244 
245                                     // Show progress dialog
246                                     showDialog(EXIT_ECM_PROGRESS_DIALOG);
247                                     mTimer.cancel();
248                                 }
249                             })
250                     .setNegativeButton(R.string.alert_dialog_no,
251                             new DialogInterface.OnClickListener() {
252                                 public void onClick(DialogInterface dialog, int whichButton) {
253                                     // User clicked No
254                                     setResult(RESULT_CANCELED);
255                                     finish();
256                                 }
257                             }).create();
258             mAlertDialog.setOnCancelListener(this);
259             return mAlertDialog;
260 
261         case EXIT_ECM_IN_EMERGENCY_CALL_DIALOG:
262             mAlertDialog = new AlertDialog.Builder(EmergencyCallbackModeExitDialog.this,
263                     android.R.style.Theme_DeviceDefault_Dialog_Alert)
264                     .setIcon(R.drawable.ic_emergency_callback_mode)
265                     .setTitle(R.string.phone_in_ecm_notification_title)
266                     .setMessage(R.string.alert_dialog_in_ecm_call)
267                     .setNeutralButton(R.string.alert_dialog_dismiss,
268                             new DialogInterface.OnClickListener() {
269                                 public void onClick(DialogInterface dialog, int whichButton) {
270                                     // User clicked Dismiss
271                                     setResult(RESULT_CANCELED);
272                                     finish();
273                                 }
274                             }).create();
275             mAlertDialog.setOnCancelListener(this);
276             return mAlertDialog;
277 
278         case EXIT_ECM_PROGRESS_DIALOG:
279             mProgressDialog = new ProgressDialog(EmergencyCallbackModeExitDialog.this);
280             mProgressDialog.setMessage(getText(R.string.progress_dialog_exiting_ecm));
281             mProgressDialog.setIndeterminate(true);
282             mProgressDialog.setCancelable(false);
283             return mProgressDialog;
284 
285         default:
286             return null;
287         }
288     }
289 
290     /**
291      * Returns dialog box text with updated timeout value
292      */
getDialogText(long millisUntilFinished)293     private CharSequence getDialogText(long millisUntilFinished) {
294         // Format time
295         int minutes = (int)(millisUntilFinished / 60000);
296         String time = String.format("%d:%02d", minutes,
297                 (millisUntilFinished % 60000) / 1000);
298 
299         switch (mDialogType) {
300         case EXIT_ECM_BLOCK_OTHERS:
301             return String.format(getResources().getQuantityText(
302                     R.plurals.alert_dialog_not_avaialble_in_ecm, minutes).toString(), time);
303         case EXIT_ECM_DIALOG:
304                 boolean shouldRestrictData = mPhone.getImsPhone() != null
305                         && mPhone.getImsPhone().isInImsEcm();
306                 return String.format(getResources().getQuantityText(
307                         // During IMS ECM, data restriction hint should be removed.
308                         shouldRestrictData
309                         ? R.plurals.alert_dialog_exit_ecm_without_data_restriction_hint
310                         : R.plurals.alert_dialog_exit_ecm,
311                         minutes).toString(), time);
312         }
313         return null;
314     }
315 
316     /**
317      * Closes activity when dialog is canceled
318      */
319     @Override
onCancel(DialogInterface dialog)320     public void onCancel(DialogInterface dialog) {
321         EmergencyCallbackModeExitDialog.this.setResult(RESULT_CANCELED);
322         finish();
323     }
324 
325     /**
326      * Listens for Emergency Callback Mode state change intents
327      */
328     private BroadcastReceiver mEcmExitReceiver = new BroadcastReceiver() {
329         @Override
330         public void onReceive(Context context, Intent intent) {
331             // Received exit Emergency Callback Mode notification close all dialogs
332             if (intent.getAction().equals(
333                     TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED)) {
334                 // Cancel if the sticky broadcast extra for whether or not we are in ECM is false.
335                 if (!intent.getBooleanExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, false)) {
336                     if (mAlertDialog != null)
337                         mAlertDialog.dismiss();
338                     if (mProgressDialog != null)
339                         mProgressDialog.dismiss();
340                     EmergencyCallbackModeExitDialog.this.setResult(RESULT_OK);
341                     finish();
342                 }
343             }
344         }
345     };
346 
347     /**
348      * Class for interacting with the interface of the service
349      */
350     private ServiceConnection mConnection = new ServiceConnection() {
351         public void onServiceConnected(ComponentName className, IBinder service) {
352             mService = ((EmergencyCallbackModeService.LocalBinder)service).getService();
353             // Notify thread that connection is ready
354             synchronized (EmergencyCallbackModeExitDialog.this) {
355                 EmergencyCallbackModeExitDialog.this.notify();
356             }
357         }
358 
359         public void onServiceDisconnected(ComponentName className) {
360             mService = null;
361         }
362     };
363 
364     /**
365      * Class for receiving framework timer reset notifications
366      */
367     private Handler mTimerResetHandler = new Handler () {
368         public void handleMessage(Message msg) {
369             switch (msg.what) {
370                 case ECM_TIMER_RESET:
371                     if(!((Boolean)((AsyncResult) msg.obj).result).booleanValue()) {
372                         EmergencyCallbackModeExitDialog.this.setResult(RESULT_CANCELED);
373                         finish();
374                     }
375                     break;
376             }
377         }
378     };
379 }
380