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 "IS_READ" status in 42 * the 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 "unread" missed calls in the system call 62 * log as "read". 63 */ clearAll()64 public ListenableFuture<Void> clearAll() { 65 ListenableFuture<Void> markReadFuture = markRead(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(markReadFuture, cancelNotificationsFuture) 77 .call( 78 () -> { 79 // Calling get() is necessary to propagate failures. 80 markReadFuture.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 "read". 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> markReadFuture = markRead(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(markReadFuture, cancelNotificationsFuture) 109 .call( 110 () -> { 111 // Calling get() is necessary to propagate failures. 112 markReadFuture.get(); 113 cancelNotificationsFuture.get(); 114 return null; 115 }, 116 MoreExecutors.directExecutor()); 117 } 118 119 /** 120 * Marks all provided system call log IDs as read, or if the provided collection is empty, marks 121 * all calls as read. 122 */ 123 @SuppressLint("MissingPermission") 124 private ListenableFuture<Void> markRead(Collection<Long> ids) { 125 return backgroundExecutor.submit( 126 () -> { 127 if (!UserManagerCompat.isUserUnlocked(appContext)) { 128 LogUtil.e("ClearMissedCalls.markRead", "locked"); 129 return null; 130 } 131 if (!PermissionsUtil.hasCallLogWritePermissions(appContext)) { 132 LogUtil.e("ClearMissedCalls.markRead", "no permission"); 133 return null; 134 } 135 136 ContentValues values = new ContentValues(); 137 values.put(Calls.IS_READ, 1); 138 139 Selection.Builder selectionBuilder = 140 Selection.builder() 141 .and( 142 Selection.column(Calls.IS_READ) 143 .is("=", 0) 144 .buildUpon() 145 .or(Selection.column(Calls.IS_READ).is("IS NULL")) 146 .build()) 147 .and(Selection.column(Calls.TYPE).is("=", Calls.MISSED_TYPE)); 148 if (!ids.isEmpty()) { 149 selectionBuilder.and(Selection.column(Calls._ID).in(toStrings(ids))); 150 } 151 Selection selection = selectionBuilder.build(); 152 appContext 153 .getContentResolver() 154 .update( 155 Calls.CONTENT_URI, 156 values, 157 selection.getSelection(), 158 selection.getSelectionArgs()); 159 return null; 160 }); 161 } 162 163 private static String[] toStrings(Collection<Long> longs) { 164 String[] strings = new String[longs.size()]; 165 int i = 0; 166 for (long value : longs) { 167 strings[i++] = Long.toString(value); 168 } 169 return strings; 170 } 171 } 172