summaryrefslogtreecommitdiff
path: root/java/com/android/dialer/calldetails/CallDetailsCursorLoader.java
blob: 9d55b9fda7f2a67f7a4604ce235728df1f434fbb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dialer.calldetails;

import android.content.Context;
import android.content.CursorLoader;
import android.database.Cursor;
import com.android.dialer.CoalescedIds;
import com.android.dialer.calldetails.CallDetailsEntries.CallDetailsEntry;
import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog;
import com.android.dialer.common.Assert;
import com.android.dialer.duo.DuoComponent;

/**
 * A {@link CursorLoader} that loads call detail entries from {@link AnnotatedCallLog} for {@link
 * CallDetailsActivity}.
 */
public final class CallDetailsCursorLoader extends CursorLoader {

  // Columns in AnnotatedCallLog that are needed to build a CallDetailsEntry proto.
  // Be sure to update (1) constants that store indexes of the elements and (2) method
  // toCallDetailsEntry(Cursor) when updating this array.
  public static final String[] COLUMNS_FOR_CALL_DETAILS =
      new String[] {
        AnnotatedCallLog._ID,
        AnnotatedCallLog.CALL_TYPE,
        AnnotatedCallLog.FEATURES,
        AnnotatedCallLog.TIMESTAMP,
        AnnotatedCallLog.DURATION,
        AnnotatedCallLog.DATA_USAGE,
        AnnotatedCallLog.PHONE_ACCOUNT_COMPONENT_NAME,
        AnnotatedCallLog.CALL_MAPPING_ID
      };

  // Indexes for COLUMNS_FOR_CALL_DETAILS
  private static final int ID = 0;
  private static final int CALL_TYPE = 1;
  private static final int FEATURES = 2;
  private static final int TIMESTAMP = 3;
  private static final int DURATION = 4;
  private static final int DATA_USAGE = 5;
  private static final int PHONE_ACCOUNT_COMPONENT_NAME = 6;
  private static final int CALL_MAPPING_ID = 7;

  CallDetailsCursorLoader(Context context, CoalescedIds coalescedIds) {
    super(
        context,
        AnnotatedCallLog.CONTENT_URI,
        COLUMNS_FOR_CALL_DETAILS,
        annotatedCallLogIdsSelection(coalescedIds),
        annotatedCallLogIdsSelectionArgs(coalescedIds),
        AnnotatedCallLog.TIMESTAMP + " DESC");
  }

  @Override
  public void onContentChanged() {
    // Do nothing here.
    // This is to prevent the loader to reload data when Loader.ForceLoadContentObserver detects a
    // change.
    // Without this, the app will crash when the user deletes call details as the deletion triggers
    // the data loading but no data can be fetched and we want to ensure the data set is not empty
    // when building CallDetailsEntries proto (see toCallDetailsEntries(Cursor)).
    //
    // OldCallDetailsActivity doesn't respond to underlying data changes and we decided to keep it
    // that way in CallDetailsActivity.
  }

  /**
   * Build a string of the form "COLUMN_NAME IN (?, ?, ..., ?)", where COLUMN_NAME is the name of
   * the ID column in {@link AnnotatedCallLog}.
   *
   * <p>This string will be used as the {@code selection} parameter to initialize the loader.
   */
  private static String annotatedCallLogIdsSelection(CoalescedIds coalescedIds) {
    // First, build a string of question marks ('?') separated by commas (',').
    StringBuilder questionMarks = new StringBuilder();
    for (int i = 0; i < coalescedIds.getCoalescedIdCount(); i++) {
      if (i != 0) {
        questionMarks.append(", ");
      }
      questionMarks.append("?");
    }

    return AnnotatedCallLog._ID + " IN (" + questionMarks + ")";
  }

  /**
   * Returns a string that will be used as the {@code selectionArgs} parameter to initialize the
   * loader.
   */
  private static String[] annotatedCallLogIdsSelectionArgs(CoalescedIds coalescedIds) {
    String[] args = new String[coalescedIds.getCoalescedIdCount()];

    for (int i = 0; i < coalescedIds.getCoalescedIdCount(); i++) {
      args[i] = String.valueOf(coalescedIds.getCoalescedId(i));
    }

    return args;
  }

  /**
   * Creates a new {@link CallDetailsEntries} from the entire data set loaded by this loader.
   *
   * @param cursor A cursor pointing to the data set loaded by this loader. The caller must ensure
   *     the cursor is not null and the data set it points to is not empty.
   * @return A {@link CallDetailsEntries} proto.
   */
  static CallDetailsEntries toCallDetailsEntries(Context context, Cursor cursor) {
    Assert.isNotNull(cursor);
    Assert.checkArgument(cursor.moveToFirst());

    CallDetailsEntries.Builder entries = CallDetailsEntries.newBuilder();

    do {
      entries.addEntries(toCallDetailsEntry(context, cursor));
    } while (cursor.moveToNext());

    return entries.build();
  }

  /** Creates a new {@link CallDetailsEntry} from the provided cursor using its current position. */
  private static CallDetailsEntry toCallDetailsEntry(Context context, Cursor cursor) {
    CallDetailsEntry.Builder entry = CallDetailsEntry.newBuilder();
    entry
        .setCallId(cursor.getLong(ID))
        .setCallType(cursor.getInt(CALL_TYPE))
        .setFeatures(cursor.getInt(FEATURES))
        .setDate(cursor.getLong(TIMESTAMP))
        .setDuration(cursor.getLong(DURATION))
        .setDataUsage(cursor.getLong(DATA_USAGE))
        .setCallMappingId(cursor.getString(CALL_MAPPING_ID));

    String phoneAccountComponentName = cursor.getString(PHONE_ACCOUNT_COMPONENT_NAME);
    entry.setIsDuoCall(DuoComponent.get(context).getDuo().isDuoAccount(phoneAccountComponentName));

    return entry.build();
  }
}