1 /* 2 * Copyright (C) 2015 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.printspooler.model; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.graphics.drawable.Icon; 22 import android.print.PrinterId; 23 import android.util.Log; 24 25 import java.io.File; 26 import java.io.FileInputStream; 27 import java.io.FileOutputStream; 28 import java.io.IOException; 29 import java.io.UnsupportedEncodingException; 30 import java.security.MessageDigest; 31 import java.security.NoSuchAlgorithmException; 32 import java.util.SortedMap; 33 import java.util.TreeMap; 34 35 /** 36 * A fixed size cache for custom printer icons. Old icons get removed with a last recently used 37 * policy. 38 */ 39 public class CustomPrinterIconCache { 40 41 private final static String LOG_TAG = "CustomPrinterIconCache"; 42 43 /** Maximum number of icons in the cache */ 44 private final static int MAX_SIZE = 1024; 45 46 /** Directory used to persist state and icons */ 47 private final File mCacheDirectory; 48 49 /** 50 * Create a new icon cache. 51 */ CustomPrinterIconCache(@onNull File cacheDirectory)52 public CustomPrinterIconCache(@NonNull File cacheDirectory) { 53 mCacheDirectory = new File(cacheDirectory, "icons"); 54 if (!mCacheDirectory.exists()) { 55 mCacheDirectory.mkdir(); 56 } 57 } 58 59 /** 60 * Return the file name to be used for the icon of a printer 61 * 62 * @param printerId the id of the printer 63 * 64 * @return The file to be used for the icon of the printer 65 */ getIconFileName(@onNull PrinterId printerId)66 private @Nullable File getIconFileName(@NonNull PrinterId printerId) { 67 StringBuffer sb = new StringBuffer(printerId.getServiceName().getPackageName()); 68 sb.append("-"); 69 70 try { 71 MessageDigest md = MessageDigest.getInstance("SHA-1"); 72 md.update( 73 (printerId.getServiceName().getClassName() + ":" + printerId.getLocalId()) 74 .getBytes("UTF-16")); 75 sb.append(String.format("%#040x", new java.math.BigInteger(1, md.digest()))); 76 } catch (UnsupportedEncodingException|NoSuchAlgorithmException e) { 77 Log.e(LOG_TAG, "Could not compute custom printer icon file name", e); 78 return null; 79 } 80 81 return new File(mCacheDirectory, sb.toString()); 82 } 83 84 /** 85 * Get the {@link Icon} to be used as a custom icon for the printer. If not available request 86 * the icon to be loaded. 87 * 88 * @param printerId the printer the icon belongs to 89 * @return the {@link Icon} if already available or null if icon is not loaded yet 90 */ getIcon(@onNull PrinterId printerId)91 public synchronized @Nullable Icon getIcon(@NonNull PrinterId printerId) { 92 Icon icon; 93 94 File iconFile = getIconFileName(printerId); 95 if (iconFile != null && iconFile.exists()) { 96 try (FileInputStream is = new FileInputStream(iconFile)) { 97 icon = Icon.createFromStream(is); 98 } catch (IOException e) { 99 icon = null; 100 Log.e(LOG_TAG, "Could not read icon from " + iconFile, e); 101 } 102 103 // Touch file so that it is the not likely to be removed 104 iconFile.setLastModified(System.currentTimeMillis()); 105 } else { 106 icon = null; 107 } 108 109 return icon; 110 } 111 112 /** 113 * Remove old icons so that only between numFilesToKeep and twice as many icons are left. 114 * 115 * @param numFilesToKeep the number of icons to keep 116 */ removeOldFiles(int numFilesToKeep)117 public void removeOldFiles(int numFilesToKeep) { 118 File files[] = mCacheDirectory.listFiles(); 119 120 // To reduce the number of shrink operations, let the cache grow to twice the max size 121 if (files.length > numFilesToKeep * 2) { 122 SortedMap<Long, File> sortedFiles = new TreeMap<>(); 123 124 for (File f : files) { 125 sortedFiles.put(f.lastModified(), f); 126 } 127 128 while (sortedFiles.size() > numFilesToKeep) { 129 sortedFiles.remove(sortedFiles.firstKey()); 130 } 131 } 132 } 133 134 /** 135 * Handle that a custom icon for a printer was loaded 136 * 137 * @param printerId the id of the printer the icon belongs to 138 * @param icon the icon that was loaded 139 */ onCustomPrinterIconLoaded(@onNull PrinterId printerId, @Nullable Icon icon)140 public synchronized void onCustomPrinterIconLoaded(@NonNull PrinterId printerId, 141 @Nullable Icon icon) { 142 File iconFile = getIconFileName(printerId); 143 144 if (iconFile == null) { 145 return; 146 } 147 148 try (FileOutputStream os = new FileOutputStream(iconFile)) { 149 icon.writeToStream(os); 150 } catch (IOException e) { 151 Log.e(LOG_TAG, "Could not write icon for " + printerId + " to storage", e); 152 } 153 154 removeOldFiles(MAX_SIZE); 155 } 156 157 /** 158 * Clear all persisted and non-persisted state from this cache. 159 */ clear()160 public synchronized void clear() { 161 for (File f : mCacheDirectory.listFiles()) { 162 f.delete(); 163 } 164 } 165 } 166