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.net.Uri;
25 import android.os.ParcelFileDescriptor;
26 import android.util.Log;
27 
28 import java.io.File;
29 import java.io.FileNotFoundException;
30 
31 /**
32  * A provider of downloaded cover art images.
33  *
34  * Cover art images are downloaded from remote devices and are promised to be "good" for the life of
35  * a connection.
36  *
37  * Android applications are provided a Uri with their MediaMetadata and MediaItem objects that
38  * points back to this provider. Uris are in the following format:
39  *
40  *   content://com.android.bluetooth.avrcpcontroller.AvrcpCoverArtProvider/<device>/<image-handle>
41  *
42  * It's expected by the Media framework that artwork at URIs will be available using the
43  * ContentResolver#openInputStream and BitmapFactory#decodeStream functions. Our provider must
44  * enable that usage pattern.
45  */
46 public class AvrcpCoverArtProvider extends ContentProvider {
47     private static final String TAG = "AvrcpCoverArtProvider";
48     private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
49 
50     private BluetoothAdapter mAdapter;
51     private AvrcpCoverArtStorage mStorage;
52 
AvrcpCoverArtProvider()53     public AvrcpCoverArtProvider() {
54     }
55 
56     static final String AUTHORITY = "com.android.bluetooth.avrcpcontroller.AvrcpCoverArtProvider";
57     static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY);
58 
59     /**
60      * Get the Uri for a cover art image based on the device and image handle
61      *
62      * @param device The Bluetooth device from which an image originated
63      * @param imageHandle The provided handle of the cover artwork
64      * @return The Uri this provider will store the downloaded image at
65      */
getImageUri(BluetoothDevice device, String imageHandle)66     public static Uri getImageUri(BluetoothDevice device, String imageHandle) {
67         if (device == null || imageHandle == null || "".equals(imageHandle)) return null;
68         Uri uri = CONTENT_URI.buildUpon().appendQueryParameter("device", device.getAddress())
69                 .appendQueryParameter("handle", imageHandle)
70                 .build();
71         debug("getImageUri -> " + uri.toString());
72         return uri;
73     }
74 
getImageDescriptor(BluetoothDevice device, String imageHandle)75     private ParcelFileDescriptor getImageDescriptor(BluetoothDevice device, String imageHandle)
76             throws FileNotFoundException {
77         debug("getImageDescriptor(" + device + ", " + imageHandle + ")");
78         File file = mStorage.getImageFile(device, imageHandle);
79         if (file == null) throw new FileNotFoundException();
80         ParcelFileDescriptor pdf = ParcelFileDescriptor.open(file,
81                 ParcelFileDescriptor.MODE_READ_ONLY);
82         return pdf;
83     }
84 
85     @Override
openFile(Uri uri, String mode)86     public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
87         debug("openFile(" + uri + ", '" + mode + "')");
88         String address = null;
89         String imageHandle = null;
90         BluetoothDevice device = null;
91         try {
92             address = uri.getQueryParameter("device");
93             imageHandle = uri.getQueryParameter("handle");
94         } catch (NullPointerException e) {
95             throw new FileNotFoundException();
96         }
97 
98         try {
99             device = mAdapter.getRemoteDevice(address);
100         } catch (IllegalArgumentException e) {
101             throw new FileNotFoundException();
102         }
103 
104         return getImageDescriptor(device, imageHandle);
105     }
106 
107     @Override
onCreate()108     public boolean onCreate() {
109         mAdapter = BluetoothAdapter.getDefaultAdapter();
110         mStorage = new AvrcpCoverArtStorage(getContext());
111         return true;
112     }
113 
114     @Override
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)115     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
116             String sortOrder) {
117         return null;
118     }
119 
120     @Override
insert(Uri uri, ContentValues values)121     public Uri insert(Uri uri, ContentValues values) {
122         return null;
123     }
124 
125     @Override
delete(Uri uri, String selection, String[] selectionArgs)126     public int delete(Uri uri, String selection, String[] selectionArgs) {
127         return 0;
128     }
129 
130     @Override
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)131     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
132         return 0;
133     }
134 
135     @Override
getType(Uri uri)136     public String getType(Uri uri) {
137         return null;
138     }
139 
debug(String msg)140     private static void debug(String msg) {
141         if (DBG) {
142             Log.d(TAG, msg);
143         }
144     }
145 }
146