Merge "Cleaning code related to the forwarding intent filters."
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_camera_alt_24dp.png b/packages/SystemUI/res/drawable-hdpi/ic_camera_alt_24dp.png
new file mode 100644
index 0000000..253c73792
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/ic_camera_alt_24dp.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_phone_24dp.png b/packages/SystemUI/res/drawable-hdpi/ic_phone_24dp.png
new file mode 100644
index 0000000..a6a6448
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/ic_phone_24dp.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_sysbar_camera.png b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_camera.png
deleted file mode 100644
index c6f03c4..0000000
--- a/packages/SystemUI/res/drawable-hdpi/ic_sysbar_camera.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_camera_alt_24dp.png b/packages/SystemUI/res/drawable-mdpi/ic_camera_alt_24dp.png
new file mode 100644
index 0000000..ee1187b
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_camera_alt_24dp.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_phone_24dp.png b/packages/SystemUI/res/drawable-mdpi/ic_phone_24dp.png
new file mode 100644
index 0000000..2286bb4
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_phone_24dp.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_camera.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_camera.png
deleted file mode 100644
index 1c2d7aa..0000000
--- a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_camera.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_camera_alt_24dp.png b/packages/SystemUI/res/drawable-xhdpi/ic_camera_alt_24dp.png
new file mode 100644
index 0000000..268eba0
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_camera_alt_24dp.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_phone_24dp.png b/packages/SystemUI/res/drawable-xhdpi/ic_phone_24dp.png
new file mode 100644
index 0000000..cd9ff60
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_phone_24dp.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_camera.png b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_camera.png
deleted file mode 100644
index fbd4d6b..0000000
--- a/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_camera.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_camera_alt_24dp.png b/packages/SystemUI/res/drawable-xxhdpi/ic_camera_alt_24dp.png
new file mode 100644
index 0000000..9175118
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xxhdpi/ic_camera_alt_24dp.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_phone_24dp.png b/packages/SystemUI/res/drawable-xxhdpi/ic_phone_24dp.png
new file mode 100644
index 0000000..3c546e5
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xxhdpi/ic_phone_24dp.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_camera.png b/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_camera.png
deleted file mode 100644
index 86df881..0000000
--- a/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_camera.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxxhdpi/ic_camera_alt_24dp.png b/packages/SystemUI/res/drawable-xxxhdpi/ic_camera_alt_24dp.png
new file mode 100644
index 0000000..20e26b8
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xxxhdpi/ic_camera_alt_24dp.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxxhdpi/ic_phone_24dp.png b/packages/SystemUI/res/drawable-xxxhdpi/ic_phone_24dp.png
new file mode 100644
index 0000000..4f7da0a
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xxxhdpi/ic_phone_24dp.png
Binary files differ
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index 809adcd..ec5acba 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -22,15 +22,29 @@
     android:layout_height="match_parent"
     android:layout_width="match_parent"
     >
-    <com.android.systemui.statusbar.policy.KeyButtonView
+    <com.android.systemui.statusbar.phone.SwipeAffordanceView
         android:id="@+id/camera_button"
-        android:layout_height="80dp"
-        android:layout_width="80dp"
-        android:layout_gravity="bottom|right"
-        android:src="@drawable/ic_sysbar_camera"
+        android:layout_height="64dp"
+        android:layout_width="64dp"
+        android:layout_gravity="bottom|end"
+        android:tint="#ffffffff"
+        android:src="@drawable/ic_camera_alt_24dp"
         android:scaleType="center"
         android:contentDescription="@string/accessibility_camera_button"
-        systemui:glowBackground="@drawable/ic_sysbar_highlight_land" />
+        systemui:glowBackground="@drawable/ic_sysbar_highlight_land"
+        systemui:swipeDirection="start"/>
+
+    <com.android.systemui.statusbar.phone.SwipeAffordanceView
+        android:id="@+id/phone_button"
+        android:layout_height="64dp"
+        android:layout_width="64dp"
+        android:layout_gravity="bottom|start"
+        android:tint="#ffffffff"
+        android:src="@drawable/ic_phone_24dp"
+        android:scaleType="center"
+        android:contentDescription="@string/accessibility_phone_button"
+        systemui:glowBackground="@drawable/ic_sysbar_highlight_land"
+        systemui:swipeDirection="end"/>
 
     <com.android.systemui.statusbar.phone.KeyguardIndicationTextView
         android:id="@+id/keyguard_indication_text"
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 734abdc..f5674d2 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -45,6 +45,12 @@
     <declare-styleable name="BatteryMeterView">
         <attr name="frameColor" format="color" />
     </declare-styleable>
