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 */
16package com.android.phone.vvm.omtp.sync;
17
18import android.content.ContentResolver;
19import android.content.ContentUris;
20import android.content.ContentValues;
21import android.content.Context;
22import android.database.Cursor;
23import android.net.Uri;
24import android.provider.VoicemailContract;
25import android.provider.VoicemailContract.Voicemails;
26import android.telecom.PhoneAccountHandle;
27import android.telecom.Voicemail;
28import android.util.Log;
29
30import java.util.ArrayList;
31import java.util.List;
32
33/**
34 * Construct queries to interact with the voicemails table.
35 */
36public 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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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