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