1 /*
2  * Copyright (C) 2018 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.systemui.statusbar.notification.row;
18 
19 import android.app.ActivityManager;
20 import android.app.Notification;
21 import android.content.Context;
22 import android.graphics.drawable.Drawable;
23 import android.net.Uri;
24 import android.os.Bundle;
25 import android.os.Parcelable;
26 import android.util.Log;
27 
28 import com.android.internal.widget.ImageResolver;
29 import com.android.internal.widget.LocalImageResolver;
30 import com.android.internal.widget.MessagingMessage;
31 
32 import java.io.IOException;
33 import java.util.HashSet;
34 import java.util.List;
35 import java.util.Set;
36 
37 /**
38  * Custom resolver with built-in image cache for image messages.
39  */
40 public class NotificationInlineImageResolver implements ImageResolver {
41     private static final String TAG = NotificationInlineImageResolver.class.getSimpleName();
42 
43     private final Context mContext;
44     private final ImageCache mImageCache;
45     private Set<Uri> mWantedUriSet;
46 
47     /**
48      * Constructor.
49      * @param context    Context.
50      * @param imageCache The implementation of internal cache.
51      */
NotificationInlineImageResolver(Context context, ImageCache imageCache)52     public NotificationInlineImageResolver(Context context, ImageCache imageCache) {
53         mContext = context.getApplicationContext();
54         mImageCache = imageCache;
55 
56         if (mImageCache != null) {
57             mImageCache.setImageResolver(this);
58         }
59     }
60 
61     /**
62      * Check if this resolver has its internal cache implementation.
63      * @return True if has its internal cache, false otherwise.
64      */
hasCache()65     public boolean hasCache() {
66         return mImageCache != null && !ActivityManager.isLowRamDeviceStatic();
67     }
68 
69     /**
70      * To resolve image from specified uri directly.
71      * @param uri Uri of the image.
72      * @return Drawable of the image.
73      * @throws IOException Throws if failed at resolving the image.
74      */
resolveImage(Uri uri)75     Drawable resolveImage(Uri uri) throws IOException {
76         return LocalImageResolver.resolveImage(uri, mContext);
77     }
78 
79     @Override
loadImage(Uri uri)80     public Drawable loadImage(Uri uri) {
81         Drawable result = null;
82         try {
83             result = hasCache() ? mImageCache.get(uri) : resolveImage(uri);
84         } catch (IOException | SecurityException ex) {
85             Log.d(TAG, "loadImage: Can't load image from " + uri, ex);
86         }
87         return result;
88     }
89 
90     /**
91      * Resolve the message list from specified notification and
92      * refresh internal cache according to the result.
93      * @param notification The Notification to be resolved.
94      */
preloadImages(Notification notification)95     public void preloadImages(Notification notification) {
96         if (!hasCache()) {
97             return;
98         }
99 
100         retrieveWantedUriSet(notification);
101         Set<Uri> wantedSet = getWantedUriSet();
102         wantedSet.forEach(uri -> {
103             if (!mImageCache.hasEntry(uri)) {
104                 // The uri is not in the cache, we need trigger a loading task for it.
105                 mImageCache.preload(uri);
106             }
107         });
108     }
109 
110     /**
111      * Try to purge unnecessary cache entries.
112      */
purgeCache()113     public void purgeCache() {
114         if (!hasCache()) {
115             return;
116         }
117         mImageCache.purge();
118     }
119 
retrieveWantedUriSet(Notification notification)120     private void retrieveWantedUriSet(Notification notification) {
121         Parcelable[] messages;
122         Parcelable[] historicMessages;
123         List<Notification.MessagingStyle.Message> messageList;
124         List<Notification.MessagingStyle.Message> historicList;
125         Set<Uri> result = new HashSet<>();
126 
127         Bundle extras = notification.extras;
128         if (extras == null) {
129             return;
130         }
131 
132         messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
133         messageList = messages == null ? null :
134                 Notification.MessagingStyle.Message.getMessagesFromBundleArray(messages);
135         if (messageList != null) {
136             for (Notification.MessagingStyle.Message message : messageList) {
137                 if (MessagingMessage.hasImage(message)) {
138                     result.add(message.getDataUri());
139                 }
140             }
141         }
142 
143         historicMessages = extras.getParcelableArray(Notification.EXTRA_HISTORIC_MESSAGES);
144         historicList = historicMessages == null ? null :
145                 Notification.MessagingStyle.Message.getMessagesFromBundleArray(historicMessages);
146         if (historicList != null) {
147             for (Notification.MessagingStyle.Message historic : historicList) {
148                 if (MessagingMessage.hasImage(historic)) {
149                     result.add(historic.getDataUri());
150                 }
151             }
152         }
153 
154         mWantedUriSet = result;
155     }
156 
getWantedUriSet()157     Set<Uri> getWantedUriSet() {
158         return mWantedUriSet;
159     }
160 
161     /**
162      * A interface for internal cache implementation of this resolver.
163      */
164     interface ImageCache {
165         /**
166          * Load the image from cache first then resolve from uri if missed the cache.
167          * @param uri The uri of the image.
168          * @return Drawable of the image.
169          */
get(Uri uri)170         Drawable get(Uri uri);
171 
172         /**
173          * Set the image resolver that actually resolves image from specified uri.
174          * @param resolver The resolver implementation that resolves image from specified uri.
175          */
setImageResolver(NotificationInlineImageResolver resolver)176         void setImageResolver(NotificationInlineImageResolver resolver);
177 
178         /**
179          * Check if the uri is in the cache no matter it is loading or loaded.
180          * @param uri The uri to check.
181          * @return True if it is already in the cache; false otherwise.
182          */
hasEntry(Uri uri)183         boolean hasEntry(Uri uri);
184 
185         /**
186          * Start a new loading task for the target uri.
187          * @param uri The target to load.
188          */
preload(Uri uri)189         void preload(Uri uri);
190 
191         /**
192          * Purge unnecessary entries in the cache.
193          */
purge()194         void purge();
195     }
196 
197 }
198