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 17 package com.android.dialer.blocking; 18 19 import android.app.AlertDialog; 20 import android.app.Dialog; 21 import android.app.DialogFragment; 22 import android.app.FragmentManager; 23 import android.content.ContentValues; 24 import android.content.Context; 25 import android.content.DialogInterface; 26 import android.net.Uri; 27 import android.os.Bundle; 28 import android.support.design.widget.Snackbar; 29 import android.telephony.PhoneNumberUtils; 30 import android.text.TextUtils; 31 import android.view.View; 32 import android.widget.Toast; 33 import com.android.contacts.common.util.ContactDisplayUtils; 34 import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler.OnBlockNumberListener; 35 import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler.OnUnblockNumberListener; 36 import com.android.dialer.logging.InteractionEvent; 37 import com.android.dialer.logging.Logger; 38 import 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 */ 44 public 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 show( Integer blockId, String number, String countryIso, String displayNumber, Integer parentViewId, FragmentManager fragmentManager, Callback callback)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 newInstance( Integer blockId, String number, String countryIso, String displayNumber, Integer parentViewId)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 setFilteredNumberAsyncQueryHandlerForTesting( FilteredNumberAsyncQueryHandler handler)98 public void setFilteredNumberAsyncQueryHandlerForTesting( 99 FilteredNumberAsyncQueryHandler handler) { 100 mHandler = handler; 101 } 102 103 @Override getContext()104 public Context getContext() { 105 return getActivity(); 106 } 107 108 @Override onCreateDialog(Bundle savedInstanceState)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 onActivityCreated(Bundle savedInstanceState)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 onPause()187 public void onPause() { 188 // Dismiss on rotation. 189 dismiss(); 190 mCallback = null; 191 192 super.onPause(); 193 } 194 setCallback(Callback callback)195 public void setCallback(Callback callback) { 196 mCallback = callback; 197 } 198 getBlockedMessage()199 private CharSequence getBlockedMessage() { 200 return ContactDisplayUtils.getTtsSpannedPhoneNumber( 201 getResources(), R.string.snackbar_number_blocked, mDisplayNumber); 202 } 203 getUnblockedMessage()204 private CharSequence getUnblockedMessage() { 205 return ContactDisplayUtils.getTtsSpannedPhoneNumber( 206 getResources(), R.string.snackbar_number_unblocked, mDisplayNumber); 207 } 208 getActionTextColor()209 private int getActionTextColor() { 210 return getContext().getResources().getColor(R.color.dialer_snackbar_action_text_color); 211 } 212 blockNumber()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 unblockNumber()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 */ onFilterNumberSuccess()320 void onFilterNumberSuccess(); 321 322 /** Called when a number is successfully removed from the set of filtered numbers */ onUnfilterNumberSuccess()323 void onUnfilterNumberSuccess(); 324 325 /** Called when the action of filtering or unfiltering a number is undone */ onChangeFilteredNumberUndo()326 void onChangeFilteredNumberUndo(); 327 } 328 } 329