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