1 /*
2  * Copyright (C) 2013 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 androidx.core.hardware.display;
18 
19 import android.annotation.SuppressLint;
20 import android.content.Context;
21 import android.hardware.display.DisplayManager;
22 import android.os.Build;
23 import android.view.Display;
24 
25 import androidx.annotation.RestrictTo;
26 import androidx.annotation.VisibleForTesting;
27 
28 import org.jspecify.annotations.NonNull;
29 import org.jspecify.annotations.Nullable;
30 
31 import java.util.Objects;
32 
33 /**
34  * Helper for accessing features in {@link android.hardware.display.DisplayManager}.
35  */
36 public final class DisplayManagerCompat {
37 
38     /**
39      * An internal category to get all the displays. This was added in SC_V2 and should only be
40      * used internally. This is not a stable API so it should not be make public.
41      */
42     @VisibleForTesting
43     @RestrictTo(RestrictTo.Scope.LIBRARY)
44     static final String DISPLAY_CATEGORY_ALL =
45             "android.hardware.display.category.ALL_INCLUDING_DISABLED";
46 
47     /**
48      * An internal copy of the type from the platform. This was added in SDK 17 and should only be
49      * used internally. This is not a stable API so it should not be made public.
50      */
51     @VisibleForTesting
52     @RestrictTo(RestrictTo.Scope.LIBRARY)
53     static final int DISPLAY_TYPE_INTERNAL = 1;
54 
55     /**
56      * Display category: Presentation displays.
57      * <p>
58      * This category can be used to identify secondary displays that are suitable for
59      * use as presentation displays.
60      * </p>
61      *
62      * @see android.app.Presentation for information about presenting content
63      * on secondary displays.
64      * @see #getDisplays(String)
65      */
66     public static final String DISPLAY_CATEGORY_PRESENTATION =
67             "android.hardware.display.category.PRESENTATION";
68 
69     /**
70      * Display category: Built in displays.
71      * <p>
72      *     This category can be used to identify displays that are built in to the device.
73      * </p>
74      * @see #getDisplays(String)
75      */
76     @ExperimentalDisplayApi
77     public static final String DISPLAY_CATEGORY_BUILT_IN_DISPLAYS =
78             "android.hardware.display.category.BUILT_IN_DISPLAYS";
79 
80     private final Context mContext;
81 
DisplayManagerCompat(Context context)82     private DisplayManagerCompat(Context context) {
83         mContext = context;
84     }
85 
86     /**
87      * Gets an instance of the display manager given the context.
88      */
getInstance(@onNull Context context)89     public static @NonNull DisplayManagerCompat getInstance(@NonNull Context context) {
90         return new DisplayManagerCompat(context);
91     }
92 
93     /**
94      * Gets information about a logical display.
95      *
96      * The display metrics may be adjusted to provide compatibility
97      * for legacy applications.
98      *
99      * @param displayId The logical display id.
100      * @return The display object, or null if there is no valid display with the given id.
101      */
getDisplay(int displayId)102     public @Nullable Display getDisplay(int displayId) {
103         DisplayManager displayManager =
104                 (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
105         return displayManager.getDisplay(displayId);
106     }
107 
108     /**
109      * Gets all currently valid logical displays.
110      *
111      * @return An array containing all displays.
112      */
getDisplays()113     public Display @NonNull [] getDisplays() {
114         return ((DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE)).getDisplays();
115     }
116 
117     /**
118      * Gets all currently valid logical displays of the specified category.
119      * <p>
120      * When there are multiple displays in a category the returned displays are sorted
121      * of preference.  For example, if the requested category is
122      * {@link #DISPLAY_CATEGORY_PRESENTATION} and there are multiple presentation displays
123      * then the displays are sorted so that the first display in the returned array
124      * is the most preferred presentation display.  The application may simply
125      * use the first display or allow the user to choose.
126      * </p>
127      *
128      * @param category The requested display category or null to return all displays.
129      * @return An array containing all displays sorted by order of preference.
130      *
131      * @see #DISPLAY_CATEGORY_PRESENTATION
132      */
getDisplays(@ullable String category)133     public Display @NonNull [] getDisplays(@Nullable String category) {
134         DisplayManager displayManager = (DisplayManager) mContext
135                 .getSystemService(Context.DISPLAY_SERVICE);
136         if (DISPLAY_CATEGORY_BUILT_IN_DISPLAYS.equals(category)) {
137             return computeBuiltInDisplays(displayManager);
138         } else {
139             return ((DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE))
140                     .getDisplays(category);
141         }
142     }
143 
144     /**
145      * Returns an array of built in displays, a built in display is one that is physically part
146      * of the device.
147      */
computeBuiltInDisplays(DisplayManager displayManager)148     private static Display[] computeBuiltInDisplays(DisplayManager displayManager) {
149         final Display[] allDisplays;
150         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S_V2) {
151             allDisplays = displayManager
152                     .getDisplays(DISPLAY_CATEGORY_ALL);
153 
154         } else {
155             allDisplays = displayManager.getDisplays();
156         }
157         final int numberOfBuiltInDisplays =
158                 numberOfDisplaysByType(DISPLAY_TYPE_INTERNAL, allDisplays);
159         final Display[] builtInDisplays = new Display[numberOfBuiltInDisplays];
160 
161         int builtInDisplayIndex = 0;
162         for (int i = 0; i < allDisplays.length; i++) {
163             Display display = allDisplays[i];
164             if (DISPLAY_TYPE_INTERNAL == getTypeCompat(display)) {
165                 builtInDisplays[builtInDisplayIndex] = display;
166                 builtInDisplayIndex = builtInDisplayIndex + 1;
167             }
168         }
169         return builtInDisplays;
170     }
171 
172     /**
173      * Returns the number of displays that have the matching type.
174      */
numberOfDisplaysByType(int type, Display[] displays)175     private static int numberOfDisplaysByType(int type, Display[] displays) {
176         int count = 0;
177         for (int i = 0; i < displays.length; i++) {
178             Display display = displays[i];
179             if (type == getTypeCompat(display)) {
180                 count = count + 1;
181             }
182         }
183         return count;
184     }
185 
186     /**
187      * An internal method to get the type of the display using reflection. This is used to support
188      * backporting of getting a display of a specific type. The preferred way to expose displays is
189      * to have a category and have developers get them using the category.
190      */
191     @SuppressLint("BanUncheckedReflection")
192     @VisibleForTesting
193     @RestrictTo(RestrictTo.Scope.LIBRARY)
getTypeCompat(@onNull Display display)194     static int getTypeCompat(@NonNull Display display) {
195         try {
196             return (Integer) Objects.requireNonNull(
197                     Display.class.getMethod("getType").invoke(display)
198             );
199         } catch (NoSuchMethodException noSuchMethodException) {
200             return 0;
201         } catch (Exception exception) {
202             throw new RuntimeException(exception);
203         }
204     }
205 }
206