/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.printspooler.model; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.drawable.Icon; import android.print.PrinterId; import android.util.Log; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.SortedMap; import java.util.TreeMap; /** * A fixed size cache for custom printer icons. Old icons get removed with a last recently used * policy. */ public class CustomPrinterIconCache { private final static String LOG_TAG = "CustomPrinterIconCache"; /** Maximum number of icons in the cache */ private final static int MAX_SIZE = 1024; /** Directory used to persist state and icons */ private final File mCacheDirectory; /** * Create a new icon cache. */ public CustomPrinterIconCache(@NonNull File cacheDirectory) { mCacheDirectory = new File(cacheDirectory, "icons"); if (!mCacheDirectory.exists()) { mCacheDirectory.mkdir(); } } /** * Return the file name to be used for the icon of a printer * * @param printerId the id of the printer * * @return The file to be used for the icon of the printer */ private @Nullable File getIconFileName(@NonNull PrinterId printerId) { StringBuffer sb = new StringBuffer(printerId.getServiceName().getPackageName()); sb.append("-"); try { MessageDigest md = MessageDigest.getInstance("SHA-1"); md.update( (printerId.getServiceName().getClassName() + ":" + printerId.getLocalId()) .getBytes("UTF-16")); sb.append(String.format("%#040x", new java.math.BigInteger(1, md.digest()))); } catch (UnsupportedEncodingException|NoSuchAlgorithmException e) { Log.e(LOG_TAG, "Could not compute custom printer icon file name", e); return null; } return new File(mCacheDirectory, sb.toString()); } /** * Get the {@link Icon} to be used as a custom icon for the printer. If not available request * the icon to be loaded. * * @param printerId the printer the icon belongs to * @return the {@link Icon} if already available or null if icon is not loaded yet */ public synchronized @Nullable Icon getIcon(@NonNull PrinterId printerId) { Icon icon; File iconFile = getIconFileName(printerId); if (iconFile != null && iconFile.exists()) { try (FileInputStream is = new FileInputStream(iconFile)) { icon = Icon.createFromStream(is); } catch (IOException e) { icon = null; Log.e(LOG_TAG, "Could not read icon from " + iconFile, e); } // Touch file so that it is the not likely to be removed iconFile.setLastModified(System.currentTimeMillis()); } else { icon = null; } return icon; } /** * Remove old icons so that only between numFilesToKeep and twice as many icons are left. * * @param numFilesToKeep the number of icons to keep */ public void removeOldFiles(int numFilesToKeep) { File files[] = mCacheDirectory.listFiles(); // To reduce the number of shrink operations, let the cache grow to twice the max size if (files.length > numFilesToKeep * 2) { SortedMap sortedFiles = new TreeMap<>(); for (File f : files) { sortedFiles.put(f.lastModified(), f); } while (sortedFiles.size() > numFilesToKeep) { sortedFiles.remove(sortedFiles.firstKey()); } } } /** * Handle that a custom icon for a printer was loaded * * @param printerId the id of the printer the icon belongs to * @param icon the icon that was loaded */ public synchronized void onCustomPrinterIconLoaded(@NonNull PrinterId printerId, @Nullable Icon icon) { File iconFile = getIconFileName(printerId); if (iconFile == null) { return; } try (FileOutputStream os = new FileOutputStream(iconFile)) { icon.writeToStream(os); } catch (IOException e) { Log.e(LOG_TAG, "Could not write icon for " + printerId + " to storage", e); } removeOldFiles(MAX_SIZE); } /** * Clear all persisted and non-persisted state from this cache. */ public synchronized void clear() { for (File f : mCacheDirectory.listFiles()) { f.delete(); } } }