summaryrefslogtreecommitdiffstats
path: root/java/com/android/voicemail/impl/sync/VoicemailsQueryHelper.java
blob: d129406fff79ac7c75ca64468a2fc6267d539b61 (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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
/*
 * Copyright (C) 2015 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.voicemail.impl.sync;

import android.annotation.TargetApi;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build.VERSION_CODES;
import android.provider.VoicemailContract;
import android.provider.VoicemailContract.Voicemails;
import android.telecom.PhoneAccountHandle;
import com.android.dialer.common.Assert;
import com.android.voicemail.impl.Voicemail;
import java.util.ArrayList;
import java.util.List;

/** Construct queries to interact with the voicemails table. */
public class VoicemailsQueryHelper {
  static final String[] PROJECTION =
      new String[] {
        Voicemails._ID, // 0
        Voicemails.SOURCE_DATA, // 1
        Voicemails.IS_READ, // 2
        Voicemails.DELETED, // 3
        Voicemails.TRANSCRIPTION // 4
      };

  public static final int _ID = 0;
  public static final int SOURCE_DATA = 1;
  public static final int IS_READ = 2;
  public static final int DELETED = 3;
  public static final int TRANSCRIPTION = 4;

  static final String READ_SELECTION =
      Voicemails.DIRTY + "=1 AND " + Voicemails.DELETED + "!=1 AND " + Voicemails.IS_READ + "=1";
  static final String DELETED_SELECTION = Voicemails.DELETED + "=1";
  static final String ARCHIVED_SELECTION = Voicemails.ARCHIVED + "=0";

  private Context mContext;
  private ContentResolver mContentResolver;
  private Uri mSourceUri;

  public VoicemailsQueryHelper(Context context) {
    mContext = context;
    mContentResolver = context.getContentResolver();
    mSourceUri = VoicemailContract.Voicemails.buildSourceUri(mContext.getPackageName());
  }

  /**
   * Get all the local read voicemails that have not been synced to the server.
   *
   * @return A list of read voicemails.
   */
  public List<Voicemail> getReadVoicemails() {
    return getLocalVoicemails(READ_SELECTION);
  }

  /**
   * Get all the locally deleted voicemails that have not been synced to the server.
   *
   * @return A list of deleted voicemails.
   */
  public List<Voicemail> getDeletedVoicemails() {
    return getLocalVoicemails(DELETED_SELECTION);
  }

  /**
   * Get all voicemails locally stored.
   *
   * @return A list of all locally stored voicemails.
   */
  public List<Voicemail> getAllVoicemails() {
    return getLocalVoicemails(null);
  }

  /**
   * Utility method to make queries to the voicemail database.
   *
   * @param selection A filter declaring which rows to return. {@code null} returns all rows.
   * @return A list of voicemails according to the selection statement.
   */
  private List<Voicemail> getLocalVoicemails(String selection) {
    Cursor cursor = mContentResolver.query(mSourceUri, PROJECTION, selection, null, null);
    if (cursor == null) {
      return null;
    }
    try {
      List<Voicemail> voicemails = new ArrayList<Voicemail>();
      while (cursor.moveToNext()) {
        final long id = cursor.getLong(_ID);
        final String sourceData = cursor.getString(SOURCE_DATA);
        final boolean isRead = cursor.getInt(IS_READ) == 1;
        final String transcription = cursor.getString(TRANSCRIPTION);
        Voicemail voicemail =
            Voicemail.createForUpdate(id, sourceData)
                .setIsRead(isRead)
                .setTranscription(transcription)
                .build();
        voicemails.add(voicemail);
      }
      return voicemails;
    } finally {
      cursor.close();
    }
  }

  /**
   * Deletes a list of voicemails from the voicemail content provider.
   *
   * @param voicemails The list of voicemails to delete
   * @return The number of voicemails deleted
   */
  public int deleteFromDatabase(List<Voicemail> voicemails) {
    int count = voicemails.size();
    if (count == 0) {
      return 0;
    }

    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < count; i++) {
      if (i > 0) {
        sb.append(",");
      }
      sb.append(voicemails.get(i).getId());
    }

    String selectionStatement = String.format(Voicemails._ID + " IN (%s)", sb.toString());
    return mContentResolver.delete(Voicemails.CONTENT_URI, selectionStatement, null);
  }

  /** Utility method to delete a single voicemail that is not archived. */
  public void deleteNonArchivedFromDatabase(Voicemail voicemail) {
    mContentResolver.delete(
        Voicemails.CONTENT_URI,
        Voicemails._ID + "=? AND " + Voicemails.ARCHIVED + "= 0",
        new String[] {Long.toString(voicemail.getId())});
  }

  public int markReadInDatabase(List<Voicemail> voicemails) {
    int count = voicemails.size();
    for (int i = 0; i < count; i++) {
      markReadInDatabase(voicemails.get(i));
    }
    return count;
  }

  /** Utility method to mark single message as read. */
  public void markReadInDatabase(Voicemail voicemail) {
    Uri uri = ContentUris.withAppendedId(mSourceUri, voicemail.getId());
    ContentValues contentValues = new ContentValues();
    contentValues.put(Voicemails.IS_READ, "1");
    mContentResolver.update(uri, contentValues, null, null);
  }

  /**
   * Sends an update command to the voicemail content provider for a list of voicemails. From the
   * view of the provider, since the updater is the owner of the entry, a blank "update" means that
   * the voicemail source is indicating that the server has up-to-date information on the voicemail.
   * This flips the "dirty" bit to "0".
   *
   * @param voicemails The list of voicemails to update
   * @return The number of voicemails updated
   */
  public int markCleanInDatabase(List<Voicemail> voicemails) {
    int count = voicemails.size();
    for (int i = 0; i < count; i++) {
      markCleanInDatabase(voicemails.get(i));
    }
    return count;
  }

  /** Utility method to mark single message as clean. */
  public void markCleanInDatabase(Voicemail voicemail) {
    Uri uri = ContentUris.withAppendedId(mSourceUri, voicemail.getId());
    ContentValues contentValues = new ContentValues();
    mContentResolver.update(uri, contentValues, null, null);
  }

  /** Utility method to add a transcription to the voicemail. */
  public void updateWithTranscription(Voicemail voicemail, String transcription) {
    Uri uri = ContentUris.withAppendedId(mSourceUri, voicemail.getId());
    ContentValues contentValues = new ContentValues();
    contentValues.put(Voicemails.TRANSCRIPTION, transcription);
    mContentResolver.update(uri, contentValues, null, null);
  }

  /**
   * Voicemail is unique if the tuple of (phone account component name, phone account id, source
   * data) is unique. If the phone account is missing, we also consider this unique since it's
   * simply an "unknown" account.
   *
   * @param voicemail The voicemail to check if it is unique.
   * @return {@code true} if the voicemail is unique, {@code false} otherwise.
   */
  public boolean isVoicemailUnique(Voicemail voicemail) {
    Cursor cursor = null;
    PhoneAccountHandle phoneAccount = voicemail.getPhoneAccount();
    if (phoneAccount != null) {
      String phoneAccountComponentName = phoneAccount.getComponentName().flattenToString();
      String phoneAccountId = phoneAccount.getId();
      String sourceData = voicemail.getSourceData();
      if (phoneAccountComponentName == null || phoneAccountId == null || sourceData == null) {
        return true;
      }
      try {
        String whereClause =
            Voicemails.PHONE_ACCOUNT_COMPONENT_NAME
                + "=? AND "
                + Voicemails.PHONE_ACCOUNT_ID
                + "=? AND "
                + Voicemails.SOURCE_DATA
                + "=?";
        String[] whereArgs = {phoneAccountComponentName, phoneAccountId, sourceData};
        cursor = mContentResolver.query(mSourceUri, PROJECTION, whereClause, whereArgs, null);
        if (cursor.getCount() == 0) {
          return true;
        } else {
          return false;
        }
      } finally {
        if (cursor != null) {
          cursor.close();
        }
      }
    }
    return true;
  }

  /**
   * Marks voicemails in the local database as archived. This indicates that the voicemails from the
   * server were removed automatically to make space for new voicemails, and are stored locally on
   * the users devices, without a corresponding server copy.
   */
  public void markArchivedInDatabase(List<Voicemail> voicemails) {
    for (Voicemail voicemail : voicemails) {
      markArchiveInDatabase(voicemail);
    }
  }

  /** Utility method to mark single voicemail as archived. */
  public void markArchiveInDatabase(Voicemail voicemail) {
    Uri uri = ContentUris.withAppendedId(mSourceUri, voicemail.getId());
    ContentValues contentValues = new ContentValues();
    contentValues.put(Voicemails.ARCHIVED, "1");
    mContentResolver.update(uri, contentValues, null, null);
  }

  /** Find the oldest voicemails that are on the device, and also on the server. */
  @TargetApi(VERSION_CODES.M) // used for try with resources
  public List<Voicemail> oldestVoicemailsOnServer(int numVoicemails) {
    if (numVoicemails <= 0) {
      Assert.fail("Query for remote voicemails cannot be <= 0");
    }

    String sortAndLimit = "date ASC limit " + numVoicemails;

    try (Cursor cursor =
        mContentResolver.query(mSourceUri, null, ARCHIVED_SELECTION, null, sortAndLimit)) {

      Assert.isNotNull(cursor);

      List<Voicemail> voicemails = new ArrayList<>();
      while (cursor.moveToNext()) {
        final String sourceData = cursor.getString(SOURCE_DATA);
        Voicemail voicemail = Voicemail.createForUpdate(cursor.getLong(_ID), sourceData).build();
        voicemails.add(voicemail);
      }

      if (voicemails.size() != numVoicemails) {
        Assert.fail(
            String.format(
                "voicemail count (%d) doesn't matched expected (%d)",
                voicemails.size(), numVoicemails));
      }
      return voicemails;
    }
  }
}