/**
* Copyright (C) 2010-2012 Regis Montoya (aka r3gis - www.r3gis.fr)
* This file is part of CSipSimple.
*
* CSipSimple is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* If you own a pjsip commercial license you can also redistribute it
* and/or modify it under the terms of the GNU Lesser General Public License
* as an android library.
*
* CSipSimple is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CSipSimple. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* This file contains relicensed code from Apache copyright of
* Copyright (C) 2008-2009 The Android Open Source Project
*/
package com.csipsimple.ui.incall.locker.slidingtab;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Vibrator;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.TextView;
import com.actionbarsherlock.internal.utils.UtilityWrapper;
import com.csipsimple.R;
import com.csipsimple.ui.incall.locker.IOnLeftRightChoice;
import com.csipsimple.ui.incall.locker.IOnLeftRightChoice.IOnLeftRightProvider;
import com.csipsimple.ui.incall.locker.IOnLeftRightChoice.TypeOfLock;
import com.csipsimple.ui.incall.locker.LeftRightChooserUtils;
import com.csipsimple.ui.incall.locker.multiwaveview.GlowPadView.OnTriggerListener;
import com.csipsimple.utils.Log;
import java.util.ArrayList;
/**
* A special widget containing two Sliders and a threshold for each. Moving
* either slider beyond the threshold will cause the registered
* OnTriggerListener.onTrigger() to be called with
* {@link OnTriggerListener#LEFT_HANDLE} or
* {@link OnTriggerListener#RIGHT_HANDLE} to be called.
* Deeply inspired from android SlidingTab internal widget but simplified for our use
*
*/
public class SlidingTab extends ViewGroup implements IOnLeftRightProvider {
private static final float TARGET_ZONE = 2.0f / 3.0f;
private static final long VIBRATE_SHORT = 30;
private static final long VIBRATE_LONG = 40;
private IOnLeftRightChoice onTriggerListener;
private boolean triggered = false;
private Vibrator mVibrator;
// used to scale dimensions for bitmaps.
private float density;
private Slider leftSlider, rightSlider, currentSlider;
private boolean tracking;
private float targetZone;
private static final String THIS_FILE = "SlidingTab";
/**
* Simple container class for all things pertinent to a slider. A slider
* consists of 3 Views:
*
* {@link #tab} is the tab shown on the screen in the default state.
* {@link #text} is the view revealed as the user slides the tab out.
* {@link #target} is the target the user must drag the slider past to
* trigger the slider.
*
*/
private static class Slider {
/**
* Tab alignment - determines which side the tab should be drawn on
*/
public static final int ALIGN_LEFT = 0;
public static final int ALIGN_RIGHT = 1;
/**
* States for the view.
*/
private static final int STATE_NORMAL = 0;
private static final int STATE_PRESSED = 1;
private static final int STATE_ACTIVE = 2;
private final ImageView tab;
private final TextView text;
private final ImageView target;
/**
* Constructor
*
* @param parent
* the container view of this one
* @param tabId
* drawable for the tab
* @param barId
* drawable for the bar
* @param targetId
* drawable for the target
*/
Slider(ViewGroup parent, int iconId, int targetId, int barId, int tabId) {
// Create tab
tab = new ImageView(parent.getContext());
tab.setBackgroundResource(tabId);
tab.setImageResource(iconId);
tab.setScaleType(ScaleType.CENTER);
tab.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
// Create hint TextView
text = new TextView(parent.getContext());
text.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
text.setBackgroundResource(barId);
if(!parent.isInEditMode()) {
text.setTextAppearance(parent.getContext(), R.style.TextAppearance_SlidingTabNormal);
}
// Create target
target = new ImageView(parent.getContext());
target.setImageResource(targetId);
target.setScaleType(ScaleType.CENTER);
target.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
target.setVisibility(View.INVISIBLE);
// this needs to be first - relies on painter's algorithm
parent.addView(target);
parent.addView(tab);
parent.addView(text);
}
private void setResources(int iconId, int targetId, int barId, int tabId) {
tab.setImageResource(iconId);
tab.setBackgroundResource(tabId);
text.setBackgroundResource(barId);
target.setImageResource(targetId);
}
private void setDrawables(Drawable iconD, Drawable targetD, Drawable barD, Drawable tabD) {
if(iconD != null) {
tab.setImageDrawable(iconD);
}
if(tabD != null) {
UtilityWrapper.getInstance().setBackgroundDrawable(tab, tabD);
}
if(barD != null) {
UtilityWrapper.getInstance().setBackgroundDrawable(text, barD);
}
if(tabD != null) {
target.setImageDrawable(targetD);
}
}
private void setHintText(int resId) {
text.setText(resId);
}
private void setHintText(String str) {
text.setText(str);
}
private void hide() {
text.setVisibility(View.INVISIBLE);
tab.setVisibility(View.INVISIBLE);
target.setVisibility(View.INVISIBLE);
}
private void setState(int state) {
text.setPressed(state == STATE_PRESSED);
tab.setPressed(state == STATE_PRESSED);
if (state == STATE_ACTIVE) {
final int[] activeState = new int[] { android.R.attr.state_active };
if (text.getBackground().isStateful()) {
text.getBackground().setState(activeState);
}
if (tab.getBackground().isStateful()) {
tab.getBackground().setState(activeState);
}
text.setTextAppearance(text.getContext(), R.style.TextAppearance_SlidingTabActive);
} else {
text.setTextAppearance(text.getContext(), R.style.TextAppearance_SlidingTabNormal);
}
}
private void showTarget() {
target.setVisibility(View.VISIBLE);
}
private void reset() {
setState(STATE_NORMAL);
text.setVisibility(View.VISIBLE);
text.setTextAppearance(text.getContext(), R.style.TextAppearance_SlidingTabNormal);
tab.setVisibility(View.VISIBLE);
target.setVisibility(View.INVISIBLE);
}
/**
* Layout the given widgets within the parent.
*
* @param l
* the parent's left border
* @param t
* the parent's top border
* @param r
* the parent's right border
* @param b
* the parent's bottom border
* @param alignment
* which side to align the widget to
*/
private void layout(int l, int t, int r, int b, int alignment) {
final int handleWidth = tab.getBackground().getIntrinsicWidth();
final int handleHeight = tab.getBackground().getIntrinsicHeight();
final int targetWidth = target.getDrawable().getIntrinsicWidth();
final int targetHeight = target.getDrawable().getIntrinsicHeight();
final int parentWidth = r - l;
final int parentHeight = b - t;
final int leftTarget = (int) (TARGET_ZONE * parentWidth) - targetWidth + handleWidth / 2;
final int rightTarget = (int) ((1.0f - TARGET_ZONE) * parentWidth) - handleWidth / 2;
final int targetTop = (parentHeight - targetHeight) / 2;
final int targetBottom = targetTop + targetHeight;
final int top = (parentHeight - handleHeight) / 2;
final int bottom = (parentHeight + handleHeight) / 2;
if (alignment == ALIGN_LEFT) {
tab.layout(0, top, handleWidth, bottom);
text.layout(0 - parentWidth, top, 0, bottom);
text.setGravity(Gravity.RIGHT);
target.layout(leftTarget, targetTop, leftTarget + targetWidth, targetBottom);
} else {
tab.layout(parentWidth - handleWidth, top, parentWidth, bottom);
text.layout(parentWidth, top, parentWidth + parentWidth, bottom);
target.layout(rightTarget, targetTop, rightTarget + targetWidth, targetBottom);
text.setGravity(Gravity.TOP);
}
}
/*
public int getTabWidth() {
return tab.getBackground().getIntrinsicWidth();
}
*/
public int getTabHeight() {
return tab.getBackground().getIntrinsicHeight();
}
}
public SlidingTab(Context context) {
this(context, null);
}
/**
* Constructor used when this widget is created from a layout file.
*/
public SlidingTab(Context context, AttributeSet attrs) {
super(context, attrs);
density = getResources().getDisplayMetrics().density;
leftSlider = new Slider(this, R.drawable.ic_jog_dial_answer, R.drawable.jog_tab_target_green, R.drawable.jog_tab_bar_left_answer, R.drawable.jog_tab_left_answer);
rightSlider = new Slider(this, R.drawable.ic_jog_dial_decline, R.drawable.jog_tab_target_red, R.drawable.jog_tab_bar_right_decline, R.drawable.jog_tab_right_decline);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
/*
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
throw new RuntimeException("Sliding tab cannot have UNSPECIFIED dimensions");
}
final int leftTabWidth = (int) (density * leftSlider.getTabWidth() + 0.5f);
final int rightTabWidth = (int) (density * rightSlider.getTabWidth() + 0.5f);
*/
final int leftTabHeight = (int) (density * leftSlider.getTabHeight() + 0.5f);
final int rightTabHeight = (int) (density * rightSlider.getTabHeight() + 0.5f);
/*final int width = Math.min(widthSpecSize, leftTabWidth + rightTabWidth);*/
final int height = Math.max(leftTabHeight, rightTabHeight);
setMeasuredDimension(widthSpecSize, height);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
final int action = event.getAction();
final float x = event.getX();
final float y = event.getY();
final Rect frame = new Rect();
View leftHandle = leftSlider.tab;
leftHandle.getHitRect(frame);
boolean leftHit = frame.contains((int) x, (int) y);
View rightHandle = rightSlider.tab;
rightHandle.getHitRect(frame);
boolean rightHit = frame.contains((int) x, (int) y);
if (!tracking && !(leftHit || rightHit)) {
return false;
}
if (action == MotionEvent.ACTION_DOWN) {
tracking = true;
triggered = false;
vibrate(VIBRATE_SHORT);
if (leftHit) {
currentSlider = leftSlider;
targetZone = TARGET_ZONE;
rightSlider.hide();
} else {
currentSlider = rightSlider;
targetZone = 1.0f - TARGET_ZONE;
leftSlider.hide();
}
currentSlider.setState(Slider.STATE_PRESSED);
currentSlider.showTarget();
}
return true;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (tracking) {
final int action = event.getAction();
final float x = event.getX();
final float y = event.getY();
final View handle = currentSlider.tab;
switch (action) {
case MotionEvent.ACTION_MOVE:
moveHandle(x, y);
float position = x;
float target = targetZone * getWidth();
boolean targetZoneReached = (currentSlider.equals(leftSlider) ? position > target : position < target);
if (!triggered && targetZoneReached) {
triggered = true;
tracking = false;
currentSlider.setState(Slider.STATE_ACTIVE);
dispatchTriggerEvent(currentSlider.equals(leftSlider) ? IOnLeftRightChoice.LEFT_HANDLE : IOnLeftRightChoice.RIGHT_HANDLE);
}
if (y <= handle.getBottom() && y >= handle.getTop()) {
break;
}
// Intentionally fall through - we're outside tracking rectangle
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
tracking = false;
triggered = false;
resetView();
break;
default:
break;
}
}
return tracking || super.onTouchEvent(event);
}
public void resetView() {
leftSlider.reset();
rightSlider.reset();
recomputeLayout(getLeft(), getTop(), getRight(), getBottom());
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (!changed) {
return;
}
recomputeLayout(l, t, r, b);
}
public void recomputeLayout(int l, int t, int r, int b) {
// Center the widgets in the view
leftSlider.layout(l, t, r, b, Slider.ALIGN_LEFT);
rightSlider.layout(l, t, r, b, Slider.ALIGN_RIGHT);
invalidate();
}
private void moveHandle(float x, float y) {
final View handle = currentSlider.tab;
final View content = currentSlider.text;
int deltaX = (int) x - handle.getLeft() - (handle.getWidth() / 2);
handle.offsetLeftAndRight(deltaX);
content.offsetLeftAndRight(deltaX);
invalidate();
}
/**
* Sets the left handle icon to a given resource.
*
* The resource should refer to a Drawable object, or use 0 to remove the
* icon.
*
* @param iconId
* the resource ID of the icon drawable
* @param targetId
* the resource of the target drawable
* @param barId
* the resource of the bar drawable (stateful)
* @param tabId
* the resource of the
*/
public void setLeftTabResources(int iconId, int targetId, int barId, int tabId) {
leftSlider.setResources(iconId, targetId, barId, tabId);
}
public void setLeftTabDrawables(Drawable iconD, Drawable targetD, Drawable barD, Drawable tabD) {
leftSlider.setDrawables(iconD, targetD, barD, tabD);
}
/**
* Sets the left handle hint text to a given resource string.
*
* @param resId
*/
public void setLeftHintText(int resId) {
leftSlider.setHintText(resId);
}
/**
* Sets the left handle hint text to a given string.
*
* @param str
*/
public void setLeftHintText(String str) {
leftSlider.setHintText(str);
}
/**
* Sets the right handle icon to a given resource.
*
* The resource should refer to a Drawable object, or use 0 to remove the
* icon.
*
* @param iconId
* the resource ID of the icon drawable
* @param targetId
* the resource of the target drawable
* @param barId
* the resource of the bar drawable (stateful)
* @param tabId
* the resource of the
*/
public void setRightTabResources(int iconId, int targetId, int barId, int tabId) {
rightSlider.setResources(iconId, targetId, barId, tabId);
}
public void setRightTabDrawables(Drawable iconD, Drawable targetD, Drawable barD, Drawable tabD) {
rightSlider.setDrawables(iconD, targetD, barD, tabD);
}
/**
* Sets the left handle hint text to a given resource string.
* @param resId
*/
public void setRightHintText(int resId) {
rightSlider.setHintText(resId);
}
/**
* Sets the left handle hint text to a given string.
* @param str
*/
public void setRightHintText(String str) {
rightSlider.setHintText(str);
}
/**
* Triggers haptic feedback.
*/
private synchronized void vibrate(long duration) {
if (mVibrator == null) {
mVibrator = (android.os.Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
}
mVibrator.vibrate(duration);
}
/**
* 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(VIBRATE_LONG);
Log.d(THIS_FILE, "We take the call....");
if (onTriggerListener != null) {
Log.d(THIS_FILE, "We transmit to the parent....");
onTriggerListener.onLeftRightChoice(whichHandle);
}
}
/**
* Registers a callback to be invoked when the user triggers an event.
*
* @param listener
* the OnDialTriggerListener to attach to this view
*/
@Override
public void setOnLeftRightListener(IOnLeftRightChoice l) {
onTriggerListener = l;
}
/* (non-Javadoc)
* @see com.csipsimple.ui.incall.locker.IOnLeftRightChoice.IOnLeftRightProvider#applyTargetTitles(int)
*/
@Override
public void applyTargetTitles(int resArrayTitles) {
ArrayList<String> strings = LeftRightChooserUtils.loadTargetsDescriptions(getContext(),
resArrayTitles);
// setRightHintText(R.string.ignore_call);
// setLeftHintText(R.string.take_call);
setRightHintText(strings.get(0));
setLeftHintText(strings.get(1));
}
/* (non-Javadoc)
* @see com.csipsimple.ui.incall.locker.IOnLeftRightChoice.IOnLeftRightProvider#setTypeOfLock(com.csipsimple.ui.incall.locker.IOnLeftRightChoice.TypeOfLock)
*/
@Override
public void setTypeOfLock(TypeOfLock lock) {
// TODO Theme
if(lock == TypeOfLock.CALL) {
// To sliding tab
setLeftTabDrawables(getContext().getResources().getDrawable(R.drawable.ic_jog_dial_answer),
getContext().getResources().getDrawable(R.drawable.jog_tab_target_green),
getContext().getResources().getDrawable(R.drawable.jog_tab_bar_left_answer),
getContext().getResources().getDrawable(R.drawable.jog_tab_left_answer));
setRightTabDrawables(
getContext().getResources().getDrawable(R.drawable.ic_jog_dial_decline),
getContext().getResources().getDrawable(R.drawable.jog_tab_target_red),
getContext().getResources().getDrawable(R.drawable.jog_tab_bar_right_decline),
getContext().getResources().getDrawable(R.drawable.jog_tab_right_decline));
applyTargetTitles(R.array.answer_choices);
}
}
/* (non-Javadoc)
* @see com.csipsimple.ui.incall.locker.IOnLeftRightChoice.IOnLeftRightProvider#getLayoutingHeight()
*/
@Override
public int getLayoutingHeight() {
return LayoutParams.WRAP_CONTENT;
}
/* (non-Javadoc)
* @see com.csipsimple.ui.incall.locker.IOnLeftRightChoice.IOnLeftRightProvider#getLayoutingWidth()
*/
@Override
public int getLayoutingWidth() {
return LayoutParams.MATCH_PARENT;
}
}