Merge "[SuwLib] Avoid infinite loop in SystemBarHelper"

This commit is contained in:
Maurice Lam 2015-08-04 00:27:54 +00:00 committed by Android (Google) Code Review
commit 4b88e9f4d9
3 changed files with 305 additions and 235 deletions

View file

@ -23,6 +23,7 @@ import android.content.Context;
import android.os.Build.VERSION; import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES; import android.os.Build.VERSION_CODES;
import android.os.Handler; import android.os.Handler;
import android.util.Log;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.Window; import android.view.Window;
@ -42,6 +43,8 @@ import com.android.setupwizardlib.R;
*/ */
public class SystemBarHelper { public class SystemBarHelper {
private static final String TAG = "SystemBarHelper";
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")
private static final int DEFAULT_IMMERSIVE_FLAGS = private static final int DEFAULT_IMMERSIVE_FLAGS =
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
@ -59,6 +62,12 @@ public class SystemBarHelper {
*/ */
private static final int STATUS_BAR_DISABLE_BACK = 0x00400000; private static final int STATUS_BAR_DISABLE_BACK = 0x00400000;
/**
* The maximum number of retries when peeking the decor view. When polling for the decor view,
* waiting it to be installed, set a maximum number of retries.
*/
private static final int PEEK_DECOR_VIEW_RETRIES = 3;
/** /**
* Hide the navigation bar for a dialog. * Hide the navigation bar for a dialog.
* *
@ -69,7 +78,8 @@ public class SystemBarHelper {
final Window window = dialog.getWindow(); final Window window = dialog.getWindow();
temporarilyDisableDialogFocus(window); temporarilyDisableDialogFocus(window);
addImmersiveFlagsToWindow(window, DIALOG_IMMERSIVE_FLAGS); addImmersiveFlagsToWindow(window, DIALOG_IMMERSIVE_FLAGS);
addImmersiveFlagsToDecorView(window, new Handler(), DIALOG_IMMERSIVE_FLAGS); addImmersiveFlagsToDecorView(window, new Handler(), DIALOG_IMMERSIVE_FLAGS,
PEEK_DECOR_VIEW_RETRIES);
} }
} }
@ -84,7 +94,8 @@ public class SystemBarHelper {
public static void hideSystemBars(final Window window) { public static void hideSystemBars(final Window window) {
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
addImmersiveFlagsToWindow(window, DEFAULT_IMMERSIVE_FLAGS); addImmersiveFlagsToWindow(window, DEFAULT_IMMERSIVE_FLAGS);
addImmersiveFlagsToDecorView(window, new Handler(), DEFAULT_IMMERSIVE_FLAGS); addImmersiveFlagsToDecorView(window, new Handler(), DEFAULT_IMMERSIVE_FLAGS,
PEEK_DECOR_VIEW_RETRIES);
} }
} }
@ -161,25 +172,30 @@ public class SystemBarHelper {
} }
/** /**
* View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN only takes effect when it is added a view instead of * View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN only takes effect when it is added to a view instead of
* the window. * the window.
*/ */
@TargetApi(VERSION_CODES.LOLLIPOP) @TargetApi(VERSION_CODES.LOLLIPOP)
private static void addImmersiveFlagsToDecorView(final Window window, final Handler handler, private static void addImmersiveFlagsToDecorView(final Window window, final Handler handler,
final int vis) { final int vis, final int retries) {
// Use peekDecorView instead of getDecorView so that clients can still set window features // Use peekDecorView instead of getDecorView so that clients can still set window features
// after calling this method. // after calling this method.
final View decorView = window.peekDecorView(); final View decorView = window.peekDecorView();
if (decorView != null) { if (decorView != null) {
addVisibilityFlag(decorView, vis); addVisibilityFlag(decorView, vis);
} else { } else {
// If the decor view is not installed yet, try again in the next loop. final int newRetries = retries - 1;
handler.post(new Runnable() { if (newRetries >= 0) {
@Override // If the decor view is not installed yet, try again in the next loop.
public void run() { handler.post(new Runnable() {
addImmersiveFlagsToDecorView(window, handler, vis); @Override
} public void run() {
}); addImmersiveFlagsToDecorView(window, handler, vis, newRetries);
}
});
} else {
Log.w(TAG, "Cannot get decor view of window: " + window);
}
} }
} }

View file

@ -19,24 +19,18 @@ package com.android.setupwizardlib.test;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Dialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.content.res.Configuration;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build.VERSION; import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES; import android.os.Build.VERSION_CODES;
import android.os.Bundle; import android.os.Handler;
import android.os.HandlerThread;
import android.os.SystemClock;
import android.test.AndroidTestCase; import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest; import android.test.suitebuilder.annotation.SmallTest;
import android.view.InputQueue;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.SurfaceHolder.Callback2;
import android.view.View; import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.Window; import android.view.Window;
import android.view.WindowManager; import android.view.WindowManager;
import com.android.setupwizardlib.test.util.MockWindow;
import com.android.setupwizardlib.util.SystemBarHelper; import com.android.setupwizardlib.util.SystemBarHelper;
public class SystemBarHelperTest extends AndroidTestCase { public class SystemBarHelperTest extends AndroidTestCase {
@ -112,6 +106,24 @@ public class SystemBarHelperTest extends AndroidTestCase {
} }
} }
@SmallTest
public void testHideSystemBarsNoInfiniteLoop() throws InterruptedException {
final TestWindow window = new TestWindow(getContext(), null);
final HandlerThread thread = new HandlerThread("SystemBarHelperTest");
thread.start();
final Handler handler = new Handler(thread.getLooper());
handler.post(new Runnable() {
@Override
public void run() {
SystemBarHelper.hideSystemBars(window);
}
});
SystemClock.sleep(500); // Wait for the looper to drain all the messages
thread.quit();
// Initial peek + 3 retries = 4 tries total
assertEquals("Peek decor view should give up after 4 tries", 4, window.peekDecorViewCount);
}
@SmallTest @SmallTest
public void testHideSystemBarsDialog() { public void testHideSystemBarsDialog() {
final Dialog dialog = new Dialog(mContext); final Dialog dialog = new Dialog(mContext);
@ -167,175 +179,16 @@ public class SystemBarHelperTest extends AndroidTestCase {
return window; return window;
} }
private static class TestWindow extends Window { private static class TestWindow extends MockWindow {
private View mDecorView; private View mDecorView;
public int peekDecorViewCount = 0;
public TestWindow(Context context, View decorView) { public TestWindow(Context context, View decorView) {
super(context); super(context);
mDecorView = decorView; mDecorView = decorView;
} }
@Override
public void takeSurface(Callback2 callback2) {
}
@Override
public void takeInputQueue(InputQueue.Callback callback) {
}
@Override
public boolean isFloating() {
return false;
}
@Override
public void setContentView(int i) {
}
@Override
public void setContentView(View view) {
}
@Override
public void setContentView(View view, LayoutParams layoutParams) {
}
@Override
public void addContentView(View view, LayoutParams layoutParams) {
}
@Override
public View getCurrentFocus() {
return null;
}
@Override
public LayoutInflater getLayoutInflater() {
return LayoutInflater.from(getContext());
}
@Override
public void setTitle(CharSequence charSequence) {
}
@Override
public void setTitleColor(int i) {
}
@Override
public void openPanel(int i, KeyEvent keyEvent) {
}
@Override
public void closePanel(int i) {
}
@Override
public void togglePanel(int i, KeyEvent keyEvent) {
}
@Override
public void invalidatePanelMenu(int i) {
}
@Override
public boolean performPanelShortcut(int i, int i2, KeyEvent keyEvent, int i3) {
return false;
}
@Override
public boolean performPanelIdentifierAction(int i, int i2, int i3) {
return false;
}
@Override
public void closeAllPanels() {
}
@Override
public boolean performContextMenuIdentifierAction(int i, int i2) {
return false;
}
@Override
public void onConfigurationChanged(Configuration configuration) {
}
@Override
public void setBackgroundDrawable(Drawable drawable) {
}
@Override
public void setFeatureDrawableResource(int i, int i2) {
}
@Override
public void setFeatureDrawableUri(int i, Uri uri) {
}
@Override
public void setFeatureDrawable(int i, Drawable drawable) {
}
@Override
public void setFeatureDrawableAlpha(int i, int i2) {
}
@Override
public void setFeatureInt(int i, int i2) {
}
@Override
public void takeKeyEvents(boolean b) {
}
@Override
public boolean superDispatchKeyEvent(KeyEvent keyEvent) {
return false;
}
@Override
public boolean superDispatchKeyShortcutEvent(KeyEvent keyEvent) {
return false;
}
@Override
public boolean superDispatchTouchEvent(MotionEvent motionEvent) {
return false;
}
@Override
public boolean superDispatchTrackballEvent(MotionEvent motionEvent) {
return false;
}
@Override
public boolean superDispatchGenericMotionEvent(MotionEvent motionEvent) {
return false;
}
@Override @Override
public View getDecorView() { public View getDecorView() {
return mDecorView; return mDecorView;
@ -343,67 +196,16 @@ public class SystemBarHelperTest extends AndroidTestCase {
@Override @Override
public View peekDecorView() { public View peekDecorView() {
peekDecorViewCount++;
return mDecorView; return mDecorView;
} }
@Override @Override
public Bundle saveHierarchyState() { public void setNavigationBarColor(int i) {
return null;
}
@Override
public void restoreHierarchyState(Bundle bundle) {
}
@Override
protected void onActive() {
}
@Override
public void setChildDrawable(int i, Drawable drawable) {
}
@Override
public void setChildInt(int i, int i2) {
}
@Override
public boolean isShortcutKey(int i, KeyEvent keyEvent) {
return false;
}
@Override
public void setVolumeControlStream(int i) {
}
@Override
public int getVolumeControlStream() {
return 0;
}
@Override
public int getStatusBarColor() {
return 0;
} }
@Override @Override
public void setStatusBarColor(int i) { public void setStatusBarColor(int i) {
}
@Override
public int getNavigationBarColor() {
return 0;
}
@Override
public void setNavigationBarColor(int i) {
} }
} }
} }

View file

@ -0,0 +1,252 @@
package com.android.setupwizardlib.test.util;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.view.InputQueue;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
public class MockWindow extends Window {
public MockWindow(Context context) {
super(context);
}
@Override
public void takeSurface(SurfaceHolder.Callback2 callback2) {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public void takeInputQueue(InputQueue.Callback callback) {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public boolean isFloating() {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public void setContentView(int i) {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public void setContentView(View view) {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams layoutParams) {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public void addContentView(View view, ViewGroup.LayoutParams layoutParams) {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public View getCurrentFocus() {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public LayoutInflater getLayoutInflater() {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public void setTitle(CharSequence charSequence) {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public void setTitleColor(int i) {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public void openPanel(int i, KeyEvent keyEvent) {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public void closePanel(int i) {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public void togglePanel(int i, KeyEvent keyEvent) {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public void invalidatePanelMenu(int i) {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public boolean performPanelShortcut(int i, int i1, KeyEvent keyEvent, int i2) {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public boolean performPanelIdentifierAction(int i, int i1, int i2) {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public void closeAllPanels() {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public boolean performContextMenuIdentifierAction(int i, int i1) {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public void onConfigurationChanged(Configuration configuration) {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public void setBackgroundDrawable(Drawable drawable) {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public void setFeatureDrawableResource(int i, int i1) {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public void setFeatureDrawableUri(int i, Uri uri) {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public void setFeatureDrawable(int i, Drawable drawable) {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public void setFeatureDrawableAlpha(int i, int i1) {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public void setFeatureInt(int i, int i1) {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public void takeKeyEvents(boolean b) {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public boolean superDispatchKeyEvent(KeyEvent keyEvent) {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public boolean superDispatchKeyShortcutEvent(KeyEvent keyEvent) {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public boolean superDispatchTouchEvent(MotionEvent motionEvent) {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public boolean superDispatchTrackballEvent(MotionEvent motionEvent) {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public boolean superDispatchGenericMotionEvent(MotionEvent motionEvent) {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public View getDecorView() {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public View peekDecorView() {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public Bundle saveHierarchyState() {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public void restoreHierarchyState(Bundle bundle) {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
protected void onActive() {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public void setChildDrawable(int i, Drawable drawable) {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public void setChildInt(int i, int i1) {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public boolean isShortcutKey(int i, KeyEvent keyEvent) {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public void setVolumeControlStream(int i) {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public int getVolumeControlStream() {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public int getStatusBarColor() {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public void setStatusBarColor(int i) {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public int getNavigationBarColor() {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
@Override
public void setNavigationBarColor(int i) {
throw new UnsupportedOperationException("Unexpected method call on mock");
}
}