• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.phonelookup.spam;
18 
19 import android.content.Context;
20 import android.content.SharedPreferences;
21 import android.support.annotation.Nullable;
22 import android.support.annotation.VisibleForTesting;
23 import com.android.dialer.DialerPhoneNumber;
24 import com.android.dialer.common.Assert;
25 import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor;
26 import com.android.dialer.common.concurrent.Annotations.LightweightExecutor;
27 import com.android.dialer.phonelookup.PhoneLookup;
28 import com.android.dialer.phonelookup.PhoneLookupInfo;
29 import com.android.dialer.phonelookup.PhoneLookupInfo.SpamInfo;
30 import com.android.dialer.spam.Spam;
31 import com.android.dialer.spam.SpamStatus;
32 import com.android.dialer.storage.Unencrypted;
33 import com.google.common.base.Optional;
34 import com.google.common.collect.ImmutableMap;
35 import com.google.common.collect.ImmutableSet;
36 import com.google.common.util.concurrent.Futures;
37 import com.google.common.util.concurrent.ListenableFuture;
38 import com.google.common.util.concurrent.ListeningExecutorService;
39 import java.util.Map.Entry;
40 import javax.inject.Inject;
41 
42 /** PhoneLookup implementation for Spam info. */
43 public final class SpamPhoneLookup implements PhoneLookup<SpamInfo> {
44 
45   @VisibleForTesting
46   static final String PREF_LAST_TIMESTAMP_PROCESSED = "spamPhoneLookupLastTimestampProcessed";
47 
48   private final ListeningExecutorService lightweightExecutorService;
49   private final ListeningExecutorService backgroundExecutorService;
50   private final SharedPreferences sharedPreferences;
51   private final Spam spam;
52 
53   @Nullable private Long currentLastTimestampProcessed;
54 
55   @Inject
SpamPhoneLookup( @ackgroundExecutor ListeningExecutorService backgroundExecutorService, @LightweightExecutor ListeningExecutorService lightweightExecutorService, @Unencrypted SharedPreferences sharedPreferences, Spam spam)56   SpamPhoneLookup(
57       @BackgroundExecutor ListeningExecutorService backgroundExecutorService,
58       @LightweightExecutor ListeningExecutorService lightweightExecutorService,
59       @Unencrypted SharedPreferences sharedPreferences,
60       Spam spam) {
61     this.backgroundExecutorService = backgroundExecutorService;
62     this.lightweightExecutorService = lightweightExecutorService;
63     this.sharedPreferences = sharedPreferences;
64     this.spam = spam;
65   }
66 
67   @Override
lookup(DialerPhoneNumber dialerPhoneNumber)68   public ListenableFuture<SpamInfo> lookup(DialerPhoneNumber dialerPhoneNumber) {
69     return Futures.transform(
70         spam.batchCheckSpamStatus(ImmutableSet.of(dialerPhoneNumber)),
71         spamStatusMap ->
72             SpamInfo.newBuilder()
73                 .setIsSpam(Assert.isNotNull(spamStatusMap.get(dialerPhoneNumber)).isSpam())
74                 .build(),
75         lightweightExecutorService);
76   }
77 
78   @Override
isDirty(ImmutableSet<DialerPhoneNumber> phoneNumbers)79   public ListenableFuture<Boolean> isDirty(ImmutableSet<DialerPhoneNumber> phoneNumbers) {
80     ListenableFuture<Long> lastTimestampProcessedFuture =
81         backgroundExecutorService.submit(
82             () -> sharedPreferences.getLong(PREF_LAST_TIMESTAMP_PROCESSED, 0L));
83 
84     return Futures.transformAsync(
85         lastTimestampProcessedFuture, spam::dataUpdatedSince, lightweightExecutorService);
86   }
87 
88   @Override
getMostRecentInfo( ImmutableMap<DialerPhoneNumber, SpamInfo> existingInfoMap)89   public ListenableFuture<ImmutableMap<DialerPhoneNumber, SpamInfo>> getMostRecentInfo(
90       ImmutableMap<DialerPhoneNumber, SpamInfo> existingInfoMap) {
91     currentLastTimestampProcessed = null;
92 
93     ListenableFuture<ImmutableMap<DialerPhoneNumber, SpamStatus>> spamStatusMapFuture =
94         spam.batchCheckSpamStatus(existingInfoMap.keySet());
95 
96     return Futures.transform(
97         spamStatusMapFuture,
98         spamStatusMap -> {
99           ImmutableMap.Builder<DialerPhoneNumber, SpamInfo> mostRecentSpamInfo =
100               new ImmutableMap.Builder<>();
101 
102           for (Entry<DialerPhoneNumber, SpamStatus> dialerPhoneNumberAndSpamStatus :
103               spamStatusMap.entrySet()) {
104             DialerPhoneNumber dialerPhoneNumber = dialerPhoneNumberAndSpamStatus.getKey();
105             SpamStatus spamStatus = dialerPhoneNumberAndSpamStatus.getValue();
106             mostRecentSpamInfo.put(
107                 dialerPhoneNumber, SpamInfo.newBuilder().setIsSpam(spamStatus.isSpam()).build());
108 
109             Optional<Long> timestampMillis = spamStatus.getTimestampMillis();
110             if (timestampMillis.isPresent()) {
111               currentLastTimestampProcessed =
112                   currentLastTimestampProcessed == null
113                       ? timestampMillis.get()
114                       : Math.max(timestampMillis.get(), currentLastTimestampProcessed);
115             }
116           }
117 
118           // If currentLastTimestampProcessed is null, it means none of the numbers in
119           // existingInfoMap has spam status in the underlying data source.
120           // We should set currentLastTimestampProcessed to the current timestamp to avoid
121           // triggering the bulk update flow repeatedly.
122           if (currentLastTimestampProcessed == null) {
123             currentLastTimestampProcessed = System.currentTimeMillis();
124           }
125 
126           return mostRecentSpamInfo.build();
127         },
128         lightweightExecutorService);
129   }
130 
131   @Override
getSubMessage(PhoneLookupInfo phoneLookupInfo)132   public SpamInfo getSubMessage(PhoneLookupInfo phoneLookupInfo) {
133     return phoneLookupInfo.getSpamInfo();
134   }
135 
136   @Override
setSubMessage(PhoneLookupInfo.Builder destination, SpamInfo subMessage)137   public void setSubMessage(PhoneLookupInfo.Builder destination, SpamInfo subMessage) {
138     destination.setSpamInfo(subMessage);
139   }
140 
141   @Override
onSuccessfulBulkUpdate()142   public ListenableFuture<Void> onSuccessfulBulkUpdate() {
143     return backgroundExecutorService.submit(
144         () -> {
145           sharedPreferences
146               .edit()
147               .putLong(
148                   PREF_LAST_TIMESTAMP_PROCESSED, Assert.isNotNull(currentLastTimestampProcessed))
149               .apply();
150           return null;
151         });
152   }
153 
154   @Override
155   public void registerContentObservers(Context appContext) {
156     // No content observer can be registered as Spam is not based on a content provider.
157     // Each Spam implementation should be responsible for notifying any data changes.
158   }
159 }
160