1 /* 2 * Copyright (C) 2017 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 package com.android.dialer.calllog.database; 17 18 import android.content.ContentProviderOperation; 19 import android.content.ContentUris; 20 import android.content.ContentValues; 21 import android.content.Context; 22 import android.content.OperationApplicationException; 23 import android.os.RemoteException; 24 import android.support.annotation.WorkerThread; 25 import android.text.TextUtils; 26 import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract; 27 import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog; 28 import com.android.dialer.calllog.datasources.CallLogMutations; 29 import com.android.dialer.common.Assert; 30 import com.android.dialer.common.LogUtil; 31 import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor; 32 import com.google.common.collect.Iterables; 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 java.util.ArrayList; 37 import java.util.Arrays; 38 import java.util.List; 39 import java.util.Map.Entry; 40 import javax.inject.Inject; 41 42 /** Applies {@link CallLogMutations} to the annotated call log. */ 43 public class MutationApplier { 44 45 private final ListeningExecutorService backgroundExecutorService; 46 47 @Inject MutationApplier(@ackgroundExecutor ListeningExecutorService backgroundExecutorService)48 public MutationApplier(@BackgroundExecutor ListeningExecutorService backgroundExecutorService) { 49 this.backgroundExecutorService = backgroundExecutorService; 50 } 51 52 /** Applies the provided {@link CallLogMutations} to the annotated call log. */ applyToDatabase(CallLogMutations mutations, Context appContext)53 public ListenableFuture<Void> applyToDatabase(CallLogMutations mutations, Context appContext) { 54 if (mutations.isEmpty()) { 55 return Futures.immediateFuture(null); 56 } 57 return backgroundExecutorService.submit( 58 () -> { 59 applyToDatabaseInternal(mutations, appContext); 60 return null; 61 }); 62 } 63 64 @WorkerThread applyToDatabaseInternal(CallLogMutations mutations, Context appContext)65 private void applyToDatabaseInternal(CallLogMutations mutations, Context appContext) 66 throws RemoteException, OperationApplicationException { 67 Assert.isWorkerThread(); 68 69 ArrayList<ContentProviderOperation> operations = new ArrayList<>(); 70 71 if (!mutations.getInserts().isEmpty()) { 72 LogUtil.i( 73 "MutationApplier.applyToDatabase", "inserting %d rows", mutations.getInserts().size()); 74 for (Entry<Long, ContentValues> entry : mutations.getInserts().entrySet()) { 75 long id = entry.getKey(); 76 ContentValues contentValues = entry.getValue(); 77 operations.add( 78 ContentProviderOperation.newInsert( 79 ContentUris.withAppendedId(AnnotatedCallLog.CONTENT_URI, id)) 80 .withValues(contentValues) 81 .build()); 82 } 83 } 84 85 if (!mutations.getUpdates().isEmpty()) { 86 LogUtil.i( 87 "MutationApplier.applyToDatabase", "updating %d rows", mutations.getUpdates().size()); 88 for (Entry<Long, ContentValues> entry : mutations.getUpdates().entrySet()) { 89 long id = entry.getKey(); 90 ContentValues contentValues = entry.getValue(); 91 operations.add( 92 ContentProviderOperation.newUpdate( 93 ContentUris.withAppendedId(AnnotatedCallLog.CONTENT_URI, id)) 94 .withValues(contentValues) 95 .build()); 96 } 97 } 98 99 if (!mutations.getDeletes().isEmpty()) { 100 LogUtil.i( 101 "MutationApplier.applyToDatabase", "deleting %d rows", mutations.getDeletes().size()); 102 103 // Batch the deletes into chunks of 999, the maximum size for SQLite selection args. 104 Iterable<List<Long>> batches = Iterables.partition(mutations.getDeletes(), 999); 105 for (List<Long> idsInBatch : batches) { 106 String[] questionMarks = new String[idsInBatch.size()]; 107 Arrays.fill(questionMarks, "?"); 108 109 String whereClause = 110 (AnnotatedCallLog._ID + " in (") + TextUtils.join(",", questionMarks) + ")"; 111 112 String[] whereArgs = new String[idsInBatch.size()]; 113 int i = 0; 114 for (long id : idsInBatch) { 115 whereArgs[i++] = String.valueOf(id); 116 } 117 118 operations.add( 119 ContentProviderOperation.newDelete(AnnotatedCallLog.CONTENT_URI) 120 .withSelection(whereClause, whereArgs) 121 .build()); 122 } 123 } 124 125 appContext.getContentResolver().applyBatch(AnnotatedCallLogContract.AUTHORITY, operations); 126 } 127 } 128