1 /*
2  * Copyright (C) 2021 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.server.wallpaper;
18 
19 import android.app.ILocalWallpaperColorConsumer;
20 import android.graphics.RectF;
21 import android.os.IBinder;
22 import android.os.RemoteCallbackList;
23 import android.os.RemoteException;
24 import android.util.ArrayMap;
25 import android.util.ArraySet;
26 import android.util.SparseArray;
27 
28 import com.android.internal.annotations.VisibleForTesting;
29 
30 import java.util.ArrayList;
31 import java.util.List;
32 import java.util.function.Consumer;
33 
34 /**
35  * Manages the lifecycle of local wallpaper color callbacks and their interested wallpaper regions.
36  */
37 public class LocalColorRepository {
38     /**
39      * Maps local wallpaper color callbacks' binders to their interested wallpaper regions, which
40      * are stored in a map of display Ids to wallpaper regions.
41      * binder callback -> [display id: int] -> areas
42      */
43     ArrayMap<IBinder, SparseArray<ArraySet<RectF>>> mLocalColorAreas = new ArrayMap();
44     RemoteCallbackList<ILocalWallpaperColorConsumer> mCallbacks = new RemoteCallbackList();
45 
46     /**
47      * Add areas to a consumer
48      * @param consumer
49      * @param areas
50      * @param displayId
51      */
addAreas(ILocalWallpaperColorConsumer consumer, List<RectF> areas, int displayId)52     public void addAreas(ILocalWallpaperColorConsumer consumer, List<RectF> areas, int displayId) {
53         IBinder binder = consumer.asBinder();
54         SparseArray<ArraySet<RectF>> displays = mLocalColorAreas.get(binder);
55         ArraySet<RectF> displayAreas = null;
56         if (displays == null) {
57             try {
58                 consumer.asBinder().linkToDeath(() ->
59                         mLocalColorAreas.remove(consumer.asBinder()), 0);
60             } catch (RemoteException e) {
61                 e.printStackTrace();
62             }
63             displays = new SparseArray<>();
64             mLocalColorAreas.put(binder, displays);
65         } else {
66             displayAreas = displays.get(displayId);
67         }
68         if (displayAreas == null) {
69             displayAreas = new ArraySet(areas);
70             displays.put(displayId, displayAreas);
71         }
72 
73         for (int i = 0; i < areas.size(); i++) {
74             displayAreas.add(areas.get(i));
75         }
76         mCallbacks.register(consumer);
77     }
78 
79     /**
80      * remove an area for a consumer
81      * @param consumer
82      * @param areas
83      * @param displayId
84      * @return the areas that are removed from all callbacks
85      */
removeAreas(ILocalWallpaperColorConsumer consumer, List<RectF> areas, int displayId)86     public List<RectF> removeAreas(ILocalWallpaperColorConsumer consumer, List<RectF> areas,
87             int displayId) {
88         IBinder binder = consumer.asBinder();
89         SparseArray<ArraySet<RectF>> displays = mLocalColorAreas.get(binder);
90         ArraySet<RectF> registeredAreas = null;
91         if (displays != null) {
92             registeredAreas = displays.get(displayId);
93             if (registeredAreas == null) {
94                 mCallbacks.unregister(consumer);
95             } else {
96                 for (int i = 0; i < areas.size(); i++) {
97                     registeredAreas.remove(areas.get(i));
98                 }
99                 if (registeredAreas.size() == 0) {
100                     displays.remove(displayId);
101                 }
102             }
103             if (displays.size() == 0) {
104                 mLocalColorAreas.remove(binder);
105                 mCallbacks.unregister(consumer);
106             }
107         } else {
108             mCallbacks.unregister(consumer);
109         }
110         ArraySet<RectF> purged = new ArraySet<>(areas);
111         for (int i = 0; i < mLocalColorAreas.size(); i++) {
112             for (int j = 0; j < mLocalColorAreas.valueAt(i).size(); j++) {
113                 for (int k = 0; k < mLocalColorAreas.valueAt(i).valueAt(j).size(); k++) {
114                     purged.remove(mLocalColorAreas.valueAt(i).valueAt(j).valueAt(k));
115                 }
116             }
117         }
118         return new ArrayList(purged);
119     }
120 
121     /**
122      * Return the local areas by display id
123      * @param displayId
124      * @return
125      */
getAreasByDisplayId(int displayId)126     public List<RectF> getAreasByDisplayId(int displayId) {
127         ArrayList<RectF> areas = new ArrayList();
128         for (int i = 0; i < mLocalColorAreas.size(); i++) {
129             SparseArray<ArraySet<RectF>> displays = mLocalColorAreas.valueAt(i);
130             if (displays == null) continue;
131             ArraySet<RectF> displayAreas = displays.get(displayId);
132             if (displayAreas == null) continue;
133             for (int j = 0; j < displayAreas.size(); j++) {
134                 areas.add(displayAreas.valueAt(j));
135             }
136         }
137         return areas;
138     }
139 
140     /**
141      * invoke a callback for each area of interest
142      * @param callback
143      * @param area
144      * @param displayId
145      */
forEachCallback(Consumer<ILocalWallpaperColorConsumer> callback, RectF area, int displayId)146     public void forEachCallback(Consumer<ILocalWallpaperColorConsumer> callback,
147             RectF area, int displayId) {
148         mCallbacks.broadcast(cb -> {
149             IBinder binder = cb.asBinder();
150             SparseArray<ArraySet<RectF>> displays = mLocalColorAreas.get(binder);
151             if (displays == null) return;
152             ArraySet<RectF> displayAreas = displays.get(displayId);
153             if (displayAreas != null && displayAreas.contains(area)) callback.accept(cb);
154         });
155     }
156 
157     /**
158      * For testing
159      * @param callback
160      * @return if the callback is registered
161      */
162     @VisibleForTesting
isCallbackAvailable(ILocalWallpaperColorConsumer callback)163     protected boolean isCallbackAvailable(ILocalWallpaperColorConsumer callback) {
164         return mLocalColorAreas.get(callback.asBinder()) != null;
165     }
166 }
167