1 /*
2  * Copyright (C) 2018 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.ActionBar;
20 import android.app.Dialog;
21 import android.content.Context;
22 import android.os.AsyncResult;
23 import android.os.Bundle;
24 import android.os.Handler;
25 import android.os.Message;
26 import android.os.PersistableBundle;
27 import android.preference.Preference;
28 import android.preference.PreferenceScreen;
29 import android.telephony.CarrierConfigManager;
30 import android.telephony.SubscriptionManager;
31 import android.telephony.TelephonyManager;
32 import android.util.Log;
33 import android.view.MenuItem;
34 import android.widget.Toast;
35 
36 import com.android.internal.telephony.CommandException;
37 import com.android.internal.telephony.CommandsInterface;
38 import com.android.internal.telephony.GsmCdmaPhone;
39 import com.android.internal.telephony.Phone;
40 import com.android.phone.settings.fdn.EditPinPreference;
41 
42 import java.util.ArrayList;
43 
44 /**
45  * Implements the preference to enable/disable calling barring options and
46  * the dialogs to change the passward.
47  */
48 public class GsmUmtsCallBarringOptions extends TimeConsumingPreferenceActivity
49         implements EditPinPreference.OnPinEnteredListener {
50     private static final String LOG_TAG = "GsmUmtsCallBarringOptions";
51     private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
52 
53     // String keys for preference lookup
54     // Preference is handled solely in xml.
55     // Block all outgoing calls
56     private static final String BUTTON_BAOC_KEY = "button_baoc_key";
57     // Block all outgoing international calls
58     private static final String BUTTON_BAOIC_KEY = "button_baoic_key";
59     // Block all outgoing international roaming calls
60     private static final String BUTTON_BAOICxH_KEY = "button_baoicxh_key";
61     // Block all incoming calls
62     private static final String BUTTON_BAIC_KEY = "button_baic_key";
63     // Block all incoming international roaming calls
64     private static final String BUTTON_BAICr_KEY = "button_baicr_key";
65     // Disable all barring
66     private static final String BUTTON_BA_ALL_KEY = "button_ba_all_key";
67     // Change passward
68     private static final String BUTTON_BA_CHANGE_PW_KEY = "button_change_pw_key";
69 
70     private static final String PW_CHANGE_STATE_KEY = "pin_change_state_key";
71     private static final String OLD_PW_KEY = "old_pw_key";
72     private static final String NEW_PW_KEY = "new_pw_key";
73     private static final String DIALOG_MESSAGE_KEY = "dialog_message_key";
74     private static final String DIALOG_PW_ENTRY_KEY = "dialog_pw_enter_key";
75     private static final String KEY_STATUS = "toggle";
76     private static final String PREFERENCE_ENABLED_KEY = "PREFERENCE_ENABLED";
77     private static final String SAVED_BEFORE_LOAD_COMPLETED_KEY = "PROGRESS_SHOWING";
78 
79     private CallBarringEditPreference mButtonBAOC;
80     private CallBarringEditPreference mButtonBAOIC;
81     private CallBarringEditPreference mButtonBAOICxH;
82     private CallBarringEditPreference mButtonBAIC;
83     private CallBarringEditPreference mButtonBAICr;
84     private CallBarringDeselectAllPreference mButtonDisableAll;
85     private EditPinPreference mButtonChangePW;
86 
87     // State variables
88     private int mPwChangeState;
89     private String mOldPassword;
90     private String mNewPassword;
91     private int mPwChangeDialogStrId;
92 
93     private static final int PW_CHANGE_OLD = 0;
94     private static final int PW_CHANGE_NEW = 1;
95     private static final int PW_CHANGE_REENTER = 2;
96 
97     private static final int BUSY_READING_DIALOG = 100;
98     private static final int BUSY_SAVING_DIALOG = 200;
99 
100     // Password change complete event
101     private static final int EVENT_PW_CHANGE_COMPLETE = 100;
102     // Disable all complete event
103     private static final int EVENT_DISABLE_ALL_COMPLETE = 200;
104 
105     private static final int PW_LENGTH = 4;
106 
107     private Phone mPhone;
108     private ArrayList<CallBarringEditPreference> mPreferences =
109             new ArrayList<CallBarringEditPreference>();
110     private int mInitIndex = 0;
111     private boolean mFirstResume;
112     private Bundle mIcicle;
113 
114     private SubscriptionInfoHelper mSubscriptionInfoHelper;
115     private Dialog mProgressDialog;
116 
117     @Override
onPinEntered(EditPinPreference preference, boolean positiveResult)118     public void onPinEntered(EditPinPreference preference, boolean positiveResult) {
119         if (preference == mButtonChangePW) {
120             updatePWChangeState(positiveResult);
121         } else if (preference == mButtonDisableAll) {
122             disableAllBarring(positiveResult);
123         }
124     }
125 
126     /**
127      * Display a toast for message.
128      */
displayMessage(int strId)129     private void displayMessage(int strId) {
130         Toast.makeText(this, getString(strId), Toast.LENGTH_SHORT).show();
131     }
132 
133     /**
134      * Attempt to disable all for call barring settings.
135      */
disableAllBarring(boolean positiveResult)136     private void disableAllBarring(boolean positiveResult) {
137         if (!positiveResult) {
138             // Return on cancel
139             return;
140         }
141 
142         String password = mButtonDisableAll.getText();
143         // Validate the length of password first, before submitting it to the
144         // RIL for CB disable.
145         if (!validatePassword(password)) {
146             mButtonDisableAll.setText("");
147             displayMessage(R.string.call_barring_right_pwd_number);
148             return;
149         }
150 
151         // Submit the disable all request
152         mButtonDisableAll.setText("");
153         Message onComplete = mHandler.obtainMessage(EVENT_DISABLE_ALL_COMPLETE);
154         mPhone.setCallBarring(CommandsInterface.CB_FACILITY_BA_ALL, false, password, onComplete, 0);
155         this.onStarted(mButtonDisableAll, false);
156     }
157 
158     /**
159      * Attempt to change the password for call barring settings.
160      */
updatePWChangeState(boolean positiveResult)161     private void updatePWChangeState(boolean positiveResult) {
162         if (!positiveResult) {
163             // Reset the state on cancel
164             resetPwChangeState();
165             return;
166         }
167 
168         // Progress through the dialog states, generally in this order:
169         // 1. Enter old password
170         // 2. Enter new password
171         // 3. Re-Enter new password
172         // In general, if any invalid entries are made, the dialog re-
173         // appears with text to indicate what the issue is.
174         switch (mPwChangeState) {
175             case PW_CHANGE_OLD:
176                 mOldPassword = mButtonChangePW.getText();
177                 mButtonChangePW.setText("");
178                 if (validatePassword(mOldPassword)) {
179                     mPwChangeState = PW_CHANGE_NEW;
180                     displayPwChangeDialog();
181                 } else {
182                     displayPwChangeDialog(R.string.call_barring_right_pwd_number, true);
183                 }
184                 break;
185             case PW_CHANGE_NEW:
186                 mNewPassword = mButtonChangePW.getText();
187                 mButtonChangePW.setText("");
188                 if (validatePassword(mNewPassword)) {
189                     mPwChangeState = PW_CHANGE_REENTER;
190                     displayPwChangeDialog();
191                 } else {
192                     displayPwChangeDialog(R.string.call_barring_right_pwd_number, true);
193                 }
194                 break;
195             case PW_CHANGE_REENTER:
196                 // If the re-entered password is not valid, display a message
197                 // and reset the state.
198                 if (!mNewPassword.equals(mButtonChangePW.getText())) {
199                     mPwChangeState = PW_CHANGE_NEW;
200                     mButtonChangePW.setText("");
201                     displayPwChangeDialog(R.string.call_barring_pwd_not_match, true);
202                 } else {
203                     // If the password is valid, then submit the change password request
204                     mButtonChangePW.setText("");
205                     Message onComplete = mHandler.obtainMessage(EVENT_PW_CHANGE_COMPLETE);
206                     ((GsmCdmaPhone) mPhone).changeCallBarringPassword(
207                             CommandsInterface.CB_FACILITY_BA_ALL,
208                             mOldPassword, mNewPassword, onComplete);
209                     this.onStarted(mButtonChangePW, false);
210                 }
211                 break;
212             default:
213                 if (DBG) {
214                     Log.d(LOG_TAG, "updatePWChangeState: Unknown password change state: "
215                             + mPwChangeState);
216                 }
217                 break;
218         }
219     }
220 
221     /**
222      * Handler for asynchronous replies from the framework layer.
223      */
224     private Handler mHandler = new Handler() {
225         @Override
226         public void handleMessage(Message msg) {
227             AsyncResult ar = (AsyncResult) msg.obj;
228             switch (msg.what) {
229                 // Handle the response message for password change from the framework layer.
230                 case EVENT_PW_CHANGE_COMPLETE: {
231                     onFinished(mButtonChangePW, false);
232                     // Unsuccessful change, display a toast to user with failure reason.
233                     if (ar.exception != null) {
234                         if (DBG) {
235                             Log.d(LOG_TAG,
236                                     "change password for call barring failed with exception: "
237                                             + ar.exception);
238                         }
239                         CommandException commandException = (CommandException) ar.exception;
240                         onException(mButtonChangePW, commandException);
241                         if (commandException.getCommandError()
242                                 != CommandException.Error.FDN_CHECK_FAILURE) {
243                             // Not a FDN_CHECK_FAILURE, enable mButtonChangePW
244                             mButtonChangePW.setEnabled(true);
245                         }
246                     } else if (ar.userObj instanceof Throwable) {
247                         onError(mButtonChangePW, RESPONSE_ERROR);
248                     } else {
249                         // Successful change.
250                         displayMessage(R.string.call_barring_change_pwd_success);
251                     }
252                     resetPwChangeState();
253                     break;
254                 }
255                 // When disabling all call barring, either fail and display a toast,
256                 // or just update the UI.
257                 case EVENT_DISABLE_ALL_COMPLETE: {
258                     onFinished(mButtonDisableAll, false);
259                     if (ar.exception != null) {
260                         if (DBG) {
261                             Log.d(LOG_TAG, "can not disable all call barring with exception: "
262                                     + ar.exception);
263                         }
264                         onException(mButtonDisableAll, (CommandException) ar.exception);
265                         mButtonDisableAll.setEnabled(true);
266                     } else if (ar.userObj instanceof Throwable) {
267                         onError(mButtonDisableAll, RESPONSE_ERROR);
268                     } else {
269                         // Reset to normal behaviour on successful change.
270                         displayMessage(R.string.call_barring_deactivate_success);
271                         resetCallBarringPrefState(false);
272                     }
273                     break;
274                 }
275                 default: {
276                     if (DBG) {
277                         Log.d(LOG_TAG, "Unknown message id: " + msg.what);
278                     }
279                     break;
280                 }
281             }
282         }
283     };
284 
285     /**
286      * The next two functions are for updating the message field on the dialog.
287      */
displayPwChangeDialog()288     private void displayPwChangeDialog() {
289         displayPwChangeDialog(0, true);
290     }
291 
displayPwChangeDialog(int strId, boolean shouldDisplay)292     private void displayPwChangeDialog(int strId, boolean shouldDisplay) {
293         int msgId = 0;
294         switch (mPwChangeState) {
295             case PW_CHANGE_OLD:
296                 msgId = R.string.call_barring_old_pwd;
297                 break;
298             case PW_CHANGE_NEW:
299                 msgId = R.string.call_barring_new_pwd;
300                 break;
301             case PW_CHANGE_REENTER:
302                 msgId = R.string.call_barring_confirm_pwd;
303                 break;
304             default:
305                 break;
306         }
307 
308         // Append the note/additional message, if needed.
309         if (strId != 0) {
310             mButtonChangePW.setDialogMessage(getText(msgId) + "\n" + getText(strId));
311         } else {
312             mButtonChangePW.setDialogMessage(msgId);
313         }
314 
315         // Only display if requested.
316         if (shouldDisplay) {
317             mButtonChangePW.showPinDialog();
318         }
319         mPwChangeDialogStrId = strId;
320     }
321 
322     /**
323      * Reset the state of the password change dialog.
324      */
resetPwChangeState()325     private void resetPwChangeState() {
326         mPwChangeState = PW_CHANGE_OLD;
327         displayPwChangeDialog(0, false);
328         mOldPassword = "";
329         mNewPassword = "";
330     }
331 
332     /**
333      * Reset the state of the all call barring setting to disable.
334      */
resetCallBarringPrefState(boolean enable)335     private void resetCallBarringPrefState(boolean enable) {
336         for (CallBarringEditPreference pref : mPreferences) {
337             pref.mIsActivated = enable;
338             pref.updateSummaryText();
339         }
340     }
341 
342     /**
343      * Validate the password entry.
344      *
345      * @param password This is the password to validate
346      */
validatePassword(String password)347     private boolean validatePassword(String password) {
348         return password != null && password.length() == PW_LENGTH;
349     }
350 
351     @Override
onCreate(Bundle icicle)352     protected void onCreate(Bundle icicle) {
353         super.onCreate(icicle);
354         if (DBG) {
355             Log.d(LOG_TAG, "onCreate, reading callbarring_options.xml file");
356         }
357         addPreferencesFromResource(R.xml.callbarring_options);
358 
359         mSubscriptionInfoHelper = new SubscriptionInfoHelper(this, getIntent());
360         mPhone = mSubscriptionInfoHelper.getPhone();
361         if (DBG) {
362             Log.d(LOG_TAG, "onCreate, reading callbarring_options.xml file finished!");
363         }
364 
365         CarrierConfigManager configManager = (CarrierConfigManager)
366                 mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
367         PersistableBundle carrierConfig;
368         if (mSubscriptionInfoHelper.hasSubId()) {
369             carrierConfig = configManager.getConfigForSubId(mSubscriptionInfoHelper.getSubId());
370         } else {
371             carrierConfig = configManager.getConfig();
372         }
373         boolean isPwChangeButtonVisible = true;
374         boolean isDisableAllButtonVisible = true;
375         if (carrierConfig != null) {
376             isPwChangeButtonVisible = carrierConfig.getBoolean(
377                     CarrierConfigManager.KEY_CALL_BARRING_SUPPORTS_PASSWORD_CHANGE_BOOL, true);
378             isDisableAllButtonVisible = carrierConfig.getBoolean(
379                     CarrierConfigManager.KEY_CALL_BARRING_SUPPORTS_DEACTIVATE_ALL_BOOL, true);
380         } else {
381             Log.w(LOG_TAG, "Couldn't access CarrierConfig bundle");
382         }
383 
384         // Get UI object references
385         PreferenceScreen prefSet = getPreferenceScreen();
386         mButtonBAOC = (CallBarringEditPreference) prefSet.findPreference(BUTTON_BAOC_KEY);
387         mButtonBAOIC = (CallBarringEditPreference) prefSet.findPreference(BUTTON_BAOIC_KEY);
388         mButtonBAOICxH = (CallBarringEditPreference) prefSet.findPreference(BUTTON_BAOICxH_KEY);
389         mButtonBAIC = (CallBarringEditPreference) prefSet.findPreference(BUTTON_BAIC_KEY);
390         mButtonBAICr = (CallBarringEditPreference) prefSet.findPreference(BUTTON_BAICr_KEY);
391         mButtonDisableAll = (CallBarringDeselectAllPreference)
392                 prefSet.findPreference(BUTTON_BA_ALL_KEY);
393         mButtonChangePW = (EditPinPreference) prefSet.findPreference(BUTTON_BA_CHANGE_PW_KEY);
394 
395         // Some carriers do not use PW change and disable all buttons. Hide them if this is the
396         // case.
397         if (!isDisableAllButtonVisible) {
398             prefSet.removePreference(mButtonDisableAll);
399         }
400         if (!isPwChangeButtonVisible) {
401             prefSet.removePreference(mButtonChangePW);
402         }
403 
404         // Assign click listener and update state
405         mButtonBAOC.setOnPinEnteredListener(this);
406         mButtonBAOIC.setOnPinEnteredListener(this);
407         mButtonBAOICxH.setOnPinEnteredListener(this);
408         mButtonBAIC.setOnPinEnteredListener(this);
409         mButtonBAICr.setOnPinEnteredListener(this);
410         mButtonDisableAll.setOnPinEnteredListener(this);
411         mButtonChangePW.setOnPinEnteredListener(this);
412 
413         // Store CallBarringEditPreferencence objects in array list.
414         mPreferences.add(mButtonBAOC);
415         mPreferences.add(mButtonBAOIC);
416         mPreferences.add(mButtonBAOICxH);
417         mPreferences.add(mButtonBAIC);
418         mPreferences.add(mButtonBAICr);
419 
420         // Find out if the sim card is ready.
421         boolean isSimReady = TelephonyManager.from(this).getSimState(
422                 SubscriptionManager.getSlotIndex(mPhone.getSubId()))
423                         == TelephonyManager.SIM_STATE_READY;
424 
425         // Deactivate all option and Change password option are unavailable
426         // when sim card is not ready.
427         if (isSimReady) {
428             mButtonDisableAll.setEnabled(true);
429             mButtonChangePW.setEnabled(true);
430         } else {
431             mButtonDisableAll.setEnabled(false);
432             mButtonChangePW.setEnabled(false);
433             mButtonChangePW.setSummary(R.string.call_barring_change_pwd_description_disabled);
434         }
435 
436         // Wait to do the initialization until onResume so that the TimeConsumingPreferenceActivity
437         // dialog can display as it relies on onResume / onPause to maintain its foreground state.
438         mFirstResume = true;
439         mIcicle = icicle;
440 
441         ActionBar actionBar = getActionBar();
442         if (actionBar != null) {
443             // android.R.id.home will be triggered in onOptionsItemSelected()
444             actionBar.setDisplayHomeAsUpEnabled(true);
445         }
446 
447         if (mIcicle != null && !mIcicle.getBoolean(SAVED_BEFORE_LOAD_COMPLETED_KEY)) {
448             if (DBG) {
449                 Log.d(LOG_TAG, "restore stored states");
450             }
451             mInitIndex = mPreferences.size();
452 
453             for (CallBarringEditPreference pref : mPreferences) {
454                 Bundle bundle = mIcicle.getParcelable(pref.getKey());
455                 if (bundle != null) {
456                     pref.handleCallBarringResult(bundle.getBoolean(KEY_STATUS));
457                     pref.init(this, true, mPhone);
458                     pref.setEnabled(bundle.getBoolean(PREFERENCE_ENABLED_KEY, pref.isEnabled()));
459                 }
460             }
461             mPwChangeState = mIcicle.getInt(PW_CHANGE_STATE_KEY);
462             mOldPassword = mIcicle.getString(OLD_PW_KEY);
463             mNewPassword = mIcicle.getString(NEW_PW_KEY);
464             displayPwChangeDialog(mIcicle.getInt(DIALOG_MESSAGE_KEY, mPwChangeDialogStrId), false);
465             mButtonChangePW.setText(mIcicle.getString(DIALOG_PW_ENTRY_KEY));
466         }
467     }
468 
469     @Override
onResume()470     public void onResume() {
471         super.onResume();
472 
473         if (mFirstResume) {
474             if (mIcicle == null || mIcicle.getBoolean(SAVED_BEFORE_LOAD_COMPLETED_KEY)) {
475                 if (DBG) {
476                     Log.d(LOG_TAG, "onResume: start to init ");
477                 }
478                 resetPwChangeState();
479                 mPreferences.get(mInitIndex).init(this, false, mPhone);
480 
481                 // Request removing BUSY_SAVING_DIALOG because reading is restarted.
482                 // (If it doesn't exist, nothing happen.)
483                 removeDialog(BUSY_SAVING_DIALOG);
484             }
485             mFirstResume = false;
486             mIcicle = null;
487         }
488     }
489 
490     @Override
onSaveInstanceState(Bundle outState)491     protected void onSaveInstanceState(Bundle outState) {
492         super.onSaveInstanceState(outState);
493 
494         for (CallBarringEditPreference pref : mPreferences) {
495             Bundle bundle = new Bundle();
496             bundle.putBoolean(KEY_STATUS, pref.mIsActivated);
497             bundle.putBoolean(PREFERENCE_ENABLED_KEY, pref.isEnabled());
498             outState.putParcelable(pref.getKey(), bundle);
499         }
500         outState.putInt(PW_CHANGE_STATE_KEY, mPwChangeState);
501         outState.putString(OLD_PW_KEY, mOldPassword);
502         outState.putString(NEW_PW_KEY, mNewPassword);
503         outState.putInt(DIALOG_MESSAGE_KEY, mPwChangeDialogStrId);
504         outState.putString(DIALOG_PW_ENTRY_KEY, mButtonChangePW.getText());
505 
506         outState.putBoolean(SAVED_BEFORE_LOAD_COMPLETED_KEY,
507                 mProgressDialog != null && mProgressDialog.isShowing());
508     }
509 
510     /**
511      * Finish initialization of this preference and start next.
512      *
513      * @param preference The preference.
514      * @param reading If true to dismiss the busy reading dialog,
515      *                false to dismiss the busy saving dialog.
516      */
onFinished(Preference preference, boolean reading)517     public void onFinished(Preference preference, boolean reading) {
518         if (mInitIndex < mPreferences.size() - 1 && !isFinishing()) {
519             mInitIndex++;
520             mPreferences.get(mInitIndex).init(this, false, mPhone);
521         }
522         super.onFinished(preference, reading);
523     }
524 
525     @Override
onOptionsItemSelected(MenuItem item)526     public boolean onOptionsItemSelected(MenuItem item) {
527         final int itemId = item.getItemId();
528         if (itemId == android.R.id.home) {
529             CallFeaturesSetting.goUpToTopLevelSetting(this, mSubscriptionInfoHelper);
530             return true;
531         }
532         return super.onOptionsItemSelected(item);
533     }
534 
535     @Override
onPrepareDialog(int id, Dialog dialog, Bundle args)536     protected void onPrepareDialog(int id, Dialog dialog, Bundle args) {
537         super.onPrepareDialog(id, dialog, args);
538         if (id == BUSY_READING_DIALOG || id == BUSY_SAVING_DIALOG) {
539             // For onSaveInstanceState, treat the SAVING dialog as the same as the READING. As
540             // the result, if the activity is recreated while waiting for SAVING, it starts reading
541             // all the newest data.
542             mProgressDialog = dialog;
543         }
544     }
545 }
546