+    <declare-styleable name="SwipeAffordanceView">
+        <attr name="swipeDirection" format="enum">
+            <enum name="start" value="0" />
+            <enum name="end" value="1" />
+        </attr>
+    </declare-styleable>
     <attr name="orientation">
         <enum name="horizontal" value="0" />
         <enum name="vertical" value="1" />
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 1900fea..28de6ac 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -258,8 +258,8 @@
     <!-- Width of the zen mode interstitial dialog. -->
     <dimen name="zen_mode_dialog_width">320dp</dimen>
 
-    <!-- Camera affordance drag distance -->
-    <dimen name="camera_drag_distance">100dp</dimen>
+    <!-- Lockscreen affordance drag distance for camera and phone. -->
+    <dimen name="affordance_drag_distance">100dp</dimen>
 
     <dimen name="quick_settings_tmp_scrim_stroke_width">8dp</dimen>
     <dimen name="quick_settings_tmp_scrim_text_size">30dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b4a13d4..3f0a60f 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -196,6 +196,8 @@
     <string name="accessibility_search_light">Search</string>
     <!-- Content description of the camera button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_camera_button">Camera</string>
+    <!-- Content description of the phone button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+    <string name="accessibility_phone_button">Phone</string>
 
     <!-- Content description of the switch input method button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_ime_switch_button">Switch input method button.</string>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarter.java
