From 86bd2351bc6efb13667612be5e82d4e38e521b36 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Sun, 19 Nov 2017 16:24:30 -0800 Subject: [PATCH] Replace answer/decline button and action for incoming calls Fixes #7199 --- .../ic_keyboard_arrow_up_white_36dp.png | Bin 0 -> 190 bytes .../ic_keyboard_arrow_up_white_36dp.png | Bin 0 -> 156 bytes .../ic_keyboard_arrow_up_white_36dp.png | Bin 0 -> 259 bytes .../ic_keyboard_arrow_up_white_36dp.png | Bin 0 -> 301 bytes .../ic_keyboard_arrow_up_white_36dp.png | Bin 0 -> 371 bytes res/layout/webrtc_answer_decline_button.xml | 79 ++ res/layout/webrtc_call_screen.xml | 12 +- res/layout/webrtc_incoming_call_overlay.xml | 29 - res/values/strings.xml | 2 + .../securesms/WebRtcCallActivity.java | 8 +- .../components/multiwaveview/Ease.java | 134 --- .../multiwaveview/MultiWaveView.java | 1039 ----------------- .../multiwaveview/TargetDrawable.java | 225 ---- .../components/multiwaveview/Tweener.java | 180 --- .../webrtc/WebRtcAnswerDeclineButton.java | 293 +++++ .../components/webrtc/WebRtcCallScreen.java | 81 +- .../webrtc/WebRtcIncomingCallOverlay.java | 113 -- 17 files changed, 422 insertions(+), 1773 deletions(-) create mode 100644 res/drawable-hdpi/ic_keyboard_arrow_up_white_36dp.png create mode 100644 res/drawable-mdpi/ic_keyboard_arrow_up_white_36dp.png create mode 100644 res/drawable-xhdpi/ic_keyboard_arrow_up_white_36dp.png create mode 100644 res/drawable-xxhdpi/ic_keyboard_arrow_up_white_36dp.png create mode 100644 res/drawable-xxxhdpi/ic_keyboard_arrow_up_white_36dp.png create mode 100644 res/layout/webrtc_answer_decline_button.xml delete mode 100644 res/layout/webrtc_incoming_call_overlay.xml delete mode 100644 src/org/thoughtcrime/securesms/components/multiwaveview/Ease.java delete mode 100644 src/org/thoughtcrime/securesms/components/multiwaveview/MultiWaveView.java delete mode 100644 src/org/thoughtcrime/securesms/components/multiwaveview/TargetDrawable.java delete mode 100644 src/org/thoughtcrime/securesms/components/multiwaveview/Tweener.java create mode 100644 src/org/thoughtcrime/securesms/components/webrtc/WebRtcAnswerDeclineButton.java delete mode 100644 src/org/thoughtcrime/securesms/components/webrtc/WebRtcIncomingCallOverlay.java diff --git a/res/drawable-hdpi/ic_keyboard_arrow_up_white_36dp.png b/res/drawable-hdpi/ic_keyboard_arrow_up_white_36dp.png new file mode 100644 index 0000000000000000000000000000000000000000..ddd0078f3f3f1777439da1fbb9cd197c8d4c1ad9 GIT binary patch literal 190 zcmeAS@N?(olHy`uVBq!ia0vp^W+2SL0wmRZ7KH(+R!42~~ zi){8NE?>cJoMx=IP)$%!S&KcWd`u#_XJh`}701KmUS2HE0Xm4m)78&qol`;+0MCp?KL7v# literal 0 HcmV?d00001 diff --git a/res/drawable-mdpi/ic_keyboard_arrow_up_white_36dp.png b/res/drawable-mdpi/ic_keyboard_arrow_up_white_36dp.png new file mode 100644 index 0000000000000000000000000000000000000000..dea8988386907cdd1741d2cd17eea26f5fe9cba4 GIT binary patch literal 156 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8K8vZsqU#L=X%DU+^-|_uBj6@R~ZHuNXXC{an^LB{Ts5 Dk$r3v literal 0 HcmV?d00001 diff --git a/res/drawable-xxhdpi/ic_keyboard_arrow_up_white_36dp.png b/res/drawable-xxhdpi/ic_keyboard_arrow_up_white_36dp.png new file mode 100644 index 0000000000000000000000000000000000000000..f91589d8bd426890b98854d7fc138823ce2dd688 GIT binary patch literal 301 zcmeAS@N?(olHy`uVBq!ia0vp^IUvlz0wh)Q=eq%^FP<)rAr-gYUW@KKq#)4{xcyPs zo;O}|^P=Kk_wc*9cki8Wp#S-6W!{G^BDUz2dZ+-o}VT;IXGr7s% zIz^|LwTVucmXI5Nuh)6@AugBMr?{MEALeqHeR|cLZ7rdOxtgnkqMEkOH1Ut!60_!1 zhGk^vMYaUvNY7jTKW(kgr}JL&d%Ay9>zeIbf`1kpGQVyu)2W}Fnq+&C@9BSAqfPI& pTv~J6>Dt%x8Zc)A-M??ScDUNl?Fa8n+X?hPgQu&X%Q~loCIIeGhCcuR literal 0 HcmV?d00001 diff --git a/res/drawable-xxxhdpi/ic_keyboard_arrow_up_white_36dp.png b/res/drawable-xxxhdpi/ic_keyboard_arrow_up_white_36dp.png new file mode 100644 index 0000000000000000000000000000000000000000..62467ced43d56deb7698ac28b25c85b8470458dc GIT binary patch literal 371 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q1xWh(YZ)^zFj{%KIEGZ*dV4+Euf;*c^`hJi zX6@W7^DNzj>~CotUEKBLTI_3SicG^e^%{^mWwTQR!)Sp>q z7pK{rF0>TRJv-Ut;yfEp;kRce>s*{@(22d7$+>cR>|glMfto#XVuflb+!ESSESEl6#9Bj*yf^WC6oFtE&m%XR~?qVjMBZB zz9oHyN&Ig1)0=0lD2bf?FYG>6rgDdMAbOJBa1Kk6~-y+W)VZAMXNjJzf1=);T3K0RYtYm)ZaT literal 0 HcmV?d00001 diff --git a/res/layout/webrtc_answer_decline_button.xml b/res/layout/webrtc_answer_decline_button.xml new file mode 100644 index 000000000..79ced8854 --- /dev/null +++ b/res/layout/webrtc_answer_decline_button.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/webrtc_call_screen.xml b/res/layout/webrtc_call_screen.xml index 52d8993a9..896f12332 100644 --- a/res/layout/webrtc_call_screen.xml +++ b/res/layout/webrtc_call_screen.xml @@ -225,10 +225,12 @@ android:contentDescription="End call" tools:visibility="visible"/> - + + diff --git a/res/layout/webrtc_incoming_call_overlay.xml b/res/layout/webrtc_incoming_call_overlay.xml deleted file mode 100644 index 4123da364..000000000 --- a/res/layout/webrtc_incoming_call_overlay.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - diff --git a/res/values/strings.xml b/res/values/strings.xml index 1ebf58a88..62d64ae65 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1251,6 +1251,8 @@ No web browser installed! Recent chats Contacts + Swipe up to answer + Swipe down to reject diff --git a/src/org/thoughtcrime/securesms/WebRtcCallActivity.java b/src/org/thoughtcrime/securesms/WebRtcCallActivity.java index 0546205c9..6f5a6e062 100644 --- a/src/org/thoughtcrime/securesms/WebRtcCallActivity.java +++ b/src/org/thoughtcrime/securesms/WebRtcCallActivity.java @@ -35,9 +35,9 @@ import android.view.WindowManager; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; +import org.thoughtcrime.securesms.components.webrtc.WebRtcAnswerDeclineButton; import org.thoughtcrime.securesms.components.webrtc.WebRtcCallControls; import org.thoughtcrime.securesms.components.webrtc.WebRtcCallScreen; -import org.thoughtcrime.securesms.components.webrtc.WebRtcIncomingCallOverlay; import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore; import org.thoughtcrime.securesms.events.WebRtcViewModel; import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess; @@ -359,14 +359,14 @@ public class WebRtcCallActivity extends Activity { } } - private class IncomingCallActionListener implements WebRtcIncomingCallOverlay.IncomingCallActionListener { + private class IncomingCallActionListener implements WebRtcAnswerDeclineButton.AnswerDeclineListener { @Override - public void onAcceptClick() { + public void onAnswered() { WebRtcCallActivity.this.handleAnswerCall(); } @Override - public void onDenyClick() { + public void onDeclined() { WebRtcCallActivity.this.handleDenyCall(); } } diff --git a/src/org/thoughtcrime/securesms/components/multiwaveview/Ease.java b/src/org/thoughtcrime/securesms/components/multiwaveview/Ease.java deleted file mode 100644 index 4042e9ec0..000000000 --- a/src/org/thoughtcrime/securesms/components/multiwaveview/Ease.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (C) 2011 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 org.thoughtcrime.securesms.components.multiwaveview; - - -import android.animation.TimeInterpolator; -import android.annotation.SuppressLint; - -@SuppressLint("NewApi") -class Ease { - private static final float DOMAIN = 1.0f; - private static final float DURATION = 1.0f; - private static final float START = 0.0f; - - static class Linear { - public static final TimeInterpolator easeNone = new TimeInterpolator() { - public float getInterpolation(float input) { - return input; - } - }; - } - - static class Cubic { - public static final TimeInterpolator easeIn = new TimeInterpolator() { - public float getInterpolation(float input) { - return DOMAIN*(input/=DURATION)*input*input + START; - } - }; - public static final TimeInterpolator easeOut = new TimeInterpolator() { - public float getInterpolation(float input) { - return DOMAIN*((input=input/DURATION-1)*input*input + 1) + START; - } - }; - public static final TimeInterpolator easeInOut = new TimeInterpolator() { - public float getInterpolation(float input) { - return ((input/=DURATION/2) < 1.0f) ? - (DOMAIN/2*input*input*input + START) - : (DOMAIN/2*((input-=2)*input*input + 2) + START); - } - }; - } - - static class Quad { - public static final TimeInterpolator easeIn = new TimeInterpolator() { - public float getInterpolation (float input) { - return DOMAIN*(input/=DURATION)*input + START; - } - }; - public static final TimeInterpolator easeOut = new TimeInterpolator() { - public float getInterpolation(float input) { - return -DOMAIN *(input/=DURATION)*(input-2) + START; - } - }; - public static final TimeInterpolator easeInOut = new TimeInterpolator() { - public float getInterpolation(float input) { - return ((input/=DURATION/2) < 1) ? - (DOMAIN/2*input*input + START) - : (-DOMAIN/2 * ((--input)*(input-2) - 1) + START); - } - }; - } - - static class Quart { - public static final TimeInterpolator easeIn = new TimeInterpolator() { - public float getInterpolation(float input) { - return DOMAIN*(input/=DURATION)*input*input*input + START; - } - }; - public static final TimeInterpolator easeOut = new TimeInterpolator() { - public float getInterpolation(float input) { - return -DOMAIN * ((input=input/DURATION-1)*input*input*input - 1) + START; - } - }; - public static final TimeInterpolator easeInOut = new TimeInterpolator() { - public float getInterpolation(float input) { - return ((input/=DURATION/2) < 1) ? - (DOMAIN/2*input*input*input*input + START) - : (-DOMAIN/2 * ((input-=2)*input*input*input - 2) + START); - } - }; - } - - static class Quint { - public static final TimeInterpolator easeIn = new TimeInterpolator() { - public float getInterpolation(float input) { - return DOMAIN*(input/=DURATION)*input*input*input*input + START; - } - }; - public static final TimeInterpolator easeOut = new TimeInterpolator() { - public float getInterpolation(float input) { - return DOMAIN*((input=input/DURATION-1)*input*input*input*input + 1) + START; - } - }; - public static final TimeInterpolator easeInOut = new TimeInterpolator() { - public float getInterpolation(float input) { - return ((input/=DURATION/2) < 1) ? - (DOMAIN/2*input*input*input*input*input + START) - : (DOMAIN/2*((input-=2)*input*input*input*input + 2) + START); - } - }; - } - - static class Sine { - public static final TimeInterpolator easeIn = new TimeInterpolator() { - public float getInterpolation(float input) { - return -DOMAIN * (float) Math.cos(input / DURATION * (Math.PI / 2)) + DOMAIN + START; - } - }; - public static final TimeInterpolator easeOut = new TimeInterpolator() { - public float getInterpolation(float input) { - return DOMAIN * (float) Math.sin(input / DURATION * (Math.PI / 2)) + START; - } - }; - public static final TimeInterpolator easeInOut = new TimeInterpolator() { - public float getInterpolation(float input) { - return -DOMAIN/2 * ((float) Math.cos(Math.PI * input / DURATION) - 1.0f) + START; - } - }; - } - -} diff --git a/src/org/thoughtcrime/securesms/components/multiwaveview/MultiWaveView.java b/src/org/thoughtcrime/securesms/components/multiwaveview/MultiWaveView.java deleted file mode 100644 index f3e33cdf8..000000000 --- a/src/org/thoughtcrime/securesms/components/multiwaveview/MultiWaveView.java +++ /dev/null @@ -1,1039 +0,0 @@ -/* - * Copyright (C) 2011 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 org.thoughtcrime.securesms.components.multiwaveview; - -//import android.animation.Animator; -//import android.animation.Animator.AnimatorListener; -//import android.animation.AnimatorListenerAdapter; -//import android.animation.TimeInterpolator; -//import android.animation.ValueAnimator; -//import android.animation.ValueAnimator.AnimatorUpdateListener; -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.RectF; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.os.Vibrator; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.util.Log; -import android.util.TypedValue; -import android.view.MotionEvent; -import android.view.View; -import android.view.accessibility.AccessibilityEvent; - - -import org.thoughtcrime.securesms.R; - -import java.util.ArrayList; - -/** - * A special widget containing a center and outer ring. Moving the center ring to the outer ring - * causes an event that can be caught by implementing OnTriggerListener. - */ -public class MultiWaveView extends View { - - private static final int ANIMATION_VERSION = Build.VERSION_CODES.JELLY_BEAN; - - private static final String TAG = "MultiWaveView"; - private static final boolean DEBUG = false; - - // Wave state machine - private static final int STATE_IDLE = 0; - private static final int STATE_FIRST_TOUCH = 1; - private static final int STATE_TRACKING = 2; - private static final int STATE_SNAP = 3; - private static final int STATE_FINISH = 4; - - // Animation properties. - private static final float SNAP_MARGIN_DEFAULT = 20.0f; // distance to ring before we snap to it - - public interface OnTriggerListener { - int NO_HANDLE = 0; - int CENTER_HANDLE = 1; - public void onGrabbed(View v, int handle); - public void onReleased(View v, int handle); - public void onTrigger(View v, int target); - public void onGrabbedStateChange(View v, int handle); - } - - // Tune-able parameters - private static final int CHEVRON_INCREMENTAL_DELAY = 160; - private static final int CHEVRON_ANIMATION_DURATION = 850; - private static final int RETURN_TO_HOME_DELAY = 1200; - private static final int RETURN_TO_HOME_DURATION = 300; - private static final int HIDE_ANIMATION_DELAY = 200; - private static final int HIDE_ANIMATION_DURATION = RETURN_TO_HOME_DELAY; - private static final int SHOW_ANIMATION_DURATION = 0; - private static final int SHOW_ANIMATION_DELAY = 0; - private static final float TAP_RADIUS_SCALE_ACCESSIBILITY_ENABLED = 1.3f; -// private TimeInterpolator mChevronAnimationInterpolator = Ease.Quad.easeOut; - - private ArrayList mTargetDrawables = new ArrayList(); - private ArrayList mChevronDrawables = new ArrayList(); - private ArrayList mChevronAnimations = new ArrayList(); - private ArrayList mTargetAnimations = new ArrayList(); - private ArrayList mTargetDescriptions; - private ArrayList mDirectionDescriptions; - private Tweener mHandleAnimation; - private OnTriggerListener mOnTriggerListener; - private TargetDrawable mHandleDrawable; - private TargetDrawable mOuterRing; - private Vibrator mVibrator; - private Context mContext; - - private int mFeedbackCount = 3; - private int mVibrationDuration = 0; - private int mGrabbedState; - private int mActiveTarget = -1; - private float mTapRadius; - private float mWaveCenterX; - private float mWaveCenterY; - private float mVerticalOffset; - private float mHorizontalOffset; - private float mOuterRadius = 0.0f; - private float mHitRadius = 0.0f; - private float mSnapMargin = 0.0f; - private boolean mDragging; - private int mNewTargetResources; - -// private AnimatorListener mResetListener = new AnimatorListenerAdapter() { -// @Override -// public void onAnimationEnd(Animator animator) { -// switchToState(STATE_IDLE, mWaveCenterX, mWaveCenterY); -// } -// }; -// -// private AnimatorListener mResetListenerWithPing = new AnimatorListenerAdapter() { -// @Override -// public void onAnimationEnd(Animator animator) { -// ping(); -// switchToState(STATE_IDLE, mWaveCenterX, mWaveCenterY); -// } -// }; -// -// private AnimatorUpdateListener mUpdateListener = new AnimatorUpdateListener() { -// public void onAnimationUpdate(ValueAnimator animation) { -// invalidateGlobalRegion(mHandleDrawable); -// invalidate(); -// } -// }; - - private AnimationsWrapper animationsWrapper; - private boolean mAnimatingTargets; -// private AnimatorListener mTargetUpdateListener = new AnimatorListenerAdapter() { -// @Override -// public void onAnimationEnd(Animator animator) { -// if (mNewTargetResources != 0) { -// internalSetTargetResources(mNewTargetResources); -// mNewTargetResources = 0; -// hideTargets(false); -// } -// mAnimatingTargets = false; -// } -// }; - private int mTargetResourceId; - private int mTargetDescriptionsResourceId; - private int mDirectionDescriptionsResourceId; - - public MultiWaveView(Context context) { - this(context, null); - this.mContext = context.getApplicationContext(); - } - - public MultiWaveView(Context context, AttributeSet attrs) { - super(context, attrs); - this.mContext = context.getApplicationContext(); - Resources res = context.getResources(); - - if (Build.VERSION.SDK_INT >= ANIMATION_VERSION) { - animationsWrapper = new AnimationsWrapper(); - } - - mOuterRadius = res.getDimension(R.dimen.incoming_widget_outer_radius); - mHorizontalOffset = res.getDimension(R.dimen.incoming_widget_horizontal_offset); - mVerticalOffset = res.getDimension(R.dimen.incoming_widget_vertical_offset); - mHitRadius = res.getDimension(R.dimen.incoming_widget_hit_radius); - mSnapMargin = res.getDimension(R.dimen.incoming_widget_snap_margin); - - mVibrationDuration = 20; - mFeedbackCount = 3; - mHandleDrawable = new TargetDrawable(res, R.drawable.redphone_ic_in_call_touch_handle); - mTapRadius = mHandleDrawable.getWidth() / 2; - mOuterRing = new TargetDrawable(res, R.drawable.redphone_ic_lockscreen_outerring); - -// TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MultiWaveView); -// mOuterRadius = a.getDimension(R.styleable.MultiWaveView_outerRadius, mOuterRadius); -// mHorizontalOffset = a.getDimension(R.styleable.MultiWaveView_horizontalOffset, -// mHorizontalOffset); -// mVerticalOffset = a.getDimension(R.styleable.MultiWaveView_verticalOffset, -// mVerticalOffset); -// mHitRadius = a.getDimension(R.styleable.MultiWaveView_hitRadius, mHitRadius); -// mSnapMargin = a.getDimension(R.styleable.MultiWaveView_snapMargin, mSnapMargin); -// mVibrationDuration = a.getInt(R.styleable.MultiWaveView_vibrationDuration, -// mVibrationDuration); -// mFeedbackCount = a.getInt(R.styleable.MultiWaveView_feedbackCount, -// mFeedbackCount); -// mHandleDrawable = new TargetDrawable(res, -// a.getDrawable(R.styleable.MultiWaveView_handleDrawable)); -// mTapRadius = mHandleDrawable.getWidth()/2; -// mOuterRing = new TargetDrawable(res, a.getDrawable(R.styleable.MultiWaveView_waveDrawable)); - - // Read chevron animation drawables - final int chevrons[] = { - R.drawable.redphone_ic_lockscreen_chevron_left, - R.drawable.redphone_ic_lockscreen_chevron_right, - R.drawable.redphone_ic_lockscreen_chevron_up, - R.drawable.redphone_ic_lockscreen_chevron_down - }; -// final int chevrons[] = { R.styleable.MultiWaveView_leftChevronDrawable, -// R.styleable.MultiWaveView_rightChevronDrawable, -// R.styleable.MultiWaveView_topChevronDrawable, -// R.styleable.MultiWaveView_bottomChevronDrawable -// }; - for (int chevron : chevrons) { - Drawable chevronDrawable = res.getDrawable(chevron); - for (int i = 0; i < mFeedbackCount; i++) { - mChevronDrawables.add( - chevronDrawable != null ? new TargetDrawable(res, chevronDrawable) : null); - } - } - - - internalSetTargetResources(R.array.incoming_call_widget_targets); - setTargetDescriptionsResourceId(R.array.incoming_call_widget_target_descriptions); - setDirectionDescriptionsResourceId(R.array.incoming_call_widget_direction_descriptions); - - setVibrateEnabled(mVibrationDuration > 0); - } - - private void dump() { - Log.v(TAG, "Outer Radius = " + mOuterRadius); - Log.v(TAG, "HitRadius = " + mHitRadius); - Log.v(TAG, "SnapMargin = " + mSnapMargin); - Log.v(TAG, "FeedbackCount = " + mFeedbackCount); - Log.v(TAG, "VibrationDuration = " + mVibrationDuration); - Log.v(TAG, "TapRadius = " + mTapRadius); - Log.v(TAG, "WaveCenterX = " + mWaveCenterX); - Log.v(TAG, "WaveCenterY = " + mWaveCenterY); - Log.v(TAG, "HorizontalOffset = " + mHorizontalOffset); - Log.v(TAG, "VerticalOffset = " + mVerticalOffset); - } - - @Override - protected int getSuggestedMinimumWidth() { - // View should be large enough to contain the background + target drawable on either edge - return mOuterRing.getWidth() - + (mTargetDrawables.size() > 0 ? (mTargetDrawables.get(0).getWidth()/2) : 0); - } - - @Override - protected int getSuggestedMinimumHeight() { - // View should be large enough to contain the unlock ring + target drawable on either edge - return mOuterRing.getHeight() - + (mTargetDrawables.size() > 0 ? (mTargetDrawables.get(0).getHeight()/2) : 0); - } - - private int resolveMeasured(int measureSpec, int desired) - { - int result = 0; - int specSize = MeasureSpec.getSize(measureSpec); - switch (MeasureSpec.getMode(measureSpec)) { - case MeasureSpec.UNSPECIFIED: - result = desired; - break; - case MeasureSpec.AT_MOST: - result = Math.min(specSize, desired); - break; - case MeasureSpec.EXACTLY: - default: - result = specSize; - } - return result; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - final int minimumWidth = getSuggestedMinimumWidth(); - final int minimumHeight = getSuggestedMinimumHeight(); - int viewWidth = resolveMeasured(widthMeasureSpec, minimumWidth); - int viewHeight = resolveMeasured(heightMeasureSpec, minimumHeight); - setMeasuredDimension(viewWidth, viewHeight); - } - - private void switchToState(int state, float x, float y) { - switch (state) { - case STATE_IDLE: - deactivateTargets(); - mHandleDrawable.setState(TargetDrawable.STATE_INACTIVE); - break; - - case STATE_FIRST_TOUCH: - stopHandleAnimation(); - deactivateTargets(); - showTargets(true); - mHandleDrawable.setState(TargetDrawable.STATE_ACTIVE); - setGrabbedState(OnTriggerListener.CENTER_HANDLE); - break; - - case STATE_TRACKING: - break; - - case STATE_SNAP: - break; - - case STATE_FINISH: - doFinish(); - break; - } - } - - /** - * Animation used to attract user's attention to the target button. - * Assumes mChevronDrawables is an a list with an even number of chevrons filled with - * mFeedbackCount items in the order: left, right, top, bottom. - */ - private void startChevronAnimation() { - if (Build.VERSION.SDK_INT < ANIMATION_VERSION) return; - - final float r = mHandleDrawable.getWidth() * 0.4f; - final float chevronAnimationDistance = mOuterRadius * 0.9f; - final float from[][] = { - {mWaveCenterX - r, mWaveCenterY}, // left - {mWaveCenterX + r, mWaveCenterY}, // right - {mWaveCenterX, mWaveCenterY - r}, // top - {mWaveCenterX, mWaveCenterY + r} }; // bottom - final float to[][] = { - {mWaveCenterX - chevronAnimationDistance, mWaveCenterY}, // left - {mWaveCenterX + chevronAnimationDistance, mWaveCenterY}, // right - {mWaveCenterX, mWaveCenterY - chevronAnimationDistance}, // top - {mWaveCenterX, mWaveCenterY + chevronAnimationDistance} }; // bottom - - mChevronAnimations.clear(); - final float startScale = 0.5f; - final float endScale = 2.0f; - for (int direction = 0; direction < 4; direction++) { - for (int count = 0; count < mFeedbackCount; count++) { - int delay = count * CHEVRON_INCREMENTAL_DELAY; - final TargetDrawable icon = mChevronDrawables.get(direction*mFeedbackCount + count); - if (icon == null) { - continue; - } - mChevronAnimations.add(Tweener.to(icon, CHEVRON_ANIMATION_DURATION, - "ease", animationsWrapper.mChevronAnimationInterpolator, - "delay", delay, - "x", new float[] { from[direction][0], to[direction][0] }, - "y", new float[] { from[direction][1], to[direction][1] }, - "alpha", new float[] {1.0f, 0.0f}, - "scaleX", new float[] {startScale, endScale}, - "scaleY", new float[] {startScale, endScale}, - "onUpdate", animationsWrapper.mUpdateListener)); - } - } - } - - private void stopChevronAnimation() { - if (Build.VERSION.SDK_INT < ANIMATION_VERSION) return; - - for (Tweener anim : mChevronAnimations) { - anim.animator.end(); - } - mChevronAnimations.clear(); - } - - private void stopHandleAnimation() { - if (Build.VERSION.SDK_INT < ANIMATION_VERSION) return; - - if (mHandleAnimation != null) { - mHandleAnimation.animator.end(); - mHandleAnimation = null; - } - } - - private void deactivateTargets() { - for (TargetDrawable target : mTargetDrawables) { - target.setState(TargetDrawable.STATE_INACTIVE); - } - mActiveTarget = -1; - } - - void invalidateGlobalRegion(TargetDrawable drawable) { - int width = drawable.getWidth(); - int height = drawable.getHeight(); - RectF childBounds = new RectF(0, 0, width, height); - childBounds.offset(drawable.getX() - width/2, drawable.getY() - height/2); - View view = this; - while (view.getParent() != null && view.getParent() instanceof View) { - view = (View) view.getParent(); -// view.getMatrix().mapRect(childBounds); - view.invalidate((int) Math.floor(childBounds.left), - (int) Math.floor(childBounds.top), - (int) Math.ceil(childBounds.right), - (int) Math.ceil(childBounds.bottom)); - } - } - - /** - * Dispatches a trigger event to listener. Ignored if a listener is not set. - * @param whichHandle the handle that triggered the event. - */ - private void dispatchTriggerEvent(int whichHandle) { - vibrate(); - if (mOnTriggerListener != null) { - mOnTriggerListener.onTrigger(this, whichHandle); - } - } - - private void dispatchGrabbedEvent(int whichHandler) { - vibrate(); - if (mOnTriggerListener != null) { - mOnTriggerListener.onGrabbed(this, whichHandler); - } - } - - private void doFinish() { - final int activeTarget = mActiveTarget; - boolean targetHit = activeTarget != -1; - - // Hide unselected targets - hideTargets(true); - - // Highlight the selected one - mHandleDrawable.setAlpha(targetHit ? 0.0f : 1.0f); - if (targetHit) { - mTargetDrawables.get(activeTarget).setState(TargetDrawable.STATE_ACTIVE); - - hideUnselected(activeTarget); - - // Inform listener of any active targets. Typically only one will be active. - if (DEBUG) Log.v(TAG, "Finish with target hit = " + targetHit); - dispatchTriggerEvent(mActiveTarget); - if (Build.VERSION.SDK_INT >= ANIMATION_VERSION) { - mHandleAnimation = Tweener.to(mHandleDrawable, 0, - "ease", Ease.Quart.easeOut, - "delay", RETURN_TO_HOME_DELAY, - "alpha", 1.0f, - "x", mWaveCenterX, - "y", mWaveCenterY, - "onUpdate", animationsWrapper.mUpdateListener, - "onComplete", animationsWrapper.mResetListener); - } else { - mHandleDrawable.setX(mWaveCenterX); - mHandleDrawable.setY(mWaveCenterY); - mHandleDrawable.setState(TargetDrawable.STATE_INACTIVE); - } - } else { - // Animate finger outline back to home position - if (Build.VERSION.SDK_INT >= ANIMATION_VERSION) { - mHandleAnimation = Tweener.to(mHandleDrawable, RETURN_TO_HOME_DURATION, - "ease", Ease.Quart.easeOut, - "delay", 0, - "alpha", 1.0f, - "x", mWaveCenterX, - "y", mWaveCenterY, - "onUpdate", animationsWrapper.mUpdateListener, - "onComplete", mDragging ? animationsWrapper.mResetListenerWithPing : animationsWrapper.mResetListener); - } else { - mHandleDrawable.setX(mWaveCenterX); - mHandleDrawable.setY(mWaveCenterY); - mHandleDrawable.setState(TargetDrawable.STATE_INACTIVE); - } - } - - setGrabbedState(OnTriggerListener.NO_HANDLE); - } - - private void hideUnselected(int active) { - for (int i = 0; i < mTargetDrawables.size(); i++) { - if (i != active) { - mTargetDrawables.get(i).setAlpha(0.0f); - } - } - mOuterRing.setAlpha(0.0f); - } - - private void hideTargets(boolean animate) { - if (mTargetAnimations.size() > 0) { - stopTargetAnimation(); - } - // Note: these animations should complete at the same time so that we can swap out - // the target assets asynchronously from the setTargetResources() call. - mAnimatingTargets = animate; - if (animate && Build.VERSION.SDK_INT >= ANIMATION_VERSION) { - final int duration = animate ? HIDE_ANIMATION_DURATION : 0; - for (TargetDrawable target : mTargetDrawables) { - target.setState(TargetDrawable.STATE_INACTIVE); - mTargetAnimations.add(Tweener.to(target, duration, - "alpha", 0.0f, - "delay", HIDE_ANIMATION_DELAY, - "onUpdate", animationsWrapper.mUpdateListener)); - } - mTargetAnimations.add(Tweener.to(mOuterRing, duration, - "alpha", 0.0f, - "delay", HIDE_ANIMATION_DELAY, - "onUpdate", animationsWrapper.mUpdateListener, - "onComplete", animationsWrapper.mTargetUpdateListener)); - } else { - for (TargetDrawable target : mTargetDrawables) { - target.setState(TargetDrawable.STATE_INACTIVE); - target.setAlpha(0.0f); - } - mOuterRing.setAlpha(0.0f); - } - } - - private void showTargets(boolean animate) { - if (mTargetAnimations.size() > 0) { - stopTargetAnimation(); - } - mAnimatingTargets = animate; - if (animate && Build.VERSION.SDK_INT >= ANIMATION_VERSION) { - for (TargetDrawable target : mTargetDrawables) { - target.setState(TargetDrawable.STATE_INACTIVE); - mTargetAnimations.add(Tweener.to(target, SHOW_ANIMATION_DURATION, - "alpha", 1.0f, - "delay", SHOW_ANIMATION_DELAY, - "onUpdate", animationsWrapper.mUpdateListener)); - } - mTargetAnimations.add(Tweener.to(mOuterRing, SHOW_ANIMATION_DURATION, - "alpha", 1.0f, - "delay", SHOW_ANIMATION_DELAY, - "onUpdate", animationsWrapper.mUpdateListener, - "onComplete", animationsWrapper.mTargetUpdateListener)); - } else { - for (TargetDrawable target : mTargetDrawables) { - target.setState(TargetDrawable.STATE_INACTIVE); - target.setAlpha(1.0f); - } - mOuterRing.setAlpha(1.0f); - } - } - - private void stopTargetAnimation() { - if (Build.VERSION.SDK_INT < ANIMATION_VERSION) return; - - for (Tweener anim : mTargetAnimations) { - anim.animator.end(); - } - mTargetAnimations.clear(); - } - - private void vibrate() { - if (mVibrator != null) { - mVibrator.vibrate(mVibrationDuration); - } - } - - private void internalSetTargetResources(int resourceId) { - Resources res = getContext().getResources(); - TypedArray array = res.obtainTypedArray(resourceId); - int count = array.length(); - ArrayList targetDrawables = new ArrayList(count); - for (int i = 0; i < count; i++) { - Drawable drawable = array.getDrawable(i); - targetDrawables.add(new TargetDrawable(res, drawable)); - } - array.recycle(); - mTargetResourceId = resourceId; - mTargetDrawables = targetDrawables; - updateTargetPositions(); - } - - /** - * Loads an array of drawables from the given resourceId. - * - * @param resourceId - */ - public void setTargetResources(int resourceId) { - if (mAnimatingTargets) { - // postpone this change until we return to the initial state - mNewTargetResources = resourceId; - } else { - internalSetTargetResources(resourceId); - } - } - - public int getTargetResourceId() { - return mTargetResourceId; - } - - /** - * Sets the resource id specifying the target descriptions for accessibility. - * - * @param resourceId The resource id. - */ - public void setTargetDescriptionsResourceId(int resourceId) { - mTargetDescriptionsResourceId = resourceId; - if (mTargetDescriptions != null) { - mTargetDescriptions.clear(); - } - } - - /** - * Gets the resource id specifying the target descriptions for accessibility. - * - * @return The resource id. - */ - public int getTargetDescriptionsResourceId() { - return mTargetDescriptionsResourceId; - } - - /** - * Sets the resource id specifying the target direction descriptions for accessibility. - * - * @param resourceId The resource id. - */ - public void setDirectionDescriptionsResourceId(int resourceId) { - mDirectionDescriptionsResourceId = resourceId; - if (mDirectionDescriptions != null) { - mDirectionDescriptions.clear(); - } - } - - /** - * Gets the resource id specifying the target direction descriptions. - * - * @return The resource id. - */ - public int getDirectionDescriptionsResourceId() { - return mDirectionDescriptionsResourceId; - } - - /** - * Enable or disable vibrate on touch. - * - * @param enabled - */ - public void setVibrateEnabled(boolean enabled) { - if (enabled && mVibrator == null) { - mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE); - } else { - mVibrator = null; - } - } - - /** - * Starts chevron animation. Example use case: show chevron animation whenever the phone rings - * or the user touches the screen. - * - */ - public void ping() { - if (Build.VERSION.SDK_INT >= ANIMATION_VERSION) { - stopChevronAnimation(); - startChevronAnimation(); - } - } - - /** - * Resets the widget to default state and cancels all animation. If animate is 'true', will - * animate objects into place. Otherwise, objects will snap back to place. - * - * @param animate - */ - public void reset(boolean animate) { - stopChevronAnimation(); - stopHandleAnimation(); - stopTargetAnimation(); - hideChevrons(); - hideTargets(animate); - mHandleDrawable.setX(mWaveCenterX); - mHandleDrawable.setY(mWaveCenterY); - mHandleDrawable.setState(TargetDrawable.STATE_INACTIVE); - - if (Build.VERSION.SDK_INT >= ANIMATION_VERSION) - Tweener.reset(); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - final int action = event.getAction(); - - boolean handled = false; - switch (action) { - case MotionEvent.ACTION_DOWN: - handleDown(event); - handled = true; - break; - - case MotionEvent.ACTION_MOVE: - handleMove(event); - handled = true; - break; - - case MotionEvent.ACTION_UP: - handleMove(event); - handleUp(event); - handled = true; - break; - - case MotionEvent.ACTION_CANCEL: - handleMove(event); - handled = true; - break; - } - invalidate(); - return handled ? true : super.onTouchEvent(event); - } - - private void moveHandleTo(float x, float y, boolean animate) { - // TODO: animate the handle based on the current state/position - mHandleDrawable.setX(x); - mHandleDrawable.setY(y); - } - - private void handleDown(MotionEvent event) { - if (!trySwitchToFirstTouchState(event)) { - mDragging = false; - stopTargetAnimation(); - ping(); - } - } - - private void handleUp(MotionEvent event) { - if (DEBUG && mDragging) Log.v(TAG, "** Handle RELEASE"); - switchToState(STATE_FINISH, event.getX(), event.getY()); - } - - private void handleMove(MotionEvent event) { - if (!mDragging) { - trySwitchToFirstTouchState(event); - return; - } - - int activeTarget = -1; - final int historySize = event.getHistorySize(); - for (int k = 0; k < historySize + 1; k++) { - float x = k < historySize ? event.getHistoricalX(k) : event.getX(); - float y = k < historySize ? event.getHistoricalY(k) : event.getY(); - float tx = x - mWaveCenterX; - float ty = y - mWaveCenterY; - float touchRadius = (float) Math.sqrt(dist2(tx, ty)); - final float scale = touchRadius > mOuterRadius ? mOuterRadius / touchRadius : 1.0f; - float limitX = mWaveCenterX + tx * scale; - float limitY = mWaveCenterY + ty * scale; - - boolean singleTarget = mTargetDrawables.size() == 1; - if (singleTarget) { - // Snap to outer ring if there's only one target - float snapRadius = mOuterRadius - mSnapMargin; - if (touchRadius > snapRadius) { - activeTarget = 0; - x = limitX; - y = limitY; - } - } else { - // If there's more than one target, snap to the closest one less than hitRadius away. - float best = Float.MAX_VALUE; - final float hitRadius2 = mHitRadius * mHitRadius; - for (int i = 0; i < mTargetDrawables.size(); i++) { - // Snap to the first target in range - TargetDrawable target = mTargetDrawables.get(i); - float dx = limitX - target.getX(); - float dy = limitY - target.getY(); - float dist2 = dx*dx + dy*dy; - if (target.isValid() && dist2 < hitRadius2 && dist2 < best) { - activeTarget = i; - best = dist2; - } - } - x = limitX; - y = limitY; - } - if (activeTarget != -1) { - switchToState(STATE_SNAP, x,y); - float newX = singleTarget ? limitX : mTargetDrawables.get(activeTarget).getX(); - float newY = singleTarget ? limitY : mTargetDrawables.get(activeTarget).getY(); - moveHandleTo(newX, newY, false); - TargetDrawable currentTarget = mTargetDrawables.get(activeTarget); -// if (currentTarget.hasState(TargetDrawable.STATE_FOCUSED)) { -// currentTarget.setState(TargetDrawable.STATE_FOCUSED); -// mHandleDrawable.setAlpha(0.0f); -// } - } else { - switchToState(STATE_TRACKING, x, y); - moveHandleTo(x, y, false); - mHandleDrawable.setAlpha(1.0f); - } - } - - // Draw handle outside parent's bounds - invalidateGlobalRegion(mHandleDrawable); - - if (mActiveTarget != activeTarget && activeTarget != -1) { - dispatchGrabbedEvent(activeTarget); -// if (AccessibilityManager.getInstance(mContext).isEnabled()) { -// String targetContentDescription = getTargetDescription(activeTarget); -// announceText(targetContentDescription); -// } - } - mActiveTarget = activeTarget; - } - - @Override - public boolean onHoverEvent(MotionEvent event) { -// if (AccessibilityManager.getInstance(mContext).isTouchExplorationEnabled()) { -// final int action = event.getAction(); -// switch (action) { -// case MotionEvent.ACTION_HOVER_ENTER: -// event.setAction(MotionEvent.ACTION_DOWN); -// break; -// case MotionEvent.ACTION_HOVER_MOVE: -// event.setAction(MotionEvent.ACTION_MOVE); -// break; -// case MotionEvent.ACTION_HOVER_EXIT: -// event.setAction(MotionEvent.ACTION_UP); -// break; -// } -// onTouchEvent(event); -// event.setAction(action); -// } - return super.onHoverEvent(event); - } - - /** - * Sets the current grabbed state, and dispatches a grabbed state change - * event to our listener. - */ - private void setGrabbedState(int newState) { - if (newState != mGrabbedState) { - if (newState != OnTriggerListener.NO_HANDLE) { - vibrate(); - } - mGrabbedState = newState; - if (mOnTriggerListener != null) { - mOnTriggerListener.onGrabbedStateChange(this, mGrabbedState); - } - } - } - - private boolean trySwitchToFirstTouchState(MotionEvent event) { - final float x = event.getX(); - final float y = event.getY(); - final float dx = x - mWaveCenterX; - final float dy = y - mWaveCenterY; - if (dist2(dx,dy) <= getScaledTapRadiusSquared()) { - if (DEBUG) Log.v(TAG, "** Handle HIT"); - switchToState(STATE_FIRST_TOUCH, x, y); - moveHandleTo(x, y, false); - mDragging = true; - return true; - } - return false; - } - - private void performInitialLayout(float centerX, float centerY) { - if (mOuterRadius == 0.0f) { - mOuterRadius = 0.5f*(float) Math.sqrt(dist2(centerX, centerY)); - } - if (mHitRadius == 0.0f) { - // Use the radius of inscribed circle of the first target. - mHitRadius = mTargetDrawables.get(0).getWidth() / 2.0f; - } - if (mSnapMargin == 0.0f) { - mSnapMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - SNAP_MARGIN_DEFAULT, getContext().getResources().getDisplayMetrics()); - } - hideChevrons(); - hideTargets(false); - moveHandleTo(centerX, centerY, false); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - final int width = right - left; - final int height = bottom - top; - float newWaveCenterX = mHorizontalOffset + Math.max(width, mOuterRing.getWidth()) / 2; - float newWaveCenterY = mVerticalOffset + Math.max(height, mOuterRing.getHeight()) / 2; - if (newWaveCenterX != mWaveCenterX || newWaveCenterY != mWaveCenterY) { - if (mWaveCenterX == 0 && mWaveCenterY == 0) { - performInitialLayout(newWaveCenterX, newWaveCenterY); - } - mWaveCenterX = newWaveCenterX; - mWaveCenterY = newWaveCenterY; - - mOuterRing.setX(mWaveCenterX); - mOuterRing.setY(Math.max(mWaveCenterY, mWaveCenterY)); - - updateTargetPositions(); - } - if (DEBUG) dump(); - } - - private void updateTargetPositions() { - // Reposition the target drawables if the view changed. - for (int i = 0; i < mTargetDrawables.size(); i++) { - final TargetDrawable targetIcon = mTargetDrawables.get(i); - double angle = -2.0f * Math.PI * i / mTargetDrawables.size(); - float xPosition = mWaveCenterX + mOuterRadius * (float) Math.cos(angle); - float yPosition = mWaveCenterY + mOuterRadius * (float) Math.sin(angle); - targetIcon.setX(xPosition); - targetIcon.setY(yPosition); - } - } - - private void hideChevrons() { - for (TargetDrawable chevron : mChevronDrawables) { - if (chevron != null) { - chevron.setAlpha(0.0f); - } - } - } - - @Override - protected void onDraw(Canvas canvas) { - mOuterRing.draw(canvas); - for (TargetDrawable target : mTargetDrawables) { - if (target != null) { - target.draw(canvas); - } - } - for (TargetDrawable target : mChevronDrawables) { - if (target != null) { - target.draw(canvas); - } - } - mHandleDrawable.draw(canvas); - } - - public void setOnTriggerListener(OnTriggerListener listener) { - mOnTriggerListener = listener; - } - - private float square(float d) { - return d * d; - } - - private float dist2(float dx, float dy) { - return dx*dx + dy*dy; - } - - private float getScaledTapRadiusSquared() { - final float scaledTapRadius; -// if (AccessibilityManager.getInstance(mContext).isEnabled()) { -// scaledTapRadius = TAP_RADIUS_SCALE_ACCESSIBILITY_ENABLED * mTapRadius; -// } else { - scaledTapRadius = mTapRadius; -// } - return square(scaledTapRadius); - } - - private void announceTargets() { - StringBuilder utterance = new StringBuilder(); - final int targetCount = mTargetDrawables.size(); - for (int i = 0; i < targetCount; i++) { - String targetDescription = getTargetDescription(i); - String directionDescription = getDirectionDescription(i); - if (!TextUtils.isEmpty(targetDescription) - && !TextUtils.isEmpty(directionDescription)) { - String text = String.format(directionDescription, targetDescription); - utterance.append(text); - } - if (utterance.length() > 0) { - announceText(utterance.toString()); - } - } - } - - private void announceText(String text) { - setContentDescription(text); - sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); - setContentDescription(null); - } - - private String getTargetDescription(int index) { - if (mTargetDescriptions == null || mTargetDescriptions.isEmpty()) { - mTargetDescriptions = loadDescriptions(mTargetDescriptionsResourceId); - if (mTargetDrawables.size() != mTargetDescriptions.size()) { - Log.w(TAG, "The number of target drawables must be" - + " euqal to the number of target descriptions."); - return null; - } - } - return mTargetDescriptions.get(index); - } - - private String getDirectionDescription(int index) { - if (mDirectionDescriptions == null || mDirectionDescriptions.isEmpty()) { - mDirectionDescriptions = loadDescriptions(mDirectionDescriptionsResourceId); - if (mTargetDrawables.size() != mDirectionDescriptions.size()) { - Log.w(TAG, "The number of target drawables must be" - + " euqal to the number of direction descriptions."); - return null; - } - } - return mDirectionDescriptions.get(index); - } - - private ArrayList loadDescriptions(int resourceId) { - TypedArray array = getContext().getResources().obtainTypedArray(resourceId); - final int count = array.length(); - ArrayList targetContentDescriptions = new ArrayList(count); - for (int i = 0; i < count; i++) { - String contentDescription = array.getString(i); - targetContentDescriptions.add(contentDescription); - } - array.recycle(); - return targetContentDescriptions; - } - - private class AnimationsWrapper { - private android.animation.TimeInterpolator mChevronAnimationInterpolator = Ease.Quad.easeOut; - - @SuppressLint("NewApi") - private android.animation.Animator.AnimatorListener mResetListener = new android.animation.AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(android.animation.Animator animator) { - switchToState(STATE_IDLE, mWaveCenterX, mWaveCenterY); - } - }; - - @SuppressLint("NewApi") - private android.animation.Animator.AnimatorListener mResetListenerWithPing = new android.animation.AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(android.animation.Animator animator) { - ping(); - switchToState(STATE_IDLE, mWaveCenterX, mWaveCenterY); - } - }; - - @SuppressLint("NewApi") - private android.animation.ValueAnimator.AnimatorUpdateListener mUpdateListener = new android.animation.ValueAnimator.AnimatorUpdateListener() { - public void onAnimationUpdate(android.animation.ValueAnimator animation) { - invalidateGlobalRegion(mHandleDrawable); - invalidate(); - } - }; - - @SuppressLint("NewApi") - private android.animation.Animator.AnimatorListener mTargetUpdateListener = new android.animation.AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(android.animation.Animator animator) { - if (mNewTargetResources != 0) { - internalSetTargetResources(mNewTargetResources); - mNewTargetResources = 0; - hideTargets(false); - } - mAnimatingTargets = false; - } - }; - } - -} diff --git a/src/org/thoughtcrime/securesms/components/multiwaveview/TargetDrawable.java b/src/org/thoughtcrime/securesms/components/multiwaveview/TargetDrawable.java deleted file mode 100644 index 6e1029d43..000000000 --- a/src/org/thoughtcrime/securesms/components/multiwaveview/TargetDrawable.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright (C) 2011 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 org.thoughtcrime.securesms.components.multiwaveview; - - -import android.content.res.Resources; -import android.graphics.Canvas; -import android.graphics.ColorFilter; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.StateListDrawable; - -public class TargetDrawable { - private static final String TAG = "TargetDrawable"; - private static final boolean DEBUG = false; - - public static final int[] STATE_ACTIVE = - { android.R.attr.state_enabled, android.R.attr.state_active }; - public static final int[] STATE_INACTIVE = - { android.R.attr.state_enabled, -android.R.attr.state_active }; - public static final int[] STATE_FOCUSED = - { android.R.attr.state_enabled, android.R.attr.state_focused }; - - private float mTranslationX = 0.0f; - private float mTranslationY = 0.0f; - private float mScaleX = 1.0f; - private float mScaleY = 1.0f; - private float mAlpha = 1.0f; - private Drawable mDrawable; - - /* package */ static class DrawableWithAlpha extends Drawable { - private float mAlpha = 1.0f; - private Drawable mRealDrawable; - public DrawableWithAlpha(Drawable realDrawable) { - mRealDrawable = realDrawable; - } - public void setAlpha(float alpha) { - mAlpha = alpha; - } - public int getAlpha() { - return (int)(mAlpha * 255); - } - @Override - public void draw(Canvas canvas) { - mRealDrawable.setAlpha((int) Math.round(mAlpha * 255f)); - mRealDrawable.draw(canvas); - } - @Override - public void setAlpha(int alpha) { - mRealDrawable.setAlpha(alpha); - } - @Override - public void setColorFilter(ColorFilter cf) { - mRealDrawable.setColorFilter(cf); - } - @Override - public int getOpacity() { - return mRealDrawable.getOpacity(); - } - } - - public TargetDrawable(Resources res, int resId) { - this(res, resId == 0 ? null : res.getDrawable(resId)); - } - - public TargetDrawable(Resources res, Drawable drawable) { - // Mutate the drawable so we can animate shared drawable properties. - mDrawable = drawable != null ? drawable.mutate() : null; - resizeDrawables(); - setState(STATE_INACTIVE); - } - - public void setState(int [] state) { - if (mDrawable instanceof StateListDrawable) { - StateListDrawable d = (StateListDrawable) mDrawable; - d.setState(state); - } - } - -// public boolean hasState(int [] state) { -// if (mDrawable instanceof StateListDrawable) { -// StateListDrawable d = (StateListDrawable) mDrawable; -// // TODO: this doesn't seem to work -// return d.getStateDrawableIndex(state) != -1; -// } -// return false; -// } - - /** - * Returns true if the drawable is a StateListDrawable and is in the focused state. - * - * @return - */ - public boolean isActive() { - if (mDrawable instanceof StateListDrawable) { - StateListDrawable d = (StateListDrawable) mDrawable; - int[] states = d.getState(); - for (int i = 0; i < states.length; i++) { - if (states[i] == android.R.attr.state_focused) { - return true; - } - } - } - return false; - } - - /** - * Returns true if this target is enabled. Typically an enabled target contains a valid - * drawable in a valid state. Currently all targets with valid drawables are valid. - * - * @return - */ - public boolean isValid() { - return mDrawable != null; - } - - /** - * Makes drawables in a StateListDrawable all the same dimensions. - * If not a StateListDrawable, then justs sets the bounds to the intrinsic size of the - * drawable. - */ - - private void resizeDrawables() { - if (mDrawable != null) - mDrawable.setBounds(0, 0, mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight()); - } -// private void resizeDrawables() { -// if (mDrawable instanceof StateListDrawable) { -// StateListDrawable d = (StateListDrawable) mDrawable; -// int maxWidth = 0; -// int maxHeight = 0; -// -// for (int i = 0; i < d.getStateCount(); i++) { -// Drawable childDrawable = d.getStateDrawable(i); -// maxWidth = Math.max(maxWidth, childDrawable.getIntrinsicWidth()); -// maxHeight = Math.max(maxHeight, childDrawable.getIntrinsicHeight()); -// } -// if (DEBUG) Log.v(TAG, "union of childDrawable rects " + d + " to: " -// + maxWidth + "x" + maxHeight); -// d.setBounds(0, 0, maxWidth, maxHeight); -// for (int i = 0; i < d.getStateCount(); i++) { -// Drawable childDrawable = d.getStateDrawable(i); -// if (DEBUG) Log.v(TAG, "sizing drawable " + childDrawable + " to: " -// + maxWidth + "x" + maxHeight); -// childDrawable.setBounds(0, 0, maxWidth, maxHeight); -// } -// } else if (mDrawable != null) { -// mDrawable.setBounds(0, 0, -// mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight()); -// } -// } - - public void setX(float x) { - mTranslationX = x; - } - - public void setY(float y) { - mTranslationY = y; - } - - public void setScaleX(float x) { - mScaleX = x; - } - - public void setScaleY(float y) { - mScaleY = y; - } - - public void setAlpha(float alpha) { - mAlpha = alpha; - } - - public float getX() { - return mTranslationX; - } - - public float getY() { - return mTranslationY; - } - - public float getScaleX() { - return mScaleX; - } - - public float getScaleY() { - return mScaleY; - } - - public float getAlpha() { - return mAlpha; - } - - public int getWidth() { - return mDrawable != null ? mDrawable.getIntrinsicWidth() : 0; - } - - public int getHeight() { - return mDrawable != null ? mDrawable.getIntrinsicHeight() : 0; - } - - public void draw(Canvas canvas) { - if (mDrawable == null) { - return; - } - canvas.save(Canvas.MATRIX_SAVE_FLAG); - canvas.translate(mTranslationX, mTranslationY); - canvas.scale(mScaleX, mScaleY); - canvas.translate(-0.5f * getWidth(), -0.5f * getHeight()); - mDrawable.setAlpha((int) Math.round(mAlpha * 255f)); - mDrawable.draw(canvas); - canvas.restore(); - } -} diff --git a/src/org/thoughtcrime/securesms/components/multiwaveview/Tweener.java b/src/org/thoughtcrime/securesms/components/multiwaveview/Tweener.java deleted file mode 100644 index 6015b68b4..000000000 --- a/src/org/thoughtcrime/securesms/components/multiwaveview/Tweener.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright (C) 2011 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 org.thoughtcrime.securesms.components.multiwaveview; - -import android.animation.Animator; -import android.animation.Animator.AnimatorListener; -import android.animation.AnimatorListenerAdapter; -import android.animation.ObjectAnimator; -import android.animation.PropertyValuesHolder; -import android.animation.TimeInterpolator; -import android.animation.ValueAnimator.AnimatorUpdateListener; -import android.annotation.SuppressLint; -import android.util.Log; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map.Entry; - -class Tweener { - private static final String TAG = "Tweener"; - private static final boolean DEBUG = false; - - ObjectAnimator animator; - private static HashMap sTweens = new HashMap(); - - public Tweener(ObjectAnimator anim) { - animator = anim; - } - - private static void remove(Animator animator) { - Iterator> iter = sTweens.entrySet().iterator(); - while (iter.hasNext()) { - Entry entry = iter.next(); - if (entry.getValue().animator == animator) { - if (DEBUG) Log.v(TAG, "Removing tweener " + sTweens.get(entry.getKey()) - + " sTweens.size() = " + sTweens.size()); - iter.remove(); - break; // an animator can only be attached to one object - } - } - } - - @SuppressLint("NewApi") - public static Tweener to(Object object, long duration, Object... vars) { - long delay = 0; - AnimatorUpdateListener updateListener = null; - AnimatorListener listener = null; - TimeInterpolator interpolator = null; - - // Iterate through arguments and discover properties to animate - ArrayList props = new ArrayList(vars.length/2); - for (int i = 0; i < vars.length; i+=2) { - if (!(vars[i] instanceof String)) { - throw new IllegalArgumentException("Key must be a string: " + vars[i]); - } - String key = (String) vars[i]; - Object value = vars[i+1]; - if ("simultaneousTween".equals(key)) { - // TODO - } else if ("ease".equals(key)) { - interpolator = (TimeInterpolator) value; // TODO: multiple interpolators? - } else if ("onUpdate".equals(key) || "onUpdateListener".equals(key)) { - updateListener = (AnimatorUpdateListener) value; - } else if ("onCodeComplete".equals(key) || "onCompleteListener".equals(key)) { - listener = (AnimatorListener) value; - } else if ("delay".equals(key)) { - delay = ((Number) value).longValue(); - } else if ("syncWith".equals(key)) { - // TODO - } else if (value instanceof float[]) { - props.add(PropertyValuesHolder.ofFloat(key, - ((float[]) value)[0], ((float[]) value)[1])); - } else if (value instanceof Number) { - float floatValue = ((Number)value).floatValue(); - props.add(PropertyValuesHolder.ofFloat(key, floatValue)); - } else { - throw new IllegalArgumentException( - "Bad argument for key \"" + key + "\" with value " + value.getClass()); - } - } - - // Re-use existing tween, if present - Tweener tween = sTweens.get(object); - ObjectAnimator anim = null; - if (tween == null) { - anim = ObjectAnimator.ofPropertyValuesHolder(object, - props.toArray(new PropertyValuesHolder[props.size()])); - tween = new Tweener(anim); - sTweens.put(object, tween); - if (DEBUG) Log.v(TAG, "Added new Tweener " + tween); - } else { - anim = sTweens.get(object).animator; - replace(props, object); // Cancel all animators for given object - } - - if (interpolator != null) { - anim.setInterpolator(interpolator); - } - - // Update animation with properties discovered in loop above - anim.setStartDelay(delay); - anim.setDuration(duration); - if (updateListener != null) { - anim.removeAllUpdateListeners(); // There should be only one - anim.addUpdateListener(updateListener); - } - if (listener != null) { - anim.removeAllListeners(); // There should be only one. - anim.addListener(listener); - } - anim.addListener(mCleanupListener); - anim.start(); - - return tween; - } - - Tweener from(Object object, long duration, Object... vars) { - // TODO: for v of vars - // toVars[v] = object[v] - // object[v] = vars[v] - return Tweener.to(object, duration, vars); - } - - // Listener to watch for completed animations and remove them. - @SuppressLint("NewApi") - private static AnimatorListener mCleanupListener = new AnimatorListenerAdapter() { - - @Override - public void onAnimationEnd(Animator animation) { - remove(animation); - } - - @Override - public void onAnimationCancel(Animator animation) { - remove(animation); - } - }; - - public static void reset() { - if (DEBUG) { - Log.v(TAG, "Reset()"); - if (sTweens.size() > 0) { - Log.v(TAG, "Cleaning up " + sTweens.size() + " animations"); - } - } - sTweens.clear(); - } - - @SuppressLint("NewApi") - private static void replace(ArrayList props, Object... args) { - for (final Object killobject : args) { - Tweener tween = sTweens.get(killobject); - if (tween != null) { - tween.animator.cancel(); - if (props != null) { - tween.animator.setValues( - props.toArray(new PropertyValuesHolder[props.size()])); - } else { - sTweens.remove(tween); - } - } - } - } -} - diff --git a/src/org/thoughtcrime/securesms/components/webrtc/WebRtcAnswerDeclineButton.java b/src/org/thoughtcrime/securesms/components/webrtc/WebRtcAnswerDeclineButton.java new file mode 100644 index 000000000..dd0c06bf2 --- /dev/null +++ b/src/org/thoughtcrime/securesms/components/webrtc/WebRtcAnswerDeclineButton.java @@ -0,0 +1,293 @@ +package org.thoughtcrime.securesms.components.webrtc; + + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.ArgbEvaluator; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.graphics.Color; +import android.os.Build; +import android.support.annotation.Nullable; +import android.support.annotation.RequiresApi; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.util.ViewUtil; + +public class WebRtcAnswerDeclineButton extends LinearLayout implements View.OnTouchListener { + + @SuppressWarnings("unused") + private static final String TAG = WebRtcAnswerDeclineButton.class.getSimpleName(); + + private static final int TOTAL_TIME = 1000; + private static final int SHAKE_TIME = 200; + + private static final int UP_TIME = (TOTAL_TIME - SHAKE_TIME) / 2; + private static final int DOWN_TIME = (TOTAL_TIME - SHAKE_TIME) / 2; + private static final int FADE_OUT_TIME = 300; + private static final int FADE_IN_TIME = 100; + private static final int SHIMMER_TOTAL = UP_TIME + SHAKE_TIME; + + private static final int ANSWER_THRESHOLD = 112; + private static final int DECLINE_THRESHOLD = 56; + + private TextView swipeUpText; + private ImageView fab; + private TextView swipeDownText; + + private ImageView arrowOne; + private ImageView arrowTwo; + private ImageView arrowThree; + private ImageView arrowFour; + + private float lastY; + + private boolean animating = false; + private AnimatorSet animatorSet; + private AnswerDeclineListener listener; + + public WebRtcAnswerDeclineButton(Context context) { + super(context); + initialize(); + } + + public WebRtcAnswerDeclineButton(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + initialize(); + } + + public WebRtcAnswerDeclineButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initialize(); + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public WebRtcAnswerDeclineButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + initialize(); + } + + private void initialize() { + setOrientation(LinearLayout.VERTICAL); + setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + + inflate(getContext(), R.layout.webrtc_answer_decline_button, this); + + this.swipeUpText = findViewById(R.id.swipe_up_text); + this.fab = findViewById(R.id.answer); + this.swipeDownText = findViewById(R.id.swipe_down_text); + + this.arrowOne = findViewById(R.id.arrow_one); + this.arrowTwo = findViewById(R.id.arrow_two); + this.arrowThree = findViewById(R.id.arrow_three); + this.arrowFour = findViewById(R.id.arrow_four); + + this.fab.setOnTouchListener(this); + } + + public void startRingingAnimation() { + if (!animating) { + animating = true; + animateElements(0); + } + } + + public void stopRingingAnimation() { + if (animating) { + animating = false; + resetElements(); + } + } + + public void setAnswerDeclineListener(AnswerDeclineListener listener) { + this.listener = listener; + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + resetElements(); + swipeUpText.animate().alpha(0).setDuration(200).start(); + swipeDownText.animate().alpha(0).setDuration(200).start(); + lastY = event.getRawY(); + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + swipeUpText.clearAnimation(); + swipeDownText.clearAnimation(); + swipeUpText.setAlpha(1); + swipeDownText.setAlpha(1); + fab.setRotation(0); + + if (Build.VERSION.SDK_INT >= 21) { + fab.getDrawable().setTint(getResources().getColor(R.color.green_600)); + fab.getBackground().setTint(Color.WHITE); + } + + animating = true; + animateElements(0); + break; + case MotionEvent.ACTION_MOVE: + float difference = event.getRawY() - lastY; + + float differenceThreshold; + float percentageToThreshold; + int backgroundColor; + int foregroundColor; + + if (difference <= 0) { + differenceThreshold = ViewUtil.dpToPx(getContext(), ANSWER_THRESHOLD); + percentageToThreshold = Math.min(1, (difference * -1) / differenceThreshold); + backgroundColor = (int) new ArgbEvaluator().evaluate(percentageToThreshold, getResources().getColor(R.color.green_100), getResources().getColor(R.color.green_600)); + + if (percentageToThreshold > 0.5) { + foregroundColor = Color.WHITE; + } else { + foregroundColor = getResources().getColor(R.color.green_600); + } + + fab.setTranslationY(difference); + + if (percentageToThreshold == 1 && listener != null) listener.onAnswered(); + } else { + differenceThreshold = ViewUtil.dpToPx(getContext(), DECLINE_THRESHOLD); + percentageToThreshold = Math.min(1, difference / differenceThreshold); + backgroundColor = (int) new ArgbEvaluator().evaluate(percentageToThreshold, getResources().getColor(R.color.red_100), getResources().getColor(R.color.red_600)); + + if (percentageToThreshold > 0.5) { + foregroundColor = Color.WHITE; + } else { + foregroundColor = getResources().getColor(R.color.green_600); + } + + fab.setRotation(135 * percentageToThreshold); + + if (percentageToThreshold == 1 && listener != null) listener.onDeclined(); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + fab.getBackground().setTint(backgroundColor); + fab.getDrawable().setTint(foregroundColor); + } + + break; + } + + return true; + } + + private void animateElements(int delay) { + ObjectAnimator fabUp = getUpAnimation(fab); + ObjectAnimator fabDown = getDownAnimation(fab); + ObjectAnimator fabShake = getShakeAnimation(fab); + + animatorSet = new AnimatorSet(); + animatorSet.play(fabUp).with(getUpAnimation(swipeUpText)); + animatorSet.play(fabShake).after(fabUp); + animatorSet.play(fabDown).with(getDownAnimation(swipeUpText)).after(fabShake); + + animatorSet.play(getFadeOut(swipeDownText)).with(fabUp); + animatorSet.play(getFadeIn(swipeDownText)).after(fabDown); + + animatorSet.play(getShimmer(arrowFour, arrowThree, arrowTwo, arrowOne)); + + animatorSet.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + } + + @Override + public void onAnimationEnd(Animator animation) { + if (animating) animateElements(1000); + } + + @Override + public void onAnimationCancel(Animator animation) {} + @Override + public void onAnimationRepeat(Animator animation) {} + }); + + animatorSet.setStartDelay(delay); + animatorSet.start(); + } + + private Animator getShimmer(View... targets) { + AnimatorSet animatorSet = new AnimatorSet(); + int evenDuration = SHIMMER_TOTAL / targets.length; + int interval = 75; + + for (int i=0;i listener.onClick()); } public void setAcceptIdentityListener(OnClickListener listener) { @@ -217,34 +218,29 @@ public class WebRtcCallScreen extends FrameLayout implements RecipientModifiedLi LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); inflater.inflate(R.layout.webrtc_call_screen, this, true); - this.elapsedTime = (TextView) findViewById(R.id.elapsedTime); - this.photo = (ImageView) findViewById(R.id.photo); - this.localRenderLayout = (PercentFrameLayout) findViewById(R.id.local_render_layout); - this.remoteRenderLayout = (PercentFrameLayout) findViewById(R.id.remote_render_layout); - this.phoneNumber = (TextView) findViewById(R.id.phoneNumber); - this.name = (TextView) findViewById(R.id.name); - this.label = (TextView) findViewById(R.id.label); - this.status = (TextView) findViewById(R.id.callStateLabel); - this.controls = (WebRtcCallControls) findViewById(R.id.inCallControls); - this.endCallButton = (FloatingActionButton) findViewById(R.id.hangup_fab); - this.incomingCallOverlay = (WebRtcIncomingCallOverlay) findViewById(R.id.callControls); + this.elapsedTime = findViewById(R.id.elapsedTime); + this.photo = findViewById(R.id.photo); + this.localRenderLayout = findViewById(R.id.local_render_layout); + this.remoteRenderLayout = findViewById(R.id.remote_render_layout); + this.phoneNumber = findViewById(R.id.phoneNumber); + this.name = findViewById(R.id.name); + this.label = findViewById(R.id.label); + this.status = findViewById(R.id.callStateLabel); + this.controls = findViewById(R.id.inCallControls); + this.endCallButton = findViewById(R.id.hangup_fab); + this.incomingCallButton = findViewById(R.id.answer_decline_button); this.untrustedIdentityContainer = findViewById(R.id.untrusted_layout); - this.untrustedIdentityExplanation = (TextView) findViewById(R.id.untrusted_explanation); - this.acceptIdentityButton = (Button)findViewById(R.id.accept_safety_numbers); - this.cancelIdentityButton = (Button)findViewById(R.id.cancel_safety_numbers); - this.expandedInfo = (RelativeLayout)findViewById(R.id.expanded_info); - this.callHeader = (ViewGroup)findViewById(R.id.call_info_1); + this.untrustedIdentityExplanation = findViewById(R.id.untrusted_explanation); + this.acceptIdentityButton = findViewById(R.id.accept_safety_numbers); + this.cancelIdentityButton = findViewById(R.id.cancel_safety_numbers); + this.expandedInfo = findViewById(R.id.expanded_info); + this.callHeader = findViewById(R.id.call_info_1); this.localRenderLayout.setHidden(true); this.remoteRenderLayout.setHidden(true); this.minimized = false; - this.remoteRenderLayout.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - setMinimized(!minimized); - } - }); + this.remoteRenderLayout.setOnClickListener(v -> setMinimized(!minimized)); } private void setConnected(SurfaceViewRenderer localRenderer, @@ -300,7 +296,7 @@ public class WebRtcCallScreen extends FrameLayout implements RecipientModifiedLi setPersonInfo(recipient); this.status.setText(status); this.untrustedIdentityContainer.setVisibility(View.GONE); - this.endCallButton.setVisibility(View.VISIBLE); + this.endCallButton.show(); } private void setMinimized(boolean minimized) { @@ -315,12 +311,9 @@ public class WebRtcCallScreen extends FrameLayout implements RecipientModifiedLi ViewCompat.animate(callHeader).translationY(0); ViewCompat.animate(status).alpha(1); ViewCompat.animate(endCallButton).translationY(0); - ViewCompat.animate(endCallButton).alpha(1).withEndAction(new Runnable() { - @Override - public void run() { - // Note: This is to work around an Android bug, see #6225 - endCallButton.requestLayout(); - } + ViewCompat.animate(endCallButton).alpha(1).withEndAction(() -> { + // Note: This is to work around an Android bug, see #6225 + endCallButton.requestLayout(); }); this.minimized = false; @@ -336,8 +329,8 @@ public class WebRtcCallScreen extends FrameLayout implements RecipientModifiedLi }); } - public static interface HangupButtonListener { - public void onClick(); + public interface HangupButtonListener { + void onClick(); } diff --git a/src/org/thoughtcrime/securesms/components/webrtc/WebRtcIncomingCallOverlay.java b/src/org/thoughtcrime/securesms/components/webrtc/WebRtcIncomingCallOverlay.java deleted file mode 100644 index 8804fa52a..000000000 --- a/src/org/thoughtcrime/securesms/components/webrtc/WebRtcIncomingCallOverlay.java +++ /dev/null @@ -1,113 +0,0 @@ -package org.thoughtcrime.securesms.components.webrtc; - -import android.content.Context; -import android.support.annotation.Nullable; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.View; -import android.view.animation.Animation; -import android.widget.RelativeLayout; -import android.widget.TextView; - -import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.components.multiwaveview.MultiWaveView; -import org.thoughtcrime.securesms.util.Util; - -/** - * Displays the controls at the bottom of the in-call screen. - * - * @author Moxie Marlinspike - * - */ - -public class WebRtcIncomingCallOverlay extends RelativeLayout { - - private MultiWaveView incomingCallWidget; - private TextView redphoneLabel; - - public WebRtcIncomingCallOverlay(Context context) { - super(context); - initialize(); - } - - public WebRtcIncomingCallOverlay(Context context, AttributeSet attrs) { - super(context, attrs); - initialize(); - } - - public WebRtcIncomingCallOverlay(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - initialize(); - } - - public void setIncomingCall() { - Animation animation = incomingCallWidget.getAnimation(); - - if (animation != null) { - animation.reset(); - incomingCallWidget.clearAnimation(); - } - - incomingCallWidget.reset(false); - incomingCallWidget.setVisibility(View.VISIBLE); - redphoneLabel.setVisibility(View.VISIBLE); - - Util.runOnMainDelayed(new Runnable() { - @Override - public void run() { - if (incomingCallWidget.getVisibility() == View.VISIBLE) { - incomingCallWidget.ping(); - Util.runOnMainDelayed(this, 1200); - } - } - }, 500); - } - - public void setActiveCall() { - incomingCallWidget.setVisibility(View.GONE); - redphoneLabel.setVisibility(View.GONE); - } - - public void setActiveCall(@Nullable String sas) { - setActiveCall(); - } - - public void reset() { - incomingCallWidget.setVisibility(View.GONE); - redphoneLabel.setVisibility(View.GONE); - } - - public void setIncomingCallActionListener(final IncomingCallActionListener listener) { - incomingCallWidget.setOnTriggerListener(new MultiWaveView.OnTriggerListener() { - @Override - public void onTrigger(View v, int target) { - switch (target) { - case 0: listener.onAcceptClick(); break; - case 2: listener.onDenyClick(); break; - } - } - - @Override - public void onReleased(View v, int handle) {} - - @Override - public void onGrabbedStateChange(View v, int handle) {} - - @Override - public void onGrabbed(View v, int handle) {} - }); - } - - private void initialize() { - LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); - inflater.inflate(R.layout.webrtc_incoming_call_overlay, this, true); - - this.incomingCallWidget = (MultiWaveView)findViewById(R.id.incomingCallWidget); - this.redphoneLabel = (TextView)findViewById(R.id.redphone_banner); - } - - public static interface IncomingCallActionListener { - public void onAcceptClick(); - public void onDenyClick(); - } -}