• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2024 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.input;
18 
19 import static android.view.PointerIcon.DEFAULT_POINTER_SCALE;
20 import static android.view.PointerIcon.POINTER_ICON_VECTOR_STYLE_FILL_BLACK;
21 import static android.view.PointerIcon.POINTER_ICON_VECTOR_STYLE_STROKE_WHITE;
22 
23 import android.annotation.NonNull;
24 import android.content.Context;
25 import android.content.res.Resources;
26 import android.hardware.display.DisplayManager;
27 import android.os.Handler;
28 import android.util.Slog;
29 import android.util.SparseArray;
30 import android.util.SparseDoubleArray;
31 import android.util.SparseIntArray;
32 import android.view.ContextThemeWrapper;
33 import android.view.Display;
34 import android.view.DisplayInfo;
35 import android.view.PointerIcon;
36 
37 import com.android.internal.annotations.GuardedBy;
38 import com.android.internal.annotations.VisibleForTesting;
39 import com.android.server.UiThread;
40 
41 import java.util.Objects;
42 
43 /**
44  * A thread-safe component of {@link InputManagerService} responsible for caching loaded
45  * {@link PointerIcon}s and triggering reloading of the icons.
46  */
47 final class PointerIconCache {
48     private static final String TAG = PointerIconCache.class.getSimpleName();
49 
50     private final Context mContext;
51 
52     // Do not hold the lock when calling into native code.
53     private final NativeInputManagerService mNative;
54 
55     // We use the UI thread for loading pointer icons.
56     private final Handler mUiThreadHandler;
57 
58     @GuardedBy("mLoadedPointerIconsByDisplayAndType")
59     private final SparseArray<SparseArray<PointerIcon>> mLoadedPointerIconsByDisplayAndType =
60             new SparseArray<>();
61     @GuardedBy("mLoadedPointerIconsByDisplayAndType")
62     private boolean mUseLargePointerIcons = false;
63     @GuardedBy("mLoadedPointerIconsByDisplayAndType")
64     private final SparseArray<Context> mDisplayContexts = new SparseArray<>();
65     @GuardedBy("mLoadedPointerIconsByDisplayAndType")
66     private final SparseIntArray mDisplayDensities = new SparseIntArray();
67     @GuardedBy("mLoadedPointerIconsByDisplayAndType")
68     private @PointerIcon.PointerIconVectorStyleFill int mPointerIconFillStyle =
69             POINTER_ICON_VECTOR_STYLE_FILL_BLACK;
70     @GuardedBy("mLoadedPointerIconsByDisplayAndType")
71     private @PointerIcon.PointerIconVectorStyleStroke int mPointerIconStrokeStyle =
72             POINTER_ICON_VECTOR_STYLE_STROKE_WHITE;
73     @GuardedBy("mLoadedPointerIconsByDisplayAndType")
74     private float mPointerIconScale = DEFAULT_POINTER_SCALE;
75     // Note that android doesn't have SparseFloatArray, so this falls back to use double instead.
76     @GuardedBy("mLoadedPointerIconsByDisplayAndType")
77     private final SparseDoubleArray mAccessibilityScaleFactorPerDisplay = new SparseDoubleArray();
78 
79     private final DisplayManager.DisplayListener mDisplayListener =
80             new DisplayManager.DisplayListener() {
81                 @Override
82                 public void onDisplayAdded(int displayId) {
83                     synchronized (mLoadedPointerIconsByDisplayAndType) {
84                         updateDisplayDensityLocked(displayId);
85                     }
86                 }
87 
88                 @Override
89                 public void onDisplayRemoved(int displayId) {
90                     synchronized (mLoadedPointerIconsByDisplayAndType) {
91                         mLoadedPointerIconsByDisplayAndType.remove(displayId);
92                         mDisplayContexts.remove(displayId);
93                         mDisplayDensities.delete(displayId);
94                         mAccessibilityScaleFactorPerDisplay.delete(displayId);
95                     }
96                 }
97 
98                 @Override
99                 public void onDisplayChanged(int displayId) {
100                     handleDisplayChanged(displayId);
101                 }
102             };
103 
PointerIconCache(Context context, NativeInputManagerService nativeService)104     /* package */ PointerIconCache(Context context, NativeInputManagerService nativeService) {
105         this(context, nativeService, UiThread.getHandler());
106     }
107 
108     @VisibleForTesting
PointerIconCache(Context context, NativeInputManagerService nativeService, Handler handler)109     /* package */ PointerIconCache(Context context, NativeInputManagerService nativeService,
110             Handler handler) {
111         mContext = context;
112         mNative = nativeService;
113         mUiThreadHandler = handler;
114     }
115 
systemRunning()116     public void systemRunning() {
117         final DisplayManager displayManager = Objects.requireNonNull(
118                 mContext.getSystemService(DisplayManager.class));
119         displayManager.registerDisplayListener(mDisplayListener, mUiThreadHandler);
120         final Display[] displays = displayManager.getDisplays();
121         for (int i = 0; i < displays.length; i++) {
122             mDisplayListener.onDisplayAdded(displays[i].getDisplayId());
123         }
124     }
125 
monitor()126     public void monitor() {
127         synchronized (mLoadedPointerIconsByDisplayAndType) { /* Test if blocked by lock */}
128     }
129 
130     /** Set whether the large pointer icons should be used for accessibility. */
setUseLargePointerIcons(boolean useLargeIcons)131     public void setUseLargePointerIcons(boolean useLargeIcons) {
132         mUiThreadHandler.post(() -> handleSetUseLargePointerIcons(useLargeIcons));
133     }
134 
135     /** Set the fill style for vector pointer icons. */
setPointerFillStyle(@ointerIcon.PointerIconVectorStyleFill int fillStyle)136     public void setPointerFillStyle(@PointerIcon.PointerIconVectorStyleFill int fillStyle) {
137         mUiThreadHandler.post(() -> handleSetPointerFillStyle(fillStyle));
138     }
139 
140     /** Set the stroke style for vector pointer icons. */
setPointerStrokeStyle(@ointerIcon.PointerIconVectorStyleStroke int strokeStyle)141     public void setPointerStrokeStyle(@PointerIcon.PointerIconVectorStyleStroke int strokeStyle) {
142         mUiThreadHandler.post(() -> handleSetPointerStrokeStyle(strokeStyle));
143     }
144 
145     /** Set the scale for vector pointer icons. */
setPointerScale(float scale)146     public void setPointerScale(float scale) {
147         mUiThreadHandler.post(() -> handleSetPointerScale(scale));
148     }
149 
150     /** Set the scale for accessibility (magnification) for vector pointer icons. */
setAccessibilityScaleFactor(int displayId, float scaleFactor)151     public void setAccessibilityScaleFactor(int displayId, float scaleFactor) {
152         mUiThreadHandler.post(() -> handleAccessibilityScaleFactor(displayId, scaleFactor));
153     }
154 
155     /**
156      * Get a loaded system pointer icon. This will fetch the icon from the cache, or load it if
157      * it isn't already cached.
158      */
getLoadedPointerIcon(int displayId, int type)159     public @NonNull PointerIcon getLoadedPointerIcon(int displayId, int type) {
160         synchronized (mLoadedPointerIconsByDisplayAndType) {
161             SparseArray<PointerIcon> iconsByType = mLoadedPointerIconsByDisplayAndType.get(
162                     displayId);
163             if (iconsByType == null) {
164                 iconsByType = new SparseArray<>();
165                 mLoadedPointerIconsByDisplayAndType.put(displayId, iconsByType);
166             }
167             PointerIcon icon = iconsByType.get(type);
168             if (icon == null) {
169                 Context context = getContextForDisplayLocked(displayId);
170                 Resources.Theme theme = context.getResources().newTheme();
171                 theme.setTo(context.getTheme());
172                 theme.applyStyle(PointerIcon.vectorFillStyleToResource(mPointerIconFillStyle),
173                         /* force= */ true);
174                 theme.applyStyle(PointerIcon.vectorStrokeStyleToResource(mPointerIconStrokeStyle),
175                         /* force= */ true);
176                 final float scale = mPointerIconScale
177                         * (float) mAccessibilityScaleFactorPerDisplay.get(displayId, 1f);
178                 icon = PointerIcon.getLoadedSystemIcon(new ContextThemeWrapper(context, theme),
179                         type, mUseLargePointerIcons, scale);
180                 iconsByType.put(type, icon);
181             }
182             return Objects.requireNonNull(icon);
183         }
184     }
185 
186     @GuardedBy("mLoadedPointerIconsByDisplayAndType")
getContextForDisplayLocked(int displayId)187     private @NonNull Context getContextForDisplayLocked(int displayId) {
188         if (displayId == Display.INVALID_DISPLAY) {
189             // Fallback to using the default context.
190             return mContext;
191         }
192         if (displayId == mContext.getDisplay().getDisplayId()) {
193             return mContext;
194         }
195 
196         Context displayContext = mDisplayContexts.get(displayId);
197         if (displayContext == null) {
198             final DisplayManager displayManager = Objects.requireNonNull(
199                     mContext.getSystemService(DisplayManager.class));
200             final Display display = displayManager.getDisplay(displayId);
201             if (display == null) {
202                 // Fallback to using the default context.
203                 return mContext;
204             }
205 
206             displayContext = mContext.createDisplayContext(display);
207             mDisplayContexts.put(displayId, displayContext);
208         }
209         return displayContext;
210     }
211 
212     @android.annotation.UiThread
handleDisplayChanged(int displayId)213     private void handleDisplayChanged(int displayId) {
214         synchronized (mLoadedPointerIconsByDisplayAndType) {
215             if (!updateDisplayDensityLocked(displayId)) {
216                 return;
217             }
218             // The display density changed, so force all cached pointer icons to be
219             // reloaded for the display.
220             Slog.i(TAG, "Reloading pointer icons due to density change on display: " + displayId);
221             var iconsByType = mLoadedPointerIconsByDisplayAndType.get(displayId);
222             if (iconsByType == null) {
223                 return;
224             }
225             iconsByType.clear();
226             mDisplayContexts.remove(displayId);
227         }
228         mNative.reloadPointerIcons();
229     }
230 
231     @android.annotation.UiThread
handleSetUseLargePointerIcons(boolean useLargeIcons)232     private void handleSetUseLargePointerIcons(boolean useLargeIcons) {
233         synchronized (mLoadedPointerIconsByDisplayAndType) {
234             if (mUseLargePointerIcons == useLargeIcons) {
235                 return;
236             }
237             mUseLargePointerIcons = useLargeIcons;
238             // Clear all cached icons on all displays.
239             mLoadedPointerIconsByDisplayAndType.clear();
240         }
241         mNative.reloadPointerIcons();
242     }
243 
244     @android.annotation.UiThread
handleSetPointerFillStyle(@ointerIcon.PointerIconVectorStyleFill int fillStyle)245     private void handleSetPointerFillStyle(@PointerIcon.PointerIconVectorStyleFill int fillStyle) {
246         synchronized (mLoadedPointerIconsByDisplayAndType) {
247             if (mPointerIconFillStyle == fillStyle) {
248                 return;
249             }
250             mPointerIconFillStyle = fillStyle;
251             // Clear all cached icons on all displays.
252             mLoadedPointerIconsByDisplayAndType.clear();
253         }
254         mNative.reloadPointerIcons();
255     }
256 
257     @android.annotation.UiThread
handleSetPointerStrokeStyle( @ointerIcon.PointerIconVectorStyleStroke int strokeStyle)258     private void handleSetPointerStrokeStyle(
259             @PointerIcon.PointerIconVectorStyleStroke int strokeStyle) {
260         synchronized (mLoadedPointerIconsByDisplayAndType) {
261             if (mPointerIconStrokeStyle == strokeStyle) {
262                 return;
263             }
264             mPointerIconStrokeStyle = strokeStyle;
265             // Clear all cached icons on all displays.
266             mLoadedPointerIconsByDisplayAndType.clear();
267         }
268         mNative.reloadPointerIcons();
269     }
270 
271     @android.annotation.UiThread
handleSetPointerScale(float scale)272     private void handleSetPointerScale(float scale) {
273         synchronized (mLoadedPointerIconsByDisplayAndType) {
274             if (mPointerIconScale == scale) {
275                 return;
276             }
277             mPointerIconScale = scale;
278             // Clear all cached icons on all displays.
279             mLoadedPointerIconsByDisplayAndType.clear();
280         }
281         mNative.reloadPointerIcons();
282     }
283 
284     @android.annotation.UiThread
handleAccessibilityScaleFactor(int displayId, float scale)285     private void handleAccessibilityScaleFactor(int displayId, float scale) {
286         synchronized (mLoadedPointerIconsByDisplayAndType) {
287             if (mAccessibilityScaleFactorPerDisplay.get(displayId, 1f) == scale) {
288                 return;
289             }
290             mAccessibilityScaleFactorPerDisplay.put(displayId, scale);
291             // Clear cached icons on the display.
292             mLoadedPointerIconsByDisplayAndType.remove(displayId);
293         }
294         mNative.reloadPointerIcons();
295     }
296 
297     // Updates the cached display density for the given displayId, and returns true if
298     // the cached density changed.
299     @GuardedBy("mLoadedPointerIconsByDisplayAndType")
updateDisplayDensityLocked(int displayId)300     private boolean updateDisplayDensityLocked(int displayId) {
301         final DisplayManager displayManager = Objects.requireNonNull(
302                 mContext.getSystemService(DisplayManager.class));
303         final Display display = displayManager.getDisplay(displayId);
304         if (display == null) {
305             return false;
306         }
307         DisplayInfo info = new DisplayInfo();
308         display.getDisplayInfo(info);
309         final int oldDensity = mDisplayDensities.get(displayId, 0 /* default */);
310         if (oldDensity == info.logicalDensityDpi) {
311             return false;
312         }
313         mDisplayDensities.put(displayId, info.logicalDensityDpi);
314         return true;
315     }
316 }
317