1 /*
2  * Copyright (C) 2019 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 package com.android.launcher3.util;
17 
18 import static android.view.Display.DEFAULT_DISPLAY;
19 
20 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
21 
22 import android.content.Context;
23 import android.graphics.Point;
24 import android.hardware.display.DisplayManager;
25 import android.hardware.display.DisplayManager.DisplayListener;
26 import android.os.Handler;
27 import android.os.Message;
28 import android.util.DisplayMetrics;
29 import android.util.Log;
30 import android.view.Display;
31 
32 import androidx.annotation.VisibleForTesting;
33 
34 import java.util.ArrayList;
35 
36 /**
37  * Utility class to cache properties of default display to avoid a system RPC on every call.
38  */
39 public class DefaultDisplay implements DisplayListener {
40 
41     public static final MainThreadInitializedObject<DefaultDisplay> INSTANCE =
42             new MainThreadInitializedObject<>(DefaultDisplay::new);
43 
44     private static final String TAG = "DefaultDisplay";
45 
46     public static final int CHANGE_SIZE = 1 << 0;
47     public static final int CHANGE_ROTATION = 1 << 1;
48     public static final int CHANGE_FRAME_DELAY = 1 << 2;
49 
50     public static final int CHANGE_ALL = CHANGE_SIZE | CHANGE_ROTATION | CHANGE_FRAME_DELAY;
51 
52     private final Context mDisplayContext;
53     private final int mId;
54     private final ArrayList<DisplayInfoChangeListener> mListeners = new ArrayList<>();
55     private final Handler mChangeHandler;
56     private Info mInfo;
57 
DefaultDisplay(Context context)58     private DefaultDisplay(Context context) {
59         DisplayManager dm = context.getSystemService(DisplayManager.class);
60         // Use application context to create display context so that it can have its own Resources.
61         mDisplayContext = context.getApplicationContext().createDisplayContext(
62                 dm.getDisplay(DEFAULT_DISPLAY));
63         // Note that the Display object must be obtained from DisplayManager which is associated to
64         // the display context, so the Display is isolated from Activity and Application to provide
65         // the actual state of device that excludes the additional adjustment and override.
66         mInfo = new Info(mDisplayContext);
67         mId = mInfo.id;
68         mChangeHandler = new Handler(this::onChange);
69 
70         dm.registerDisplayListener(this, UI_HELPER_EXECUTOR.getHandler());
71     }
72 
73     @Override
onDisplayAdded(int displayId)74     public final void onDisplayAdded(int displayId) {  }
75 
76     @Override
onDisplayRemoved(int displayId)77     public final void onDisplayRemoved(int displayId) { }
78 
79     @Override
onDisplayChanged(int displayId)80     public final void onDisplayChanged(int displayId) {
81         if (displayId != mId) {
82             return;
83         }
84 
85         Info oldInfo = mInfo;
86         Info info = new Info(mDisplayContext);
87 
88         int change = 0;
89         if (info.hasDifferentSize(oldInfo)) {
90             change |= CHANGE_SIZE;
91         }
92         if (oldInfo.rotation != info.rotation) {
93             change |= CHANGE_ROTATION;
94         }
95         if (info.singleFrameMs != oldInfo.singleFrameMs) {
96             change |= CHANGE_FRAME_DELAY;
97         }
98 
99         if (change != 0) {
100             mInfo = info;
101             mChangeHandler.sendEmptyMessage(change);
102         }
103     }
104 
getSingleFrameMs(Context context)105     public static int getSingleFrameMs(Context context) {
106         return INSTANCE.get(context).getInfo().singleFrameMs;
107     }
108 
getInfo()109     public Info getInfo() {
110         return mInfo;
111     }
112 
addChangeListener(DisplayInfoChangeListener listener)113     public void addChangeListener(DisplayInfoChangeListener listener) {
114         mListeners.add(listener);
115     }
116 
removeChangeListener(DisplayInfoChangeListener listener)117     public void removeChangeListener(DisplayInfoChangeListener listener) {
118         mListeners.remove(listener);
119     }
120 
onChange(Message msg)121     private boolean onChange(Message msg) {
122         for (int i = mListeners.size() - 1; i >= 0; i--) {
123             mListeners.get(i).onDisplayInfoChanged(mInfo, msg.what);
124         }
125         return true;
126     }
127 
128     public static class Info {
129 
130         public final int id;
131         public final int rotation;
132         public final int singleFrameMs;
133 
134         public final Point realSize;
135         public final Point smallestSize;
136         public final Point largestSize;
137 
138         public final DisplayMetrics metrics;
139 
140         @VisibleForTesting
Info(int id, int rotation, int singleFrameMs, Point realSize, Point smallestSize, Point largestSize, DisplayMetrics metrics)141         public Info(int id, int rotation, int singleFrameMs, Point realSize, Point smallestSize,
142                 Point largestSize, DisplayMetrics metrics) {
143             this.id = id;
144             this.rotation = rotation;
145             this.singleFrameMs = singleFrameMs;
146             this.realSize = realSize;
147             this.smallestSize = smallestSize;
148             this.largestSize = largestSize;
149             this.metrics = metrics;
150         }
151 
Info(Context context)152         private Info(Context context) {
153             this(context, context.getSystemService(DisplayManager.class)
154                     .getDisplay(DEFAULT_DISPLAY));
155         }
156 
Info(Context context, Display display)157         public Info(Context context, Display display) {
158             id = display.getDisplayId();
159             rotation = display.getRotation();
160 
161             float refreshRate = display.getRefreshRate();
162             singleFrameMs = refreshRate > 0 ? (int) (1000 / refreshRate) : 16;
163 
164             realSize = new Point();
165             smallestSize = new Point();
166             largestSize = new Point();
167             display.getRealSize(realSize);
168             display.getCurrentSizeRange(smallestSize, largestSize);
169 
170             metrics = context.getResources().getDisplayMetrics();
171         }
172 
hasDifferentSize(Info info)173         private boolean hasDifferentSize(Info info) {
174             if (!realSize.equals(info.realSize)
175                     && !realSize.equals(info.realSize.y, info.realSize.x)) {
176                 Log.d(TAG, String.format("Display size changed from %s to %s",
177                         info.realSize, realSize));
178                 return true;
179             }
180 
181             if (!smallestSize.equals(info.smallestSize) || !largestSize.equals(info.largestSize)) {
182                 Log.d(TAG, String.format("Available size changed from [%s, %s] to [%s, %s]",
183                         smallestSize, largestSize, info.smallestSize, info.largestSize));
184                 return true;
185             }
186 
187             return false;
188         }
189     }
190 
191     /**
192      * Interface for listening for display changes
193      */
194     public interface DisplayInfoChangeListener {
195 
onDisplayInfoChanged(Info info, int flags)196         void onDisplayInfoChanged(Info info, int flags);
197     }
198 }
199