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 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 
     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    */
     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    */
     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    */
     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    */
    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    */
    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. */
    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 
    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. */
    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    */
    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. */
    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. */
    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    */
    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    */
    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. */
    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
    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