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.os.Environment;
24 import android.util.Log;
25 
26 import java.io.File;
27 import java.io.FileOutputStream;
28 import java.io.IOException;
29 
30 /**
31  * An abstraction of the file system storage of the downloaded cover art images.
32  */
33 public class AvrcpCoverArtStorage {
34     private static final String TAG = "AvrcpCoverArtStorage";
35     private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
36 
37     private final Context mContext;
38 
39     /**
40      * Create and initialize this Cover Art storage interface
41      */
AvrcpCoverArtStorage(Context context)42     public AvrcpCoverArtStorage(Context context) {
43         mContext = context;
44     }
45 
46     /**
47      * Determine if an image already exists in storage
48      *
49      * @param device - The device the images was downloaded from
50      * @param imageHandle - The handle that identifies the image
51      */
doesImageExist(BluetoothDevice device, String imageHandle)52     public boolean doesImageExist(BluetoothDevice device, String imageHandle) {
53         if (device == null || imageHandle == null || "".equals(imageHandle)) return false;
54         String path = getImagePath(device, imageHandle);
55         if (path == null) return false;
56         File file = new File(path);
57         return file.exists();
58     }
59 
60     /**
61      * Retrieve an image file from storage
62      *
63      * @param device - The device the images was downloaded from
64      * @param imageHandle - The handle that identifies the image
65      * @return A file descriptor for the image
66      */
getImageFile(BluetoothDevice device, String imageHandle)67     public File getImageFile(BluetoothDevice device, String imageHandle) {
68         if (device == null || imageHandle == null || "".equals(imageHandle)) return null;
69         String path = getImagePath(device, imageHandle);
70         if (path == null) return null;
71         File file = new File(path);
72         return file.exists() ? file : null;
73     }
74 
75     /**
76      * Add an image to storage
77      *
78      * @param device - The device the images was downloaded from
79      * @param imageHandle - The handle that identifies the image
80      * @param image - The image
81      */
addImage(BluetoothDevice device, String imageHandle, Bitmap image)82     public Uri addImage(BluetoothDevice device, String imageHandle, Bitmap image) {
83         debug("Storing image '" + imageHandle + "' from device " + device);
84         if (device == null || imageHandle == null || "".equals(imageHandle) || image == null) {
85             debug("Cannot store image. Improper aruguments");
86             return null;
87         }
88 
89         String path = getImagePath(device, imageHandle);
90         if (path == null) {
91             error("Cannot store image. Cannot provide a valid path to storage");
92             return null;
93         }
94 
95         try {
96             String deviceDirectoryPath = getDevicePath(device);
97             if (deviceDirectoryPath == null) {
98                 error("Cannot store image. Cannot get a valid path to per-device storage");
99                 return null;
100             }
101             File deviceDirectory = new File(deviceDirectoryPath);
102             if (!deviceDirectory.exists()) {
103                 deviceDirectory.mkdirs();
104             }
105 
106             FileOutputStream outputStream = new FileOutputStream(path);
107             image.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
108             outputStream.flush();
109             outputStream.close();
110         } catch (IOException e) {
111             error("Failed to store '" + imageHandle + "' to '" + path + "'");
112             return null;
113         }
114         Uri uri = AvrcpCoverArtProvider.getImageUri(device, imageHandle);
115         mContext.getContentResolver().notifyChange(uri, null);
116         debug("Image stored at '" + path + "'");
117         return uri;
118     }
119 
120     /**
121      * Remove a specific image
122      *
123      * @param device The device you wish to have images removed for
124      * @param imageHandle The handle that identifies the image to delete
125      */
removeImage(BluetoothDevice device, String imageHandle)126     public void removeImage(BluetoothDevice device, String imageHandle) {
127         debug("Removing image '" + imageHandle + "' from device " + device);
128         if (device == null || imageHandle == null || "".equals(imageHandle)) return;
129         String path = getImagePath(device, imageHandle);
130         if (path == null) {
131             error("Cannot remove image. Cannot get a valid path to storage");
132             return;
133         }
134         File file = new File(path);
135         if (!file.exists()) return;
136         file.delete();
137         debug("Image deleted at '" + path + "'");
138     }
139 
140     /**
141      * Remove all stored images associated with a device
142      *
143      * @param device The device you wish to have images removed for
144      */
removeImagesForDevice(BluetoothDevice device)145     public void removeImagesForDevice(BluetoothDevice device) {
146         if (device == null) return;
147         debug("Remove cover art for device " + device.getAddress());
148         String deviceDirectoryPath = getDevicePath(device);
149         if (deviceDirectoryPath == null) {
150             error("Cannot remove images for device. Cannot get a valid path to storage");
151             return;
152         }
153         File deviceDirectory = new File(deviceDirectoryPath);
154         deleteStorageDirectory(deviceDirectory);
155     }
156 
157     /**
158      * Clear the entirety of storage
159      */
clear()160     public void clear() {
161         String storageDirectoryPath = getStorageDirectory();
162         if (storageDirectoryPath == null) {
163             error("Cannot remove images, cannot get a valid path to storage. Is it mounted?");
164             return;
165         }
166         File storageDirectory = new File(storageDirectoryPath);
167         deleteStorageDirectory(storageDirectory);
168     }
169 
getStorageDirectory()170     private String getStorageDirectory() {
171         String dir = null;
172         if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
173             dir = mContext.getExternalFilesDir(null).getAbsolutePath() + "/coverart";
174         } else {
175             error("Cannot get storage directory, state=" + Environment.getExternalStorageState());
176         }
177         return dir;
178     }
179 
getDevicePath(BluetoothDevice device)180     private String getDevicePath(BluetoothDevice device) {
181         String storageDir = getStorageDirectory();
182         if (storageDir == null) return null;
183         return storageDir + "/" + device.getAddress().replace(":", "");
184     }
185 
getImagePath(BluetoothDevice device, String imageHandle)186     private String getImagePath(BluetoothDevice device, String imageHandle) {
187         String deviceDir = getDevicePath(device);
188         if (deviceDir == null) return null;
189         return deviceDir + "/" + imageHandle + ".png";
190     }
191 
deleteStorageDirectory(File directory)192     private void deleteStorageDirectory(File directory) {
193         if (directory == null) {
194             error("Cannot delete directory, file is null");
195             return;
196         }
197         if (!directory.exists()) return;
198         File[] files = directory.listFiles();
199         if (files == null) {
200             return;
201         }
202         for (int i = 0; i < files.length; i++) {
203             debug("Deleting " + files[i].getAbsolutePath());
204             if (files[i].isDirectory()) {
205                 deleteStorageDirectory(files[i]);
206             } else {
207                 files[i].delete();
208             }
209         }
210         directory.delete();
211     }
212 
213     @Override
toString()214     public String toString() {
215         String s = "CoverArtStorage:\n";
216         String storageDirectory = getStorageDirectory();
217         s += "    Storage Directory: " + storageDirectory + "\n";
218         if (storageDirectory == null) {
219             return s;
220         }
221 
222         File storage = new File(storageDirectory);
223         File[] devices = storage.listFiles();
224         if (devices != null) {
225             for (File deviceDirectory : devices) {
226                 s += "    " + deviceDirectory.getName() + ":\n";
227                 File[] images = deviceDirectory.listFiles();
228                 if (images == null) continue;
229                 for (File image : images) {
230                     s += "      " + image.getName() + "\n";
231                 }
232             }
233         }
234         return s;
235     }
236 
debug(String msg)237     private void debug(String msg) {
238         if (DBG) {
239             Log.d(TAG, msg);
240         }
241     }
242 
error(String msg)243     private void error(String msg) {
244         Log.e(TAG, msg);
245     }
246 }
247