• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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 package com.android.voicemail.impl.sync;
17 
18 import android.annotation.TargetApi;
19 import android.content.ContentResolver;
20 import android.content.ContentUris;
21 import android.content.ContentValues;
22 import android.content.Context;
23 import android.database.Cursor;
24 import android.net.Uri;
25 import android.os.Build.VERSION_CODES;
26 import android.provider.VoicemailContract;
27 import android.provider.VoicemailContract.Voicemails;
28 import android.support.annotation.NonNull;
29 import android.telecom.PhoneAccountHandle;
30 import com.android.dialer.common.Assert;
31 import com.android.voicemail.impl.Voicemail;
32 import java.util.ArrayList;
33 import java.util.List;
34 
35 /** Construct queries to interact with the voicemails table. */
36 public class VoicemailsQueryHelper {
37   static final String[] PROJECTION =
38       new String[] {
39         Voicemails._ID, // 0
40         Voicemails.SOURCE_DATA, // 1
41         Voicemails.IS_READ, // 2
42         Voicemails.DELETED, // 3
43         Voicemails.TRANSCRIPTION // 4
44       };
45 
46   public static final int _ID = 0;
47   public static final int SOURCE_DATA = 1;
48   public static final int IS_READ = 2;
49   public static final int DELETED = 3;
50   public static final int TRANSCRIPTION = 4;
51 
52   static final String DELETED_SELECTION = Voicemails.DELETED + "=1";
53   static final String ARCHIVED_SELECTION = Voicemails.ARCHIVED + "=0";
54 
55   private Context context;
56   private ContentResolver contentResolver;
57   private Uri sourceUri;
58 
VoicemailsQueryHelper(Context context)59   public VoicemailsQueryHelper(Context context) {
60     this.context = context;
61     contentResolver = context.getContentResolver();
62     sourceUri = VoicemailContract.Voicemails.buildSourceUri(this.context.getPackageName());
63   }
64 
65   /**
66    * Get all the locally deleted voicemails that have not been synced to the server.
67    *
68    * @return A list of deleted voicemails.
69    */
getDeletedVoicemails(@onNull PhoneAccountHandle phoneAccountHandle)70   public List<Voicemail> getDeletedVoicemails(@NonNull PhoneAccountHandle phoneAccountHandle) {
71     return getLocalVoicemails(phoneAccountHandle, DELETED_SELECTION);
72   }
73 
74   /**
75    * Get all voicemails locally stored.
76    *
77    * @return A list of all locally stored voicemails.
78    */
getAllVoicemails(@onNull PhoneAccountHandle phoneAccountHandle)79   public List<Voicemail> getAllVoicemails(@NonNull PhoneAccountHandle phoneAccountHandle) {
80     return getLocalVoicemails(phoneAccountHandle, null);
81   }
82 
83   /**
84    * Utility method to make queries to the voicemail database.
85    *
86    * <p>TODO(a bug) add PhoneAccountHandle filtering back
87    *
88    * @param selection A filter declaring which rows to return. {@code null} returns all rows.
89    * @return A list of voicemails according to the selection statement.
90    */
getLocalVoicemails( @onNull PhoneAccountHandle unusedPhoneAccountHandle, String selection)91   private List<Voicemail> getLocalVoicemails(
92       @NonNull PhoneAccountHandle unusedPhoneAccountHandle, String selection) {
93     Cursor cursor = contentResolver.query(sourceUri, PROJECTION, selection, null, null);
94     if (cursor == null) {
95       return null;
96     }
97     try {
98       List<Voicemail> voicemails = new ArrayList<Voicemail>();
99       while (cursor.moveToNext()) {
100         final long id = cursor.getLong(_ID);
101         final String sourceData = cursor.getString(SOURCE_DATA);
102         final boolean isRead = cursor.getInt(IS_READ) == 1;
103         final String transcription = cursor.getString(TRANSCRIPTION);
104         Voicemail voicemail =
105             Voicemail.createForUpdate(id, sourceData)
106                 .setIsRead(isRead)
107                 .setTranscription(transcription)
108                 .build();
109         voicemails.add(voicemail);
110       }
111       return voicemails;
112     } finally {
113       cursor.close();
114     }
115   }
116 
117   /**
118    * Deletes a list of voicemails from the voicemail content provider.
119    *
120    * @param voicemails The list of voicemails to delete
121    * @return The number of voicemails deleted
122    */
deleteFromDatabase(List<Voicemail> voicemails)123   public int deleteFromDatabase(List<Voicemail> voicemails) {
124     int count = voicemails.size();
125     if (count == 0) {
126       return 0;
127     }
128 
129     StringBuilder sb = new StringBuilder();
130     for (int i = 0; i < count; i++) {
131       if (i > 0) {
132         sb.append(",");
133       }
134       sb.append(voicemails.get(i).getId());
135     }
136 
137     String selectionStatement = String.format(Voicemails._ID + " IN (%s)", sb.toString());
138     return contentResolver.delete(Voicemails.CONTENT_URI, selectionStatement, null);
139   }
140 
141   /** Utility method to delete a single voicemail that is not archived. */
deleteNonArchivedFromDatabase(Voicemail voicemail)142   public void deleteNonArchivedFromDatabase(Voicemail voicemail) {
143     contentResolver.delete(
144         Voicemails.CONTENT_URI,
145         Voicemails._ID + "=? AND " + Voicemails.ARCHIVED + "= 0",
146         new String[] {Long.toString(voicemail.getId())});
147   }
148 
markReadInDatabase(List<Voicemail> voicemails)149   public int markReadInDatabase(List<Voicemail> voicemails) {
150     int count = voicemails.size();
151     for (int i = 0; i < count; i++) {
152       markReadInDatabase(voicemails.get(i));
153     }
154     return count;
155   }
156 
157   /** Utility method to mark single message as read. */
markReadInDatabase(Voicemail voicemail)158   public void markReadInDatabase(Voicemail voicemail) {
159     Uri uri = ContentUris.withAppendedId(sourceUri, voicemail.getId());
160     ContentValues contentValues = new ContentValues();
161     contentValues.put(Voicemails.IS_READ, "1");
162     contentResolver.update(uri, contentValues, null, null);
163   }
164 
165   /**
166    * Sends an update command to the voicemail content provider for a list of voicemails. From the
167    * view of the provider, since the updater is the owner of the entry, a blank "update" means that
168    * the voicemail source is indicating that the server has up-to-date information on the voicemail.
169    * This flips the "dirty" bit to "0".
170    *
171    * @param voicemails The list of voicemails to update
172    * @return The number of voicemails updated
173    */
markCleanInDatabase(List<Voicemail> voicemails)174   public int markCleanInDatabase(List<Voicemail> voicemails) {
175     int count = voicemails.size();
176     for (int i = 0; i < count; i++) {
177       markCleanInDatabase(voicemails.get(i));
178     }
179     return count;
180   }
181 
182   /** Utility method to mark single message as clean. */
markCleanInDatabase(Voicemail voicemail)183   public void markCleanInDatabase(Voicemail voicemail) {
184     Uri uri = ContentUris.withAppendedId(sourceUri, voicemail.getId());
185     ContentValues contentValues = new ContentValues();
186     contentResolver.update(uri, contentValues, null, null);
187   }
188 
189   /** Utility method to add a transcription to the voicemail. */
updateWithTranscription(Voicemail voicemail, String transcription)190   public void updateWithTranscription(Voicemail voicemail, String transcription) {
191     Uri uri = ContentUris.withAppendedId(sourceUri, voicemail.getId());
192     ContentValues contentValues = new ContentValues();
193     contentValues.put(Voicemails.TRANSCRIPTION, transcription);
194     contentResolver.update(uri, contentValues, null, null);
195   }
196 
197   /**
198    * Voicemail is unique if the tuple of (phone account component name, phone account id, source
199    * data) is unique. If the phone account is missing, we also consider this unique since it's
200    * simply an "unknown" account.
201    *
202    * @param voicemail The voicemail to check if it is unique.
203    * @return {@code true} if the voicemail is unique, {@code false} otherwise.
204    */
isVoicemailUnique(Voicemail voicemail)205   public boolean isVoicemailUnique(Voicemail voicemail) {
206     Cursor cursor = null;
207     PhoneAccountHandle phoneAccount = voicemail.getPhoneAccount();
208     if (phoneAccount != null) {
209       String phoneAccountComponentName = phoneAccount.getComponentName().flattenToString();
210       String phoneAccountId = phoneAccount.getId();
211       String sourceData = voicemail.getSourceData();
212       if (phoneAccountComponentName == null || phoneAccountId == null || sourceData == null) {
213         return true;
214       }
215       try {
216         String whereClause =
217             Voicemails.PHONE_ACCOUNT_COMPONENT_NAME
218                 + "=? AND "
219                 + Voicemails.PHONE_ACCOUNT_ID
220                 + "=? AND "
221                 + Voicemails.SOURCE_DATA
222                 + "=?";
223         String[] whereArgs = {phoneAccountComponentName, phoneAccountId, sourceData};
224         cursor = contentResolver.query(sourceUri, PROJECTION, whereClause, whereArgs, null);
225         if (cursor.getCount() == 0) {
226           return true;
227         } else {
228           return false;
229         }
230       } finally {
231         if (cursor != null) {
232           cursor.close();
233         }
234       }
235     }
236     return true;
237   }
238 
239   /**
240    * Marks voicemails in the local database as archived. This indicates that the voicemails from the
241    * server were removed automatically to make space for new voicemails, and are stored locally on
242    * the users devices, without a corresponding server copy.
243    */
markArchivedInDatabase(List<Voicemail> voicemails)244   public void markArchivedInDatabase(List<Voicemail> voicemails) {
245     for (Voicemail voicemail : voicemails) {
246       markArchiveInDatabase(voicemail);
247     }
248   }
249 
250   /** Utility method to mark single voicemail as archived. */
markArchiveInDatabase(Voicemail voicemail)251   public void markArchiveInDatabase(Voicemail voicemail) {
252     Uri uri = ContentUris.withAppendedId(sourceUri, voicemail.getId());
253     ContentValues contentValues = new ContentValues();
254     contentValues.put(Voicemails.ARCHIVED, "1");
255     contentResolver.update(uri, contentValues, null, null);
256   }
257 
258   /** Find the oldest voicemails that are on the device, and also on the server. */
259   @TargetApi(VERSION_CODES.M) // used for try with resources
oldestVoicemailsOnServer(int numVoicemails)260   public List<Voicemail> oldestVoicemailsOnServer(int numVoicemails) {
261     if (numVoicemails <= 0) {
262       Assert.fail("Query for remote voicemails cannot be <= 0");
263     }
264 
265     String sortAndLimit = "date ASC limit " + numVoicemails;
266 
267     try (Cursor cursor =
268         contentResolver.query(sourceUri, PROJECTION, ARCHIVED_SELECTION, null, sortAndLimit)) {
269 
270       Assert.isNotNull(cursor);
271 
272       List<Voicemail> voicemails = new ArrayList<>();
273       while (cursor.moveToNext()) {
274         final long id = cursor.getLong(_ID);
275         final String sourceData = cursor.getString(SOURCE_DATA);
276         Voicemail voicemail = Voicemail.createForUpdate(id, sourceData).build();
277         voicemails.add(voicemail);
278       }
279 
280       if (voicemails.size() != numVoicemails) {
281         Assert.fail(
282             String.format(
283                 "voicemail count (%d) doesn't matched expected (%d)",
284                 voicemails.size(), numVoicemails));
285       }
286       return voicemails;
287     }
288   }
289 }
290