• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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