• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  * Cover art images are downloaded from remote devices and are promised to be "good" for the life of
37  * a connection.
38  *
39  * 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  *   content://com.android.bluetooth.avrcpcontroller.AvrcpCoverArtProvider/<device>/<image-handle>
43  *
44  * 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";
50     private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
51 
52     private BluetoothAdapter mAdapter;
53 
AvrcpCoverArtProvider()54     public AvrcpCoverArtProvider() {
55     }
56 
57     static final String AUTHORITY = "com.android.bluetooth.avrcpcontroller.AvrcpCoverArtProvider";
58     static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY);
59 
60     /**
61      * Get the Uri for a cover art image based on the device and image handle
62      *
63      * @param device The Bluetooth device from which an image originated
64      * @param imageUuid The provided UUID of the cover artwork
65      * @return The Uri this provider will store the downloaded image at
66      */
getImageUri(BluetoothDevice device, String imageUuid)67     public static Uri getImageUri(BluetoothDevice device, String imageUuid) {
68         if (device == null || imageUuid == null || "".equals(imageUuid)) return null;
69         Uri uri = CONTENT_URI.buildUpon().appendQueryParameter("device", device.getAddress())
70                 .appendQueryParameter("uuid", imageUuid)
71                 .build();
72         debug("getImageUri -> " + uri.toString());
73         return uri;
74     }
75 
getImage(BluetoothDevice device, String imageUuid)76     private Bitmap getImage(BluetoothDevice device, String imageUuid) {
77         AvrcpControllerService service = AvrcpControllerService.getAvrcpControllerService();
78         if (service == null) {
79             debug("Failed to get service, cover art not available");
80             return null;
81         }
82 
83         AvrcpCoverArtManager manager = service.getCoverArtManager();
84         if (manager == null) {
85             debug("Failed to get cover art manager. Cover art may not be enabled.");
86             return null;
87         }
88         return manager.getImage(device, imageUuid);
89     }
90 
getImageDescriptor(BluetoothDevice device, String imageUuid)91     private ParcelFileDescriptor getImageDescriptor(BluetoothDevice device, String imageUuid)
92             throws FileNotFoundException, IOException {
93         debug("getImageDescriptor(" + device + ", " + imageUuid + ")");
94         Bitmap image = getImage(device, imageUuid);
95         if (image == null) {
96             debug("Could not get requested image");
97             throw new FileNotFoundException();
98         }
99 
100         final ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
101         Thread transferThread = new Thread() {
102             public void run() {
103                 try {
104                     FileOutputStream fout =
105                             new ParcelFileDescriptor.AutoCloseOutputStream(pipe[1]);
106                     image.compress(Bitmap.CompressFormat.PNG, 100, fout);
107                     fout.flush();
108                     fout.close();
109                 } catch (IOException e) {
110                     /* Something bad must have happened writing the image data */
111                 }
112             }
113         };
114         transferThread.start();
115         return pipe[0];
116     }
117 
118     @Override
openFile(Uri uri, String mode)119     public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
120         debug("openFile(" + uri + ", '" + mode + "')");
121         String address = null;
122         String imageUuid = null;
123         BluetoothDevice device = null;
124         try {
125             address = uri.getQueryParameter("device");
126             imageUuid = uri.getQueryParameter("uuid");
127         } catch (NullPointerException e) {
128             throw new FileNotFoundException();
129         }
130 
131         try {
132             device = mAdapter.getRemoteDevice(address);
133         } catch (IllegalArgumentException e) {
134             throw new FileNotFoundException();
135         }
136 
137         ParcelFileDescriptor pfd = null;
138         try {
139             pfd = getImageDescriptor(device, imageUuid);
140         } catch (IOException e) {
141             debug("Failed to create inputstream from Bitmap");
142             throw new FileNotFoundException();
143         }
144         return pfd;
145     }
146 
147     @Override
onCreate()148     public boolean onCreate() {
149         mAdapter = BluetoothAdapter.getDefaultAdapter();
150         return true;
151     }
152 
153     @Override
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)154     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
155             String sortOrder) {
156         return null;
157     }
158 
159     @Override
insert(Uri uri, ContentValues values)160     public Uri insert(Uri uri, ContentValues values) {
161         return null;
162     }
163 
164     @Override
delete(Uri uri, String selection, String[] selectionArgs)165     public int delete(Uri uri, String selection, String[] selectionArgs) {
166         return 0;
167     }
168 
169     @Override
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)170     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
171         return 0;
172     }
173 
174     @Override
getType(Uri uri)175     public String getType(Uri uri) {
176         return null;
177     }
178 
debug(String msg)179     private static void debug(String msg) {
180         if (DBG) {
181             Log.d(TAG, msg);
182         }
183     }
184 }
185