Home | History | Annotate | Download | only in sync
      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 
     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    */
     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    */
     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    */
     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    */
    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. */
    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 
    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. */
    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    */
    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. */
    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. */
    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    */
    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    */
    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. */
    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
    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