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.phone.vvm.omtp.sync; 17 18 import android.content.ContentResolver; 19 import android.content.ContentUris; 20 import android.content.ContentValues; 21 import android.content.Context; 22 import android.database.Cursor; 23 import android.net.Uri; 24 import android.provider.VoicemailContract; 25 import android.provider.VoicemailContract.Voicemails; 26 import android.telecom.PhoneAccountHandle; 27 import android.telecom.Voicemail; 28 import android.util.Log; 29 30 import java.util.ArrayList; 31 import java.util.List; 32 33 /** 34 * Construct queries to interact with the voicemails table. 35 */ 36 public class VoicemailsQueryHelper { 37 private static final String TAG = "VoicemailsQueryHelper"; 38 39 final static String[] PROJECTION = new String[] { 40 Voicemails._ID, // 0 41 Voicemails.SOURCE_DATA, // 1 42 Voicemails.IS_READ, // 2 43 Voicemails.DELETED, // 3 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 51 final static String READ_SELECTION = Voicemails.DIRTY + "=1 AND " 52 + Voicemails.DELETED + "!=1 AND " + Voicemails.IS_READ + "=1"; 53 final static String DELETED_SELECTION = Voicemails.DELETED + "=1"; 54 55 private Context mContext; 56 private ContentResolver mContentResolver; 57 private Uri mSourceUri; 58 VoicemailsQueryHelper(Context context)59 public VoicemailsQueryHelper(Context context) { 60 mContext = context; 61 mContentResolver = context.getContentResolver(); 62 mSourceUri = VoicemailContract.Voicemails.buildSourceUri(mContext.getPackageName()); 63 } 64 65 /** 66 * Get all the local read voicemails that have not been synced to the server. 67 * 68 * @return A list of read voicemails. 69 */ getReadVoicemails()70 public List<Voicemail> getReadVoicemails() { 71 return getLocalVoicemails(READ_SELECTION); 72 } 73 74 /** 75 * Get all the locally deleted voicemails that have not been synced to the server. 76 * 77 * @return A list of deleted voicemails. 78 */ getDeletedVoicemails()79 public List<Voicemail> getDeletedVoicemails() { 80 return getLocalVoicemails(DELETED_SELECTION); 81 } 82 83 /** 84 * Get all voicemails locally stored. 85 * 86 * @return A list of all locally stored voicemails. 87 */ getAllVoicemails()88 public List<Voicemail> getAllVoicemails() { 89 return getLocalVoicemails(null); 90 } 91 92 /** 93 * Utility method to make queries to the voicemail database. 94 * 95 * @param selection A filter declaring which rows to return. {@code null} returns all rows. 96 * @return A list of voicemails according to the selection statement. 97 */ getLocalVoicemails(String selection)98 private List<Voicemail> getLocalVoicemails(String selection) { 99 Cursor cursor = mContentResolver.query(mSourceUri, PROJECTION, selection, null, null); 100 if (cursor == null) { 101 return null; 102 } 103 try { 104 List<Voicemail> voicemails = new ArrayList<Voicemail>(); 105 while (cursor.moveToNext()) { 106 final long id = cursor.getLong(_ID); 107 final String sourceData = cursor.getString(SOURCE_DATA); 108 final boolean isRead = cursor.getInt(IS_READ) == 1; 109 Voicemail voicemail = Voicemail 110 .createForUpdate(id, sourceData) 111 .setIsRead(isRead).build(); 112 voicemails.add(voicemail); 113 } 114 return voicemails; 115 } finally { 116 cursor.close(); 117 } 118 } 119 120 /** 121 * Deletes a list of voicemails from the voicemail content provider. 122 * 123 * @param voicemails The list of voicemails to delete 124 * @return The number of voicemails deleted 125 */ deleteFromDatabase(List<Voicemail> voicemails)126 public int deleteFromDatabase(List<Voicemail> voicemails) { 127 int count = voicemails.size(); 128 if (count == 0) { 129 return 0; 130 } 131 132 StringBuilder sb = new StringBuilder(); 133 for (int i = 0; i < count; i++) { 134 if (i > 0) { 135 sb.append(","); 136 } 137 sb.append(voicemails.get(i).getId()); 138 } 139 140 String selectionStatement = String.format(Voicemails._ID + " IN (%s)", sb.toString()); 141 return mContentResolver.delete(Voicemails.CONTENT_URI, selectionStatement, null); 142 } 143 144 /** 145 * Utility method to delete a single voicemail. 146 */ deleteFromDatabase(Voicemail voicemail)147 public void deleteFromDatabase(Voicemail voicemail) { 148 mContentResolver.delete(Voicemails.CONTENT_URI, Voicemails._ID + "=?", 149 new String[] { Long.toString(voicemail.getId()) }); 150 } 151 152 /** 153 * Sends an update command to the voicemail content provider for a list of voicemails. 154 * From the view of the provider, since the updater is the owner of the entry, a blank 155 * "update" means that the voicemail source is indicating that the server has up-to-date 156 * information on the voicemail. This flips the "dirty" bit to "0". 157 * 158 * @param voicemails The list of voicemails to update 159 * @return The number of voicemails updated 160 */ markReadInDatabase(List<Voicemail> voicemails)161 public int markReadInDatabase(List<Voicemail> voicemails) { 162 int count = voicemails.size(); 163 for (int i = 0; i < count; i++) { 164 markReadInDatabase(voicemails.get(i)); 165 } 166 return count; 167 } 168 169 /** 170 * Utility method to mark single message as read. 171 */ markReadInDatabase(Voicemail voicemail)172 public void markReadInDatabase(Voicemail voicemail) { 173 Uri uri = ContentUris.withAppendedId(mSourceUri, voicemail.getId()); 174 ContentValues contentValues = new ContentValues(); 175 contentValues.put(Voicemails.IS_READ, "1"); 176 mContentResolver.update(uri, contentValues, null, null); 177 } 178 179 /** 180 * Check if a particular voicemail has already been inserted. If not, insert the new voicemail. 181 * @param voicemail The voicemail to insert. 182 */ insertIfUnique(Voicemail voicemail)183 public void insertIfUnique(Voicemail voicemail) { 184 if (isVoicemailUnique(voicemail)) { 185 VoicemailContract.Voicemails.insert(mContext, voicemail); 186 } else { 187 Log.w(TAG, "Voicemail already exists."); 188 } 189 } 190 191 /** 192 * Voicemail is unique if the tuple of (phone account component name, phone account id, source 193 * data) is unique. If the phone account is missing, we also consider this unique since it's 194 * simply an "unknown" account. 195 * @param voicemail The voicemail to check if it is unique. 196 * @return {@code true} if the voicemail is unique, {@code false} otherwise. 197 */ isVoicemailUnique(Voicemail voicemail)198 private boolean isVoicemailUnique(Voicemail voicemail) { 199 Cursor cursor = null; 200 PhoneAccountHandle phoneAccount = voicemail.getPhoneAccount(); 201 if (phoneAccount != null) { 202 String phoneAccountComponentName = phoneAccount.getComponentName().flattenToString(); 203 String phoneAccountId = phoneAccount.getId(); 204 String sourceData = voicemail.getSourceData(); 205 if (phoneAccountComponentName == null || phoneAccountId == null || sourceData == null) { 206 return true; 207 } 208 try { 209 String whereClause = 210 Voicemails.PHONE_ACCOUNT_COMPONENT_NAME + "=? AND " + 211 Voicemails.PHONE_ACCOUNT_ID + "=? AND " + Voicemails.SOURCE_DATA + "=?"; 212 String[] whereArgs = { phoneAccountComponentName, phoneAccountId, sourceData }; 213 cursor = mContentResolver.query( 214 mSourceUri, PROJECTION, whereClause, whereArgs, null); 215 if (cursor.getCount() == 0) { 216 return true; 217 } else { 218 return false; 219 } 220 } 221 finally { 222 if (cursor != null) { 223 cursor.close(); 224 } 225 } 226 } 227 return true; 228 } 229 } 230