1/*
2 * Copyright (C) 2014 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
17package com.android.systemui.recents.model;
18
19import android.util.Log;
20import android.util.LruCache;
21import android.util.SparseArray;
22
23import java.io.PrintWriter;
24import java.util.ArrayList;
25
26/**
27 * A mapping of {@link Task.TaskKey} to value, with additional LRU functionality where the least
28 * recently referenced key/values will be evicted as more values than the given cache size are
29 * inserted.
30 *
31 * In addition, this also allows the caller to invalidate cached values for keys that have since
32 * changed.
33 */
34public class TaskKeyLruCache<V> {
35
36    public interface EvictionCallback {
37        public void onEntryEvicted(Task.TaskKey key);
38    }
39
40    private static final String TAG = "TaskKeyLruCache";
41
42    private final SparseArray<Task.TaskKey> mKeys = new SparseArray<>();
43    private final LruCache<Integer, V> mCache;
44    private final EvictionCallback mEvictionCallback;
45
46    public TaskKeyLruCache(int cacheSize) {
47        this(cacheSize, null);
48    }
49
50    public TaskKeyLruCache(int cacheSize, EvictionCallback evictionCallback) {
51        mEvictionCallback = evictionCallback;
52        mCache = new LruCache<Integer, V>(cacheSize) {
53
54            @Override
55            protected void entryRemoved(boolean evicted, Integer taskId, V oldV, V newV) {
56                if (mEvictionCallback != null) {
57                    mEvictionCallback.onEntryEvicted(mKeys.get(taskId));
58                }
59                mKeys.remove(taskId);
60            }
61        };
62    }
63
64    /**
65     * Gets a specific entry in the cache with the specified key, regardless of whether the cached
66     * value is valid or not.
67     */
68    final V get(Task.TaskKey key) {
69        return mCache.get(key.id);
70    }
71
72    /**
73     * Returns the value only if the key is valid (has not been updated since the last time it was
74     * in the cache)
75     */
76    final V getAndInvalidateIfModified(Task.TaskKey key) {
77        Task.TaskKey lastKey = mKeys.get(key.id);
78        if (lastKey != null) {
79            if ((lastKey.stackId != key.stackId) ||
80                    (lastKey.lastActiveTime != key.lastActiveTime)) {
81                // The task has updated (been made active since the last time it was put into the
82                // LRU cache) or the stack id for the task has changed, invalidate that cache item
83                remove(key);
84                return null;
85            }
86        }
87        // Either the task does not exist in the cache, or the last active time is the same as
88        // the key specified, so return what is in the cache
89        return mCache.get(key.id);
90    }
91
92    /** Puts an entry in the cache for a specific key. */
93    final void put(Task.TaskKey key, V value) {
94        if (key == null || value == null) {
95            Log.e(TAG, "Unexpected null key or value: " + key + ", " + value);
96            return;
97        }
98        mKeys.put(key.id, key);
99        mCache.put(key.id, value);
100    }
101
102    /** Removes a cache entry for a specific key. */
103    final void remove(Task.TaskKey key) {
104        // Remove the key after the cache value because we need it to make the callback
105        mCache.remove(key.id);
106        mKeys.remove(key.id);
107    }
108
109    /** Removes all the entries in the cache. */
110    final void evictAll() {
111        mCache.evictAll();
112        mKeys.clear();
113    }
114
115    /** Trims the cache to a specific size */
116    final void trimToSize(int cacheSize) {
117        mCache.trimToSize(cacheSize);
118    }
119
120    public void dump(String prefix, PrintWriter writer) {
121        String innerPrefix = prefix + "  ";
122
123        writer.print(prefix); writer.print(TAG);
124        writer.print(" numEntries="); writer.print(mKeys.size());
125        writer.println();
126        int keyCount = mKeys.size();
127        for (int i = 0; i < keyCount; i++) {
128            writer.print(innerPrefix); writer.println(mKeys.get(mKeys.keyAt(i)));
129        }
130    }
131}
132