new file mode 100644
index 0000000..5bc7e5a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarter.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2014 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.systemui.statusbar.phone;
+
+import android.content.Intent;
+
+/**
+ * An interface to start activities. This is used to as a callback from the views to
+ * {@link PhoneStatusBar} to allow custom handling for starting the activity, i.e. dismissing the
+ * Keyguard.
+ */
+public interface ActivityStarter {
+    public void startActivity(Intent intent);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 3cc22ef..58b3f2e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -42,14 +43,18 @@
  * Implementation for the bottom area of the Keyguard, including camera/phone affordance and status
  * text.
  */
-public class KeyguardBottomAreaView extends FrameLayout {
+public class KeyguardBottomAreaView extends FrameLayout
+        implements SwipeAffordanceView.AffordanceListener {
 
     final static String TAG = "PhoneStatusBar/KeyguardBottomAreaView";
 
-    private View mCameraButton;
-    private float mCameraDragDistance;
+    private static final Intent PHONE_INTENT = new Intent(Intent.ACTION_DIAL);
+
+    private SwipeAffordanceView mCameraButton;
+    private SwipeAffordanceView mPhoneButton;
+
     private PowerManager mPowerManager;
-    private int mScaledTouchSlop;
+    private ActivityStarter mActivityStarter;
 
     public KeyguardBottomAreaView(Context context) {
         super(context);
@@ -71,20 +76,37 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mCameraButton = findViewById(R.id.camera_button);
+        mCameraButton = (SwipeAffordanceView) findViewById(R.id.camera_button);
+        mPhoneButton = (SwipeAffordanceView) findViewById(R.id.phone_button);
+        mCameraButton.setAffordanceListener(this);
+        mPhoneButton.setAffordanceListener(this);
         watchForDevicePolicyChanges();
         watchForAccessibilityChanges();
         updateCameraVisibility();
-        mCameraDragDistance = getResources().getDimension(R.dimen.camera_drag_distance);
-        mScaledTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
+        updatePhoneVisibility();
         mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
     }
 
+    public void setActivityStarter(ActivityStarter activityStarter) {
+        mActivityStarter = activityStarter;
+    }
+
     private void updateCameraVisibility() {
         boolean visible = !isCameraDisabledByDpm();
         mCameraButton.setVisibility(visible ? View.VISIBLE : View.GONE);
     }
 
+    private void updatePhoneVisibility() {
+        boolean visible = isPhoneVisible();
+        mPhoneButton.setVisibility(visible ? View.VISIBLE : View.GONE);
+    }
+
+    private boolean isPhoneVisible() {
+        PackageManager pm = mContext.getPackageManager();
+        return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
+                && pm.resolveActivity(PHONE_INTENT, 0) != null;
+    }
+
     private boolean isCameraDisabledByDpm() {
         final DevicePolicyManager dpm =
                 (DevicePolicyManager) getContext().getSystemService(Context.DEVICE_POLICY_SERVICE);
@@ -136,15 +158,8 @@
     }
 
     private void enableAccessibility(boolean touchExplorationEnabled) {
-
-        // Add a touch handler or accessibility click listener for camera button.
-        if (touchExplorationEnabled) {
-            mCameraButton.setOnTouchListener(null);
-            mCameraButton.setOnClickListener(mCameraClickListener);
-        } else {
-            mCameraButton.setOnTouchListener(mCameraTouchListener);
-            mCameraButton.setOnClickListener(null);
-        }
+        mCameraButton.enableAccessibility(touchExplorationEnabled);
+        mPhoneButton.enableAccessibility(touchExplorationEnabled);
     }
 
     private void launchCamera() {
@@ -153,80 +168,21 @@
                 UserHandle.CURRENT);
     }
 
-    private final OnClickListener mCameraClickListener = new OnClickListener() {
-        @Override
-        public void onClick(View v) {
+    private void launchPhone() {
+        mActivityStarter.startActivity(PHONE_INTENT);
+    }
+
+    @Override
+    public void onUserActivity(long when) {
+        mPowerManager.userActivity(when, false);
+    }
+
+    @Override
+    public void onActionPerformed(SwipeAffordanceView view) {
+        if (view == mCameraButton) {
             launchCamera();
+        } else if (view == mPhoneButton) {
+            launchPhone();
         }
-    };
-
-    private final OnTouchListener mCameraTouchListener = new OnTouchListener() {
-        private float mStartX;
-        private boolean mTouchSlopReached;
-        private boolean mSkipCancelAnimation;
-
-        @Override
-        public boolean onTouch(final View cameraButtonView, MotionEvent event) {
-            float realX = event.getRawX();
-            switch (event.getAction()) {
-                case MotionEvent.ACTION_DOWN:
-                    mStartX = realX;
-                    mTouchSlopReached = false;
-                    mSkipCancelAnimation = false;
-                    break;
-                case MotionEvent.ACTION_MOVE:
-                    if (realX > mStartX) {
-                        realX = mStartX;
-                    }
-                    if (realX < mStartX - mCameraDragDistance) {
-                        cameraButtonView.setPressed(true);
-                        mPowerManager.userActivity(event.getEventTime(), false);
-                    } else {
-                        cameraButtonView.setPressed(false);
-                    }
-                    if (realX < mStartX - mScaledTouchSlop) {
-                        mTouchSlopReached = true;
-                    }
-                    cameraButtonView.setTranslationX(Math.max(realX - mStartX,
-                            -mCameraDragDistance));
-                    break;
-                case MotionEvent.ACTION_UP:
-                    if (realX < mStartX - mCameraDragDistance) {
-                        launchCamera();
-                        cameraButtonView.animate().x(-cameraButtonView.getWidth())
-                                .setInterpolator(new AccelerateInterpolator(2f)).withEndAction(
-                                new Runnable() {
-                                    @Override
-                                    public void run() {
-                                        cameraButtonView.setTranslationX(0);
-                                    }
-                                });
-                        mSkipCancelAnimation = true;
-                    }
-                    if (realX < mStartX - mScaledTouchSlop) {
-                        mTouchSlopReached = true;
-                    }
-                    if (!mTouchSlopReached) {
-                        mSkipCancelAnimation = true;
-                        cameraButtonView.animate().translationX(-mCameraDragDistance / 2).
-                                setInterpolator(new DecelerateInterpolator()).withEndAction(
-                                new Runnable() {
-                                    @Override
-                                    public void run() {
-                                        cameraButtonView.animate().translationX(0).
-                                                setInterpolator(new AccelerateInterpolator());
-                                    }
-                                });
-                    }
-                case MotionEvent.ACTION_CANCEL:
-                    cameraButtonView.setPressed(false);
-                    if (!mSkipCancelAnimation) {
-                        cameraButtonView.animate().translationX(0)
-                                .setInterpolator(new AccelerateInterpolator(2f));
-                    }
-                    break;
-            }
-            return true;
-        }
-    };
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index 328a1728..0cdca66 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -623,9 +623,7 @@
                 mExpandedHeight = mMaxPanelHeight;
             }
         }
-        heightMeasureSpec = MeasureSpec.makeMeasureSpec(
-                getDesiredMeasureHeight(), MeasureSpec.AT_MOST);
-        setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
+        setMeasuredDimension(getMeasuredWidth(), getDesiredMeasureHeight());
     }
 
     protected int getDesiredMeasureHeight() {
@@ -705,11 +703,6 @@
      * @return the default implementation simply returns the maximum height.
      */
     protected int getMaxPanelHeight() {
-        if (mMaxPanelHeight <= 0) {
-            if (DEBUG) logf("Forcing measure() since mMaxPanelHeight=" + mMaxPanelHeight);
-            measure(MeasureSpec.makeMeasureSpec(android.view.ViewGroup.LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY),
-                    MeasureSpec.makeMeasureSpec(android.view.ViewGroup.LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY));
-        }
         return mMaxPanelHeight;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 0db6914..80eff13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -124,7 +124,7 @@
 import java.util.Collections;
 
 public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
-        DragDownHelper.OnDragDownListener {
+        DragDownHelper.OnDragDownListener, ActivityStarter {
     static final String TAG = "PhoneStatusBar";
     public static final boolean DEBUG = BaseStatusBar.DEBUG;
     public static final boolean SPEW = false;
@@ -236,7 +236,7 @@
     // top bar
     View mNotificationPanelHeader;
     View mKeyguardStatusView;
-    View mKeyguardBottomArea;
+    KeyguardBottomAreaView mKeyguardBottomArea;
     boolean mLeaveOpenOnKeyguardHide;
     KeyguardIndicationTextView mKeyguardIndicationTextView;
 
@@ -639,7 +639,9 @@
 
         mNotificationPanelHeader = mStatusBarWindow.findViewById(R.id.header);
         mKeyguardStatusView = mStatusBarWindow.findViewById(R.id.keyguard_status_view);
-        mKeyguardBottomArea = mStatusBarWindow.findViewById(R.id.keyguard_bottom_area);
+        mKeyguardBottomArea =
+                (KeyguardBottomAreaView) mStatusBarWindow.findViewById(R.id.keyguard_bottom_area);
+        mKeyguardBottomArea.setActivityStarter(this);
         mKeyguardIndicationTextView = (KeyguardIndicationTextView) mStatusBarWindow.findViewById(
                 R.id.keyguard_indication_text);
         mClearButton = mStatusBarWindow.findViewById(R.id.clear_all_button);
@@ -1578,6 +1580,11 @@
         return new PhoneStatusBar.H();
     }
 
+    @Override
+    public void startActivity(Intent intent) {
+        startActivityDismissingKeyguard(intent, false);
+    }
+
     /**
      * All changes to the status bar and notifications funnel through here and are batched.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SwipeAffordanceView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SwipeAffordanceView.java
new file mode 100644
index 0000000..049c5fc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SwipeAffordanceView.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2014 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.systemui.statusbar.phone;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.Button;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.KeyButtonView;
+
+/**
+ * A swipeable button for affordances on the lockscreen. This is used for the camera and phone
+ * affordance.
+ */
+public class SwipeAffordanceView extends KeyButtonView {
+
+    private static final int SWIPE_DIRECTION_START = 0;
+    private static final int SWIPE_DIRECTION_END = 1;
+
+    private static final int SWIPE_DIRECTION_LEFT = 0;
+    private static final int SWIPE_DIRECTION_RIGHT = 1;
+
+    private AffordanceListener mListener;
+    private int mScaledTouchSlop;
+    private float mDragDistance;
+    private int mResolvedSwipeDirection;
+    private int mSwipeDirection;
+
+    public SwipeAffordanceView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public SwipeAffordanceView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        TypedArray a = context.getTheme().obtainStyledAttributes(
+                attrs,
+                R.styleable.SwipeAffordanceView,
+                0, 0);
+        try {
+            mSwipeDirection = a.getInt(R.styleable.SwipeAffordanceView_swipeDirection, 0);
+        } finally {
+            a.recycle();
+        }
+    }
+
+    @Override
+    public void onRtlPropertiesChanged(int layoutDirection) {
+        super.onRtlPropertiesChanged(layoutDirection);
+        if (!isLayoutRtl()) {
+            mResolvedSwipeDirection = mSwipeDirection;
+        } else {
+            mResolvedSwipeDirection = mSwipeDirection == SWIPE_DIRECTION_START
+                    ? SWIPE_DIRECTION_RIGHT
+                    : SWIPE_DIRECTION_LEFT;
+        }
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mDragDistance = getResources().getDimension(R.dimen.affordance_drag_distance);
+        mScaledTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
+    }
+
+    public void enableAccessibility(boolean touchExplorationEnabled) {
+
+        // Add a touch handler or accessibility click listener for camera button.
+        if (touchExplorationEnabled) {
+            setOnTouchListener(null);
+            setOnClickListener(mClickListener);
+        } else {
+            setOnTouchListener(mTouchListener);
+            setOnClickListener(null);
+        }
+    }
+
+    public void setAffordanceListener(AffordanceListener listener) {
+        mListener = listener;
+    }
+
+    private void onActionPerformed() {
+        if (mListener != null) {
+            mListener.onActionPerformed(this);
+        }
+    }
+
+    private void onUserActivity(long when) {
+        if (mListener != null) {
+            mListener.onUserActivity(when);
+        }
+    }
+
+    private final OnClickListener mClickListener = new OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            onActionPerformed();
+        }
+    };
+
+    private final OnTouchListener mTouchListener = new OnTouchListener() {
+        private float mStartX;
+        private boolean mTouchSlopReached;
+        private boolean mSkipCancelAnimation;
+
+        @Override
+        public boolean onTouch(final View view, MotionEvent event) {
+            float realX = event.getRawX();
+            switch (event.getAction()) {
+                case MotionEvent.ACTION_DOWN:
+                    mStartX = realX;
+                    mTouchSlopReached = false;
+                    mSkipCancelAnimation = false;
+                    break;
+                case MotionEvent.ACTION_MOVE:
+                    if (mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT
+                            ? realX > mStartX
+                            : realX < mStartX) {
+                        realX = mStartX;
+                    }
+                    if (mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT
+                            ? realX < mStartX - mDragDistance
+                            : realX > mStartX + mDragDistance) {
+                        view.setPressed(true);
+                        onUserActivity(event.getEventTime());
+                    } else {
+                        view.setPressed(false);
+                    }
+                    if (mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT
+                            ? realX < mStartX - mScaledTouchSlop
+                            : realX > mStartX + mScaledTouchSlop) {
+                        mTouchSlopReached = true;
+                    }
+                    view.setTranslationX(mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT
+                                    ? Math.max(realX - mStartX, -mDragDistance)
+                                    : Math.min(realX - mStartX, mDragDistance));
+                    break;
+                case MotionEvent.ACTION_UP:
+                    if (mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT
+                            ? realX < mStartX - mDragDistance
+                            : realX > mStartX + mDragDistance) {
+                        onActionPerformed();
+                        view.animate().x(mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT
+                                ? -view.getWidth()
+                                : ((View) view.getParent()).getWidth() + view.getWidth())
+                                .setInterpolator(new AccelerateInterpolator(2f)).withEndAction(
+                                new Runnable() {
+                                    @Override
+                                    public void run() {
+                                        view.setTranslationX(0);
+                                    }
+                                });
+                        mSkipCancelAnimation = true;
+                    }
+                    if (mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT
+                            ? realX < mStartX - mScaledTouchSlop
+                            : realX > mStartX + mScaledTouchSlop) {
+                        mTouchSlopReached = true;
+                    }
+                    if (!mTouchSlopReached) {
+                        mSkipCancelAnimation = true;
+                        view.animate().translationX(mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT
+                                ? -mDragDistance / 2
+                                : mDragDistance / 2).
+                                setInterpolator(new DecelerateInterpolator()).withEndAction(
+                                new Runnable() {
+                                    @Override
+                                    public void run() {
+                                        view.animate().translationX(0).
+                                                setInterpolator(new AccelerateInterpolator());
+                                    }
+                                });
+                    }
+                case MotionEvent.ACTION_CANCEL:
+                    view.setPressed(false);
+                    if (!mSkipCancelAnimation) {
+                        view.animate().translationX(0)
+                                .setInterpolator(new AccelerateInterpolator(2f));
+                    }
+                    break;
+            }
+            return true;
+        }
+    };
+
+    public interface AffordanceListener {
+
+        /**
+         * Called when the view would like to report user activity.
+         *
+         * @param when The timestamp of the user activity in {@link SystemClock#uptimeMillis} time
+         *             base.
+         */
+        void onUserActivity(long when);
+
+        /**
+         * Called when the action of the affordance has been performed.
+         */
+        void onActionPerformed(SwipeAffordanceView view);
+    }
+}