1/*
2 * Copyright (C) 2015 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.dialer.blocking;
18
19import android.app.AlertDialog;
20import android.app.Dialog;
21import android.app.DialogFragment;
22import android.app.FragmentManager;
23import android.content.ContentValues;
24import android.content.Context;
25import android.content.DialogInterface;
26import android.net.Uri;
27import android.os.Bundle;
28import android.support.design.widget.Snackbar;
29import android.telephony.PhoneNumberUtils;
30import android.text.TextUtils;
31import android.view.View;
32import android.widget.Toast;
33import com.android.contacts.common.util.ContactDisplayUtils;
34import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler.OnBlockNumberListener;
35import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler.OnUnblockNumberListener;
36import com.android.dialer.logging.InteractionEvent;
37import com.android.dialer.logging.Logger;
38import com.android.dialer.voicemailstatus.VisualVoicemailEnabledChecker;
39
40/**
41 * Fragment for confirming and enacting blocking/unblocking a number. Also invokes snackbar
42 * providing undo functionality.
43 */
44public class BlockNumberDialogFragment extends DialogFragment {
45
46  private static final String BLOCK_DIALOG_FRAGMENT = "BlockNumberDialog";
47  private static final String ARG_BLOCK_ID = "argBlockId";
48  private static final String ARG_NUMBER = "argNumber";
49  private static final String ARG_COUNTRY_ISO = "argCountryIso";
50  private static final String ARG_DISPLAY_NUMBER = "argDisplayNumber";
51  private static final String ARG_PARENT_VIEW_ID = "parentViewId";
52  private String mNumber;
53  private String mDisplayNumber;
54  private String mCountryIso;
55  private FilteredNumberAsyncQueryHandler mHandler;
56  private View mParentView;
57  private VisualVoicemailEnabledChecker mVoicemailEnabledChecker;
58  private Callback mCallback;
59
60  public static BlockNumberDialogFragment show(
61      Integer blockId,
62      String number,
63      String countryIso,
64      String displayNumber,
65      Integer parentViewId,
66      FragmentManager fragmentManager,
67      Callback callback) {
68    final BlockNumberDialogFragment newFragment =
69        BlockNumberDialogFragment.newInstance(
70            blockId, number, countryIso, displayNumber, parentViewId);
71
72    newFragment.setCallback(callback);
73    newFragment.show(fragmentManager, BlockNumberDialogFragment.BLOCK_DIALOG_FRAGMENT);
74    return newFragment;
75  }
76
77  private static BlockNumberDialogFragment newInstance(
78      Integer blockId,
79      String number,
80      String countryIso,
81      String displayNumber,
82      Integer parentViewId) {
83    final BlockNumberDialogFragment fragment = new BlockNumberDialogFragment();
84    final Bundle args = new Bundle();
85    if (blockId != null) {
86      args.putInt(ARG_BLOCK_ID, blockId.intValue());
87    }
88    if (parentViewId != null) {
89      args.putInt(ARG_PARENT_VIEW_ID, parentViewId.intValue());
90    }
91    args.putString(ARG_NUMBER, number);
92    args.putString(ARG_COUNTRY_ISO, countryIso);
93    args.putString(ARG_DISPLAY_NUMBER, displayNumber);
94    fragment.setArguments(args);
95    return fragment;
96  }
97
98  public void setFilteredNumberAsyncQueryHandlerForTesting(
99      FilteredNumberAsyncQueryHandler handler) {
100    mHandler = handler;
101  }
102
103  @Override
104  public Context getContext() {
105    return getActivity();
106  }
107
108  @Override
109  public Dialog onCreateDialog(Bundle savedInstanceState) {
110    super.onCreateDialog(savedInstanceState);
111    final boolean isBlocked = getArguments().containsKey(ARG_BLOCK_ID);
112
113    mNumber = getArguments().getString(ARG_NUMBER);
114    mDisplayNumber = getArguments().getString(ARG_DISPLAY_NUMBER);
115    mCountryIso = getArguments().getString(ARG_COUNTRY_ISO);
116
117    if (TextUtils.isEmpty(mDisplayNumber)) {
118      mDisplayNumber = mNumber;
119    }
120
121    mHandler = new FilteredNumberAsyncQueryHandler(getContext());
122    mVoicemailEnabledChecker = new VisualVoicemailEnabledChecker(getActivity(), null);
123    // Choose not to update VoicemailEnabledChecker, as checks should already been done in
124    // all current use cases.
125    mParentView = getActivity().findViewById(getArguments().getInt(ARG_PARENT_VIEW_ID));
126
127    CharSequence title;
128    String okText;
129    String message;
130    if (isBlocked) {
131      title = null;
132      okText = getString(R.string.unblock_number_ok);
133      message =
134          ContactDisplayUtils.getTtsSpannedPhoneNumber(
135                  getResources(), R.string.unblock_number_confirmation_title, mDisplayNumber)
136              .toString();
137    } else {
138      title =
139          ContactDisplayUtils.getTtsSpannedPhoneNumber(
140              getResources(), R.string.block_number_confirmation_title, mDisplayNumber);
141      okText = getString(R.string.block_number_ok);
142      if (FilteredNumberCompat.useNewFiltering(getContext())) {
143        message = getString(R.string.block_number_confirmation_message_new_filtering);
144      } else if (mVoicemailEnabledChecker.isVisualVoicemailEnabled()) {
145        message = getString(R.string.block_number_confirmation_message_vvm);
146      } else {
147        message = getString(R.string.block_number_confirmation_message_no_vvm);
148      }
149    }
150
151    AlertDialog.Builder builder =
152        new AlertDialog.Builder(getActivity())
153            .setTitle(title)
154            .setMessage(message)
155            .setPositiveButton(
156                okText,
157                new DialogInterface.OnClickListener() {
158                  @Override
159                  public void onClick(DialogInterface dialog, int id) {
160                    if (isBlocked) {
161                      unblockNumber();
162                    } else {
163                      blockNumber();
164                    }
165                  }
166                })
167            .setNegativeButton(android.R.string.cancel, null);
168    return builder.create();
169  }
170
171  @Override
172  public void onActivityCreated(Bundle savedInstanceState) {
173    super.onActivityCreated(savedInstanceState);
174    String e164Number = PhoneNumberUtils.formatNumberToE164(mNumber, mCountryIso);
175    if (!FilteredNumbersUtil.canBlockNumber(getContext(), e164Number, mNumber)) {
176      dismiss();
177      Toast.makeText(
178              getContext(),
179              ContactDisplayUtils.getTtsSpannedPhoneNumber(
180                  getResources(), R.string.invalidNumber, mDisplayNumber),
181              Toast.LENGTH_SHORT)
182          .show();
183    }
184  }
185
186  @Override
187  public void onPause() {
188    // Dismiss on rotation.
189    dismiss();
190    mCallback = null;
191
192    super.onPause();
193  }
194
195  public void setCallback(Callback callback) {
196    mCallback = callback;
197  }
198
199  private CharSequence getBlockedMessage() {
200    return ContactDisplayUtils.getTtsSpannedPhoneNumber(
201        getResources(), R.string.snackbar_number_blocked, mDisplayNumber);
202  }
203
204  private CharSequence getUnblockedMessage() {
205    return ContactDisplayUtils.getTtsSpannedPhoneNumber(
206        getResources(), R.string.snackbar_number_unblocked, mDisplayNumber);
207  }
208
209  private int getActionTextColor() {
210    return getContext().getResources().getColor(R.color.dialer_snackbar_action_text_color);
211  }
212
213  private void blockNumber() {
214    final CharSequence message = getBlockedMessage();
215    final CharSequence undoMessage = getUnblockedMessage();
216    final Callback callback = mCallback;
217    final int actionTextColor = getActionTextColor();
218    final Context applicationContext = getContext().getApplicationContext();
219
220    final OnUnblockNumberListener onUndoListener =
221        new OnUnblockNumberListener() {
222          @Override
223          public void onUnblockComplete(int rows, ContentValues values) {
224            Snackbar.make(mParentView, undoMessage, Snackbar.LENGTH_LONG).show();
225            if (callback != null) {
226              callback.onChangeFilteredNumberUndo();
227            }
228          }
229        };
230
231    final OnBlockNumberListener onBlockNumberListener =
232        new OnBlockNumberListener() {
233          @Override
234          public void onBlockComplete(final Uri uri) {
235            final View.OnClickListener undoListener =
236                new View.OnClickListener() {
237                  @Override
238                  public void onClick(View view) {
239                    // Delete the newly created row on 'undo'.
240                    Logger.get(applicationContext)
241                        .logInteraction(InteractionEvent.Type.UNDO_BLOCK_NUMBER);
242                    mHandler.unblock(onUndoListener, uri);
243                  }
244                };
245
246            Snackbar.make(mParentView, message, Snackbar.LENGTH_LONG)
247                .setAction(R.string.block_number_undo, undoListener)
248                .setActionTextColor(actionTextColor)
249                .show();
250
251            if (callback != null) {
252              callback.onFilterNumberSuccess();
253            }
254
255            if (FilteredNumbersUtil.hasRecentEmergencyCall(applicationContext)) {
256              FilteredNumbersUtil.maybeNotifyCallBlockingDisabled(applicationContext);
257            }
258          }
259        };
260
261    mHandler.blockNumber(onBlockNumberListener, mNumber, mCountryIso);
262  }
263
264  private void unblockNumber() {
265    final CharSequence message = getUnblockedMessage();
266    final CharSequence undoMessage = getBlockedMessage();
267    final Callback callback = mCallback;
268    final int actionTextColor = getActionTextColor();
269    final Context applicationContext = getContext().getApplicationContext();
270
271    final OnBlockNumberListener onUndoListener =
272        new OnBlockNumberListener() {
273          @Override
274          public void onBlockComplete(final Uri uri) {
275            Snackbar.make(mParentView, undoMessage, Snackbar.LENGTH_LONG).show();
276            if (callback != null) {
277              callback.onChangeFilteredNumberUndo();
278            }
279          }
280        };
281
282    mHandler.unblock(
283        new OnUnblockNumberListener() {
284          @Override
285          public void onUnblockComplete(int rows, final ContentValues values) {
286            final View.OnClickListener undoListener =
287                new View.OnClickListener() {
288                  @Override
289                  public void onClick(View view) {
290                    // Re-insert the row on 'undo', with a new ID.
291                    Logger.get(applicationContext)
292                        .logInteraction(InteractionEvent.Type.UNDO_UNBLOCK_NUMBER);
293                    mHandler.blockNumber(onUndoListener, values);
294                  }
295                };
296
297            Snackbar.make(mParentView, message, Snackbar.LENGTH_LONG)
298                .setAction(R.string.block_number_undo, undoListener)
299                .setActionTextColor(actionTextColor)
300                .show();
301
302            if (callback != null) {
303              callback.onUnfilterNumberSuccess();
304            }
305          }
306        },
307        getArguments().getInt(ARG_BLOCK_ID));
308  }
309
310  /**
311   * Use a callback interface to update UI after success/undo. Favor this approach over other more
312   * standard paradigms because of the variety of scenarios in which the DialogFragment can be
313   * invoked (by an Activity, by a fragment, by an adapter, by an adapter list item). Because of
314   * this, we do NOT support retaining state on rotation, and will dismiss the dialog upon rotation
315   * instead.
316   */
317  public interface Callback {
318
319    /** Called when a number is successfully added to the set of filtered numbers */
320    void onFilterNumberSuccess();
321
322    /** Called when a number is successfully removed from the set of filtered numbers */
323    void onUnfilterNumberSuccess();
324
325    /** Called when the action of filtering or unfiltering a number is undone */
326    void onChangeFilteredNumberUndo();
327  }
328}
329