Home | History | Annotate | Download | only in model
      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      */
     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      */
     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      */
     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      */
    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      */
    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      */
    160     public synchronized void clear() {
    161         for (File f : mCacheDirectory.listFiles()) {
    162             f.delete();
    163         }
    164     }
    165 }
    166