• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.incallui.spam;
18 
19 import android.app.AlertDialog;
20 import android.app.Dialog;
21 import android.app.DialogFragment;
22 import android.content.Context;
23 import android.content.DialogInterface;
24 import android.content.Intent;
25 import android.os.Bundle;
26 import android.provider.CallLog;
27 import android.provider.ContactsContract;
28 import android.support.v4.app.FragmentActivity;
29 import android.telephony.PhoneNumberUtils;
30 import com.android.dialer.blocking.BlockedNumbersMigrator;
31 import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler;
32 import com.android.dialer.blocking.FilteredNumberCompat;
33 import com.android.dialer.blockreportspam.BlockReportSpamDialogs;
34 import com.android.dialer.common.LogUtil;
35 import com.android.dialer.location.GeoUtil;
36 import com.android.dialer.logging.ContactLookupResult;
37 import com.android.dialer.logging.DialerImpression;
38 import com.android.dialer.logging.Logger;
39 import com.android.dialer.logging.ReportingLocation;
40 import com.android.dialer.notification.DialerNotificationManager;
41 import com.android.dialer.phonenumberutil.PhoneNumberHelper;
42 import com.android.dialer.spam.SpamComponent;
43 import com.android.incallui.call.DialerCall;
44 
45 /** Creates the after call notification dialogs. */
46 public class SpamNotificationActivity extends FragmentActivity {
47 
48   /** Action to add number to contacts. */
49   static final String ACTION_ADD_TO_CONTACTS = "com.android.incallui.spam.ACTION_ADD_TO_CONTACTS";
50   /** Action to show dialog. */
51   static final String ACTION_SHOW_DIALOG = "com.android.incallui.spam.ACTION_SHOW_DIALOG";
52   /** Action to mark a number as spam. */
53   static final String ACTION_MARK_NUMBER_AS_SPAM =
54       "com.android.incallui.spam.ACTION_MARK_NUMBER_AS_SPAM";
55   /** Action to mark a number as not spam. */
56   static final String ACTION_MARK_NUMBER_AS_NOT_SPAM =
57       "com.android.incallui.spam.ACTION_MARK_NUMBER_AS_NOT_SPAM";
58 
59   private static final String TAG = "SpamNotifications";
60   private static final String EXTRA_NOTIFICATION_TAG = "notification_tag";
61   private static final String EXTRA_NOTIFICATION_ID = "notification_id";
62   private static final String EXTRA_CALL_INFO = "call_info";
63 
64   private static final String CALL_INFO_KEY_PHONE_NUMBER = "phone_number";
65   private static final String CALL_INFO_KEY_IS_SPAM = "is_spam";
66   private static final String CALL_INFO_KEY_CALL_ID = "call_id";
67   private static final String CALL_INFO_KEY_START_TIME_MILLIS = "call_start_time_millis";
68   private static final String CALL_INFO_CONTACT_LOOKUP_RESULT_TYPE = "contact_lookup_result_type";
69   private final DialogInterface.OnDismissListener dismissListener =
70       new DialogInterface.OnDismissListener() {
71         @Override
72         public void onDismiss(DialogInterface dialog) {
73           if (!isFinishing()) {
74             finish();
75           }
76         }
77       };
78   private FilteredNumberAsyncQueryHandler filteredNumberAsyncQueryHandler;
79 
80   /**
81    * Creates an intent to start this activity.
82    *
83    * @return Intent intent that starts this activity.
84    */
createActivityIntent( Context context, DialerCall call, String action, String notificationTag, int notificationId)85   public static Intent createActivityIntent(
86       Context context, DialerCall call, String action, String notificationTag, int notificationId) {
87     Intent intent = new Intent(context, SpamNotificationActivity.class);
88     intent.setAction(action);
89     // This ensures only one activity of this kind exists at a time.
90     intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
91     intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
92     intent.putExtra(EXTRA_NOTIFICATION_TAG, notificationTag);
93     intent.putExtra(EXTRA_NOTIFICATION_ID, notificationId);
94     intent.putExtra(EXTRA_CALL_INFO, newCallInfoBundle(call));
95     return intent;
96   }
97 
98   /** Creates the intent to insert a contact. */
createInsertContactsIntent(String number)99   private static Intent createInsertContactsIntent(String number) {
100     Intent intent = new Intent(ContactsContract.Intents.Insert.ACTION);
101     // This ensures that the edit contact number field gets updated if called more than once.
102     intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
103     intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
104     intent.setType(ContactsContract.RawContacts.CONTENT_TYPE);
105     intent.putExtra(ContactsContract.Intents.Insert.PHONE, number);
106     return intent;
107   }
108 
109   /** Returns the formatted version of the given number. */
getFormattedNumber(String number, Context context)110   private static String getFormattedNumber(String number, Context context) {
111     String formattedNumber =
112         PhoneNumberHelper.formatNumber(context, number, GeoUtil.getCurrentCountryIso(context));
113     return PhoneNumberUtils.createTtsSpannable(formattedNumber).toString();
114   }
115 
logCallImpression(DialerImpression.Type impression)116   private void logCallImpression(DialerImpression.Type impression) {
117     logCallImpression(this, getCallInfo(), impression);
118   }
119 
logCallImpression( Context context, Bundle bundle, DialerImpression.Type impression)120   private static void logCallImpression(
121       Context context, Bundle bundle, DialerImpression.Type impression) {
122     Logger.get(context)
123         .logCallImpression(
124             impression,
125             bundle.getString(CALL_INFO_KEY_CALL_ID),
126             bundle.getLong(CALL_INFO_KEY_START_TIME_MILLIS, 0));
127   }
128 
newCallInfoBundle(DialerCall call)129   private static Bundle newCallInfoBundle(DialerCall call) {
130     Bundle bundle = new Bundle();
131     bundle.putString(CALL_INFO_KEY_PHONE_NUMBER, call.getNumber());
132     bundle.putBoolean(CALL_INFO_KEY_IS_SPAM, call.isSpam());
133     bundle.putString(CALL_INFO_KEY_CALL_ID, call.getUniqueCallId());
134     bundle.putLong(CALL_INFO_KEY_START_TIME_MILLIS, call.getTimeAddedMs());
135     bundle.putInt(
136         CALL_INFO_CONTACT_LOOKUP_RESULT_TYPE, call.getLogState().contactLookupResult.getNumber());
137     return bundle;
138   }
139 
140   @Override
onCreate(Bundle savedInstanceState)141   protected void onCreate(Bundle savedInstanceState) {
142     LogUtil.i(TAG, "onCreate");
143     super.onCreate(savedInstanceState);
144     setFinishOnTouchOutside(true);
145     filteredNumberAsyncQueryHandler = new FilteredNumberAsyncQueryHandler(this);
146     cancelNotification();
147   }
148 
149   @Override
onResume()150   protected void onResume() {
151     LogUtil.i(TAG, "onResume");
152     super.onResume();
153     Intent intent = getIntent();
154     String number = getCallInfo().getString(CALL_INFO_KEY_PHONE_NUMBER);
155     boolean isSpam = getCallInfo().getBoolean(CALL_INFO_KEY_IS_SPAM);
156     ContactLookupResult.Type contactLookupResultType =
157         ContactLookupResult.Type.forNumber(
158             getCallInfo().getInt(CALL_INFO_CONTACT_LOOKUP_RESULT_TYPE, 0));
159     switch (intent.getAction()) {
160       case ACTION_ADD_TO_CONTACTS:
161         logCallImpression(DialerImpression.Type.SPAM_AFTER_CALL_NOTIFICATION_ADD_TO_CONTACTS);
162         startActivity(createInsertContactsIntent(number));
163         finish();
164         break;
165       case ACTION_MARK_NUMBER_AS_SPAM:
166         assertDialogsEnabled();
167         maybeShowBlockReportSpamDialog(number, contactLookupResultType);
168         break;
169       case ACTION_MARK_NUMBER_AS_NOT_SPAM:
170         assertDialogsEnabled();
171         maybeShowNotSpamDialog(number, contactLookupResultType);
172         break;
173       case ACTION_SHOW_DIALOG:
174         if (isSpam) {
175           showSpamFullDialog();
176         } else {
177           showNonSpamDialog();
178         }
179         break;
180       default: // fall out
181     }
182   }
183 
184   @Override
onPause()185   protected void onPause() {
186     LogUtil.d(TAG, "onPause");
187     // Finish activity on pause (e.g: orientation change or back button pressed)
188     filteredNumberAsyncQueryHandler = null;
189     if (!isFinishing()) {
190       finish();
191     }
192     super.onPause();
193   }
194 
195   /** Creates and displays the dialog for whitelisting a number. */
maybeShowNotSpamDialog( final String number, final ContactLookupResult.Type contactLookupResultType)196   private void maybeShowNotSpamDialog(
197       final String number, final ContactLookupResult.Type contactLookupResultType) {
198     if (SpamComponent.get(this).spam().isDialogEnabledForSpamNotification()) {
199       BlockReportSpamDialogs.ReportNotSpamDialogFragment.newInstance(
200               getFormattedNumber(number, this),
201               new BlockReportSpamDialogs.OnConfirmListener() {
202                 @Override
203                 public void onClick() {
204                   reportNotSpamAndFinish(number, contactLookupResultType);
205                 }
206               },
207               dismissListener)
208           .show(getFragmentManager(), BlockReportSpamDialogs.NOT_SPAM_DIALOG_TAG);
209     } else {
210       reportNotSpamAndFinish(number, contactLookupResultType);
211     }
212   }
213 
214   /** Creates and displays the dialog for blocking/reporting a number as spam. */
maybeShowBlockReportSpamDialog( final String number, final ContactLookupResult.Type contactLookupResultType)215   private void maybeShowBlockReportSpamDialog(
216       final String number, final ContactLookupResult.Type contactLookupResultType) {
217     if (SpamComponent.get(this).spam().isDialogEnabledForSpamNotification()) {
218       String displayNumber = getFormattedNumber(number, this);
219       maybeShowBlockNumberMigrationDialog(
220           new BlockedNumbersMigrator.Listener() {
221             @Override
222             public void onComplete() {
223               BlockReportSpamDialogs.BlockReportSpamDialogFragment.newInstance(
224                       displayNumber,
225                       SpamComponent.get(SpamNotificationActivity.this)
226                           .spam()
227                           .isDialogReportSpamCheckedByDefault(),
228                       new BlockReportSpamDialogs.OnSpamDialogClickListener() {
229                         @Override
230                         public void onClick(boolean isSpamChecked) {
231                           blockReportNumberAndFinish(
232                               number, isSpamChecked, contactLookupResultType);
233                         }
234                       },
235                       dismissListener)
236                   .show(getFragmentManager(), BlockReportSpamDialogs.BLOCK_REPORT_SPAM_DIALOG_TAG);
237             }
238           });
239     } else {
240       blockReportNumberAndFinish(number, true, contactLookupResultType);
241     }
242   }
243 
244   /**
245    * Displays the dialog for the first time unknown calls with actions "Add contact", "Block/report
246    * spam", and "Dismiss".
247    */
showNonSpamDialog()248   private void showNonSpamDialog() {
249     logCallImpression(DialerImpression.Type.SPAM_AFTER_CALL_NOTIFICATION_SHOW_NON_SPAM_DIALOG);
250     FirstTimeNonSpamCallDialogFragment.newInstance(getCallInfo())
251         .show(getFragmentManager(), FirstTimeNonSpamCallDialogFragment.TAG);
252   }
253 
254   /**
255    * Displays the dialog for first time spam calls with actions "Not spam", "Block", and "Dismiss".
256    */
showSpamFullDialog()257   private void showSpamFullDialog() {
258     logCallImpression(DialerImpression.Type.SPAM_AFTER_CALL_NOTIFICATION_SHOW_SPAM_DIALOG);
259     FirstTimeSpamCallDialogFragment.newInstance(getCallInfo())
260         .show(getFragmentManager(), FirstTimeSpamCallDialogFragment.TAG);
261   }
262 
263   /** Checks if the user has migrated to the new blocking and display a dialog if necessary. */
maybeShowBlockNumberMigrationDialog(BlockedNumbersMigrator.Listener listener)264   private void maybeShowBlockNumberMigrationDialog(BlockedNumbersMigrator.Listener listener) {
265     if (!FilteredNumberCompat.maybeShowBlockNumberMigrationDialog(
266         this, getFragmentManager(), listener)) {
267       listener.onComplete();
268     }
269   }
270 
271   /** Block and report the number as spam. */
blockReportNumberAndFinish( String number, boolean reportAsSpam, ContactLookupResult.Type contactLookupResultType)272   private void blockReportNumberAndFinish(
273       String number, boolean reportAsSpam, ContactLookupResult.Type contactLookupResultType) {
274     if (reportAsSpam) {
275       logCallImpression(DialerImpression.Type.SPAM_AFTER_CALL_NOTIFICATION_MARKED_NUMBER_AS_SPAM);
276       SpamComponent.get(this)
277           .spam()
278           .reportSpamFromAfterCallNotification(
279               number,
280               getCountryIso(),
281               CallLog.Calls.INCOMING_TYPE,
282               ReportingLocation.Type.FEEDBACK_PROMPT,
283               contactLookupResultType);
284     }
285 
286     logCallImpression(DialerImpression.Type.SPAM_AFTER_CALL_NOTIFICATION_BLOCK_NUMBER);
287     filteredNumberAsyncQueryHandler.blockNumber(null, number, getCountryIso());
288     // TODO: DialerCall finish() after block/reporting async tasks complete (a bug)
289     finish();
290   }
291 
292   /** Report the number as not spam. */
reportNotSpamAndFinish( String number, ContactLookupResult.Type contactLookupResultType)293   private void reportNotSpamAndFinish(
294       String number, ContactLookupResult.Type contactLookupResultType) {
295     logCallImpression(DialerImpression.Type.SPAM_AFTER_CALL_NOTIFICATION_REPORT_NUMBER_AS_NOT_SPAM);
296     SpamComponent.get(this)
297         .spam()
298         .reportNotSpamFromAfterCallNotification(
299             number,
300             getCountryIso(),
301             CallLog.Calls.INCOMING_TYPE,
302             ReportingLocation.Type.FEEDBACK_PROMPT,
303             contactLookupResultType);
304     // TODO: DialerCall finish() after async task completes (a bug)
305     finish();
306   }
307 
308   /** Cancels the notification associated with the number. */
cancelNotification()309   private void cancelNotification() {
310     String notificationTag = getIntent().getStringExtra(EXTRA_NOTIFICATION_TAG);
311     int notificationId = getIntent().getIntExtra(EXTRA_NOTIFICATION_ID, 1);
312     DialerNotificationManager.cancel(this, notificationTag, notificationId);
313   }
314 
getCountryIso()315   private String getCountryIso() {
316     return GeoUtil.getCurrentCountryIso(this);
317   }
318 
assertDialogsEnabled()319   private void assertDialogsEnabled() {
320     if (!SpamComponent.get(this).spam().isDialogEnabledForSpamNotification()) {
321       throw new IllegalStateException(
322           "Cannot start this activity with given action because dialogs are not enabled.");
323     }
324   }
325 
getCallInfo()326   private Bundle getCallInfo() {
327     return getIntent().getBundleExtra(EXTRA_CALL_INFO);
328   }
329 
330   /** Dialog that displays "Not spam", "Block/report spam" and "Dismiss". */
331   public static class FirstTimeSpamCallDialogFragment extends DialogFragment {
332 
333     public static final String TAG = "FirstTimeSpamDialog";
334 
335     private boolean dismissed;
336     private Context applicationContext;
337 
newInstance(Bundle bundle)338     private static DialogFragment newInstance(Bundle bundle) {
339       FirstTimeSpamCallDialogFragment fragment = new FirstTimeSpamCallDialogFragment();
340       fragment.setArguments(bundle);
341       return fragment;
342     }
343 
344     @Override
onPause()345     public void onPause() {
346       dismiss();
347       super.onPause();
348     }
349 
350     @Override
onDismiss(DialogInterface dialog)351     public void onDismiss(DialogInterface dialog) {
352       logCallImpression(
353           applicationContext,
354           getArguments(),
355           DialerImpression.Type.SPAM_AFTER_CALL_NOTIFICATION_ON_DISMISS_SPAM_DIALOG);
356       super.onDismiss(dialog);
357       // If dialog was not dismissed by user pressing one of the buttons, finish activity
358       if (!dismissed && getActivity() != null && !getActivity().isFinishing()) {
359         getActivity().finish();
360       }
361     }
362 
363     @Override
onAttach(Context context)364     public void onAttach(Context context) {
365       super.onAttach(context);
366       applicationContext = context.getApplicationContext();
367     }
368 
369     @Override
onCreateDialog(Bundle savedInstanceState)370     public Dialog onCreateDialog(Bundle savedInstanceState) {
371       super.onCreateDialog(savedInstanceState);
372       final SpamNotificationActivity spamNotificationActivity =
373           (SpamNotificationActivity) getActivity();
374       final String number = getArguments().getString(CALL_INFO_KEY_PHONE_NUMBER);
375       final ContactLookupResult.Type contactLookupResultType =
376           ContactLookupResult.Type.forNumber(
377               getArguments().getInt(CALL_INFO_CONTACT_LOOKUP_RESULT_TYPE, 0));
378 
379       return new AlertDialog.Builder(getActivity())
380           .setCancelable(false)
381           .setTitle(
382               getString(
383                   R.string.spam_notification_title, getFormattedNumber(number, applicationContext)))
384           .setNeutralButton(
385               getString(R.string.spam_notification_action_dismiss),
386               new DialogInterface.OnClickListener() {
387                 @Override
388                 public void onClick(DialogInterface dialog, int which) {
389                   dismiss();
390                 }
391               })
392           .setPositiveButton(
393               getString(R.string.spam_notification_block_spam_action_text),
394               new DialogInterface.OnClickListener() {
395                 @Override
396                 public void onClick(DialogInterface dialog, int which) {
397                   dismissed = true;
398                   dismiss();
399                   spamNotificationActivity.maybeShowBlockReportSpamDialog(
400                       number, contactLookupResultType);
401                 }
402               })
403           .setNegativeButton(
404               getString(R.string.spam_notification_was_not_spam_action_text),
405               new DialogInterface.OnClickListener() {
406                 @Override
407                 public void onClick(DialogInterface dialog, int which) {
408                   dismissed = true;
409                   dismiss();
410                   spamNotificationActivity.maybeShowNotSpamDialog(number, contactLookupResultType);
411                 }
412               })
413           .create();
414     }
415   }
416 
417   /** Dialog that displays "Add contact", "Block/report spam" and "Dismiss". */
418   public static class FirstTimeNonSpamCallDialogFragment extends DialogFragment {
419 
420     public static final String TAG = "FirstTimeNonSpamDialog";
421 
422     private boolean dismissed;
423     private Context context;
424 
425     private static DialogFragment newInstance(Bundle bundle) {
426       FirstTimeNonSpamCallDialogFragment fragment = new FirstTimeNonSpamCallDialogFragment();
427       fragment.setArguments(bundle);
428       return fragment;
429     }
430 
431     @Override
432     public void onPause() {
433       // Dismiss on pause e.g: orientation change
434       dismiss();
435       super.onPause();
436     }
437 
438     @Override
439     public void onDismiss(DialogInterface dialog) {
440       super.onDismiss(dialog);
441       logCallImpression(
442           context,
443           getArguments(),
444           DialerImpression.Type.SPAM_AFTER_CALL_NOTIFICATION_ON_DISMISS_NON_SPAM_DIALOG);
445       // If dialog was not dismissed by user pressing one of the buttons, finish activity
446       if (!dismissed && getActivity() != null && !getActivity().isFinishing()) {
447         getActivity().finish();
448       }
449     }
450 
451     @Override
452     public void onAttach(Context context) {
453       super.onAttach(context);
454       this.context = context.getApplicationContext();
455     }
456 
457     @Override
458     public Dialog onCreateDialog(Bundle savedInstanceState) {
459       super.onCreateDialog(savedInstanceState);
460       final SpamNotificationActivity spamNotificationActivity =
461           (SpamNotificationActivity) getActivity();
462       final String number = getArguments().getString(CALL_INFO_KEY_PHONE_NUMBER);
463       final ContactLookupResult.Type contactLookupResultType =
464           ContactLookupResult.Type.forNumber(
465               getArguments().getInt(CALL_INFO_CONTACT_LOOKUP_RESULT_TYPE, 0));
466       return new AlertDialog.Builder(getActivity())
467           .setTitle(
468               getString(R.string.non_spam_notification_title, getFormattedNumber(number, context)))
469           .setCancelable(false)
470           .setMessage(getString(R.string.spam_notification_non_spam_call_expanded_text))
471           .setNeutralButton(
472               getString(R.string.spam_notification_action_dismiss),
473               new DialogInterface.OnClickListener() {
474                 @Override
475                 public void onClick(DialogInterface dialog, int which) {
476                   dismiss();
477                 }
478               })
479           .setPositiveButton(
480               getString(R.string.spam_notification_dialog_add_contact_action_text),
481               new DialogInterface.OnClickListener() {
482                 @Override
483                 public void onClick(DialogInterface dialog, int which) {
484                   dismissed = true;
485                   dismiss();
486                   startActivity(createInsertContactsIntent(number));
487                 }
488               })
489           .setNegativeButton(
490               getString(R.string.spam_notification_dialog_block_report_spam_action_text),
491               new DialogInterface.OnClickListener() {
492                 @Override
493                 public void onClick(DialogInterface dialog, int which) {
494                   dismissed = true;
495                   dismiss();
496                   spamNotificationActivity.maybeShowBlockReportSpamDialog(
497                       number, contactLookupResultType);
498                 }
499               })
500           .create();
501     }
502   }
503 }
504