Commit d1c11daa authored by Sinan Sahin's avatar Sinan Sahin Committed by Commit Bot

[Offline indicator v2] Add the status indicator's Android view

Bug: 989148
Change-Id: I889d7c52f4aa241d276e3665475362fa6178b091
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1809813Reviewed-by: default avatarTheresa  <twellington@chromium.org>
Reviewed-by: default avatarMatthew Jones <mdjones@chromium.org>
Commit-Queue: Sinan Sahin <sinansahin@google.com>
Cr-Commit-Position: refs/heads/master@{#699894}
parent c4dff9c3
......@@ -1506,7 +1506,9 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/ssl/CaptivePortalHelper.java",
"java/src/org/chromium/chrome/browser/ssl/SecurityStateModel.java",
"java/src/org/chromium/chrome/browser/status_indicator/StatusIndicatorCoordinator.java",
"java/src/org/chromium/chrome/browser/status_indicator/StatusIndicatorProperties.java",
"java/src/org/chromium/chrome/browser/status_indicator/StatusIndicatorSceneLayer.java",
"java/src/org/chromium/chrome/browser/status_indicator/StatusIndicatorViewBinder.java",
"java/src/org/chromium/chrome/browser/subresource_filter/TestSubresourceFilterPublisher.java",
"java/src/org/chromium/chrome/browser/suggestions/DestructionObserver.java",
"java/src/org/chromium/chrome/browser/suggestions/ImageFetcher.java",
......
......@@ -436,6 +436,8 @@ chrome_test_java_sources = [
"javatests/src/org/chromium/chrome/browser/snackbar/SnackbarTest.java",
"javatests/src/org/chromium/chrome/browser/snackbar/undo/UndoBarControllerTest.java",
"javatests/src/org/chromium/chrome/browser/ssl/CaptivePortalTest.java",
"javatests/src/org/chromium/chrome/browser/status_indicator/StatusIndicatorTest.java",
"javatests/src/org/chromium/chrome/browser/status_indicator/StatusIndicatorViewBinderTest.java",
"javatests/src/org/chromium/chrome/browser/suggestions/ContentSuggestionsTest.java",
"javatests/src/org/chromium/chrome/browser/suggestions/HomeSheetCardsUiCaptureTest.java",
"javatests/src/org/chromium/chrome/browser/suggestions/HomeSheetNoTilesUiCaptureTest.java",
......
......@@ -91,6 +91,13 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ViewStub
android:id="@+id/status_indicator_stub"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inflatedId="@+id/status_indicator"
android:layout="@layout/status_indicator_container" />
<ViewStub
android:id="@+id/empty_container_stub"
android:inflatedId="@+id/empty_container"
......
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2019 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. -->
<org.chromium.chrome.browser.widget.ViewResourceFrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/status_indicator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/black">
<org.chromium.chrome.browser.ui.widget.text.TextViewWithCompoundDrawables
android:id="@+id/status_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:minHeight="20dp"
android:textAlignment="center"
android:drawablePadding="8dp" />
</org.chromium.chrome.browser.widget.ViewResourceFrameLayout>
......@@ -856,6 +856,15 @@ public class LayoutManager implements LayoutUpdateHost, LayoutProvider,
// Nothing to do here yet.
}
// TODO(crbug.com/1002519): This should be a temporary solution until the scene layer ownership
// is redone and the toolbar component owns its scene layer.
/**
* @return The {@link ToolbarSceneLayer}.
*/
public ToolbarSceneLayer getToolbarSceneLayer() {
return mToolbarOverlay;
}
/**
* @return The {@link EdgeSwipeHandler} responsible for processing swipe events for the toolbar.
* By default this returns null.
......@@ -873,7 +882,7 @@ public class LayoutManager implements LayoutUpdateHost, LayoutProvider,
}
/**
* Set the {@link SceneOverlay} and add it to the layout.
* Set the status indicator {@link SceneOverlay} to be added to the layout.
* @param overlay The {@link SceneOverlay} to set.
*/
public void setStatusIndicatorSceneOverlay(SceneOverlay overlay) {
......
......@@ -49,6 +49,9 @@ public class ToolbarSceneLayer extends SceneOverlayLayer implements SceneOverlay
/** A LayoutRenderHost for accessing drawing information about the toolbar. */
private LayoutRenderHost mRenderHost;
/** The static Y offset for the cases where there is a another cc layer above the toolbar. */
private int mStaticYOffset;
/**
* @param context An Android context to use.
* @param provider A LayoutProvider for accessing the current layout.
......@@ -61,6 +64,14 @@ public class ToolbarSceneLayer extends SceneOverlayLayer implements SceneOverlay
mRenderHost = renderHost;
}
/**
* Set a static Y offset for the toolbar.
* @param staticYOffset The Y offset in pixels.
*/
public void setStaticYOffset(int staticYOffset) {
mStaticYOffset = staticYOffset;
}
/**
* Update the toolbar and progress bar layers.
*
......@@ -114,7 +125,8 @@ public class ToolbarSceneLayer extends SceneOverlayLayer implements SceneOverlay
ToolbarSceneLayerJni.get().updateToolbarLayer(mNativePtr, ToolbarSceneLayer.this,
resourceManager, R.id.control_container, browserControlsBackgroundColor,
textBoxResourceId, browserControlsUrlBarAlpha, textBoxColor,
fullscreenManager.getTopControlOffset(), windowHeight, useTexture, showShadow);
fullscreenManager.getTopControlOffset() + mStaticYOffset, windowHeight, useTexture,
showShadow);
if (mProgressBarDrawingInfo == null) return;
ToolbarSceneLayerJni.get().updateProgressBar(mNativePtr, ToolbarSceneLayer.this,
......
......@@ -4,24 +4,110 @@
package org.chromium.chrome.browser.status_indicator;
import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.view.ViewStub;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.widget.ViewResourceFrameLayout;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
import org.chromium.ui.resources.ResourceManager;
import java.util.HashSet;
/**
* The coordinator for a status indicator that is positioned below the status bar and is persistent.
* Typically used to relay status, e.g. indicate user is offline.
*/
public class StatusIndicatorCoordinator {
/** An observer that will be notified of the changes to the status indicator, e.g. height. */
public interface StatusIndicatorObserver {
/**
* Called when the height of the status indicator changes.
* @param newHeight The new height in pixels.
*/
void onStatusIndicatorHeightChanged(int newHeight);
}
private PropertyModel mModel;
private View mView;
private StatusIndicatorSceneLayer mSceneLayer;
private HashSet<StatusIndicatorObserver> mObservers = new HashSet<>();
public StatusIndicatorCoordinator() {
mSceneLayer = new StatusIndicatorSceneLayer();
public StatusIndicatorCoordinator(Activity activity, ResourceManager resourceManager) {
// TODO(crbug.com/1005843): Create this view lazily if/when we need it. This is a task for
// when we have the public API figured out.
final ViewStub stub = activity.findViewById(R.id.status_indicator_stub);
ViewResourceFrameLayout root = (ViewResourceFrameLayout) stub.inflate();
mView = root;
mSceneLayer = new StatusIndicatorSceneLayer(root);
mModel = new PropertyModel.Builder(StatusIndicatorProperties.ALL_KEYS)
.with(StatusIndicatorProperties.ANDROID_VIEW_VISIBLE, false)
.with(StatusIndicatorProperties.COMPOSITED_VIEW_VISIBLE, false)
.build();
PropertyModelChangeProcessor.create(mModel,
new StatusIndicatorViewBinder.ViewHolder(root, mSceneLayer),
StatusIndicatorViewBinder::bind);
resourceManager.getDynamicResourceLoader().registerResource(
root.getId(), root.getResourceAdapter());
}
/**
* Set the {@link String} the status indicator should display.
* @param statusText The string.
*/
public void setStatusText(String statusText) {
mModel.set(StatusIndicatorProperties.STATUS_TEXT, statusText);
}
/**
* Change the visibility of the status indicator.
* @param visible True if visible.
* Set the {@link Drawable} the status indicator should display next to the status text.
* @param statusIcon The icon drawable.
*/
public void setIsVisible(boolean visible) {
assert mSceneLayer != null;
mSceneLayer.setIsVisible(visible);
public void setStatusIcon(Drawable statusIcon) {
mModel.set(StatusIndicatorProperties.STATUS_ICON, statusIcon);
}
// TODO(sinansahin): With animation.
// TODO(sinansahin): Destroy the view when not needed.
/** Show the status indicator. */
public void show() {
mModel.set(StatusIndicatorProperties.COMPOSITED_VIEW_VISIBLE, true);
mModel.set(StatusIndicatorProperties.ANDROID_VIEW_VISIBLE, true);
// TODO(crbug.com/1005843): We will need a measure pass before we can get the real height of
// this view. We should keep this in mind when inflating the view lazily.
mView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
final int height = v.getHeight();
for (StatusIndicatorObserver observer : mObservers) {
observer.onStatusIndicatorHeightChanged(height);
}
mView.removeOnLayoutChangeListener(this);
}
});
}
// TODO(sinansahin): With animation as well.
/** Hide the status indicator. */
public void hide() {
mModel.set(StatusIndicatorProperties.COMPOSITED_VIEW_VISIBLE, false);
mModel.set(StatusIndicatorProperties.ANDROID_VIEW_VISIBLE, false);
for (StatusIndicatorObserver observer : mObservers) {
observer.onStatusIndicatorHeightChanged(0);
}
}
public void addObserver(StatusIndicatorObserver observer) {
mObservers.add(observer);
}
public void removeObserver(StatusIndicatorObserver observer) {
mObservers.remove(observer);
}
/**
......@@ -29,7 +115,7 @@ public class StatusIndicatorCoordinator {
* @return True if visible.
*/
public boolean isVisible() {
return mSceneLayer.isSceneOverlayTreeShowing();
return mModel.get(StatusIndicatorProperties.COMPOSITED_VIEW_VISIBLE);
}
/**
......
// Copyright 2019 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.chrome.browser.status_indicator;
import android.graphics.drawable.Drawable;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel;
class StatusIndicatorProperties {
/** The text that describes status. */
static final PropertyModel.WritableObjectPropertyKey<String> STATUS_TEXT =
new PropertyModel.WritableObjectPropertyKey<>();
/** The {@link Drawable} that will be displayed next to the status text. */
static final PropertyModel.WritableObjectPropertyKey<Drawable> STATUS_ICON =
new PropertyModel.WritableObjectPropertyKey<>();
/** Whether the Android view version of the status indicator is visible. */
static final PropertyModel.WritableBooleanPropertyKey ANDROID_VIEW_VISIBLE =
new PropertyModel.WritableBooleanPropertyKey();
/** Whether the composited version of the status indicator is visible. */
static final PropertyModel.WritableBooleanPropertyKey COMPOSITED_VIEW_VISIBLE =
new PropertyModel.WritableBooleanPropertyKey();
static final PropertyKey[] ALL_KEYS = new PropertyKey[] {
STATUS_TEXT, STATUS_ICON, ANDROID_VIEW_VISIBLE, COMPOSITED_VIEW_VISIBLE};
}
......@@ -31,21 +31,18 @@ class StatusIndicatorSceneLayer extends SceneOverlayLayer implements SceneOverla
/** The resource ID used to reference the view bitmap in native. */
private int mResourceId;
private boolean mIsVisible;
/** The {@link ViewResourceFrameLayout} that this scene layer represents. */
private ViewResourceFrameLayout mStatusIndicator;
/** Build a composited status view layer. */
public StatusIndicatorSceneLayer() {}
private boolean mIsVisible;
/**
* Build a composited status view layer.
* @param resourceManager A resource manager for dynamic resource creation.
* @param statusIndicator The view used to generate the composited version.
*/
public StatusIndicatorSceneLayer(
ResourceManager resourceManager, ViewResourceFrameLayout statusIndicator) {
mResourceId = statusIndicator.getId();
resourceManager.getDynamicResourceLoader().registerResource(
mResourceId, statusIndicator.getResourceAdapter());
public StatusIndicatorSceneLayer(ViewResourceFrameLayout statusIndicator) {
mStatusIndicator = statusIndicator;
mResourceId = mStatusIndicator.getId();
}
/**
......
// Copyright 2019 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.chrome.browser.status_indicator;
import android.view.View;
import android.widget.TextView;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.widget.ViewResourceFrameLayout;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel;
class StatusIndicatorViewBinder {
/**
* A wrapper class that holds a {@link ViewResourceFrameLayout} and a composited layer to be
* used with the {@link StatusIndicatorSceneLayer}.
*/
static class ViewHolder {
/** A handle to the Android View based version of the status indicator. */
public final ViewResourceFrameLayout javaViewRoot;
/** A handle to the composited status indicator layer. */
public final StatusIndicatorSceneLayer sceneLayer;
/**
* @param root The Android View based status indicator.
*/
public ViewHolder(ViewResourceFrameLayout root, StatusIndicatorSceneLayer overlay) {
javaViewRoot = root;
sceneLayer = overlay;
}
}
static void bind(PropertyModel model, ViewHolder view, PropertyKey propertyKey) {
if (StatusIndicatorProperties.STATUS_TEXT == propertyKey) {
((TextView) view.javaViewRoot.findViewById(R.id.status_text))
.setText(model.get(StatusIndicatorProperties.STATUS_TEXT));
} else if (StatusIndicatorProperties.STATUS_ICON == propertyKey) {
((TextView) view.javaViewRoot.findViewById(R.id.status_text))
.setCompoundDrawablesRelativeWithIntrinsicBounds(
model.get(StatusIndicatorProperties.STATUS_ICON), null, null, null);
} else if (StatusIndicatorProperties.COMPOSITED_VIEW_VISIBLE == propertyKey) {
assert view.sceneLayer != null;
view.sceneLayer.setIsVisible(
model.get(StatusIndicatorProperties.COMPOSITED_VIEW_VISIBLE));
} else if (StatusIndicatorProperties.ANDROID_VIEW_VISIBLE == propertyKey) {
view.javaViewRoot.setVisibility(
model.get(StatusIndicatorProperties.ANDROID_VIEW_VISIBLE) ? View.VISIBLE
: View.GONE);
} else {
assert false : "Unhandled property detected in StatusIndicatorViewBinder!";
}
}
}
......@@ -6,10 +6,14 @@ package org.chromium.chrome.browser.tabbed_mode;
import androidx.annotation.Nullable;
import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.browser.AppHooks;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.appmenu.AppMenuHandler;
import org.chromium.chrome.browser.compositor.layouts.LayoutManager;
import org.chromium.chrome.browser.lifecycle.NativeInitObserver;
import org.chromium.chrome.browser.status_indicator.StatusIndicatorCoordinator;
import org.chromium.chrome.browser.ui.ImmersiveModeManager;
import org.chromium.chrome.browser.ui.RootUiCoordinator;
import org.chromium.chrome.browser.ui.tablet.emptybackground.EmptyBackgroundViewWrapper;
......@@ -23,6 +27,9 @@ public class TabbedRootUiCoordinator extends RootUiCoordinator implements Native
private TabbedSystemUiCoordinator mSystemUiCoordinator;
private @Nullable EmptyBackgroundViewWrapper mEmptyBackgroundViewWrapper;
private StatusIndicatorCoordinator mStatusIndicatorCoordinator;
private StatusIndicatorCoordinator.StatusIndicatorObserver mStatusIndicatorObserver;
public TabbedRootUiCoordinator(ChromeActivity activity) {
super(activity);
}
......@@ -33,6 +40,10 @@ public class TabbedRootUiCoordinator extends RootUiCoordinator implements Native
if (mSystemUiCoordinator != null) mSystemUiCoordinator.destroy();
if (mEmptyBackgroundViewWrapper != null) mEmptyBackgroundViewWrapper.destroy();
if (mStatusIndicatorCoordinator != null) {
mStatusIndicatorCoordinator.removeObserver(mStatusIndicatorObserver);
}
super.destroy();
}
......@@ -64,4 +75,39 @@ public class TabbedRootUiCoordinator extends RootUiCoordinator implements Native
mEmptyBackgroundViewWrapper.initialize();
}
}
// Protected class methods
@Override
protected void onLayoutManagerAvailable(LayoutManager layoutManager) {
super.onLayoutManagerAvailable(layoutManager);
initStatusIndicatorCoordinator(layoutManager);
}
// Private class methods
private void initStatusIndicatorCoordinator(LayoutManager layoutManager) {
if (!ChromeFeatureList.isEnabled(ChromeFeatureList.OFFLINE_INDICATOR_V2)) {
return;
}
mStatusIndicatorCoordinator = new StatusIndicatorCoordinator(
mActivity, mActivity.getCompositorViewHolder().getResourceManager());
layoutManager.setStatusIndicatorSceneOverlay(mStatusIndicatorCoordinator.getSceneLayer());
mStatusIndicatorObserver = (indicatorHeight -> {
mActivity.getToolbarManager().setControlContainerTopMargin(indicatorHeight);
layoutManager.getToolbarSceneLayer().setStaticYOffset(indicatorHeight);
final int resourceId = mActivity.getControlContainerHeightResource();
final int topControlsNewHeight =
mActivity.getResources().getDimensionPixelSize(resourceId) + indicatorHeight;
mActivity.getFullscreenManager().setTopControlsHeight(topControlsNewHeight);
});
mStatusIndicatorCoordinator.addObserver(mStatusIndicatorObserver);
}
@VisibleForTesting
public StatusIndicatorCoordinator getStatusIndicatorCoordinatorForTesting() {
return mStatusIndicatorCoordinator;
}
}
......@@ -17,6 +17,7 @@ import android.view.View;
import android.view.View.OnAttachStateChangeListener;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
......@@ -1616,6 +1617,17 @@ public class ToolbarManager implements ScrimObserver, ToolbarTabController, UrlF
if (toolbar != null) toolbar.setVisibility(visibility);
}
/**
* Sets the top margin for the control container.
* @param margin The margin in pixels.
*/
public void setControlContainerTopMargin(int margin) {
final ViewGroup.MarginLayoutParams layoutParams =
((ViewGroup.MarginLayoutParams) mControlContainer.getLayoutParams());
layoutParams.topMargin = margin;
mControlContainer.setLayoutParams(layoutParams);
}
/**
* Gets the Toolbar view.
*/
......
......@@ -11,7 +11,6 @@ import org.chromium.base.VisibleForTesting;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.MenuOrKeyboardActionController;
import org.chromium.chrome.browser.appmenu.AppMenuBlocker;
import org.chromium.chrome.browser.appmenu.AppMenuCoordinator;
......@@ -25,7 +24,6 @@ import org.chromium.chrome.browser.findinpage.FindToolbarManager;
import org.chromium.chrome.browser.findinpage.FindToolbarObserver;
import org.chromium.chrome.browser.lifecycle.Destroyable;
import org.chromium.chrome.browser.lifecycle.InflationObserver;
import org.chromium.chrome.browser.status_indicator.StatusIndicatorCoordinator;
import org.chromium.chrome.browser.vr.VrModeObserver;
import org.chromium.chrome.browser.vr.VrModuleProvider;
import org.chromium.ui.base.DeviceFormFactor;
......@@ -55,8 +53,6 @@ public class RootUiCoordinator
private OverviewModeBehavior mOverviewModeBehavior;
private OverviewModeBehavior.OverviewModeObserver mOverviewModeObserver;
private StatusIndicatorCoordinator mStatusIndicatorCoordinator;
private VrModeObserver mVrModeObserver;
/**
......@@ -71,7 +67,9 @@ public class RootUiCoordinator
mMenuOrKeyboardActionController = mActivity.getMenuOrKeyboardActionController();
mMenuOrKeyboardActionController.registerMenuOrKeyboardActionHandler(this);
initLayoutManagerSupplierObserver();
mLayoutManagerSupplierCallback = this::onLayoutManagerAvailable;
mActivity.getLayoutManagerSupplier().addObserver(mLayoutManagerSupplierCallback);
initOverviewModeSupplierObserver();
}
......@@ -80,6 +78,7 @@ public class RootUiCoordinator
mMenuOrKeyboardActionController.unregisterMenuOrKeyboardActionHandler(this);
mActivity.getLayoutManagerSupplier().removeObserver(mLayoutManagerSupplierCallback);
if (mOverlayPanelManager != null) {
mOverlayPanelManager.removeObserver(mOverlayPanelManagerObserver);
}
......@@ -175,41 +174,33 @@ public class RootUiCoordinator
return true;
}
// Private class methods
// Protected class methods
private void initLayoutManagerSupplierObserver() {
mLayoutManagerSupplierCallback = layoutManager -> {
if (mOverlayPanelManager != null) {
mOverlayPanelManager.removeObserver(mOverlayPanelManagerObserver);
}
mOverlayPanelManager = layoutManager.getOverlayPanelManager();
if (mOverlayPanelManagerObserver == null) {
mOverlayPanelManagerObserver =
new OverlayPanelManager.OverlayPanelManagerObserver() {
@Override
public void onOverlayPanelShown() {
if (mFindToolbarManager != null) {
mFindToolbarManager.hideToolbar(false);
}
}
@Override
public void onOverlayPanelHidden() {}
};
}
protected void onLayoutManagerAvailable(LayoutManager layoutManager) {
if (mOverlayPanelManager != null) {
mOverlayPanelManager.removeObserver(mOverlayPanelManagerObserver);
}
mOverlayPanelManager = layoutManager.getOverlayPanelManager();
if (mOverlayPanelManagerObserver == null) {
mOverlayPanelManagerObserver = new OverlayPanelManager.OverlayPanelManagerObserver() {
@Override
public void onOverlayPanelShown() {
if (mFindToolbarManager != null) {
mFindToolbarManager.hideToolbar(false);
}
}
mOverlayPanelManager.addObserver(mOverlayPanelManagerObserver);
@Override
public void onOverlayPanelHidden() {}
};
}
if (ChromeFeatureList.isEnabled(ChromeFeatureList.OFFLINE_INDICATOR_V2)) {
mStatusIndicatorCoordinator = new StatusIndicatorCoordinator();
layoutManager.setStatusIndicatorSceneOverlay(
mStatusIndicatorCoordinator.getSceneLayer());
}
};
mActivity.getLayoutManagerSupplier().addObserver(mLayoutManagerSupplierCallback);
mOverlayPanelManager.addObserver(mOverlayPanelManagerObserver);
}
// Private class methods
private void initOverviewModeSupplierObserver() {
if (mActivity.getOverviewModeBehaviorSupplier() != null) {
mOverviewModeBehaviorSupplierCallback = overviewModeBehavior -> {
......
file://chrome/android/java/src/org/chromium/chrome/browser/status_indicator/OWNERS
// Copyright 2019 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.chrome.browser.status_indicator;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.MediumTest;
import android.view.View;
import android.view.ViewGroup;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.tabbed_mode.TabbedRootUiCoordinator;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.chrome.test.util.browser.Features;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
/**
* Integration tests for status indicator covering related code in
* {@link StatusIndicatorCoordinator} and {@link TabbedRootUiCoordinator}.
*/
// clang-format off
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
@Features.EnableFeatures({ChromeFeatureList.OFFLINE_INDICATOR_V2})
public class StatusIndicatorTest {
// clang-format on
@Rule
public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
private StatusIndicatorCoordinator mStatusIndicatorCoordinator;
private StatusIndicatorSceneLayer mStatusIndicatorSceneLayer;
private View mStatusIndicatorContainer;
private ViewGroup.MarginLayoutParams mControlContainerLayoutParams;
@Before
public void setUp() throws InterruptedException {
mActivityTestRule.startMainActivityOnBlankPage();
mStatusIndicatorCoordinator = ((TabbedRootUiCoordinator) mActivityTestRule.getActivity()
.getRootUiCoordinatorForTesting())
.getStatusIndicatorCoordinatorForTesting();
mStatusIndicatorSceneLayer = mStatusIndicatorCoordinator.getSceneLayer();
mStatusIndicatorContainer =
mActivityTestRule.getActivity().findViewById(R.id.status_indicator);
final View controlContainer =
mActivityTestRule.getActivity().findViewById(R.id.control_container);
mControlContainerLayoutParams =
(ViewGroup.MarginLayoutParams) controlContainer.getLayoutParams();
}
@Test
@MediumTest
public void testShowAndHide() {
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
assertThat("Wrong initial Android view visibility.",
mStatusIndicatorContainer.getVisibility(), equalTo(View.GONE));
Assert.assertFalse("Wrong initial composited view visibility.",
mStatusIndicatorSceneLayer.isSceneOverlayTreeShowing());
assertThat("Wrong initial control container top margin.",
mControlContainerLayoutParams.topMargin, equalTo(0));
TestThreadUtils.runOnUiThreadBlocking(mStatusIndicatorCoordinator::show);
assertThat("Android view is not visible.", mStatusIndicatorContainer.getVisibility(),
equalTo(View.VISIBLE));
Assert.assertTrue("Composited view is not visible.",
mStatusIndicatorSceneLayer.isSceneOverlayTreeShowing());
assertThat("Wrong control container top margin.", mControlContainerLayoutParams.topMargin,
equalTo(mStatusIndicatorContainer.getHeight()));
TestThreadUtils.runOnUiThreadBlocking(mStatusIndicatorCoordinator::hide);
assertThat("Android view is not gone.", mStatusIndicatorContainer.getVisibility(),
equalTo(View.GONE));
Assert.assertFalse("Composited view is visible.",
mStatusIndicatorSceneLayer.isSceneOverlayTreeShowing());
assertThat("Wrong control container top margin.", mControlContainerLayoutParams.topMargin,
equalTo(0));
}
}
// Copyright 2019 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.chrome.browser.status_indicator;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import android.graphics.drawable.Drawable;
import android.support.test.annotation.UiThreadTest;
import android.support.test.filters.SmallTest;
import android.support.v4.content.res.ResourcesCompat;
import android.text.TextUtils;
import android.view.View;
import android.widget.TextView;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.widget.ViewResourceFrameLayout;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ui.DummyUiActivity;
import org.chromium.chrome.test.ui.DummyUiActivityTestCase;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
/**
* Tests for {@link StatusIndicatorViewBinder}.
*/
@RunWith(ChromeJUnit4ClassRunner.class)
public class StatusIndicatorViewBinderTest extends DummyUiActivityTestCase {
private static final String STATUS_TEXT = "Offline";
private ViewResourceFrameLayout mContainer;
private TextView mStatusTextView;
private MockStatusIndicatorSceneLayer mSceneLayer;
private PropertyModel mModel;
private PropertyModelChangeProcessor mMCP;
@BeforeClass
public static void setUpBeforeActivityLaunched() {
DummyUiActivity.setTestLayout(R.layout.status_indicator_container);
}
@Override
public void setUpTest() throws Exception {
super.setUpTest();
TestThreadUtils.runOnUiThreadBlocking(() -> {
mContainer = getActivity().findViewById(R.id.status_indicator);
mStatusTextView = mContainer.findViewById(R.id.status_text);
});
mSceneLayer = new MockStatusIndicatorSceneLayer(mContainer);
mModel = new PropertyModel.Builder(StatusIndicatorProperties.ALL_KEYS)
.with(StatusIndicatorProperties.STATUS_TEXT, "")
.with(StatusIndicatorProperties.STATUS_ICON, null)
.with(StatusIndicatorProperties.ANDROID_VIEW_VISIBLE, false)
.with(StatusIndicatorProperties.COMPOSITED_VIEW_VISIBLE, false)
.build();
mMCP = PropertyModelChangeProcessor.create(mModel,
new StatusIndicatorViewBinder.ViewHolder(mContainer, mSceneLayer),
StatusIndicatorViewBinder::bind);
}
@Override
public void tearDownTest() throws Exception {
mMCP.destroy();
super.tearDownTest();
}
@Test
@SmallTest
@UiThreadTest
public void testTextView() {
Assert.assertTrue(
"Wrong initial status text.", TextUtils.isEmpty(mStatusTextView.getText()));
Assert.assertNull(
"Wrong initial status icon.", mStatusTextView.getCompoundDrawablesRelative()[0]);
Assert.assertTrue(
"Rest of the compound drawables are not null.", areRestOfCompoundDrawablesNull());
Drawable drawable = ResourcesCompat.getDrawable(getActivity().getResources(),
R.drawable.ic_error_white_24dp_filled, getActivity().getTheme());
TestThreadUtils.runOnUiThreadBlocking(() -> {
mModel.set(StatusIndicatorProperties.STATUS_TEXT, STATUS_TEXT);
mModel.set(StatusIndicatorProperties.STATUS_ICON, drawable);
});
assertThat("Wrong status text.", mStatusTextView.getText(), equalTo(STATUS_TEXT));
assertThat("Wrong status icon.", mStatusTextView.getCompoundDrawablesRelative()[0],
equalTo(drawable));
Assert.assertTrue(
"Rest of the compound drawables are not null.", areRestOfCompoundDrawablesNull());
}
@Test
@SmallTest
@UiThreadTest
public void testVisibility() {
assertThat("Wrong initial Android view visibility.", mContainer.getVisibility(),
equalTo(View.GONE));
Assert.assertFalse("Wrong initial composited view visibility.",
mSceneLayer.isSceneOverlayTreeShowing());
TestThreadUtils.runOnUiThreadBlocking(() -> {
mModel.set(StatusIndicatorProperties.ANDROID_VIEW_VISIBLE, true);
mModel.set(StatusIndicatorProperties.COMPOSITED_VIEW_VISIBLE, true);
});
assertThat(
"Android view is not visible.", mContainer.getVisibility(), equalTo(View.VISIBLE));
Assert.assertTrue(
"Composited view is not visible.", mSceneLayer.isSceneOverlayTreeShowing());
TestThreadUtils.runOnUiThreadBlocking(() -> {
mModel.set(StatusIndicatorProperties.ANDROID_VIEW_VISIBLE, false);
mModel.set(StatusIndicatorProperties.COMPOSITED_VIEW_VISIBLE, false);
});
assertThat("Android view is not gone.", mContainer.getVisibility(), equalTo(View.GONE));
Assert.assertFalse("Composited view is visible.", mSceneLayer.isSceneOverlayTreeShowing());
}
private boolean areRestOfCompoundDrawablesNull() {
final Drawable[] drawables = mStatusTextView.getCompoundDrawablesRelative();
for (int i = 1; i < drawables.length; i++) {
if (drawables[i] != null) {
return false;
}
}
return true;
}
/** Mock {@link StatusIndicatorSceneLayer} class to avoid native initialization. */
private class MockStatusIndicatorSceneLayer extends StatusIndicatorSceneLayer {
MockStatusIndicatorSceneLayer(ViewResourceFrameLayout statusIndicator) {
super(statusIndicator);
}
@Override
protected void initializeNative() {}
@Override
public void destroy() {}
}
}
......@@ -4,7 +4,7 @@
#include "chrome/browser/android/compositor/scene_layer/status_indicator_scene_layer.h"
#include "cc/layers/solid_color_layer.h"
#include "cc/layers/ui_resource_layer.h"
#include "chrome/android/chrome_jni_headers/StatusIndicatorSceneLayer_jni.h"
using base::android::JavaParamRef;
......@@ -17,14 +17,13 @@ StatusIndicatorSceneLayer::StatusIndicatorSceneLayer(
const JavaRef<jobject>& jobj)
: SceneLayer(env, jobj),
view_container_(cc::Layer::Create()),
view_layer_(cc::SolidColorLayer::Create()) {
view_layer_(cc::UIResourceLayer::Create()) {
layer()->SetIsDrawable(true);
view_container_->SetIsDrawable(true);
view_container_->SetMasksToBounds(true);
view_layer_->SetIsDrawable(true);
view_layer_->SetBackgroundColor(SK_ColorRED);
view_container_->AddChild(view_layer_);
}
......@@ -35,12 +34,22 @@ void StatusIndicatorSceneLayer::UpdateStatusIndicatorLayer(
const base::android::JavaParamRef<jobject>& object,
const base::android::JavaParamRef<jobject>& jresource_manager,
jint view_resource_id) {
// This is temporary as the size should come from the resource.
view_container_->SetBounds(gfx::Size(1440, 70));
ui::ResourceManager* resource_manager =
ui::ResourceManagerImpl::FromJavaObject(jresource_manager);
ui::Resource* resource = resource_manager->GetResource(
ui::ANDROID_RESOURCE_TYPE_DYNAMIC, view_resource_id);
// If the resource isn't available, don't bother doing anything else.
if (!resource)
return;
view_layer_->SetUIResourceId(resource->ui_resource()->id());
view_container_->SetBounds(resource->size());
view_container_->SetPosition(gfx::PointF(0, 0));
// The view's layer should be the same size as the texture.
view_layer_->SetBounds(gfx::Size(gfx::Size(1440, 70)));
view_layer_->SetBounds(resource->size());
view_layer_->SetPosition(gfx::PointF(0, 0));
}
......@@ -57,6 +66,18 @@ void StatusIndicatorSceneLayer::SetContentTree(
layer_->AddChild(content_tree->layer());
layer_->AddChild(view_container_);
}
// Propagate the background color up from the content layer.
should_show_background_ = content_tree->ShouldShowBackground();
background_color_ = content_tree->GetBackgroundColor();
}
SkColor StatusIndicatorSceneLayer::GetBackgroundColor() {
return background_color_;
}
bool StatusIndicatorSceneLayer::ShouldShowBackground() {
return should_show_background_;
}
static jlong JNI_StatusIndicatorSceneLayer_Init(
......
......@@ -14,7 +14,7 @@
namespace cc {
class Layer;
class SolidColorLayer;
class UIResourceLayer;
} // namespace cc
namespace android {
......@@ -37,9 +37,14 @@ class StatusIndicatorSceneLayer : public SceneLayer {
const base::android::JavaParamRef<jobject>& jobj,
const base::android::JavaParamRef<jobject>& jcontent_tree);
SkColor GetBackgroundColor() override;
bool ShouldShowBackground() override;
private:
bool should_show_background_;
SkColor background_color_;
scoped_refptr<cc::Layer> view_container_;
scoped_refptr<cc::SolidColorLayer> view_layer_;
scoped_refptr<cc::UIResourceLayer> view_layer_;
DISALLOW_COPY_AND_ASSIGN(StatusIndicatorSceneLayer);
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment