Fullscreen image preview (Part 1)
Posting to unblock live wallpaper development
Bug: 151285903
Test: https://screenshot.googleplex.com/iOp6705ZVUU (Filed b/155042852 for extra white space in the bottom sheet)
Change-Id: I8632cb1ea0585474af09a6e316a8b08a7c7e2938
diff --git a/res/layout/fragment_image_preview_v2.xml b/res/layout/fragment_image_preview_v2.xml
new file mode 100644
index 0000000..041db44
--- /dev/null
+++ b/res/layout/fragment_image_preview_v2.xml
@@ -0,0 +1,161 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="?android:colorPrimary">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginBottom="@dimen/bottom_navbar_height"
+ android:orientation="vertical">
+
+ <include layout="@layout/section_header" />
+
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:orientation="vertical">
+
+ <FrameLayout
+ android:id="@+id/wallpaper_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <SurfaceView
+ android:id="@+id/wallpaper_surface"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <ImageView
+ android:id="@+id/low_res_image"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scaleType="centerCrop"
+ android:background="@android:color/black" />
+
+ <com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
+ android:id="@+id/full_res_image"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <androidx.core.widget.ContentLoadingProgressBar
+ android:id="@+id/loading_indicator"
+ style="@android:style/Widget.DeviceDefault.ProgressBar"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:indeterminate="true"/>
+ </FrameLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <com.android.wallpaper.picker.TouchForwardingLayout
+ android:id="@+id/touch_forwarding_layout"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1">
+
+ <FrameLayout
+ android:id="@+id/workspace_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/fullscreen_preview_background">
+
+ <androidx.cardview.widget.CardView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <SurfaceView
+ android:id="@+id/workspace_surface"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+ </androidx.cardview.widget.CardView>
+ </FrameLayout>
+ </com.android.wallpaper.picker.TouchForwardingLayout>
+
+ <LinearLayout
+ android:id="@+id/tabs_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@color/fullscreen_preview_background"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/lock"
+ android:layout_width="wrap_content"
+ android:layout_height="64dp"
+ android:layout_weight="1"
+ android:paddingTop="20dp"
+ android:paddingBottom="20dp"
+ android:gravity="center"
+ android:text="@string/lock_screen_message" />
+
+ <TextView
+ android:id="@+id/home"
+ android:layout_width="wrap_content"
+ android:layout_height="64dp"
+ android:layout_weight="1"
+ android:paddingTop="20dp"
+ android:paddingBottom="20dp"
+ android:gravity="center"
+ android:text="@string/home_screen_message" />
+ </LinearLayout>
+ </LinearLayout>
+ </FrameLayout>
+
+
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone">
+
+ <androidx.coordinatorlayout.widget.CoordinatorLayout
+ android:id="@+id/coordinator_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom">
+
+ <FrameLayout
+ android:id="@+id/bottom_sheet"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@drawable/preview_bottom_sheet_background"
+ android:theme="@style/WallpaperPicker.BottomPaneStyle"
+ app:behavior_peekHeight="@dimen/preview_attribution_pane_collapsed_height"
+ app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
+ <include
+ layout="@layout/preview_page_info"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+ </FrameLayout>
+
+ </androidx.coordinatorlayout.widget.CoordinatorLayout>
+
+ </FrameLayout>
+ </LinearLayout>
+
+ <include layout="@layout/bottom_action_bar" />
+
+</FrameLayout>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index c8599c9..f44bdaa 100755
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -36,6 +36,7 @@
<color name="preview_pager_arrow_disabled">@android:color/darker_gray</color>
<color name="preview_pager_background">@color/secondary_color</color>
+ <color name="fullscreen_preview_background">@color/secondary_color</color>
<color name="google_grey900">#202124</color>
<color name="google_grey700">#5f6368</color>
diff --git a/src/com/android/wallpaper/picker/ImagePreviewFragment.java b/src/com/android/wallpaper/picker/ImagePreviewFragment.java
index 4620954..83af285 100755
--- a/src/com/android/wallpaper/picker/ImagePreviewFragment.java
+++ b/src/com/android/wallpaper/picker/ImagePreviewFragment.java
@@ -15,6 +15,13 @@
*/
package com.android.wallpaper.picker;
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.makeMeasureSpec;
+
+import static com.android.wallpaper.widget.BottomActionBar.BottomAction.APPLY;
+import static com.android.wallpaper.widget.BottomActionBar.BottomAction.EDIT;
+import static com.android.wallpaper.widget.BottomActionBar.BottomAction.INFORMATION;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.app.Activity;
@@ -26,13 +33,25 @@
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Bundle;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.DisplayMetrics;
import android.view.Display;
import android.view.LayoutInflater;
+import android.view.Surface;
+import android.view.SurfaceControlViewHost;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.FrameLayout;
import android.widget.ImageView;
+import android.widget.TextView;
+import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
+import androidx.appcompat.widget.Toolbar;
+import androidx.cardview.widget.CardView;
import androidx.fragment.app.FragmentActivity;
import com.android.wallpaper.R;
@@ -40,8 +59,12 @@
import com.android.wallpaper.model.WallpaperInfo;
import com.android.wallpaper.module.WallpaperPersister.Destination;
import com.android.wallpaper.module.WallpaperPersister.SetWallpaperCallback;
+import com.android.wallpaper.util.PreviewUtils;
import com.android.wallpaper.util.ScreenSizeCalculator;
+import com.android.wallpaper.util.SurfaceViewUtils;
+import com.android.wallpaper.util.TileSizeCalculator;
import com.android.wallpaper.util.WallpaperCropUtils;
+import com.android.wallpaper.widget.BottomActionBar;
import com.bumptech.glide.Glide;
import com.bumptech.glide.MemoryCategory;
@@ -49,6 +72,9 @@
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Fragment which displays the UI for previewing an individual static wallpaper and its attribution
* information.
@@ -57,12 +83,28 @@
private static final float DEFAULT_WALLPAPER_MAX_ZOOM = 8f;
+ private static final int MODE_DEFAULT = 0;
+ private static final int MODE_SHOW_TABS = 1;
+
+ @IntDef({MODE_DEFAULT, MODE_SHOW_TABS})
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface Mode {}
+
+ private @Mode int mMode = MODE_DEFAULT;
+
private SubsamplingScaleImageView mFullResImageView;
private Asset mWallpaperAsset;
private Point mDefaultCropSurfaceSize;
private Point mScreenSize;
private Point mRawWallpaperSize; // Native size of wallpaper image.
private ImageView mLowResImageView;
+ private TouchForwardingLayout mTouchForwardingLayout;
+ private FrameLayout mWorkspaceContainer;
+ private SurfaceView mWorkspaceSurface;
+ private SurfaceView mWallpaperSurface;
+ private View mTabs;
+ private PreviewUtils mPreviewUtils;
+ private BottomActionBar mBottomActionBar;
private InfoPageController mInfoPageController;
@Override
@@ -73,7 +115,7 @@
@Override
protected int getLayoutResId() {
- return R.layout.fragment_image_preview;
+ return USE_NEW_UI ? R.layout.fragment_image_preview_v2 : R.layout.fragment_image_preview;
}
@@ -92,21 +134,46 @@
View view = super.onCreateView(inflater, container, savedInstanceState);
Activity activity = requireActivity();
+ mScreenSize = ScreenSizeCalculator.getInstance().getScreenSize(
+ activity.getWindowManager().getDefaultDisplay());
- mFullResImageView = view.findViewById(R.id.full_res_image);
+ mLowResImageView = view.findViewById(R.id.low_res_image);
+ if (USE_NEW_UI) {
+ // TODO: Switch to use AppbarFragment functionality to setTitle when switching
+ // PreviewFragment to extend AppbarFragment
+ Toolbar toolbar = view.findViewById(R.id.toolbar);
+ TextView toolbarTitleView = toolbar.findViewById(R.id.custom_toolbar_title);
+ toolbarTitleView.setText((R.string.preview));
+ // TODO: Consider moving some part of this to the base class when live preview is ready.
+ mTouchForwardingLayout = view.findViewById(R.id.touch_forwarding_layout);
+ mWorkspaceContainer = mTouchForwardingLayout.findViewById(R.id.workspace_container);
+ mWorkspaceSurface = mWorkspaceContainer.findViewById(R.id.workspace_surface);
+ mWallpaperSurface = view.findViewById(R.id.wallpaper_surface);
+ mTabs = view.findViewById(R.id.tabs_container);
+ mPreviewUtils = new PreviewUtils(getContext(),
+ getString(R.string.grid_control_metadata_name));
+ mBottomActionBar = view.findViewById(R.id.bottom_actionbar);
+ mBottomActionBar.showActionsOnly(INFORMATION, EDIT, APPLY);
+ mBottomActionBar.bindBackButtonToSystemBackKey(getActivity());
+ mBottomActionBar.show();
+ view.measure(makeMeasureSpec(mScreenSize.x, EXACTLY),
+ makeMeasureSpec(mScreenSize.y, EXACTLY));
+
+ renderImageWallpaper();
+ setupPreview();
+ renderWorkspaceSurface();
+ } else {
+ mFullResImageView = view.findViewById(R.id.full_res_image);
+ }
mInfoPageController = new InfoPageController(view.findViewById(R.id.page_info),
mPreviewMode);
- mLowResImageView = view.findViewById(R.id.low_res_image);
-
// Trim some memory from Glide to make room for the full-size image in this fragment.
Glide.get(activity).setMemoryCategory(MemoryCategory.LOW);
mDefaultCropSurfaceSize = WallpaperCropUtils.getDefaultCropSurfaceSize(
getResources(), activity.getWindowManager().getDefaultDisplay());
- mScreenSize = ScreenSizeCalculator.getInstance().getScreenSize(
- activity.getWindowManager().getDefaultDisplay());
// Load a low-res placeholder image if there's a thumbnail available from the asset that can
// be shown to the user more quickly than the full-sized image.
@@ -226,7 +293,15 @@
}
getActivity().invalidateOptionsMenu();
- populateInfoPage(mInfoPageController);
+ if (USE_NEW_UI) {
+ if (mWallpaper != null) {
+ mBottomActionBar.populateInfoPage(
+ mWallpaper.getAttributions(getContext()),
+ shouldShowMetadataInPreview(mWallpaper));
+ }
+ } else {
+ populateInfoPage(mInfoPageController);
+ }
});
}
@@ -280,7 +355,9 @@
float defaultWallpaperZoom =
WallpaperCropUtils.calculateMinZoom(mRawWallpaperSize, mDefaultCropSurfaceSize);
float minWallpaperZoom =
- WallpaperCropUtils.calculateMinZoom(mRawWallpaperSize, mScreenSize);
+ WallpaperCropUtils.calculateMinZoom(mRawWallpaperSize,
+ USE_NEW_UI ? new Point(mTouchForwardingLayout.getWidth(),
+ mTouchForwardingLayout.getHeight()) : mScreenSize);
// Set min wallpaper zoom and max zoom on MosaicView widget.
mFullResImageView.setMaxScale(Math.max(DEFAULT_WALLPAPER_MAX_ZOOM, defaultWallpaperZoom));
@@ -321,4 +398,120 @@
}
});
}
+
+ private void renderWorkspaceSurface() {
+ mWorkspaceSurface.setZOrderMediaOverlay(true);
+ mWorkspaceSurface.getHolder().addCallback(mWorkspaceSurfaceCallback);
+ }
+
+ private void renderImageWallpaper() {
+ mWallpaperSurface.getHolder().addCallback(mWallpaperSurfaceCallback);
+ }
+
+ // TODO(tracyzhou): Refactor this into a utility class.
+ private final SurfaceHolder.Callback mWorkspaceSurfaceCallback = new SurfaceHolder.Callback() {
+
+ private Surface mLastSurface;
+ private Message mCallback;
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ if (mPreviewUtils.supportsPreview() && mLastSurface != holder.getSurface()) {
+ mLastSurface = holder.getSurface();
+ Bundle result = mPreviewUtils.renderPreview(
+ SurfaceViewUtils.createSurfaceViewRequest(mWorkspaceSurface));
+ if (result != null) {
+ mWorkspaceSurface.setChildSurfacePackage(
+ SurfaceViewUtils.getSurfacePackage(result));
+ mCallback = SurfaceViewUtils.getCallback(result);
+ }
+ }
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ if (mCallback != null) {
+ try {
+ mCallback.replyTo.send(mCallback);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ } finally {
+ mCallback = null;
+ }
+ }
+ }
+ };
+
+ // TODO(tracyzhou): Refactor this into a utility class.
+ private final SurfaceHolder.Callback mWallpaperSurfaceCallback = new SurfaceHolder.Callback() {
+
+ private Surface mLastSurface;
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ if (mLastSurface != holder.getSurface()) {
+ mLastSurface = holder.getSurface();
+ mFullResImageView = new SubsamplingScaleImageView(getContext());
+ mFullResImageView.measure(makeMeasureSpec(mWallpaperSurface.getWidth(), EXACTLY),
+ makeMeasureSpec(mWallpaperSurface.getHeight(), EXACTLY));
+ mFullResImageView.layout(0, 0, mWallpaperSurface.getWidth(),
+ mWallpaperSurface.getHeight());
+ mTouchForwardingLayout.setView(mFullResImageView);
+
+ SurfaceControlViewHost host = new SurfaceControlViewHost(getContext(),
+ getContext().getDisplay(), mWallpaperSurface.getHostToken());
+ host.setView(mFullResImageView, mFullResImageView.getWidth(),
+ mFullResImageView.getHeight());
+ mWallpaperSurface.setChildSurfacePackage(host.getSurfacePackage());
+ }
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) { }
+ };
+
+ // TODO(chriscsli): Investigate the possibility of moving this to the base class.
+ private void setupPreview() {
+ if (USE_NEW_UI) {
+ mTabs.setVisibility(mMode == MODE_SHOW_TABS ? View.VISIBLE : View.GONE);
+
+ final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
+ int containerWidth = mWorkspaceContainer.getMeasuredWidth();
+ int containerHeight = mWorkspaceContainer.getMeasuredHeight();
+
+ int topPadding = dpToPx(24, displayMetrics.density);
+ int bottomPadding = dpToPx(getBottomPaddingInDp(), displayMetrics.density);
+ double horizontalPadding = containerWidth
+ - (containerHeight - topPadding - bottomPadding) * 1.0
+ * displayMetrics.widthPixels / displayMetrics.heightPixels;
+ int leftPadding = (int) horizontalPadding / 2;
+ int rightPadding = (int) horizontalPadding - leftPadding;
+ mWorkspaceContainer.setPaddingRelative(leftPadding, topPadding, rightPadding,
+ bottomPadding);
+ ((CardView) mWorkspaceSurface.getParent())
+ .setRadius(TileSizeCalculator.getPreviewCornerRadius(
+ getActivity(),
+ (int) (mWorkspaceContainer.getMeasuredWidth() - horizontalPadding)));
+ }
+ }
+
+ private int getBottomPaddingInDp() {
+ switch (mMode) {
+ case MODE_SHOW_TABS:
+ return 0;
+ case MODE_DEFAULT:
+ default:
+ return 32;
+ }
+ }
+
+ private static int dpToPx(int dp, float density) {
+ return (int) (dp * density + 0.5f);
+ }
}
diff --git a/src/com/android/wallpaper/picker/PreviewFragment.java b/src/com/android/wallpaper/picker/PreviewFragment.java
index 7c29e9d..ddbcc50 100755
--- a/src/com/android/wallpaper/picker/PreviewFragment.java
+++ b/src/com/android/wallpaper/picker/PreviewFragment.java
@@ -79,6 +79,8 @@
SetWallpaperDialogFragment.Listener, SetWallpaperErrorDialogFragment.Listener,
LoadWallpaperErrorDialogFragment.Listener {
+ protected static final boolean USE_NEW_UI = true;
+
/**
* User can view wallpaper and attributions in full screen, but "Set wallpaper" button is
* hidden.
@@ -210,13 +212,15 @@
toolbar.getNavigationIcon().setTint(getAttrColor(activity, android.R.attr.colorPrimary));
toolbar.getNavigationIcon().setAutoMirrored(true);
- ViewCompat.setPaddingRelative(toolbar,
- /* start */ getResources().getDimensionPixelSize(
- R.dimen.preview_toolbar_up_button_start_padding),
- /* top */ 0,
- /* end */ getResources().getDimensionPixelSize(
- R.dimen.preview_toolbar_set_wallpaper_button_end_padding),
- /* bottom */ 0);
+ if (!USE_NEW_UI) {
+ ViewCompat.setPaddingRelative(toolbar,
+ /* start */ getResources().getDimensionPixelSize(
+ R.dimen.preview_toolbar_up_button_start_padding),
+ /* top */ 0,
+ /* end */ getResources().getDimensionPixelSize(
+ R.dimen.preview_toolbar_set_wallpaper_button_end_padding),
+ /* bottom */ 0);
+ }
mLoadingProgressBar = view.findViewById(getLoadingIndicatorResId());
mLoadingProgressBar.show();
@@ -239,18 +243,21 @@
: savedInstanceState.getInt(KEY_BOTTOM_SHEET_STATE, STATE_EXPANDED);
setUpBottomSheetListeners();
- view.setOnApplyWindowInsetsListener((v, windowInsets) -> {
- toolbar.setPadding(toolbar.getPaddingLeft(),
- toolbar.getPaddingTop() + windowInsets.getSystemWindowInsetTop(),
- toolbar.getPaddingRight(), toolbar.getPaddingBottom());
- mBottomSheet.setPadding(mBottomSheet.getPaddingLeft(),
- mBottomSheet.getPaddingTop(), mBottomSheet.getPaddingRight(),
- mBottomSheet.getPaddingBottom() + windowInsets.getSystemWindowInsetBottom());
- WindowInsets.Builder builder = new WindowInsets.Builder(windowInsets);
- builder.setSystemWindowInsets(Insets.of(windowInsets.getSystemWindowInsetLeft(),
- 0, windowInsets.getStableInsetRight(), 0));
- return builder.build();
- });
+ if (!USE_NEW_UI) {
+ view.setOnApplyWindowInsetsListener((v, windowInsets) -> {
+ toolbar.setPadding(toolbar.getPaddingLeft(),
+ toolbar.getPaddingTop() + windowInsets.getSystemWindowInsetTop(),
+ toolbar.getPaddingRight(), toolbar.getPaddingBottom());
+ mBottomSheet.setPadding(mBottomSheet.getPaddingLeft(),
+ mBottomSheet.getPaddingTop(), mBottomSheet.getPaddingRight(),
+ mBottomSheet.getPaddingBottom()
+ + windowInsets.getSystemWindowInsetBottom());
+ WindowInsets.Builder builder = new WindowInsets.Builder(windowInsets);
+ builder.setSystemWindowInsets(Insets.of(windowInsets.getSystemWindowInsetLeft(),
+ 0, windowInsets.getStableInsetRight(), 0));
+ return builder.build();
+ });
+ }
return view;
}
@@ -580,6 +587,11 @@
== View.LAYOUT_DIRECTION_RTL;
}
+ protected static boolean shouldShowMetadataInPreview(WallpaperInfo wallpaperInfo) {
+ android.app.WallpaperInfo wallpaperComponent = wallpaperInfo.getWallpaperComponent();
+ return wallpaperComponent == null || wallpaperComponent.getShowMetadataInPreview();
+ }
+
protected static class InfoPageController {
public static View createView(LayoutInflater inflater) {
diff --git a/src/com/android/wallpaper/picker/TouchForwardingLayout.java b/src/com/android/wallpaper/picker/TouchForwardingLayout.java
new file mode 100644
index 0000000..3b44755
--- /dev/null
+++ b/src/com/android/wallpaper/picker/TouchForwardingLayout.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wallpaper.picker;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.FrameLayout;
+
+/** A frame layout that listens to touch events and routes them to another view. */
+public class TouchForwardingLayout extends FrameLayout {
+
+ private View mView;
+
+ public TouchForwardingLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ if (mView != null) {
+ mView.dispatchTouchEvent(ev);
+ }
+ return true;
+ }
+
+ /** Set the view that the touch events are routed to */
+ public void setView(View view) {
+ mView = view;
+ }
+}