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