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