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