Skip to content
Permalink
Browse files

Add new feature of One-hand UI Mode [1/3]

Bug: 33118157
Test: builds, and test feature in bug 33118157

@nathanchance edit: Did not include edits in NavigationBarView.java since I use DUI; courtesy of @ezio84
Original commit if you need it: https://android-review.googlesource.com/#/c/307569/

Change-Id: Ic1b62a8d0821385cfe05818c72f9554588457a65
Signed-off-by: Pranav Vashi <neobuddy89@gmail.com>
  • Loading branch information
Bangbang Huang authored and eyosen committed Jan 24, 2017
1 parent 63c9cca commit e82e2375d8a99f74b4b8901e61237fd85562e2d1
@@ -9776,6 +9776,9 @@ private static final int getLocationModeForUser(ContentResolver cr, int userId)
*/
public static final String ALARM_MANAGER_CONSTANTS = "alarm_manager_constants";

/** {@hide} */
public static final String SINGLE_HAND_MODE = "single_hand_mode";

/**
* Job scheduler specific settings.
* This is encoded as a key=value list, separated by commas. Ex:
@@ -90,6 +90,7 @@

public final static int FLAG_INTERACTIVE = 0x20000000;
public final static int FLAG_PASS_TO_USER = 0x40000000;
public final static int POLICY_FLAG_REMOVE_HANDYMODE = 0x80000000;

// Flags for IActivityManager.keyguardGoingAway()
public final static int KEYGUARD_GOING_AWAY_FLAG_TO_SHADE = 1 << 0;
@@ -1448,4 +1449,11 @@ public void getNonDecorInsetsLw(int displayRotation, int displayWidth, int displ
public void onConfigurationChanged();

public boolean shouldRotateSeamlessly(int oldRotation, int newRotation);

/**
* Lock the device orientation to the specified rotation,
* Sensor input or hdmi will be ignored until
* freezeOrThawRotation(-1) is called or reboot the devcie.
*/
public void freezeOrThawRotation(int rotation);
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>

<selector
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true" android:drawable="@android:drawable/singlehandmode_info_pressed" />
<item android:state_focused="false" android:state_pressed="false" android:drawable="@android:drawable/singlehandmode_info" />
<item android:state_focused="false" android:state_pressed="true" android:drawable="@android:drawable/singlehandmode_info_pressed" />
</selector>
@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2012 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.
-->

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout android:layout_width="match_parent"
android:layout_height="0px"
android:background="#FF000000"
android:id="@+id/relative_top">
<FrameLayout android:id="@+id/hint_section"
android:layout_width="60dp"
android:layout_height="70dp"
android:clickable="true">
<ImageView
android:id="@+id/hint_info"
android:visibility="invisible"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginTop="16dip"
android:layout_marginStart="10dip"
android:focusable="true"
android:clickable="true"
android:background="@android:drawable/singlehandmode_hintinfo" />
</FrameLayout>
<TextView android:id="@+id/single_hand_window_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dip"
android:layout_marginStart="18dip"
android:layout_marginEnd="18dip"
android:textSize="14dp"
android:textColor="#d9ffffff"
android:text="@android:string/singlehandmode_click_hint_message"
android:visibility="invisible"
android:gravity="center" />
<ImageView
android:id="@+id/click_hint"
android:layout_below="@id/single_hand_window_title"
android:visibility="invisible"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dip"
android:layout_centerHorizontal="true"
android:src="@android:drawable/singlehandmode_click_hint" />
</RelativeLayout>
<RelativeLayout android:layout_width="0px"
android:layout_height="0px"
android:background="#FF000000"
android:id="@+id/relative_bottom" />
<LinearLayout android:id="@+id/slide_hint_area"
android:visibility="invisible"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="vertical">
<TextView android:id="@+id/singlehandmode_slide_hint_text"
android:layout_width="240dip"
android:layout_height="wrap_content"
android:visibility="invisible"
android:textSize="14dp"
android:textColor="#d9ffffff"
android:text="@android:string/singlehandmode_slide_hint_message"
android:layout_gravity="center_horizontal"
android:gravity="center" />
<ImageView
android:id="@+id/slide_hint"
android:visibility="invisible"
android:layout_width="0px"
android:layout_height="0px"
android:layout_marginTop="12dip"
android:background="@android:drawable/singlehandmode_slide_hint" />
</LinearLayout>
</RelativeLayout>
@@ -36,4 +36,5 @@
needs to be set to false, to prevent the doze notifications from being light -->
<bool name="config_invert_colors_on_doze">true</bool>

