• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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