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.calllog; 18 19 import android.annotation.SuppressLint; 20 import android.content.ContentValues; 21 import android.content.Context; 22 import android.net.Uri; 23 import android.provider.CallLog.Calls; 24 import android.support.v4.os.UserManagerCompat; 25 import com.android.dialer.common.LogUtil; 26 import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor; 27 import com.android.dialer.common.concurrent.Annotations.Ui; 28 import com.android.dialer.common.database.Selection; 29 import com.android.dialer.inject.ApplicationContext; 30 import com.android.dialer.notification.missedcalls.MissedCallNotificationCanceller; 31 import com.android.dialer.util.PermissionsUtil; 32 import com.google.common.collect.ImmutableSet; 33 import com.google.common.util.concurrent.Futures; 34 import com.google.common.util.concurrent.ListenableFuture; 35 import com.google.common.util.concurrent.ListeningExecutorService; 36 import com.google.common.util.concurrent.MoreExecutors; 37 import java.util.Collection; 38 import javax.inject.Inject; 39 40 /** 41 * Clears missed calls. This includes cancelling notifications and updating the "NEW" status in the 42 * system call log. 43 */ 44 public final class ClearMissedCalls { 45 46 private final Context appContext; 47 private final ListeningExecutorService backgroundExecutor; 48 private final ListeningExecutorService uiThreadExecutor; 49 50 @Inject ClearMissedCalls( @pplicationContext Context appContext, @BackgroundExecutor ListeningExecutorService backgroundExecutor, @Ui ListeningExecutorService uiThreadExecutor)51 ClearMissedCalls( 52 @ApplicationContext Context appContext, 53 @BackgroundExecutor ListeningExecutorService backgroundExecutor, 54 @Ui ListeningExecutorService uiThreadExecutor) { 55 this.appContext = appContext; 56 this.backgroundExecutor = backgroundExecutor; 57 this.uiThreadExecutor = uiThreadExecutor; 58 } 59 60 /** 61 * Cancels all missed call notifications and marks all "new" missed calls in the system call log 62 * as "not new". 63 */ clearAll()64 public ListenableFuture<Void> clearAll() { 65 ListenableFuture<Void> markNewFuture = markNotNew(ImmutableSet.of()); 66 ListenableFuture<Void> cancelNotificationsFuture = 67 uiThreadExecutor.submit( 68 () -> { 69 MissedCallNotificationCanceller.cancelAll(appContext); 70 return null; 71 }); 72 73 // Note on this usage of whenAllComplete: 74 // -The returned future completes when all sub-futures complete (whether they fail or not) 75 // -The returned future fails if any sub-future fails 76 return Futures.whenAllComplete(markNewFuture, cancelNotificationsFuture) 77 .call( 78 () -> { 79 // Calling get() is necessary to propagate failures. 80 markNewFuture.get(); 81 cancelNotificationsFuture.get(); 82 return null; 83 }, 84 MoreExecutors.directExecutor()); 85 } 86 87 /** 88 * For the provided set of IDs from the system call log, cancels their missed call notifications 89 * and marks them "not new". 90 * 91 * @param ids IDs from the system call log (see {@link Calls#_ID}}. 92 */ clearBySystemCallLogId(Collection<Long> ids)93 public ListenableFuture<Void> clearBySystemCallLogId(Collection<Long> ids) { 94 ListenableFuture<Void> markNewFuture = markNotNew(ids); 95 ListenableFuture<Void> cancelNotificationsFuture = 96 uiThreadExecutor.submit( 97 () -> { 98 for (long id : ids) { 99 Uri callUri = Calls.CONTENT_URI.buildUpon().appendPath(Long.toString(id)).build(); 100 MissedCallNotificationCanceller.cancelSingle(appContext, callUri); 101 } 102 return null; 103 }); 104 105 // Note on this usage of whenAllComplete: 106 // -The returned future completes when all sub-futures complete (whether they fail or not) 107 // -The returned future fails if any sub-future fails 108 return Futures.whenAllComplete(markNewFuture, cancelNotificationsFuture) 109 .call( 110 () -> { 111 // Calling get() is necessary to propagate failures. 112 markNewFuture.get(); 113 cancelNotificationsFuture.get(); 114 return null; 115 }, 116 MoreExecutors.directExecutor()); 117 } 118 119 /** 120 * Marks all provided system call log IDs as not new, or if the provided collection is empty, 121 * marks all calls as not new. 122 */ 123 @SuppressLint("MissingPermission") 124 private ListenableFuture<Void> markNotNew(Collection<Long> ids) { 125 return backgroundExecutor.submit( 126 () -> { 127 if (!UserManagerCompat.isUserUnlocked(appContext)) { 128 LogUtil.e("ClearMissedCalls.markNotNew", "locked"); 129 return null; 130 } 131 if (!PermissionsUtil.hasCallLogWritePermissions(appContext)) { 132 LogUtil.e("ClearMissedCalls.markNotNew", "no permission"); 133 return null; 134 } 135 136 ContentValues values = new ContentValues(); 137 values.put(Calls.NEW, 0); 138 139 Selection.Builder selectionBuilder = 140 Selection.builder() 141 .and(Selection.column(Calls.NEW).is("=", 1)) 142 .and(Selection.column(Calls.TYPE).is("=", Calls.MISSED_TYPE)); 143 if (!ids.isEmpty()) { 144 selectionBuilder.and(Selection.column(Calls._ID).in(toStrings(ids))); 145 } 146 Selection selection = selectionBuilder.build(); 147 appContext 148 .getContentResolver() 149 .update( 150 Calls.CONTENT_URI, 151 values, 152 selection.getSelection(), 153 selection.getSelectionArgs()); 154 return null; 155 }); 156 } 157 158 private static String[] toStrings(Collection<Long> longs) { 159 String[] strings = new String[longs.size()]; 160 int i = 0; 161 for (long value : longs) { 162 strings[i++] = Long.toString(value); 163 } 164 return strings; 165 } 166 } 167