Index: ui/android/java/src/org/chromium/ui/display/DisplayAndroidManager.java |
diff --git a/ui/android/java/src/org/chromium/ui/display/DisplayAndroidManager.java b/ui/android/java/src/org/chromium/ui/display/DisplayAndroidManager.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..96a310dfd809a8616fdf2aeb53aaad408eb5e5b6 |
--- /dev/null |
+++ b/ui/android/java/src/org/chromium/ui/display/DisplayAndroidManager.java |
@@ -0,0 +1,237 @@ |
+// Copyright 2016 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+package org.chromium.ui.display; |
+ |
+import android.annotation.SuppressLint; |
+import android.content.ComponentCallbacks; |
+import android.content.Context; |
+import android.content.res.Configuration; |
+import android.hardware.display.DisplayManager; |
+import android.hardware.display.DisplayManager.DisplayListener; |
+import android.os.Build; |
+import android.util.SparseArray; |
+import android.view.Display; |
+import android.view.WindowManager; |
+ |
+import org.chromium.base.ContextUtils; |
+import org.chromium.base.ThreadUtils; |
+import org.chromium.ui.gfx.DeviceDisplayInfo; |
+ |
+/** |
+ * DisplayAndroidManager is a class that informs its observers Display changes. |
+ */ |
+/* package */ class DisplayAndroidManager { |
+ /** |
+ * DisplayListenerBackend is an interface that abstract the mechanism used for the actual |
+ * display update listening. The reason being that from Android API Level 17 DisplayListener |
+ * will be used. Before that, an unreliable solution based on onConfigurationChanged has to be |
+ * used. |
+ */ |
+ private interface DisplayListenerBackend { |
+ |
+ /** |
+ * Starts to listen for display changes. This will be called |
+ * when the first observer is added. |
+ */ |
+ void startListening(); |
+ |
+ /** |
+ * Toggle the accurate mode if it wasn't already doing so. The backend |
+ * will keep track of the number of times this has been called. |
+ */ |
+ void startAccurateListening(); |
+ |
+ /** |
+ * Request to stop the accurate mode. It will effectively be stopped |
+ * only if this method is called as many times as |
+ * startAccurateListening(). |
+ */ |
+ void stopAccurateListening(); |
+ } |
+ |
+ /** |
+ * DisplayListenerAPI16 implements DisplayListenerBackend |
+ * to use ComponentCallbacks in order to listen for display |
+ * changes. |
+ * |
+ * This method is known to not correctly detect 180 degrees changes but it |
+ * is the only method that will work before API Level 17 (excluding polling). |
+ * When toggleAccurateMode() is called, it will start polling in order to |
+ * find out if the display has changed. |
+ */ |
+ private class DisplayListenerAPI16 |
+ implements DisplayListenerBackend, ComponentCallbacks { |
+ |
+ private static final long POLLING_DELAY = 500; |
+ |
+ private int mMainSdkDisplayId; |
+ private int mAccurateCount; |
+ |
+ public DisplayListenerAPI16(int sdkDisplayId) { |
+ mMainSdkDisplayId = sdkDisplayId; |
+ } |
+ |
+ // DisplayListenerBackend implementation: |
+ |
+ @Override |
+ public void startListening() { |
+ getContext().registerComponentCallbacks(this); |
+ } |
+ |
+ @Override |
+ public void startAccurateListening() { |
+ ++mAccurateCount; |
+ |
+ if (mAccurateCount > 1) return; |
+ |
+ // Start polling if we went from 0 to 1. The polling will |
+ // automatically stop when mAccurateCount reaches 0. |
+ final DisplayListenerAPI16 self = this; |
+ ThreadUtils.postOnUiThreadDelayed(new Runnable() { |
+ @Override |
+ public void run() { |
+ self.onConfigurationChanged(null); |
+ |
+ if (self.mAccurateCount < 1) return; |
+ |
+ ThreadUtils.postOnUiThreadDelayed(this, |
+ DisplayListenerAPI16.POLLING_DELAY); |
+ } |
+ }, POLLING_DELAY); |
+ } |
+ |
+ @Override |
+ public void stopAccurateListening() { |
+ --mAccurateCount; |
+ assert mAccurateCount >= 0; |
+ } |
+ |
+ // ComponentCallbacks implementation: |
+ |
+ @Override |
+ public void onConfigurationChanged(Configuration newConfig) { |
+ updateDeviceDisplayInfo(); |
+ mIdMap.get(mMainSdkDisplayId).updateFromDisplay(getDisplayFromContext(getContext())); |
+ } |
+ |
+ @Override |
+ public void onLowMemory() { |
+ } |
+ } |
+ |
+ /** |
+ * DisplayListenerBackendImpl implements DisplayListenerBackend |
+ * to use DisplayListener in order to listen for display changes. |
+ * |
+ * This method is reliable but DisplayListener is only available for API Level 17+. |
+ */ |
+ @SuppressLint("NewApi") |
+ private class DisplayListenerBackendImpl |
+ implements DisplayListenerBackend, DisplayListener { |
+ |
+ // DisplayListenerBackend implementation: |
+ |
+ @Override |
+ public void startListening() { |
+ getDisplayManager().registerDisplayListener(this, null); |
+ } |
+ |
+ @Override |
+ public void startAccurateListening() { |
+ // Always accurate. Do nothing. |
+ } |
+ |
+ @Override |
+ public void stopAccurateListening() { |
+ // Always accurate. Do nothing. |
+ } |
+ |
+ // DisplayListener implementation: |
+ |
+ @Override |
+ public void onDisplayAdded(int sdkDisplayId) { |
+ addDisplay(getDisplayManager().getDisplay(sdkDisplayId)); |
+ } |
+ |
+ @Override |
+ public void onDisplayRemoved(int sdkDisplayId) { |
+ mIdMap.remove(sdkDisplayId); |
+ } |
+ |
+ @Override |
+ public void onDisplayChanged(int sdkDisplayId) { |
+ updateDeviceDisplayInfo(); |
+ mIdMap.get(sdkDisplayId) |
+ .updateFromDisplay(getDisplayManager().getDisplay(sdkDisplayId)); |
+ } |
+ } |
+ |
+ private static DisplayAndroidManager sDisplayAndroidManager; |
+ |
+ private SparseArray<DisplayAndroid> mIdMap; |
+ private final DisplayListenerBackend mBackend; |
+ |
+ /* package */ static DisplayAndroidManager getInstance() { |
+ if (sDisplayAndroidManager == null) { |
+ sDisplayAndroidManager = new DisplayAndroidManager(); |
+ } |
+ return sDisplayAndroidManager; |
+ } |
+ |
+ /* package */ static Display getDisplayFromContext(Context context) { |
+ WindowManager windowManager = |
+ (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); |
+ return windowManager.getDefaultDisplay(); |
+ } |
+ |
+ private static Context getContext() { |
+ // Using the global application context is probably ok. |
+ // The DisplayManager API observers all display updates, so in theory it should not matter |
+ // which context is used to obtain it. If this turns out not to be true in practice, it's |
+ // possible register from all context used though quite complex. |
+ return ContextUtils.getApplicationContext(); |
+ } |
+ |
+ private static DisplayManager getDisplayManager() { |
+ return (DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE); |
+ } |
+ |
+ private static void updateDeviceDisplayInfo() { |
+ DeviceDisplayInfo.create(getContext()).updateNativeSharedDisplayInfo(); |
+ } |
+ |
+ private DisplayAndroidManager() { |
+ mIdMap = new SparseArray<>(); |
+ |
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { |
+ mBackend = new DisplayListenerBackendImpl(); |
+ for (Display display : getDisplayManager().getDisplays()) { |
+ addDisplay(display); |
+ } |
+ } else { |
+ Display display = getDisplayFromContext(getContext()); |
+ mBackend = new DisplayListenerAPI16(display.getDisplayId()); |
+ addDisplay(display); // Note this display is never removed. |
+ } |
+ mBackend.startListening(); |
+ } |
+ |
+ /* package */ DisplayAndroid getDisplayAndroid(int sdkDisplayId) { |
+ return mIdMap.get(sdkDisplayId); |
+ } |
+ |
+ /* package */ void startAccurateListening() { |
+ mBackend.startAccurateListening(); |
+ } |
+ |
+ /* package */ void stopAccurateListening() { |
+ mBackend.stopAccurateListening(); |
+ } |
+ |
+ private void addDisplay(Display display) { |
+ int sdkDisplayId = display.getDisplayId(); |
+ mIdMap.put(sdkDisplayId, new DisplayAndroid(display)); |
+ } |
+} |