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 17 package com.android.dialer.calldetails; 18 19 import android.content.Context; 20 import android.content.CursorLoader; 21 import android.database.Cursor; 22 import com.android.dialer.CoalescedIds; 23 import com.android.dialer.calldetails.CallDetailsEntries.CallDetailsEntry; 24 import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog; 25 import com.android.dialer.common.Assert; 26 import com.android.dialer.duo.DuoConstants; 27 28 /** 29 * A {@link CursorLoader} that loads call detail entries from {@link AnnotatedCallLog} for {@link 30 * CallDetailsActivity}. 31 */ 32 public final class CallDetailsCursorLoader extends CursorLoader { 33 34 // Columns in AnnotatedCallLog that are needed to build a CallDetailsEntry proto. 35 // Be sure to update (1) constants that store indexes of the elements and (2) method 36 // toCallDetailsEntry(Cursor) when updating this array. 37 public static final String[] COLUMNS_FOR_CALL_DETAILS = 38 new String[] { 39 AnnotatedCallLog._ID, 40 AnnotatedCallLog.CALL_TYPE, 41 AnnotatedCallLog.FEATURES, 42 AnnotatedCallLog.TIMESTAMP, 43 AnnotatedCallLog.DURATION, 44 AnnotatedCallLog.DATA_USAGE, 45 AnnotatedCallLog.PHONE_ACCOUNT_COMPONENT_NAME 46 }; 47 48 // Indexes for COLUMNS_FOR_CALL_DETAILS 49 private static final int ID = 0; 50 private static final int CALL_TYPE = 1; 51 private static final int FEATURES = 2; 52 private static final int TIMESTAMP = 3; 53 private static final int DURATION = 4; 54 private static final int DATA_USAGE = 5; 55 private static final int PHONE_ACCOUNT_COMPONENT_NAME = 6; 56 CallDetailsCursorLoader(Context context, CoalescedIds coalescedIds)57 CallDetailsCursorLoader(Context context, CoalescedIds coalescedIds) { 58 super( 59 context, 60 AnnotatedCallLog.CONTENT_URI, 61 COLUMNS_FOR_CALL_DETAILS, 62 annotatedCallLogIdsSelection(coalescedIds), 63 annotatedCallLogIdsSelectionArgs(coalescedIds), 64 AnnotatedCallLog.TIMESTAMP + " DESC"); 65 } 66 67 @Override onContentChanged()68 public void onContentChanged() { 69 // Do nothing here. 70 // This is to prevent the loader to reload data when Loader.ForceLoadContentObserver detects a 71 // change. 72 // Without this, the app will crash when the user deletes call details as the deletion triggers 73 // the data loading but no data can be fetched and we want to ensure the data set is not empty 74 // when building CallDetailsEntries proto (see toCallDetailsEntries(Cursor)). 75 // 76 // CallDetailsActivity doesn't respond to underlying data changes when launched from the old 77 // call log and we decided to keep it that way when launched from the new call log. 78 } 79 80 /** 81 * Build a string of the form "COLUMN_NAME IN (?, ?, ..., ?)", where COLUMN_NAME is the name of 82 * the ID column in {@link AnnotatedCallLog}. 83 * 84 * <p>This string will be used as the {@code selection} parameter to initialize the loader. 85 */ annotatedCallLogIdsSelection(CoalescedIds coalescedIds)86 private static String annotatedCallLogIdsSelection(CoalescedIds coalescedIds) { 87 // First, build a string of question marks ('?') separated by commas (','). 88 StringBuilder questionMarks = new StringBuilder(); 89 for (int i = 0; i < coalescedIds.getCoalescedIdCount(); i++) { 90 if (i != 0) { 91 questionMarks.append(", "); 92 } 93 questionMarks.append("?"); 94 } 95 96 return AnnotatedCallLog._ID + " IN (" + questionMarks + ")"; 97 } 98 99 /** 100 * Returns a string that will be used as the {@code selectionArgs} parameter to initialize the 101 * loader. 102 */ annotatedCallLogIdsSelectionArgs(CoalescedIds coalescedIds)103 private static String[] annotatedCallLogIdsSelectionArgs(CoalescedIds coalescedIds) { 104 String[] args = new String[coalescedIds.getCoalescedIdCount()]; 105 106 for (int i = 0; i < coalescedIds.getCoalescedIdCount(); i++) { 107 args[i] = String.valueOf(coalescedIds.getCoalescedId(i)); 108 } 109 110 return args; 111 } 112 113 /** 114 * Creates a new {@link CallDetailsEntries} from the entire data set loaded by this loader. 115 * 116 * @param cursor A cursor pointing to the data set loaded by this loader. The caller must ensure 117 * the cursor is not null and the data set it points to is not empty. 118 * @return A {@link CallDetailsEntries} proto. 119 */ toCallDetailsEntries(Cursor cursor)120 static CallDetailsEntries toCallDetailsEntries(Cursor cursor) { 121 Assert.isNotNull(cursor); 122 Assert.checkArgument(cursor.moveToFirst()); 123 124 CallDetailsEntries.Builder entries = CallDetailsEntries.newBuilder(); 125 126 do { 127 entries.addEntries(toCallDetailsEntry(cursor)); 128 } while (cursor.moveToNext()); 129 130 return entries.build(); 131 } 132 133 /** Creates a new {@link CallDetailsEntry} from the provided cursor using its current position. */ toCallDetailsEntry(Cursor cursor)134 private static CallDetailsEntry toCallDetailsEntry(Cursor cursor) { 135 CallDetailsEntry.Builder entry = CallDetailsEntry.newBuilder(); 136 entry 137 .setCallId(cursor.getLong(ID)) 138 .setCallType(cursor.getInt(CALL_TYPE)) 139 .setFeatures(cursor.getInt(FEATURES)) 140 .setDate(cursor.getLong(TIMESTAMP)) 141 .setDuration(cursor.getLong(DURATION)) 142 .setDataUsage(cursor.getLong(DATA_USAGE)); 143 144 String phoneAccountComponentName = cursor.getString(PHONE_ACCOUNT_COMPONENT_NAME); 145 entry.setIsDuoCall( 146 DuoConstants.PHONE_ACCOUNT_COMPONENT_NAME 147 .flattenToString() 148 .equals(phoneAccountComponentName)); 149 150 return entry.build(); 151 } 152 } 153