1 /*
2  * Copyright (C) 2019 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 
17 package com.android.bluetooth.avrcpcontroller;
18 
19 import android.bluetooth.BluetoothAdapter;
20 import android.bluetooth.BluetoothDevice;
21 import android.content.ContentProvider;
22 import android.content.ContentValues;
23 import android.database.Cursor;
24 import android.graphics.Bitmap;
25 import android.net.Uri;
26 import android.os.ParcelFileDescriptor;
27 import android.util.Log;
28 
29 import java.io.FileNotFoundException;
30 import java.io.FileOutputStream;
31 import java.io.IOException;
32 
33 /**
34  * A provider of downloaded cover art images.
35  *
36  * <p>Cover art images are downloaded from remote devices and are promised to be "good" for the life
37  * of a connection.
38  *
39  * <p>Android applications are provided a Uri with their MediaMetadata and MediaItem objects that
40  * points back to this provider. Uris are in the following format:
41  *
42  * <p>content://com.android.bluetooth.avrcpcontroller.AvrcpCoverArtProvider/<device>/<image-handle>
43  *
44  * <p>It's expected by the Media framework that artwork at URIs will be available using the
45  * ContentResolver#openInputStream and BitmapFactory#decodeStream functions. Our provider must
46  * enable that usage pattern.
47  */
48 public class AvrcpCoverArtProvider extends ContentProvider {
49     private static final String TAG = AvrcpCoverArtProvider.class.getSimpleName();
50 
51     private BluetoothAdapter mAdapter;
52 
AvrcpCoverArtProvider()53     public AvrcpCoverArtProvider() {}
54 
55     static final String AUTHORITY = "com.android.bluetooth.avrcpcontroller.AvrcpCoverArtProvider";
56     static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY);
57 
58     /**
59      * Get the Uri for a cover art image based on the device and image handle
60      *
61      * @param device The Bluetooth device from which an image originated
62      * @param imageUuid The provided UUID of the cover artwork
63      * @return The Uri this provider will store the downloaded image at
64      */
getImageUri(BluetoothDevice device, String imageUuid)65     public static Uri getImageUri(BluetoothDevice device, String imageUuid) {
66         if (device == null || imageUuid == null || "".equals(imageUuid)) return null;
67         Uri uri =
68                 CONTENT_URI
69                         .buildUpon()
70                         .appendQueryParameter("device", device.getAddress())
71                         .appendQueryParameter("uuid", imageUuid)
72                         .build();
73         debug("getImageUri -> " + uri.toString());
74         return uri;
75     }
76 
getImage(BluetoothDevice device, String imageUuid)77     private Bitmap getImage(BluetoothDevice device, String imageUuid) {
78         AvrcpControllerService service = AvrcpControllerService.getAvrcpControllerService();
79         if (service == null) {
80             debug("Failed to get service, cover art not available");
81             return null;
82         }
83 
84         AvrcpCoverArtManager manager = service.getCoverArtManager();
85         if (manager == null) {
86             debug("Failed to get cover art manager. Cover art may not be enabled.");
87             return null;
88         }
89         return manager.getImage(device, imageUuid);
90     }
91 
getImageDescriptor(BluetoothDevice device, String imageUuid)92     private ParcelFileDescriptor getImageDescriptor(BluetoothDevice device, String imageUuid)
93             throws FileNotFoundException, IOException {
94         debug("getImageDescriptor(" + device + ", " + imageUuid + ")");
95         Bitmap image = getImage(device, imageUuid);
96         if (image == null) {
97             debug("Could not get requested image");
98             throw new FileNotFoundException();
99         }
100 
101         final ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
102         Thread transferThread =
103                 new Thread() {
104                     public void run() {
105                         try {
106                             FileOutputStream fout =
107                                     new ParcelFileDescriptor.AutoCloseOutputStream(pipe[1]);
108                             image.compress(Bitmap.CompressFormat.PNG, 100, fout);
109                             fout.flush();
110                             fout.close();
111                         } catch (IOException e) {
112                             /* Something bad must have happened writing the image data */
113                         }
114                     }
115                 };
116         transferThread.start();
117         return pipe[0];
118     }
119 
120     @Override
openFile(Uri uri, String mode)121     public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
122         debug("openFile(" + uri + ", '" + mode + "')");
123         String address = null;
124         String imageUuid = null;
125         BluetoothDevice device = null;
126         try {
127             address = uri.getQueryParameter("device");
128             imageUuid = uri.getQueryParameter("uuid");
129         } catch (NullPointerException e) {
130             throw new FileNotFoundException();
131         }
132 
133         try {
134             device = mAdapter.getRemoteDevice(address);
135         } catch (IllegalArgumentException e) {
136             throw new FileNotFoundException();
137         }
138 
139         ParcelFileDescriptor pfd = null;
140         try {
141             pfd = getImageDescriptor(device, imageUuid);
142         } catch (IOException e) {
143             debug("Failed to create inputstream from Bitmap");
144             throw new FileNotFoundException();
145         }
146         return pfd;
147     }
148 
149     @Override
onCreate()150     public boolean onCreate() {
151         mAdapter = BluetoothAdapter.getDefaultAdapter();
152         return true;
153     }
154 
155     @Override
query( Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)156     public Cursor query(
157             Uri uri,
158             String[] projection,
159             String selection,
160             String[] selectionArgs,
161             String sortOrder) {
162         return null;
163     }
164 
165     @Override
insert(Uri uri, ContentValues values)166     public Uri insert(Uri uri, ContentValues values) {
167         return null;
168     }
169 
170     @Override
delete(Uri uri, String selection, String[] selectionArgs)171     public int delete(Uri uri, String selection, String[] selectionArgs) {
172         return 0;
173     }
174 
175     @Override
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)176     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
177         return 0;
178     }
179 
180     @Override
getType(Uri uri)181     public String getType(Uri uri) {
182         return null;
183     }
184 
debug(String msg)185     private static void debug(String msg) {
186         Log.d(TAG, msg);
187     }
188 }
189