<bool name="single_hand_mode">true</bool>
</resources>
@@ -27,4 +27,8 @@

<!-- Screenshoot edit action -->
<string name="edit">Edit</string>

<!-- Singlehanded Mode -->
<string name="singlehandmode_click_hint_message">Touch outside the screen to exit mini screen view</string>
<string name="singlehandmode_slide_hint_message">Slide your finger across the navigation bar to switch between the standard and mini screen views</string>
</resources>
@@ -77,4 +77,22 @@
<java-symbol type="dimen" name="edge_gesture_trigger_distance" />
<java-symbol type="dimen" name="edge_gesture_perpendicular_distance" />
<java-symbol type="dimen" name="edge_gesture_trigger_thickness" />

<!-- Singlehanded mode -->
<java-symbol type="id" name="relative_top" />
<java-symbol type="id" name="hint_info" />
<java-symbol type="id" name="single_hand_window_title" />
<java-symbol type="id" name="click_hint" />
<java-symbol type="id" name="hint_section" />
<java-symbol type="id" name="relative_bottom" />
<java-symbol type="id" name="singlehandmode_slide_hint_text" />
<java-symbol type="id" name="slide_hint" />
<java-symbol type="id" name="slide_hint_area" />

<java-symbol type="bool" name="single_hand_mode" />

<java-symbol type="string" name="singlehandmode_click_hint_message" />
<java-symbol type="string" name="singlehandmode_slide_hint_message" />

<java-symbol type="layout" name="single_hand_window" />
</resources>
@@ -77,4 +77,8 @@
<dimen name="pie_touch_offset">15dp</dimen>
<dimen name="pie_tab_title_height">24dp</dimen>
<dimen name="pie_panel_padding">20dp</dimen>

<!-- Singlehanded mode dimensions -->
<dimen name="navbar_single_hand_mode_horizontal_threshhold">90dp</dimen>
<dimen name="navbar_single_hand_mode_vertical_threshhold">48dp</dimen>
</resources>
@@ -0,0 +1,199 @@
package com.android.systemui.singlehandmode;

import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources.NotFoundException;
import android.graphics.Rect;
import android.os.*;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.*;
import com.android.systemui.R;

