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 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