• 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.PendingIntent;
20 import android.app.Service;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.os.IBinder;
24 import android.provider.CallLog;
25 import android.support.annotation.Nullable;
26 import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler;
27 import com.android.dialer.common.LogUtil;
28 import com.android.dialer.location.GeoUtil;
29 import com.android.dialer.logging.ContactLookupResult;
30 import com.android.dialer.logging.DialerImpression;
31 import com.android.dialer.logging.Logger;
32 import com.android.dialer.logging.ReportingLocation;
33 import com.android.dialer.notification.DialerNotificationManager;
34 import com.android.dialer.spam.SpamComponent;
35 import com.android.dialer.spam.SpamSettings;
36 import com.android.dialer.spam.promo.SpamBlockingPromoHelper;
37 import com.android.incallui.call.DialerCall;
38 
39 /**
40  * This service determines if the device is locked/unlocked and takes an action based on the state.
41  * A service is used to to determine this, as opposed to an activity, because the user must unlock
42  * the device before a notification can start an activity. This is not the case for a service, and
43  * intents can be sent to this service even from the lock screen. This allows users to quickly
44  * report a number as spam or not spam from their lock screen.
45  */
46 public class SpamNotificationService extends Service {
47 
48   private static final String TAG = "SpamNotificationSvc";
49 
50   private static final String EXTRA_PHONE_NUMBER = "service_phone_number";
51   private static final String EXTRA_CALL_ID = "service_call_id";
52   private static final String EXTRA_CALL_START_TIME_MILLIS = "service_call_start_time_millis";
53   private static final String EXTRA_NOTIFICATION_TAG = "service_notification_tag";
54   private static final String EXTRA_NOTIFICATION_ID = "service_notification_id";
55   private static final String EXTRA_CONTACT_LOOKUP_RESULT_TYPE =
56       "service_contact_lookup_result_type";
57 
58   private String notificationTag;
59   private int notificationId;
60 
61   /** Creates an intent to start this service. */
createServiceIntent( Context context, @Nullable DialerCall call, String action, String notificationTag, int notificationId)62   public static Intent createServiceIntent(
63       Context context,
64       @Nullable DialerCall call,
65       String action,
66       String notificationTag,
67       int notificationId) {
68     Intent intent = new Intent(context, SpamNotificationService.class);
69     intent.setAction(action);
70     intent.putExtra(EXTRA_NOTIFICATION_TAG, notificationTag);
71     intent.putExtra(EXTRA_NOTIFICATION_ID, notificationId);
72 
73     if (call != null) {
74       intent.putExtra(EXTRA_PHONE_NUMBER, call.getNumber());
75       intent.putExtra(EXTRA_CALL_ID, call.getUniqueCallId());
76       intent.putExtra(EXTRA_CALL_START_TIME_MILLIS, call.getTimeAddedMs());
77       intent.putExtra(
78           EXTRA_CONTACT_LOOKUP_RESULT_TYPE, call.getLogState().contactLookupResult.getNumber());
79     }
80     return intent;
81   }
82 
83   @Nullable
84   @Override
onBind(Intent intent)85   public IBinder onBind(Intent intent) {
86     // Return null because clients cannot bind to this service
87     return null;
88   }
89 
90   @Override
onStartCommand(Intent intent, int flags, int startId)91   public int onStartCommand(Intent intent, int flags, int startId) {
92     LogUtil.d(TAG, "onStartCommand");
93     if (intent == null) {
94       LogUtil.d(TAG, "Null intent");
95       stopSelf();
96       // Return {@link #START_NOT_STICKY} so service is not restarted.
97       return START_NOT_STICKY;
98     }
99     String number = intent.getStringExtra(EXTRA_PHONE_NUMBER);
100     notificationTag = intent.getStringExtra(EXTRA_NOTIFICATION_TAG);
101     notificationId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, 1);
102     String countryIso = GeoUtil.getCurrentCountryIso(this);
103     ContactLookupResult.Type contactLookupResultType =
104         ContactLookupResult.Type.forNumber(intent.getIntExtra(EXTRA_CONTACT_LOOKUP_RESULT_TYPE, 0));
105 
106     SpamSettings spamSettings = SpamComponent.get(this).spamSettings();
107     SpamBlockingPromoHelper spamBlockingPromoHelper =
108         new SpamBlockingPromoHelper(this, SpamComponent.get(this).spamSettings());
109     boolean shouldShowSpamBlockingPromo =
110         SpamNotificationActivity.ACTION_MARK_NUMBER_AS_SPAM.equals(intent.getAction())
111             && spamBlockingPromoHelper.shouldShowAfterCallSpamBlockingPromo();
112 
113     // Cancel notification only if we are not showing spam blocking promo. Otherwise we will show
114     // spam blocking promo notification in place.
115     if (!shouldShowSpamBlockingPromo) {
116       DialerNotificationManager.cancel(this, notificationTag, notificationId);
117     }
118 
119     switch (intent.getAction()) {
120       case SpamNotificationActivity.ACTION_MARK_NUMBER_AS_SPAM:
121         logCallImpression(
122             intent, DialerImpression.Type.SPAM_NOTIFICATION_SERVICE_ACTION_MARK_NUMBER_AS_SPAM);
123         SpamComponent.get(this)
124             .spam()
125             .reportSpamFromAfterCallNotification(
126                 number,
127                 countryIso,
128                 CallLog.Calls.INCOMING_TYPE,
129                 ReportingLocation.Type.FEEDBACK_PROMPT,
130                 contactLookupResultType);
131         new FilteredNumberAsyncQueryHandler(this).blockNumber(null, number, countryIso);
132         if (shouldShowSpamBlockingPromo) {
133           spamBlockingPromoHelper.showSpamBlockingPromoNotification(
134               notificationTag,
135               notificationId,
136               createPromoActivityPendingIntent(),
137               createEnableSpamBlockingPendingIntent());
138         }
139         break;
140       case SpamNotificationActivity.ACTION_MARK_NUMBER_AS_NOT_SPAM:
141         logCallImpression(
142             intent, DialerImpression.Type.SPAM_NOTIFICATION_SERVICE_ACTION_MARK_NUMBER_AS_NOT_SPAM);
143         SpamComponent.get(this)
144             .spam()
145             .reportNotSpamFromAfterCallNotification(
146                 number,
147                 countryIso,
148                 CallLog.Calls.INCOMING_TYPE,
149                 ReportingLocation.Type.FEEDBACK_PROMPT,
150                 contactLookupResultType);
151         break;
152       case SpamNotificationActivity.ACTION_ENABLE_SPAM_BLOCKING:
153         Logger.get(this)
154             .logImpression(
155                 DialerImpression.Type.SPAM_BLOCKING_ENABLED_THROUGH_AFTER_CALL_NOTIFICATION_PROMO);
156         spamSettings.modifySpamBlockingSetting(
157             true,
158             success -> {
159               if (!success) {
160                 Logger.get(this)
161                     .logImpression(
162                         DialerImpression.Type
163                             .SPAM_BLOCKING_MODIFY_FAILURE_THROUGH_AFTER_CALL_NOTIFICATION_PROMO);
164               }
165               spamBlockingPromoHelper.showModifySettingOnCompleteToast(success);
166             });
167         break;
168       default: // fall out
169     }
170     // TODO: call stopSelf() after async tasks complete (a bug)
171     stopSelf();
172     return START_NOT_STICKY;
173   }
174 
175   @Override
onDestroy()176   public void onDestroy() {
177     super.onDestroy();
178     LogUtil.d(TAG, "onDestroy");
179   }
180 
logCallImpression(Intent intent, DialerImpression.Type impression)181   private void logCallImpression(Intent intent, DialerImpression.Type impression) {
182     Logger.get(this)
183         .logCallImpression(
184             impression,
185             intent.getStringExtra(EXTRA_CALL_ID),
186             intent.getLongExtra(EXTRA_CALL_START_TIME_MILLIS, 0));
187   }
188 
createPromoActivityPendingIntent()189   private PendingIntent createPromoActivityPendingIntent() {
190     Intent intent =
191         SpamNotificationActivity.createActivityIntent(
192             this,
193             null,
194             SpamNotificationActivity.ACTION_SHOW_SPAM_BLOCKING_PROMO_DIALOG,
195             notificationTag,
196             notificationId);
197     return PendingIntent.getActivity(
198         this, (int) System.currentTimeMillis(), intent, PendingIntent.FLAG_ONE_SHOT);
199   }
200 
createEnableSpamBlockingPendingIntent()201   private PendingIntent createEnableSpamBlockingPendingIntent() {
202     Intent intent =
203         SpamNotificationService.createServiceIntent(
204             this,
205             null,
206             SpamNotificationActivity.ACTION_ENABLE_SPAM_BLOCKING,
207             notificationTag,
208             notificationId);
209     return PendingIntent.getService(
210         this, (int) System.currentTimeMillis(), intent, PendingIntent.FLAG_ONE_SHOT);
211   }
212 }
213