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.BluetoothDevice;
20 import android.content.Context;
21 import android.graphics.Bitmap;
22 import android.net.Uri;
23 import android.util.Log;
24 
25 import java.util.Map;
26 import java.util.concurrent.ConcurrentHashMap;
27 
28 /**
29  * An abstraction of the cover art image storage mechanism.
30  */
31 public class AvrcpCoverArtStorage {
32     private static final String TAG = "AvrcpCoverArtStorage";
33     private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
34 
35     private final Context mContext;
36 
37     /* Each device gets its own place to land images. This makes it easier to clean things up on a
38      * per device basis. This also allows us to be confident that acting on one device will not
39      * impact the images of another.
40      *
41      * The "landing place" is simply a map that will direct a given UUID to the proper bitmap image
42      */
43     private final Map<BluetoothDevice, Map<String, Bitmap>> mDeviceImages =
44             new ConcurrentHashMap<>(1);
45 
46     /**
47      * Create and initialize this Cover Art storage interface
48      */
AvrcpCoverArtStorage(Context context)49     public AvrcpCoverArtStorage(Context context) {
50         mContext = context;
51     }
52 
53     /**
54      * Determine if an image already exists in storage
55      *
56      * @param device - The device the images was downloaded from
57      * @param imageUuid - The UUID that identifies the image
58      */
doesImageExist(BluetoothDevice device, String imageUuid)59     public boolean doesImageExist(BluetoothDevice device, String imageUuid) {
60         if (device == null || imageUuid == null || "".equals(imageUuid)) return false;
61         Map<String, Bitmap> images = mDeviceImages.get(device);
62         if (images == null) return false;
63         return images.containsKey(imageUuid);
64     }
65 
66     /**
67      * Retrieve an image file from storage
68      *
69      * @param device - The device the images was downloaded from
70      * @param imageUuid - The UUID that identifies the image
71      * @return A Bitmap object of the image
72      */
getImage(BluetoothDevice device, String imageUuid)73     public Bitmap getImage(BluetoothDevice device, String imageUuid) {
74         if (device == null || imageUuid == null || "".equals(imageUuid)) return null;
75         Map<String, Bitmap> images = mDeviceImages.get(device);
76         if (images == null) return null;
77         return images.get(imageUuid);
78     }
79 
80     /**
81      * Add an image to storage
82      *
83      * @param device - The device the images was downloaded from
84      * @param imageUuid - The UUID that identifies the image
85      * @param image - The image
86      */
addImage(BluetoothDevice device, String imageUuid, Bitmap image)87     public Uri addImage(BluetoothDevice device, String imageUuid, Bitmap image) {
88         debug("Storing image '" + imageUuid + "' from device " + device);
89         if (device == null || imageUuid == null || "".equals(imageUuid) || image == null) {
90             debug("Cannot store image. Improper aruguments");
91             return null;
92         }
93 
94         // A Thread safe way of creating a new UUID->Image set for a device. The putIfAbsent()
95         // function will return the value of the key if it wasn't absent. If it returns null, then
96         // there was no value there and we are to assume the reference we passed in was added.
97         Map<String, Bitmap> newImageSet = new ConcurrentHashMap<String, Bitmap>(1);
98         Map<String, Bitmap> images = mDeviceImages.putIfAbsent(device, newImageSet);
99         if (images == null) {
100             newImageSet.put(imageUuid, image);
101         } else {
102             images.put(imageUuid, image);
103         }
104 
105         Uri uri = AvrcpCoverArtProvider.getImageUri(device, imageUuid);
106         mContext.getContentResolver().notifyChange(uri, null);
107         debug("Image '" + imageUuid + "' stored for device '" + device.getAddress() + "'");
108         return uri;
109     }
110 
111     /**
112      * Remove a specific image
113      *
114      * @param device The device the image belongs to
115      * @param imageUuid - The UUID that identifies the image
116      */
removeImage(BluetoothDevice device, String imageUuid)117     public void removeImage(BluetoothDevice device, String imageUuid) {
118         debug("Removing image '" + imageUuid + "' from device " + device);
119         if (device == null || imageUuid == null || "".equals(imageUuid)) return;
120 
121         Map<String, Bitmap> images = mDeviceImages.get(device);
122         if (images == null) {
123             return;
124         }
125 
126         images.remove(imageUuid);
127         if (images.size() == 0) {
128             mDeviceImages.remove(device);
129         }
130 
131         debug("Image '" + imageUuid + "' removed for device '" + device.getAddress() + "'");
132     }
133 
134     /**
135      * Remove all stored images associated with a device
136      *
137      * @param device The device you wish to have images removed for
138      */
removeImagesForDevice(BluetoothDevice device)139     public void removeImagesForDevice(BluetoothDevice device) {
140         if (device == null) return;
141         debug("Remove cover art for device " + device.getAddress());
142         mDeviceImages.remove(device);
143     }
144 
145     /**
146      * Clear the entirety of storage
147      */
clear()148     public void clear() {
149         debug("Clearing all images");
150         mDeviceImages.clear();
151     }
152 
153     @Override
toString()154     public String toString() {
155         String s = "CoverArtStorage:\n";
156         for (BluetoothDevice device : mDeviceImages.keySet()) {
157             Map<String, Bitmap> images = mDeviceImages.get(device);
158             s += "  " + device.getAddress() + " (" + images.size() + "):";
159             for (String uuid : images.keySet()) {
160                 s += "\n    " + uuid;
161             }
162             s += "\n";
163         }
164         return s;
165     }
166 
debug(String msg)167     private void debug(String msg) {
168         if (DBG) {
169             Log.d(TAG, msg);
170         }
171     }
172 
error(String msg)173     private void error(String msg) {
174         Log.e(TAG, msg);
175     }
176 }
177