1326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde/*
2326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde * Copyright (C) 2013 The Android Open Source Project
3326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde *
4326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde * Licensed under the Apache License, Version 2.0 (the "License");
5326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde * you may not use this file except in compliance with the License.
6326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde * You may obtain a copy of the License at
7326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde *
8326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde *      http://www.apache.org/licenses/LICENSE-2.0
9326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde *
10326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde * Unless required by applicable law or agreed to in writing, software
11326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde * distributed under the License is distributed on an "AS IS" BASIS,
12326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde * See the License for the specific language governing permissions and
14326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde * limitations under the License.
15326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde */
16326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
17326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bondepackage com.android.bluetooth.mapapi;
18326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
19326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bondeimport android.content.ContentProvider;
20326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bondeimport android.content.ContentResolver;
21326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bondeimport android.content.ContentValues;
22326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bondeimport android.content.Context;
23326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bondeimport android.content.UriMatcher;
24326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bondeimport android.content.pm.ProviderInfo;
25326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bondeimport android.database.Cursor;
26326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bondeimport android.net.Uri;
27326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bondeimport android.os.AsyncTask;
28326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bondeimport android.os.Binder;
29326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bondeimport android.os.Bundle;
30326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bondeimport android.os.ParcelFileDescriptor;
31326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bondeimport android.util.Log;
32326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
33326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bondeimport java.io.FileInputStream;
34326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bondeimport java.io.FileNotFoundException;
35326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bondeimport java.io.FileOutputStream;
36326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bondeimport java.io.IOException;
37326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bondeimport java.util.List;
38326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bondeimport java.util.Map;
39326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bondeimport java.util.Map.Entry;
40326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bondeimport java.util.Set;
41326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
42326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde/**
43326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde * A base implementation of the BluetoothMapEmailContract.
44326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde * A base class for a ContentProvider that allows access to Email messages from a Bluetooth
45326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde * device through the Message Access Profile.
46326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde */
47326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bondepublic abstract class BluetoothMapEmailProvider extends ContentProvider {
48326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
49326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    private static final String TAG = "BluetoothMapEmailProvider";
50326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    private static final boolean D = true;
51326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
52326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    private static final int MATCH_ACCOUNT = 1;
53326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    private static final int MATCH_MESSAGE = 2;
54326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    private static final int MATCH_FOLDER = 3;
55326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
56326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    protected ContentResolver mResolver;
57326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
58326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    private Uri CONTENT_URI = null;
59326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    private String mAuthority;
60326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    private UriMatcher mMatcher;
61326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
62326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
63326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    private PipeReader mPipeReader = new PipeReader();
64326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    private PipeWriter mPipeWriter = new PipeWriter();
65326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
66326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    /**
67326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * Write the content of a message to a stream as MIME encoded RFC-2822 data.
68326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param accountId the ID of the account to which the message belong
69326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param messageId the ID of the message to write to the stream
70326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param includeAttachment true if attachments should be included
71326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param download true if any missing part of the message shall be downloaded
72326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *        before written to the stream. The download flag will determine
73326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *        whether or not attachments shall be downloaded or only the message content.
74326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param out the FileOurputStream to write to.
75326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @throws IOException
76326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     */
77326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    abstract protected void WriteMessageToStream(long accountId, long messageId,
78326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            boolean includeAttachment, boolean download, FileOutputStream out)
79326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        throws IOException;
80326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
81326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    /**
82326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @return the CONTENT_URI exposed. This will be used to send out notifications.
83326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     */
84326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    abstract protected Uri getContentUri();
85326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
86326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde   /**
87326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    * Implementation is provided by the parent class.
88326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    */
89326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde   @Override
90326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde   public void attachInfo(Context context, ProviderInfo info) {
91326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde       mAuthority = info.authority;
92326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
93326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde       mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
94326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde       mMatcher.addURI(mAuthority, BluetoothMapContract.TABLE_ACCOUNT, MATCH_ACCOUNT);
95326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde       mMatcher.addURI(mAuthority, "#/"+BluetoothMapContract.TABLE_FOLDER, MATCH_FOLDER);
96326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde       mMatcher.addURI(mAuthority, "#/"+BluetoothMapContract.TABLE_MESSAGE, MATCH_MESSAGE);
97326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
98326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde       // Sanity check our setup
99326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde       if (!info.exported) {
100326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde           throw new SecurityException("Provider must be exported");
101326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde       }
102326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde       // Enforce correct permissions are used
103326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde       if (!android.Manifest.permission.BLUETOOTH_MAP.equals(info.writePermission)){
104326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde           throw new SecurityException("Provider must be protected by " +
105326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                   android.Manifest.permission.BLUETOOTH_MAP);
106326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde       }
107326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde       mResolver = context.getContentResolver();
108326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde       super.attachInfo(context, info);
109326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde   }
110326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
111326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
112326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    /**
113326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * Interface to write a stream of data to a pipe.  Use with
114326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * {@link ContentProvider#openPipeHelper}.
115326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     */
116326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    public interface PipeDataReader<T> {
117326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        /**
118326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde         * Called from a background thread to stream data from a pipe.
119326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde         * Note that the pipe is blocking, so this thread can block on
120326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde         * reads for an arbitrary amount of time if the client is slow
121326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde         * at writing.
122326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde         *
123326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde         * @param input The pipe where data should be read. This will be
124326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde         * closed for you upon returning from this function.
125326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde         * @param uri The URI whose data is to be written.
126326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde         * @param mimeType The desired type of data to be written.
127326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde         * @param opts Options supplied by caller.
128326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde         * @param args Your own custom arguments.
129326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde         */
130326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        public void readDataFromPipe(ParcelFileDescriptor input, Uri uri, String mimeType,
131326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                Bundle opts, T args);
132326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    }
133326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
134326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    public class PipeReader implements PipeDataReader<Cursor> {
135326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        /**
136326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde         * Read the data from the pipe and generate a message.
137326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde         * Use the message to do an update of the message specified by the URI.
138326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde         */
139326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        @Override
140326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        public void readDataFromPipe(ParcelFileDescriptor input, Uri uri,
141326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                String mimeType, Bundle opts, Cursor args) {
142326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            Log.v(TAG, "readDataFromPipe(): uri=" + uri.toString());
143326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            FileInputStream fIn = null;
144326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            try {
145326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                fIn = new FileInputStream(input.getFileDescriptor());
146326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                long messageId = Long.valueOf(uri.getLastPathSegment());
147326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                long accountId = Long.valueOf(getAccountId(uri));
148326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                UpdateMimeMessageFromStream(fIn, accountId, messageId);
149326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            } catch (IOException e) {
150326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                Log.w(TAG,"IOException: ", e);
151326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                /* TODO: How to signal the error to the calling entity? Had expected readDataFromPipe
152326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                 *       to throw IOException?
153326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                 */
154326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            } finally {
155326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                try {
156326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    if(fIn != null)
157326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                        fIn.close();
158326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                } catch (IOException e) {
159326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    Log.w(TAG,e);
160326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                }
161326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            }
162326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        }
163326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    }
164326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
165326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    /**
166326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * Read a MIME encoded RFC-2822 fileStream and update the message content.
167326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * The Date and/or From headers may not be present in the MIME encoded
168326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * message, and this function shall add appropriate values if the headers
169326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * are missing. From should be set to the owner of the account.
170326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *
171326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param input the file stream to read data from
172326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param accountId the accountId
173326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param messageId ID of the message to update
174326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     */
175326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    abstract protected void UpdateMimeMessageFromStream(FileInputStream input, long accountId,
176326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            long messageId) throws IOException;
177326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
178326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    public class PipeWriter implements PipeDataWriter<Cursor> {
179326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        /**
180326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde         * Generate a message based on the cursor, and write the encoded data to the stream.
181326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde         */
182326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
183326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        public void writeDataToPipe(ParcelFileDescriptor output, Uri uri, String mimeType,
184326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                Bundle opts, Cursor c) {
185326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            if (D) Log.d(TAG, "writeDataToPipe(): uri=" + uri.toString() +
186326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    " - getLastPathSegment() = " + uri.getLastPathSegment());
187326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
188326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            FileOutputStream fout = null;
189326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
190326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            try {
191326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                fout = new FileOutputStream(output.getFileDescriptor());
192326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
193326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                boolean includeAttachments = true;
194326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                boolean download = false;
195326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                List<String> segments = uri.getPathSegments();
196326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                long messageId = Long.parseLong(segments.get(2));
197326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                long accountId = Long.parseLong(getAccountId(uri));
198326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                if(segments.size() >= 4) {
199326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    String format = segments.get(3);
200326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    if(format.equalsIgnoreCase(BluetoothMapContract.FILE_MSG_NO_ATTACHMENTS)) {
201326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                        includeAttachments = false;
202326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    } else if(format.equalsIgnoreCase(BluetoothMapContract.FILE_MSG_DOWNLOAD_NO_ATTACHMENTS)) {
203326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                        includeAttachments = false;
204326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                        download = true;
205326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    } else if(format.equalsIgnoreCase(BluetoothMapContract.FILE_MSG_DOWNLOAD)) {
206326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                        download = true;
207326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    }
208326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                }
209326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
210326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                WriteMessageToStream(accountId, messageId, includeAttachments, download, fout);
211326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            } catch (IOException e) {
212326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                Log.w(TAG, e);
213326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                /* TODO: How to signal the error to the calling entity? Had expected writeDataToPipe
214326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                 *       to throw IOException?
215326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                 */
216326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            } finally {
217326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                try {
218326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    fout.flush();
219326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                } catch (IOException e) {
220326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    Log.w(TAG, "IOException: ", e);
221326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                }
222326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                try {
223326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    fout.close();
224326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                } catch (IOException e) {
225326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    Log.w(TAG, "IOException: ", e);
226326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                }
227326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            }
228326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        }
229326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    }
230326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
231326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    /**
232326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * This function shall be called when any Account database content have changed
233326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * to Notify any attached observers.
234326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param accountId the ID of the account that changed. Null is a valid value,
235326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *        if accountId is unknown or multiple accounts changed.
236326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     */
237326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    protected void onAccountChanged(String accountId) {
238326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        Uri newUri = null;
239326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
240326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        if(mAuthority == null){
241326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            return;
242326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        }
243326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        if(accountId == null){
244326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            newUri = BluetoothMapContract.buildAccountUri(mAuthority);
245326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        } else {
246326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            newUri = BluetoothMapContract.buildAccountUriwithId(mAuthority, accountId);
247326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        }
248326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        if(D) Log.d(TAG,"onAccountChanged() accountId = " + accountId + " URI: " + newUri);
249326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        mResolver.notifyChange(newUri, null);
250326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    }
251326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
252326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    /**
253326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * This function shall be called when any Message database content have changed
254326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * to notify any attached observers.
255326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param accountId Null is a valid value, if accountId is unknown, but
256326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *        recommended for increased performance.
257326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param messageId Null is a valid value, if multiple messages changed or the
258326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *        messageId is unknown, but recommended for increased performance.
259326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     */
260326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    protected void onMessageChanged(String accountId, String messageId) {
261326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        Uri newUri = null;
262326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
263326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        if(mAuthority == null){
264326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            return;
265326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        }
266326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
267326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        if(accountId == null){
268326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            newUri = BluetoothMapContract.buildMessageUri(mAuthority);
269326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        } else {
270326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            if(messageId == null)
271326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            {
272326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                newUri = BluetoothMapContract.buildMessageUri(mAuthority,accountId);
273326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            } else {
274326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                newUri = BluetoothMapContract.buildMessageUriWithId(mAuthority,accountId, messageId);
275326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            }
276326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        }
277326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        if(D) Log.d(TAG,"onMessageChanged() accountId = " + accountId + " messageId = " + messageId + " URI: " + newUri);
278326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        mResolver.notifyChange(newUri, null);
279326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    }
280326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
281326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    /**
282326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * Not used, this is just a dummy implementation.
283326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     */
284326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    @Override
285326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    public String getType(Uri uri) {
286326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        return "Email";
287326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    }
288326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
289326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    /**
290326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * Open a file descriptor to a message.
291326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * Two modes supported for read: With and without attachments.
292326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * One mode exist for write and the actual content will be with or without
293326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * attachments.
294326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *
295326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * Mode will be "r" or "w".
296326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *
297326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * URI format:
298326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * The URI scheme is as follows.
299326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * For messages with attachments:
300326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *   content://com.android.mail.bluetoothprovider/Messages/msgId#
301326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *
302326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * For messages without attachments:
303326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *   content://com.android.mail.bluetoothprovider/Messages/msgId#/NO_ATTACHMENTS
304326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *
305326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * UPDATE: For write.
306326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *         First create a message in the DB using insert into the message DB
307326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *         Then open a file handle to the #id
308326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *         write the data to a stream created from the fileHandle.
309326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *
310326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param uri the URI to open. ../Messages/#id
311326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param mode the mode to use. The following modes exist: - UPDATE do not work - use URI
312326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *  - "read_with_attachments" - to read an e-mail including any attachments
313326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *  - "read_no_attachments" - to read an e-mail excluding any attachments
314326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *  - "write" - to add a mime encoded message to the database. This write
315326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *              should not trigger the message to be send.
316326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @return the ParcelFileDescriptor
317326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *  @throws FileNotFoundException
318326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     */
319326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    @Override
320326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
321326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        long callingId = Binder.clearCallingIdentity();
322326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        if(D)Log.d(TAG, "openFile(): uri=" + uri.toString() + " - getLastPathSegment() = " +
323326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                uri.getLastPathSegment());
324326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        try {
325326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            /* To be able to do abstraction of the file IO, we simply ignore the URI at this
326326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde             * point and let the read/write function implementations parse the URI. */
327326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            if(mode.equals("w")) {
328326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                return openInversePipeHelper(uri, null, null, null, mPipeReader);
329326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            } else {
330326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                return openPipeHelper (uri, null, null, null, mPipeWriter);
331326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            }
332326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        } catch (IOException e) {
333326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            Log.w(TAG,e);
334326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        } finally {
335326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            Binder.restoreCallingIdentity(callingId);
336326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        }
337326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        return null;
338326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    }
339326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
340326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    /**
341326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * A helper function for implementing {@link #openFile}, for
342326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * creating a data pipe and background thread allowing you to stream
343326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * data back from the client.  This function returns a new
344326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * ParcelFileDescriptor that should be returned to the caller (the caller
345326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * is responsible for closing it).
346326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *
347326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param uri The URI whose data is to be written.
348326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param mimeType The desired type of data to be written.
349326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param opts Options supplied by caller.
350326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param args Your own custom arguments.
351326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param func Interface implementing the function that will actually
352326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * stream the data.
353326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @return Returns a new ParcelFileDescriptor holding the read side of
354326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * the pipe.  This should be returned to the caller for reading; the caller
355326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * is responsible for closing it when done.
356326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     */
357326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    private <T> ParcelFileDescriptor openInversePipeHelper(final Uri uri, final String mimeType,
358326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            final Bundle opts, final T args, final PipeDataReader<T> func)
359326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            throws FileNotFoundException {
360326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        try {
361326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            final ParcelFileDescriptor[] fds = ParcelFileDescriptor.createPipe();
362326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
363326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            AsyncTask<Object, Object, Object> task = new AsyncTask<Object, Object, Object>() {
364326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                @Override
365326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                protected Object doInBackground(Object... params) {
366326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    func.readDataFromPipe(fds[0], uri, mimeType, opts, args);
367326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    try {
368326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                        fds[0].close();
369326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    } catch (IOException e) {
370326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                        Log.w(TAG, "Failure closing pipe", e);
371326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    }
372326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    return null;
373326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                }
374326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            };
375326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Object[])null);
376326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
377326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            return fds[1];
378326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        } catch (IOException e) {
379326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            throw new FileNotFoundException("failure making pipe");
380326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        }
381326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    }
382326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
383326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    /**
384326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * The MAP specification states that a delete request from MAP client is a folder shift to the
385326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * 'deleted' folder.
386326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * Only use case of delete() is when transparency is requested for push messages, then
387326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * message should not remain in sent folder and therefore must be deleted
388326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     */
389326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    @Override
390326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    public int delete(Uri uri, String where, String[] selectionArgs) {
391326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        if (D) Log.d(TAG, "delete(): uri=" + uri.toString() );
392326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        int result = 0;
393326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
394326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        String table = uri.getPathSegments().get(1);
395326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        if(table == null)
396326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            throw new IllegalArgumentException("Table missing in URI");
397326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        // the id of the entry to be deleted from the database
398326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        String messageId = uri.getLastPathSegment();
399326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        if (messageId == null)
400326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            throw new IllegalArgumentException("Message ID missing in update values!");
401326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
402326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
403326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        String accountId = getAccountId(uri);
404326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        if (accountId == null)
405326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            throw new IllegalArgumentException("Account ID missing in update values!");
406326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
407326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        long callingId = Binder.clearCallingIdentity();
408326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        try {
409326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            if(table.equals(BluetoothMapContract.TABLE_MESSAGE)) {
410326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                return deleteMessage(accountId, messageId);
411326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            } else {
412326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                if (D) Log.w(TAG, "Unknown table name: " + table);
413326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                return result;
414326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            }
415326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        } finally {
416326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            Binder.restoreCallingIdentity(callingId);
417326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        }
418326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    }
419326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
420326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    /**
421326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * This function deletes a message.
422326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param accountId the ID of the Account
423326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param messageId the ID of the message to delete.
424326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @return the number of messages deleted - 0 if the message was not found.
425326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     */
426326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    abstract protected int deleteMessage(String accountId, String messageId);
427326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
428326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    /**
429326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * Insert is used to add new messages to the data base.
430326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * Insert message approach:
431326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *   - Insert an empty message to get an _id with only a folder_id
432326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *   - Open the _id for write
433326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *   - Write the message content
434326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *     (When the writer completes, this provider should do an update of the message)
435326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     */
436326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    @Override
437326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    public Uri insert(Uri uri, ContentValues values) {
438326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        String table = uri.getLastPathSegment();
439326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        if(table == null){
440326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            throw new IllegalArgumentException("Table missing in URI");
441326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        }
442326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        String accountId = getAccountId(uri);
443326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        Long folderId = values.getAsLong(BluetoothMapContract.MessageColumns.FOLDER_ID);
444326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        if(folderId == null) {
445326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            throw new IllegalArgumentException("FolderId missing in ContentValues");
446326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        }
447326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
448326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        String id; // the id of the entry inserted into the database
449326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        long callingId = Binder.clearCallingIdentity();
450326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        Log.d(TAG, "insert(): uri=" + uri.toString() + " - getLastPathSegment() = " +
451326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                uri.getLastPathSegment());
452326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        try {
453326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            if(table.equals(BluetoothMapContract.TABLE_MESSAGE)) {
454326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                id = insertMessage(accountId, folderId.toString());
455326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                if(D) Log.i(TAG, "insert() ID: " + id);
456326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                return Uri.parse(uri.toString() + "/" + id);
457326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            } else {
458326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                Log.w(TAG, "Unknown table name: " + table);
459326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                return null;
460326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            }
461326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        } finally {
462326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            Binder.restoreCallingIdentity(callingId);
463326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        }
464326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    }
465326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
466326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
467326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    /**
468326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * Inserts an empty message into the Message data base in the specified folder.
469326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * This is done before the actual message content is written by fileIO.
470326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param accountId the ID of the account
471326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param folderId the ID of the folder to create a new message in.
472326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @return the message id as a string
473326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     */
474326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    abstract protected String insertMessage(String accountId, String folderId);
475326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
476326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     /**
477326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * Utility function to build a projection based on a projectionMap.
478326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *
479326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *   "btColumnName" -> "emailColumnName as btColumnName" for each entry.
480326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *
481326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * This supports SQL statements in the emailColumnName entry.
482326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param projection
483326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param projectionMap <btColumnName, emailColumnName>
484326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @return the converted projection
485326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     */
486326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    protected String[] convertProjection(String[] projection, Map<String,String> projectionMap) {
487326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        String[] newProjection = new String[projection.length];
488326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        for(int i = 0; i < projection.length; i++) {
489326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            newProjection[i] = projectionMap.get(projection[i]) + " as " + projection[i];
490326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        }
491326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        return newProjection;
492326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    }
493326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
494326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    /**
495326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * This query needs to map from the data used in the e-mail client to BluetoothMapContract type of data.
496326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     */
497326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    @Override
498326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
499326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            String sortOrder) {
500326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        long callingId = Binder.clearCallingIdentity();
501326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        try {
502326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            String accountId = null;
503326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            switch (mMatcher.match(uri)) {
504326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                case MATCH_ACCOUNT:
505326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    return queryAccount(projection, selection, selectionArgs, sortOrder);
506326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                case MATCH_FOLDER:
507326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    accountId = getAccountId(uri);
508326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    return queryFolder(accountId, projection, selection, selectionArgs, sortOrder);
509326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                case MATCH_MESSAGE:
510326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    accountId = getAccountId(uri);
511326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    return queryMessage(accountId, projection, selection, selectionArgs, sortOrder);
512326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                default:
513326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    throw new UnsupportedOperationException("Unsupported Uri " + uri);
514326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            }
515326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        } finally {
516326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            Binder.restoreCallingIdentity(callingId);
517326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        }
518326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    }
519326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
520326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    /**
521326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * Query account information.
522326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * This function shall return only exposable e-mail accounts. Hence shall not
523326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * return accounts that has policies suggesting not to be shared them.
524326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param projection
525326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param selection
526326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param selectionArgs
527326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param sortOrder
528326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @return a cursor to the accounts that are subject to exposure over BT.
529326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     */
530326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    abstract protected Cursor queryAccount(String[] projection, String selection, String[] selectionArgs,
531326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            String sortOrder);
532326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
533326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    /**
534326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * Filter out the non usable folders and ensure to name the mandatory folders
535326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * inbox, outbox, sent, deleted and draft.
536326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param accountId
537326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param projection
538326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param selection
539326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param selectionArgs
540326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param sortOrder
541326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @return
542326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     */
543326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    abstract protected Cursor queryFolder(String accountId, String[] projection, String selection, String[] selectionArgs,
544326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            String sortOrder);
545326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    /**
546326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * For the message table the selection (where clause) can only include the following columns:
547326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *    date: less than, greater than and equals
548326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *    flagRead: = 1 or = 0
549326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *    flagPriority: = 1 or = 0
550326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *    folder_id: the ID of the folder only equals
551326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *    toList: partial name/address search
552326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *    ccList: partial name/address search
553326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *    bccList: partial name/address search
554326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *    fromList: partial name/address search
555326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * Additionally the COUNT and OFFSET shall be supported.
556326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param accountId the ID of the account
557326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param projection
558326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param selection
559326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param selectionArgs
560326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param sortOrder
561326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @return a cursor to query result
562326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     */
563326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    abstract protected Cursor queryMessage(String accountId, String[] projection, String selection, String[] selectionArgs,
564326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            String sortOrder);
565326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
566326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    /**
567326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * update()
568326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * Messages can be modified in the following cases:
569326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *  - the folder_key of a message - hence the message can be moved to a new folder,
570326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *                                  but the content cannot be modified.
571326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     *  - the FLAG_READ state can be changed.
572326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * The selection statement will always be selection of a message ID, when updating a message,
573326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * hence this function will be called multiple times if multiple messages must be updated
574326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * due to the nature of the Bluetooth Message Access profile.
575326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     */
576326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    @Override
577326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
578326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
579326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        String table = uri.getLastPathSegment();
580326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        if(table == null){
581326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            throw new IllegalArgumentException("Table missing in URI");
582326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        }
583326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        if(selection != null) {
584326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            throw new IllegalArgumentException("selection shall not be used, ContentValues shall contain the data");
585326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        }
586326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
587326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        long callingId = Binder.clearCallingIdentity();
588326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        if(D)Log.w(TAG, "update(): uri=" + uri.toString() + " - getLastPathSegment() = " +
589326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                uri.getLastPathSegment());
590326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        try {
591326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            if(table.equals(BluetoothMapContract.TABLE_ACCOUNT)) {
592326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                String accountId = values.getAsString(BluetoothMapContract.AccountColumns._ID);
593326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                if(accountId == null) {
594326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    throw new IllegalArgumentException("Account ID missing in update values!");
595326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                }
596326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                Integer exposeFlag = values.getAsInteger(BluetoothMapContract.AccountColumns.FLAG_EXPOSE);
597326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                if(exposeFlag == null){
598326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    throw new IllegalArgumentException("Expose flag missing in update values!");
599326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                }
600326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                return updateAccount(accountId, exposeFlag);
601326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            } else if(table.equals(BluetoothMapContract.TABLE_FOLDER)) {
602326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                return 0; // We do not support changing folders
603326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            } else if(table.equals(BluetoothMapContract.TABLE_MESSAGE)) {
604326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                String accountId = getAccountId(uri);
605326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                Long messageId = values.getAsLong(BluetoothMapContract.MessageColumns._ID);
606326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                if(messageId == null) {
607326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    throw new IllegalArgumentException("Message ID missing in update values!");
608326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                }
609326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                Long folderId = values.getAsLong(BluetoothMapContract.MessageColumns.FOLDER_ID);
610326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                Boolean flagRead = values.getAsBoolean(BluetoothMapContract.MessageColumns.FLAG_READ);
611326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                return updateMessage(accountId, messageId, folderId, flagRead);
612326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            } else {
613326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                if(D)Log.w(TAG, "Unknown table name: " + table);
614326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                return 0;
615326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            }
616326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        } finally {
617326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            Binder.restoreCallingIdentity(callingId);
618326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        }
619326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    }
620326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
621326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    /**
622326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * Update an entry in the account table. Only the expose flag will be
623326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * changed through this interface.
624326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param accountId the ID of the account to change.
625326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param flagExpose the updated value.
626326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @return the number of entries changed - 0 if account not found or value cannot be changed.
627326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     */
628326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    abstract protected int updateAccount(String accountId, int flagExpose);
629326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
630326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    /**
631326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * Update an entry in the message table.
632326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param accountId ID of the account to which the messageId relates
633326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param messageId the ID of the message to update
634326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param folderId the new folder ID value to set - ignore if null.
635326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param flagRead the new flagRead value to set - ignore if null.
636326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @return
637326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     */
638326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    abstract protected int updateMessage(String accountId, Long messageId, Long folderId, Boolean flagRead);
639326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
640326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
641326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    @Override
642326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    public Bundle call(String method, String arg, Bundle extras) {
643326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        long callingId = Binder.clearCallingIdentity();
644326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        if(D)Log.d(TAG, "call(): method=" + method + " arg=" + arg + "ThreadId: " + Thread.currentThread().getId());
645326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
646326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        try {
647326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            if(method.equals(BluetoothMapContract.METHOD_UPDATE_FOLDER)) {
648326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                long accountId = extras.getLong(BluetoothMapContract.EXTRA_UPDATE_ACCOUNT_ID, -1);
649326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                if(accountId == -1) {
650326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    Log.w(TAG, "No account ID in CALL");
651326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    return null;
652326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                }
653326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                long folderId = extras.getLong(BluetoothMapContract.EXTRA_UPDATE_FOLDER_ID, -1);
654326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                if(folderId == -1) {
655326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    Log.w(TAG, "No folder ID in CALL");
656326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    return null;
657326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                }
658326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                int ret = syncFolder(accountId, folderId);
659326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                if(ret == 0) {
660326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                    return new Bundle();
661326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                }
662326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde                return null;
663326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            }
664326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        } finally {
665326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            Binder.restoreCallingIdentity(callingId);
666326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        }
667326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        return null;
668326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    }
669326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
670326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    /**
671326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * Trigger a sync of the specified folder.
672326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param accountId the ID of the account that owns the folder
673326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @param folderId the ID of the folder.
674326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * @return 0 at success
675326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     */
676326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    abstract protected int syncFolder(long accountId, long folderId);
677326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
678326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    /**
679326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * Need this to suppress warning in unit tests.
680326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     */
681326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    @Override
682326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    public void shutdown() {
683326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        // Don't call super.shutdown(), which emits a warning...
684326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    }
685326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde
686326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    /**
687326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     * Extract the BluetoothMapContract.AccountColumns._ID from the given URI.
688326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde     */
689326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    public static String getAccountId(Uri uri) {
690326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        final List<String> segments = uri.getPathSegments();
691326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        if (segments.size() < 1) {
692326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde            throw new IllegalArgumentException("No AccountId pressent in URI: " + uri);
693326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        }
694326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde        return segments.get(0);
695326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde    }
696326b5e610063ac24c0ba467ac585bd4c7f618a67Casper Bonde}
697