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_CODES;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
@ -42,6 +43,8 @@ import com.android.setupwizardlib.R;
*/
public class SystemBarHelper {
private static final String TAG = "SystemBarHelper";
@SuppressLint("InlinedApi")
private static final int DEFAULT_IMMERSIVE_FLAGS =
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
@ -59,6 +62,12 @@ public class SystemBarHelper {
*/
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.
*
@ -69,7 +78,8 @@ public class SystemBarHelper {
final Window window = dialog.getWindow();
temporarilyDisableDialogFocus(window);
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) {
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
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.
*/
@TargetApi(VERSION_CODES.LOLLIPOP)
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
// after calling this method.
final View decorView = window.peekDecorView();
if (decorView != null) {
addVisibilityFlag(decorView, vis);
} else {
// If the decor view is not installed yet, try again in the next loop.
handler.post(new Runnable() {
@Override
public void run() {
addImmersiveFlagsToDecorView(window, handler, vis);
}
});
final int newRetries = retries - 1;
if (newRetries >= 0) {
// If the decor view is not installed yet, try again in the next loop.
handler.post(new Runnable() {
@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.app.Dialog;
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_CODES;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.SystemClock;
import android.test.AndroidTestCase;
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.ViewGroup.LayoutParams;
import android.view.Window;
import android.view.WindowManager;
import com.android.setupwizardlib.test.util.MockWindow;
import com.android.setupwizardlib.util.SystemBarHelper;
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
public void testHideSystemBarsDialog() {
final Dialog dialog = new Dialog(mContext);
@ -167,175 +179,16 @@ public class SystemBarHelperTest extends AndroidTestCase {
return window;
}
private static class TestWindow extends Window {
private static class TestWindow extends MockWindow {
private View mDecorView;
public int peekDecorViewCount = 0;
public TestWindow(Context context, View decorView) {
super(context);
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
public View getDecorView() {
return mDecorView;
@ -343,67 +196,16 @@ public class SystemBarHelperTest extends AndroidTestCase {
@Override
public View peekDecorView() {
peekDecorViewCount++;
return mDecorView;
}
@Override
public Bundle saveHierarchyState() {
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;
public void setNavigationBarColor(int i) {
}
@Override
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");
}
}