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