public class SlideTouchEvent {
private static final String TAG = "SlideTouchEvent";
private static final String LEFT = "left";
private static final String RIGHT = "right";

/**
* The units you would like the velocity in. A value of 1 provides pixels per millisecond, 1000 provides pixels per second, etc.
*/
private static final int UNITS = 1000;

public static final float SCALE = (float) 3 / 4;

public static final String KEY_SINGLE_HAND_SCREEN_ZOOM = "single_hand_screen_zoom";
private boolean mIsSupport = true;
private boolean mScreenZoomEnabled = true;
private boolean mZoomGestureEnabled = false;

private float[] mDownPoint = new float[2];
private float mTriggerSingleHandMode;
private float mVerticalProhibit;

private int mMinimumFlingVelocity;
private int mMaximumFlingVelocity;

private VelocityTracker mVelocityTracker;
private Handler mHandler = new Handler();
private Context mContext;

private boolean mFlag = false;

public SlideTouchEvent(Context context) {
mContext = context;
init();
}

private void init() {
if (null == mContext) {
Log.e(TAG, "SlideTouchEvent init return...");
return;
}
mIsSupport = isSupportSingleHand();
if (!mIsSupport) return;

final ViewConfiguration configuration = ViewConfiguration.get(mContext);
mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity();

mTriggerSingleHandMode = mContext.getResources().getDimension(R.dimen.navbar_single_hand_mode_horizontal_threshhold);
mVerticalProhibit = mContext.getResources().getDimension(R.dimen.navbar_single_hand_mode_vertical_threshhold);
}

/**
* handle MotionEvent, maybe trigger single hand mode
* @param event MotionEvent
*/
public void handleTouchEvent(MotionEvent event) {
//Log.i(TAG, "handleTouchEvent:" + event);
if (event == null) {
return;
}
if (!mIsSupport) {
return;
}

if (!mScreenZoomEnabled) {
return;
}

if (mZoomGestureEnabled) {
return;
}

if (null == mVelocityTracker) {
mVelocityTracker = VelocityTracker.obtain();
}

mVelocityTracker.addMovement(event);

switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mFlag = true;
mDownPoint[0] = event.getX();
mDownPoint[1] = event.getY();
break;
case MotionEvent.ACTION_POINTER_UP:
if (event.getActionIndex() == 0) {
mFlag = false;
}
break;
case MotionEvent.ACTION_UP:
if (!mFlag) {
break;
}
mFlag = false;
if (true) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(UNITS, mMaximumFlingVelocity);

final int pointerId = event.getPointerId(0);
final float velocityX = velocityTracker.getXVelocity(pointerId);

Log.i(TAG, "vel=" + Math.abs(velocityX) + ", MinimumFlingVelocity=" + mMinimumFlingVelocity);
if (Math.abs(velocityX) > mMinimumFlingVelocity) {

final int historySize = event.getHistorySize();

for (int i = 0; i < historySize + 1; i++) {

float x = i < historySize ? event.getHistoricalX(i) : event.getX();
float y = i < historySize ? event.getHistoricalY(i) : event.getY();
float distanceX = mDownPoint[0] - x;
float distanceY = mDownPoint[1] - y;
if (Math.abs(distanceY) > Math.abs(distanceX) || Math.abs(distanceY) > mVerticalProhibit) {
Log.i(TAG, "Sliding distanceY > distancex, " + distanceY + ", " + distanceX);
return;
}
if (Math.abs(distanceX) > mTriggerSingleHandMode) {
if (Configuration.ORIENTATION_PORTRAIT == mContext.getResources().getConfiguration().orientation) {
startSingleHandMode(distanceX);
}
} else {
Log.i(TAG, "Sliding distance is too short, can not trigger the single hand mode");
}
}
}
}
break;
default:
break;
}
}

/**
* start single hand mode.
* @param distanceX Sliding X distance
*/
private void startSingleHandMode(float distanceX) {
String str = Settings.Global.getString(mContext.getContentResolver(), Settings.Global.SINGLE_HAND_MODE);
Log.i("SingleHand", "start single hand mode str: " + str + " distanceX: " + distanceX);
if (distanceX > 0 && TextUtils.isEmpty(str)) {
Settings.Global.putString(mContext.getContentResolver(), Settings.Global.SINGLE_HAND_MODE, LEFT);
}

if (distanceX < 0 && TextUtils.isEmpty(str)) {
Settings.Global.putString(mContext.getContentResolver(), Settings.Global.SINGLE_HAND_MODE, RIGHT);
}

if (distanceX < 0 && str != null && str.contains(LEFT)) {
quitLSingleHandMode();
}

if (distanceX > 0 && str != null && str.contains(RIGHT)) {
quitLSingleHandMode();
}
}

public static final int STATE_MIDDLE = 0;
public static final int STATE_LEFT = 1;
public static final int STATE_RIGHT = 2;

public static int getSingleHandState(Context context) {
String str = Settings.Global.getString(context.getContentResolver(), Settings.Global.SINGLE_HAND_MODE);
if (TextUtils.isEmpty(str)) {
return STATE_MIDDLE;
} else if (str.contains(LEFT)) {
return STATE_LEFT;
} else if (str.contains(RIGHT)) {
return STATE_RIGHT;
}
return STATE_MIDDLE;
}

public static boolean isSingleHandMode(Context context) {
return getSingleHandState(context) == STATE_MIDDLE ? false : true;
}

private void quitLSingleHandMode() {
Log.i(TAG, "quitLSingleHandMode");
Settings.Global.putString(mContext.getContentResolver(), Settings.Global.SINGLE_HAND_MODE, "");
}

private boolean isSupportSingleHand() {
return true;
}
}

0 comments on commit e82e237

Please sign in to comment.
You can’t perform that action at this time.