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