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