1 /*
2  * Copyright (C) 2014 Samsung System LSI
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 package com.android.bluetooth.map;
16 
17 import android.bluetooth.BluetoothProfile;
18 import android.bluetooth.BluetoothProtoEnums;
19 import android.content.ContentProvider;
20 import android.content.ContentValues;
21 import android.database.Cursor;
22 import android.net.Uri;
23 import android.os.Bundle;
24 import android.os.ParcelFileDescriptor;
25 import android.provider.Telephony.Mms;
26 import android.util.Log;
27 
28 import com.android.bluetooth.BluetoothStatsLog;
29 import com.android.bluetooth.content_profiles.ContentProfileErrorReportUtils;
30 
31 import com.google.android.mms.MmsException;
32 import com.google.android.mms.pdu.GenericPdu;
33 import com.google.android.mms.pdu.PduComposer;
34 import com.google.android.mms.pdu.PduPersister;
35 
36 import java.io.FileNotFoundException;
37 import java.io.FileOutputStream;
38 import java.io.IOException;
39 
40 /**
41  * Provider to let the MMS subsystem read data from it own database from another process. Workaround
42  * for missing access to sendStoredMessage().
43  */
44 // Next tag value for ContentProfileErrorReportUtils.report(): 5
45 public class MmsFileProvider extends ContentProvider {
46     static final String TAG = "BluetoothMmsFileProvider";
47     private PipeWriter mPipeWriter = new PipeWriter();
48 
49     /*package*/
50     static final Uri CONTENT_URI = Uri.parse("content://com.android.bluetooth.map.MmsFileProvider");
51 
52     @Override
onCreate()53     public boolean onCreate() {
54         return true;
55     }
56 
57     @Override
query( Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)58     public Cursor query(
59             Uri uri,
60             String[] projection,
61             String selection,
62             String[] selectionArgs,
63             String sortOrder) {
64         // Don't support queries.
65         return null;
66     }
67 
68     @Override
insert(Uri uri, ContentValues values)69     public Uri insert(Uri uri, ContentValues values) {
70         // Don't support inserts.
71         return null;
72     }
73 
74     @Override
delete(Uri uri, String selection, String[] selectionArgs)75     public int delete(Uri uri, String selection, String[] selectionArgs) {
76         // Don't support deletes.
77         return 0;
78     }
79 
80     @Override
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)81     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
82         // Don't support updates.
83         return 0;
84     }
85 
86     @Override
getType(Uri uri)87     public String getType(Uri uri) {
88         // For this sample, assume all files have no type.
89         return null;
90     }
91 
92     @Override
openFile(Uri uri, String fileMode)93     public ParcelFileDescriptor openFile(Uri uri, String fileMode) throws FileNotFoundException {
94         String idStr = uri.getLastPathSegment();
95         if (idStr == null) {
96             throw new FileNotFoundException("Unable to extract message handle from: " + uri);
97         }
98         try {
99             Long.parseLong(idStr);
100         } catch (NumberFormatException e) {
101             ContentProfileErrorReportUtils.report(
102                     BluetoothProfile.MAP,
103                     BluetoothProtoEnums.BLUETOOTH_MMS_FILE_PROVIDER,
104                     BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
105                     0);
106             Log.w(TAG, e);
107             throw new FileNotFoundException("Unable to extract message handle from: " + uri);
108         }
109         Uri messageUri = Mms.CONTENT_URI.buildUpon().appendEncodedPath(idStr).build();
110 
111         return openPipeHelper(messageUri, null, null, null, mPipeWriter);
112     }
113 
114     public class PipeWriter implements PipeDataWriter<Cursor> {
115         /** Generate a message based on the cursor, and write the encoded data to the stream. */
116         @Override
writeDataToPipe( ParcelFileDescriptor output, Uri uri, String mimeType, Bundle opts, Cursor c)117         public void writeDataToPipe(
118                 ParcelFileDescriptor output, Uri uri, String mimeType, Bundle opts, Cursor c) {
119             Log.d(
120                     TAG,
121                     "writeDataToPipe(): uri="
122                             + uri.toString()
123                             + " - getLastPathSegment() = "
124                             + uri.getLastPathSegment());
125 
126             FileOutputStream fout = null;
127             GenericPdu pdu = null;
128             PduPersister pduPersister = null;
129 
130             try {
131                 fout = new FileOutputStream(output.getFileDescriptor());
132                 pduPersister = PduPersister.getPduPersister(getContext());
133                 pdu = pduPersister.load(uri);
134                 byte[] bytes = (new PduComposer(getContext(), pdu)).make();
135                 fout.write(bytes);
136 
137             } catch (IOException e) {
138                 ContentProfileErrorReportUtils.report(
139                         BluetoothProfile.MAP,
140                         BluetoothProtoEnums.BLUETOOTH_MMS_FILE_PROVIDER,
141                         BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
142                         1);
143                 Log.w(TAG, e);
144                 /* TODO: How to signal the error to the calling entity? Had expected writeDataToPipe
145                  *       to throw IOException?
146                  */
147             } catch (MmsException e) {
148                 ContentProfileErrorReportUtils.report(
149                         BluetoothProfile.MAP,
150                         BluetoothProtoEnums.BLUETOOTH_MMS_FILE_PROVIDER,
151                         BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
152                         2);
153                 Log.w(TAG, e);
154                 /* TODO: How to signal the error to the calling entity? Had expected writeDataToPipe
155                  *       to throw IOException?
156                  */
157             } finally {
158                 if (pduPersister != null) {
159                     pduPersister.release();
160                 }
161                 try {
162                     fout.flush();
163                 } catch (IOException e) {
164                     ContentProfileErrorReportUtils.report(
165                             BluetoothProfile.MAP,
166                             BluetoothProtoEnums.BLUETOOTH_MMS_FILE_PROVIDER,
167                             BluetoothStatsLog
168                                     .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
169                             3);
170                     Log.w(TAG, "IOException: ", e);
171                 }
172                 try {
173                     fout.close();
174                 } catch (IOException e) {
175                     ContentProfileErrorReportUtils.report(
176                             BluetoothProfile.MAP,
177                             BluetoothProtoEnums.BLUETOOTH_MMS_FILE_PROVIDER,
178                             BluetoothStatsLog
179                                     .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
180                             4);
181                     Log.w(TAG, "IOException: ", e);
182                 }
183             }
184         }
185     }
186 }
187