Home | History | Annotate | Download | only in blockednumber
      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.blockednumber;
     18 
     19 import android.annotation.TargetApi;
     20 import android.content.Context;
     21 import android.database.Cursor;
     22 import android.os.Build.VERSION;
     23 import android.os.Build.VERSION_CODES;
     24 import android.provider.BlockedNumberContract.BlockedNumbers;
     25 import android.support.annotation.NonNull;
     26 import android.support.annotation.WorkerThread;
     27 import android.util.ArraySet;
     28 import com.android.dialer.DialerPhoneNumber;
     29 import com.android.dialer.blocking.FilteredNumberCompat;
     30 import com.android.dialer.calllog.observer.MarkDirtyObserver;
     31 import com.android.dialer.common.Assert;
     32 import com.android.dialer.common.LogUtil;
     33 import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor;
     34 import com.android.dialer.common.database.Selection;
     35 import com.android.dialer.inject.ApplicationContext;
     36 import com.android.dialer.phonelookup.PhoneLookup;
     37 import com.android.dialer.phonelookup.PhoneLookupInfo;
     38 import com.android.dialer.phonelookup.PhoneLookupInfo.BlockedState;
     39 import com.android.dialer.phonelookup.PhoneLookupInfo.Builder;
     40 import com.android.dialer.phonelookup.PhoneLookupInfo.SystemBlockedNumberInfo;
     41 import com.android.dialer.phonenumberproto.PartitionedNumbers;
     42 import com.google.common.collect.ImmutableMap;
     43 import com.google.common.collect.ImmutableSet;
     44 import com.google.common.util.concurrent.Futures;
     45 import com.google.common.util.concurrent.ListenableFuture;
     46 import com.google.common.util.concurrent.ListeningExecutorService;
     47 import java.util.Set;
     48 import javax.inject.Inject;
     49 
     50 /**
     51  * Lookup blocked numbers in the system database. Requires N+ and migration from dialer database
     52  * completed (need user consent to move data into system).
     53  */
     54 public class SystemBlockedNumberPhoneLookup implements PhoneLookup<SystemBlockedNumberInfo> {
     55 
     56   private final Context appContext;
     57   private final ListeningExecutorService executorService;
     58   private final MarkDirtyObserver markDirtyObserver;
     59 
     60   @Inject
     61   SystemBlockedNumberPhoneLookup(
     62       @ApplicationContext Context appContext,
     63       @BackgroundExecutor ListeningExecutorService executorService,
     64       MarkDirtyObserver markDirtyObserver) {
     65     this.appContext = appContext;
     66     this.executorService = executorService;
     67     this.markDirtyObserver = markDirtyObserver;
     68   }
     69 
     70   @Override
     71   public ListenableFuture<SystemBlockedNumberInfo> lookup(@NonNull DialerPhoneNumber number) {
     72     if (!FilteredNumberCompat.useNewFiltering(appContext)) {
     73       return Futures.immediateFuture(SystemBlockedNumberInfo.getDefaultInstance());
     74     }
     75     return executorService.submit(
     76         () -> {
     77           return queryNumbers(ImmutableSet.of(number)).get(number);
     78         });
     79   }
     80 
     81   @Override
     82   public ListenableFuture<Boolean> isDirty(ImmutableSet<DialerPhoneNumber> phoneNumbers) {
     83     // Dirty state is recorded with PhoneLookupDataSource.markDirtyAndNotify(), which will force
     84     // rebuild with the CallLogFramework
     85     return Futures.immediateFuture(false);
     86   }
     87 
     88   @Override
     89   public ListenableFuture<ImmutableMap<DialerPhoneNumber, SystemBlockedNumberInfo>>
     90       getMostRecentInfo(ImmutableMap<DialerPhoneNumber, SystemBlockedNumberInfo> existingInfoMap) {
     91     LogUtil.enterBlock("SystemBlockedNumberPhoneLookup.getMostRecentPhoneLookupInfo");
     92     if (!FilteredNumberCompat.useNewFiltering(appContext)) {
     93       return Futures.immediateFuture(existingInfoMap);
     94     }
     95     return executorService.submit(() -> queryNumbers(existingInfoMap.keySet()));
     96   }
     97 
     98   @WorkerThread
     99   @TargetApi(VERSION_CODES.N)
    100   private ImmutableMap<DialerPhoneNumber, SystemBlockedNumberInfo> queryNumbers(
    101       ImmutableSet<DialerPhoneNumber> numbers) {
    102     Assert.isWorkerThread();
    103     PartitionedNumbers partitionedNumbers = new PartitionedNumbers(numbers);
    104 
    105     Set<DialerPhoneNumber> blockedNumbers = new ArraySet<>();
    106 
    107     Selection normalizedSelection =
    108         Selection.column(BlockedNumbers.COLUMN_E164_NUMBER)
    109             .in(partitionedNumbers.validE164Numbers());
    110     try (Cursor cursor =
    111         appContext
    112             .getContentResolver()
    113             .query(
    114                 BlockedNumbers.CONTENT_URI,
    115                 new String[] {BlockedNumbers.COLUMN_E164_NUMBER},
    116                 normalizedSelection.getSelection(),
    117                 normalizedSelection.getSelectionArgs(),
    118                 null)) {
    119       while (cursor != null && cursor.moveToNext()) {
    120         blockedNumbers.addAll(
    121             partitionedNumbers.dialerPhoneNumbersForValidE164(cursor.getString(0)));
    122       }
    123     }
    124 
    125     Selection rawSelection =
    126         Selection.column(BlockedNumbers.COLUMN_ORIGINAL_NUMBER)
    127             .in(partitionedNumbers.invalidNumbers());
    128     try (Cursor cursor =
    129         appContext
    130             .getContentResolver()
    131             .query(
    132                 BlockedNumbers.CONTENT_URI,
    133                 new String[] {BlockedNumbers.COLUMN_ORIGINAL_NUMBER},
    134                 rawSelection.getSelection(),
    135                 rawSelection.getSelectionArgs(),
    136                 null)) {
    137       while (cursor != null && cursor.moveToNext()) {
    138         blockedNumbers.addAll(partitionedNumbers.dialerPhoneNumbersForInvalid(cursor.getString(0)));
    139       }
    140     }
    141 
    142     ImmutableMap.Builder<DialerPhoneNumber, SystemBlockedNumberInfo> result =
    143         ImmutableMap.builder();
    144 
    145     for (DialerPhoneNumber number : numbers) {
    146       result.put(
    147           number,
    148           SystemBlockedNumberInfo.newBuilder()
    149               .setBlockedState(
    150                   blockedNumbers.contains(number) ? BlockedState.BLOCKED : BlockedState.NOT_BLOCKED)
    151               .build());
    152     }
    153 
    154     return result.build();
    155   }
    156 
    157   @Override
    158   public void setSubMessage(Builder phoneLookupInfo, SystemBlockedNumberInfo subMessage) {
    159     phoneLookupInfo.setSystemBlockedNumberInfo(subMessage);
    160   }
    161 
    162   @Override
    163   public SystemBlockedNumberInfo getSubMessage(PhoneLookupInfo phoneLookupInfo) {
    164     return phoneLookupInfo.getSystemBlockedNumberInfo();
    165   }
    166 
    167   @Override
    168   public ListenableFuture<Void> onSuccessfulBulkUpdate() {
    169     return Futures.immediateFuture(null);
    170   }
    171 
    172   @Override
    173   public void registerContentObservers(Context appContext) {
    174     if (VERSION.SDK_INT < VERSION_CODES.N) {
    175       return;
    176     }
    177     appContext
    178         .getContentResolver()
    179         .registerContentObserver(
    180             BlockedNumbers.CONTENT_URI,
    181             true, // BlockedNumbers notifies on the item
    182             markDirtyObserver);
    183   }
    184 }
    185