Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit df374f62 authored by Steve McKay's avatar Steve McKay Committed by android-build-merger
Browse files

Add gesture scaling support for grid mode.

am: b03a59c4

Change-Id: I964fd13563c23d4456b630e398b6b743f8abaa0b
parents ac550818 b03a59c4
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -24,6 +24,8 @@
    <dimen name="list_item_thumbnail_size">40dp</dimen>
    <dimen name="grid_item_icon_size">30dp</dimen>
    <dimen name="progress_bar_height">4dp</dimen>
    <fraction name="grid_scale_min">85%</fraction>
    <fraction name="grid_scale_max">200%</fraction>
    <dimen name="grid_width">152dp</dimen>
    <dimen name="grid_height">176dp</dimen>
    <dimen name="grid_item_width">152dp</dimen>
+1 −1
Original line number Diff line number Diff line
@@ -733,7 +733,7 @@ public abstract class BaseActivity<T extends ActionHandler>
        });
    }

    public final class RetainedState {
    public static final class RetainedState {
        public @Nullable Selection selection;

        public boolean hasSelection() {
+45 −0
Original line number Diff line number Diff line
@@ -13,36 +13,33 @@
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.documentsui.queries;

import android.text.TextUtils;
import android.util.Log;

import com.android.documentsui.base.EventHandler;

public class SetQuickViewerCommand implements EventHandler<String[]> {

    // This is a quick/easy shortcut to sharing quick viewer debug settings
    // with QuickViewIntent builder. Tried setting at a system property
    // but got a native error. This being quick and easy, didn't investigate that err.
    public static String sQuickViewer;
    private static final String TAG = "SetQuickViewerCommand";

    @Override
    public boolean accept(String[] tokens) {
        if ("setqv".equals(tokens[0])) {
            if (tokens.length == 2 && !TextUtils.isEmpty(tokens[1])) {
                sQuickViewer = tokens[1];
                Log.i(TAG, "Set quick viewer to: " + sQuickViewer);
                return true;
            } else {
                Log.w(TAG, "Invalid command structure: " + tokens);
package com.android.documentsui.base;

import javax.annotation.Nullable;

/**
 * Shared values that may be set by {@link DebugCommandProcessor}.
 */
public final class DebugFlags {

    private DebugFlags() {}

    private static String mQvPackage;
    private static boolean sGestureScaleEnabled;

    public static void setQuickViewer(@Nullable String qvPackage) {
        mQvPackage = qvPackage;
    }
        } else if ("unsetqv".equals(tokens[0])) {
            Log.i(TAG, "Unset quick viewer");
            sQuickViewer = null;
            return true;

    public static @Nullable String getQuickViewer() {
        return mQvPackage;
    }
        return false;

    public static void setGestureScaleEnabled(boolean enabled) {
        sGestureScaleEnabled = enabled;
    }

    public static boolean getGestureScaleEnabled() {
        return sGestureScaleEnabled;
    }
}
+61 −9
Original line number Diff line number Diff line
@@ -22,6 +22,8 @@ import static com.android.documentsui.base.Shared.DEBUG;
import static com.android.documentsui.base.State.MODE_GRID;
import static com.android.documentsui.base.State.MODE_LIST;

import android.annotation.DimenRes;
import android.annotation.FractionRes;
import android.annotation.IntDef;
import android.annotation.StringRes;
import android.app.Activity;
@@ -36,6 +38,7 @@ import android.content.Intent;
import android.content.Loader;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcelable;
@@ -66,8 +69,8 @@ import com.android.documentsui.ActivityConfig;
import com.android.documentsui.BaseActivity;
import com.android.documentsui.BaseActivity.RetainedState;
import com.android.documentsui.DirectoryLoader;
import com.android.documentsui.DirectoryResult;
import com.android.documentsui.DirectoryReloadLock;
import com.android.documentsui.DirectoryResult;
import com.android.documentsui.DocumentsApplication;
import com.android.documentsui.FocusManager;
import com.android.documentsui.ItemDragListener;
@@ -183,6 +186,9 @@ public class DirectoryFragment extends Fragment
    private GridLayoutManager mLayout;
    private int mColumnCount = 1;  // This will get updated when layout changes.

    private float mLiveScale = 1.0f;
    private int mMode;

    private MessageBar mMessageBar;
    private View mProgressBar;

@@ -204,7 +210,7 @@ public class DirectoryFragment extends Fragment
    public View onCreateView(
            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        BaseActivity activity = (BaseActivity<?>) getActivity();
        BaseActivity<?> activity = (BaseActivity<?>) getActivity();
        final View view = inflater.inflate(R.layout.fragment_directory, container, false);

        mMessageBar = MessageBar.create(getChildFragmentManager());
@@ -342,6 +348,7 @@ public class DirectoryFragment extends Fragment
        EventHandler<InputEvent> gestureHandler = mState.allowMultiple
                ? gestureSel::start
                : EventHandler.createStub(false);

        mInputHandler = new UserInputHandler<>(
                mActions,
                mFocusManager,
@@ -359,7 +366,8 @@ public class DirectoryFragment extends Fragment
                mDragStartListener::onMouseDragEvent,
                gestureSel,
                mInputHandler,
                mBandController);
                mBandController,
                this::scaleLayout);

        mMenuManager = mActivity.getMenuManager();

@@ -497,6 +505,7 @@ public class DirectoryFragment extends Fragment
     * @param mode The new view mode.
     */
    private void updateLayout(@ViewMode int mode) {
        mMode = mode;
        mColumnCount = calculateColumnCount(mode);
        if (mLayout != null) {
            mLayout.setSpanCount(mColumnCount);
@@ -511,22 +520,65 @@ public class DirectoryFragment extends Fragment
        mIconHelper.setViewMode(mode);
    }

    /**
     * Updates the layout after the view mode switches.
     * @param mode The new view mode.
     */
    private void scaleLayout(float scale) {
        assert(Build.IS_DEBUGGABLE);
        if (DEBUG) Log.v(TAG, "Handling scale event: " + scale + ", existing scale: " + mLiveScale);

        if (mMode == MODE_GRID) {
            float minScale = getFraction(R.fraction.grid_scale_min);
            float maxScale = getFraction(R.fraction.grid_scale_max);
            float nextScale = mLiveScale * scale;

            if (DEBUG) Log.v(TAG,
                    "Next scale " + nextScale + ", Min/max scale " + minScale + "/" + maxScale);

            if (nextScale > minScale && nextScale < maxScale) {
                if (DEBUG) Log.d(TAG, "Updating grid scale: " + scale);
                mLiveScale = nextScale;
                updateLayout(mMode);
            }

        } else {
            if (DEBUG) Log.d(TAG, "List mode, ignoring scale: " + scale);
            mLiveScale = 1.0f;
        }
    }

    private int calculateColumnCount(@ViewMode int mode) {
        if (mode == MODE_LIST) {
            // List mode is a "grid" with 1 column.
            return 1;
        }

        int cellWidth = getResources().getDimensionPixelSize(R.dimen.grid_width);
        int cellMargin = 2 * getResources().getDimensionPixelSize(R.dimen.grid_item_margin);
        int viewPadding = mRecView.getPaddingLeft() + mRecView.getPaddingRight();
        int cellWidth = getScaledSize(R.dimen.grid_width);
        int cellMargin = 2 * getScaledSize(R.dimen.grid_item_margin);
        int viewPadding =
                (int) ((mRecView.getPaddingLeft() + mRecView.getPaddingRight()) * mLiveScale);

        // RecyclerView sometimes gets a width of 0 (see b/27150284).  Clamp so that we always lay
        // out the grid with at least 2 columns.
        // RecyclerView sometimes gets a width of 0 (see b/27150284).
        // Clamp so that we always lay out the grid with at least 2 columns by default.
        int columnCount = Math.max(2,
                (mRecView.getWidth() - viewPadding) / (cellWidth + cellMargin));

        return columnCount;
        // Finally with our grid count logic firmly in place, we apply any live scaling
        // captured by the scale gesture detector.
        return Math.max(1, Math.round(columnCount / mLiveScale));
    }


    /**
     * Moderately abuse the "fraction" resource type for our purposes.
     */
    private float getFraction(@FractionRes int id) {
        return getResources().getFraction(id, 1, 0);
    }

    private int getScaledSize(@DimenRes int id) {
        return (int) (getResources().getDimensionPixelSize(id) * mLiveScale);
    }

    private int getDirectoryPadding(@ViewMode int mode) {
+40 −1
Original line number Diff line number Diff line
@@ -16,15 +16,21 @@

package com.android.documentsui.dirlist;

import static com.android.documentsui.base.Shared.DEBUG;

import android.annotation.Nullable;
import android.content.Context;
import android.os.Build;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.OnItemTouchListener;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.view.View.OnTouchListener;

import com.android.documentsui.base.DebugFlags;
import com.android.documentsui.base.EventHandler;
import com.android.documentsui.base.Events;
import com.android.documentsui.base.Events.InputEvent;
@@ -32,18 +38,25 @@ import com.android.documentsui.base.Events.MotionInputEvent;
import com.android.documentsui.selection.BandController;
import com.android.documentsui.selection.GestureSelector;

import java.util.function.Consumer;

//Receives event meant for both directory and empty view, and either pass them to
//{@link UserInputHandler} for simple gestures (Single Tap, Long-Press), or intercept them for
//other types of gestures (drag n' drop)
final class ListeningGestureDetector extends GestureDetector
        implements OnItemTouchListener, OnTouchListener {

    private static final String TAG = "ListeningGestureDetector";

    private final GestureSelector mGestureSelector;
    private final EventHandler<InputEvent> mMouseDragListener;
    private final BandController mBandController;
    private final MouseDelegate mMouseDelegate = new MouseDelegate();
    private final TouchDelegate mTouchDelegate = new TouchDelegate();

    // Currently only initialized on IS_DEBUGGABLE builds.
    private final @Nullable ScaleGestureDetector mScaleDetector;

    public ListeningGestureDetector(
            Context context,
            RecyclerView recView,
@@ -51,19 +64,45 @@ final class ListeningGestureDetector extends GestureDetector
            EventHandler<InputEvent> mouseDragListener,
            GestureSelector gestureSelector,
            UserInputHandler<? extends InputEvent> handler,
            @Nullable BandController bandController) {
            @Nullable BandController bandController,
            Consumer<Float> scaleHandler) {

        super(context, handler);

        mMouseDragListener = mouseDragListener;
        mGestureSelector = gestureSelector;
        mBandController = bandController;
        recView.addOnItemTouchListener(this);
        emptyView.setOnTouchListener(this);

        mScaleDetector = !Build.IS_DEBUGGABLE
                ? null
                : new ScaleGestureDetector(
                        context,
                        new ScaleGestureDetector.SimpleOnScaleGestureListener() {
                            @Override
                            public boolean onScale(ScaleGestureDetector detector) {
                                if (DEBUG) Log.v(TAG,
                                        "Received scale event: " + detector.getScaleFactor());
                                scaleHandler.accept(detector.getScaleFactor());
                                return true;
                            }
                        });
    }

    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        boolean handled = false;

        // This is an in-development feature.
        // TODO: Re-wire event handling so that we're not dispatching
        //     events to to scaledetector's #onTouchEvent from this
        //     #onInterceptTouchEvent touch event.
        if (DebugFlags.getGestureScaleEnabled()
                && mScaleDetector != null) {
            mScaleDetector.onTouchEvent(e);
        }

        try (InputEvent event = MotionInputEvent.obtain(e, rv)) {
            if (event.isMouseEvent()) {
                handled |= mMouseDelegate.onInterceptTouchEvent(event);
Loading