blob: 9f61622ec64f9225b9a0ad9e29781da51096314a [file] [log] [blame]
/*
* Copyright (C) 2004-2013 Savoir-Faire Linux Inc.
*
* Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
* Adrien BĂ©raud <adrien.beraud@gmail.com>
*
* This program 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.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* Additional permission under GNU GPL version 3 section 7:
*
* If you modify this program, or any covered work, by linking or
* combining it with the OpenSSL project's OpenSSL library (or a
* modified version of that library), containing parts covered by the
* terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
* grants you additional permission to convey the resulting work.
* Corresponding Source for a non-source form of such a combination
* shall include the source code for the parts of OpenSSL used as well
* as that of the covered work.
*/
package com.savoirfairelinux.sflphone.model;
import java.util.List;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.RectF;
import android.graphics.Shader.TileMode;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.Toast;
import com.savoirfairelinux.sflphone.R;
import com.savoirfairelinux.sflphone.client.CallActivity;
import com.savoirfairelinux.sflphone.fragments.CallFragment;
public class BubblesView extends SurfaceView implements SurfaceHolder.Callback, OnTouchListener {
private static final String TAG = BubblesView.class.getSimpleName();
private BubblesThread thread = null;
private BubbleModel model;
private Paint attractor_paint = new Paint();
private Paint black_name_paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint white_name_paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private GestureDetector gDetector;
private float density;
private float textDensity;
private boolean dragging_bubble = false;
private CallFragment callback;
public BubblesView(Context context, AttributeSet attrs) {
super(context, attrs);
density = getResources().getDisplayMetrics().density;
textDensity = getResources().getDisplayMetrics().scaledDensity;
SurfaceHolder holder = getHolder();
holder.addCallback(this);
// create thread only; it's started in surfaceCreated()
createThread();
setOnTouchListener(this);
setFocusable(true);
attractor_paint.setColor(Color.RED);
// attractor_paint.set
black_name_paint.setTextSize(18 * textDensity);
black_name_paint.setColor(0xFF303030);
black_name_paint.setTextAlign(Align.CENTER);
white_name_paint.setTextSize(18 * textDensity);
white_name_paint.setColor(0xFFEEEEEE);
white_name_paint.setTextAlign(Align.CENTER);
gDetector = new GestureDetector(getContext(), new MyOnGestureListener());
}
private void createThread() {
if (thread != null)
return;
thread = new BubblesThread(getHolder(), getContext(), new Handler() {
@Override
public void handleMessage(Message m) {
/*
* mStatusText.setVisibility(m.getData().getInt("viz")); mStatusText.setText(m.getData().getString("text"));
*/
}
});
if (model != null)
thread.setModel(model);
}
public void setModel(BubbleModel model) {
this.model = model;
thread.setModel(model);
}
/*
* @Override public void onWindowFocusChanged(boolean hasWindowFocus) { if (!hasWindowFocus) { thread.pause(); } }
*/
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.w(TAG, "surfaceChanged " + width + "-" + height);
thread.setSurfaceSize(width, height);
}
/*
* Callback invoked when the Surface has been created and is ready to be used.
*/
@Override
public void surfaceCreated(SurfaceHolder holder) {
// start the thread here so that we don't busy-wait in run()
// waiting for the surface to be created
createThread();
Log.w(TAG, "surfaceCreated");
thread.setRunning(true);
thread.start();
}
/*
* Callback invoked when the Surface has been destroyed and must no longer be touched. WARNING: after this method returns, the Surface/Canvas must
* never be touched again!
*/
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// we have to tell thread to shut down & wait for it to finish, or else
// it might touch the Surface after we return and explode
Log.w(TAG, "surfaceDestroyed");
boolean retry = true;
thread.setRunning(false);
thread.setPaused(false);
while (retry) {
try {
Log.w(TAG, "joining...");
thread.join();
retry = false;
} catch (InterruptedException e) {
}
}
Log.w(TAG, "done");
thread = null;
}
public boolean isDraggingBubble() {
return dragging_bubble;
}
class BubblesThread extends Thread {
private boolean running = false;
private SurfaceHolder surfaceHolder;
public Boolean suspendFlag = false;
BubbleModel model = null;
public BubblesThread(SurfaceHolder holder, Context context, Handler handler) {
surfaceHolder = holder;
}
public void setModel(BubbleModel model) {
this.model = model;
}
@Override
public void run() {
while (running) {
Canvas c = null;
try {
if (suspendFlag) {
synchronized (this) {
while (suspendFlag) {
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
} else {
c = surfaceHolder.lockCanvas(null);
// for the case the surface is destroyed while already in the loop
if (c == null || model == null)
continue;
synchronized (surfaceHolder) {
// Log.w(TAG, "Thread doDraw");
model.update();
doDraw(c);
}
}
} finally {
if (c != null)
surfaceHolder.unlockCanvasAndPost(c);
}
}
}
public void setPaused(boolean wantToPause) {
synchronized (this) {
suspendFlag = wantToPause;
notify();
}
}
public void setRunning(boolean b) {
running = b;
}
public void setSurfaceSize(int width, int height) {
synchronized (surfaceHolder) {
if (model != null) {
model.width = width;
model.height = height;
}
}
}
/**
* I got multiple IndexOutOfBoundsException, when switching calls. //FIXME
*
* @param canvas
*/
private void doDraw(Canvas canvas) {
synchronized (model) {
List<Bubble> bubbles = model.getBubbles();
List<Attractor> attractors = model.getAttractors();
Paint tryMe = new Paint();
canvas.drawColor(Color.WHITE);
if (dragging_bubble) {
// Draw red gradient around to hang up call
// canvas.drawColor(Color.RED);
LinearGradient grTop = new LinearGradient(0, 0, 0, 40, Color.RED, Color.WHITE, TileMode.CLAMP);
Paint p = new Paint();
p.setDither(true);
p.setShader(grTop);
canvas.drawRect(new RectF(0, 0, model.width, 40), p);
LinearGradient grBottom = new LinearGradient(0, model.height, 0, model.height - 40, Color.RED, Color.WHITE, TileMode.CLAMP);
p.setDither(true);
p.setShader(grBottom);
canvas.drawRect(new RectF(0, model.height - 40, model.width, model.height), p);
LinearGradient grLeft = new LinearGradient(0, 0, 40, 0, Color.RED, Color.WHITE, TileMode.CLAMP);
p.setDither(true);
p.setShader(grLeft);
canvas.drawRect(new RectF(0, 0, 40, model.height), p);
LinearGradient grRight = new LinearGradient(model.width, 0, model.width - 40, 0, Color.RED, Color.WHITE, TileMode.CLAMP);
p.setDither(true);
p.setShader(grRight);
canvas.drawRect(new RectF(model.width - 40, 0, model.width, model.height), p);
// tryMe.setColor(getResources().getColor(R.color.lighter_gray));
// tryMe.setStyle(Paint.Style.FILL);
// tryMe.setXfermode(new PorterDuffXfermode(Mode.SRC_OUT));
// canvas.drawArc(new RectF(15, 30, model.width - 15, model.height - 30), 0, 360, false, tryMe);
}
tryMe.setStyle(Paint.Style.STROKE);
tryMe.setColor(getResources().getColor(R.color.darker_gray));
tryMe.setXfermode(null);
canvas.drawCircle(model.width / 2, model.height / 2, model.width / 2 - getResources().getDimension(R.dimen.bubble_size), tryMe);
try {
for (int i = 0, n = attractors.size(); i < n; i++) {
Attractor a = attractors.get(i);
canvas.drawBitmap(a.getBitmap(), null, a.getBounds(), null);
}
for (int i = 0, n = bubbles.size(); i < n; i++) {
Bubble b = bubbles.get(i);
if (b.expanded) {
continue;
}
canvas.drawBitmap(b.getBitmap(), null, b.getBounds(), null);
canvas.drawText(b.associated_call.getContact().getmDisplayName(), b.getPosX(), (float) (b.getPosY() - b.getRetractedRadius()
* 1.2 * density), getNamePaint(b));
}
Bubble first_plan = getExpandedBubble();
if (first_plan != null) {
canvas.drawBitmap(first_plan.getBitmap(), null, first_plan.getBounds(), null);
canvas.drawText(first_plan.associated_call.getContact().getmDisplayName(), first_plan.getPosX(),
(float) (first_plan.getPosY() - first_plan.getRetractedRadius() * 1.2 * density), getNamePaint(first_plan));
canvas.drawText("Transfer", first_plan.getPosX(), (float) (first_plan.getPosY() + first_plan.getRetractedRadius() * 1.5
* density), getNamePaint(first_plan));
canvas.drawText(getResources().getString(first_plan.getHoldStatus()),
(float) (first_plan.getPosX() - first_plan.getRetractedRadius() * 1.5 * density - 15), first_plan.getPosY(),
getNamePaint(first_plan));
canvas.drawText(getResources().getString(first_plan.getRecordStatus()),
(float) (first_plan.getPosX() + first_plan.getRetractedRadius() * 1.5 * density + 15), first_plan.getPosY(),
getNamePaint(first_plan));
}
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, e.toString());
}
}
}
}
private Paint getNamePaint(Bubble b) {
if (b.expanded) {
white_name_paint.setTextSize(15 * b.target_scale * textDensity);
return white_name_paint;
}
black_name_paint.setTextSize(18 * b.target_scale * textDensity);
return black_name_paint;
}
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.w(TAG, "onTouch " + event.getAction());
int action = event.getActionMasked();
if (action == MotionEvent.ACTION_UP) {
if (thread.suspendFlag) {
Log.i(TAG, "Relaunch drawing thread");
thread.setPaused(false);
}
List<Bubble> bubbles = model.getBubbles();
final int n_bubbles = bubbles.size();
for (int i = 0; i < n_bubbles; i++) {
Bubble b = bubbles.get(i);
if (b.dragged) {
b.dragged = false;
b.target_scale = 1.f;
if (b.isOnBorder(model.width, model.height)){
b.markedToDie = true;
((CallActivity) callback.getActivity()).onCallEnded(b.associated_call);
}
}
}
dragging_bubble = false;
} else if (action != MotionEvent.ACTION_DOWN && !isDraggingBubble() && !thread.suspendFlag) {
Log.i(TAG, "Not dragging thread should be stopped");
thread.setPaused(true);
// thread.holdDrawing();
}
return gDetector.onTouchEvent(event);
}
private Bubble getExpandedBubble() {
List<Bubble> bubbles = model.getBubbles();
final int n_bubbles = bubbles.size();
for (int i = 0; i < n_bubbles; i++) {
Bubble b = bubbles.get(i);
if (b.expanded) {
return b;
}
}
return null;
}
public void restartDrawing() {
if (thread != null && thread.suspendFlag) {
Log.i(TAG, "Relaunch drawing thread");
thread.setPaused(false);
}
}
public void setFragment(CallFragment callFragment) {
callback = callFragment;
}
public void stopThread() {
if (thread != null && thread.suspendFlag) {
Log.i(TAG, "Stop drawing thread");
thread.setRunning(false);
thread.setPaused(false);
}
}
class MyOnGestureListener implements OnGestureListener {
@Override
public boolean onDown(MotionEvent event) {
List<Bubble> bubbles = model.getBubbles();
final int n_bubbles = bubbles.size();
Bubble expand = getExpandedBubble();
if (expand != null) {
if (!expand.intersects(event.getX(), event.getY())) {
expand.retract();
} else {
Log.d("Main", "getAction");
switch (expand.getAction(event.getX(), event.getY())) {
case 0:
expand.retract();
break;
case 1:
if (expand.associated_call.isOnHold()) {
((CallActivity) callback.getActivity()).onCallResumed(expand.associated_call);
} else {
((CallActivity) callback.getActivity()).onCallSuspended(expand.associated_call);
}
break;
case 2:
Log.d("Main", "onRecordCall");
((CallActivity) callback.getActivity()).onRecordCall(expand.associated_call);
break;
case 3:
callback.makeTransfer(expand);
Toast.makeText(getContext(), "Not implemented here", Toast.LENGTH_SHORT).show();
break;
}
}
return true;
}
Log.d("Main", "onDown");
for (int i = 0; i < n_bubbles; i++) {
Bubble b = bubbles.get(i);
if (b.intersects(event.getX(), event.getY()) && !b.expanded) {
b.dragged = true;
b.last_drag = System.nanoTime();
b.setPos(event.getX(), event.getY());
b.target_scale = .8f;
dragging_bubble = true;
}
}
return true;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
Log.d("Main", "onFling");
return true;
}
@Override
public void onLongPress(MotionEvent e) {
Log.d("Main", "onLongPress");
if (isDraggingBubble()) {
Bubble b = getDraggedBubble(e);
b.expand();
}
}
private Bubble getDraggedBubble(MotionEvent e) {
List<Bubble> bubbles = model.getBubbles();
final int n_bubbles = bubbles.size();
for (int i = 0; i < n_bubbles; i++) {
Bubble b = bubbles.get(i);
if (b.intersects(e.getX(), e.getY())) {
return b;
}
}
return null;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent event, float distanceX, float distanceY) {
Log.d("Main", "onScroll");
List<Bubble> bubbles = model.getBubbles();
final int n_bubbles = bubbles.size();
long now = System.nanoTime();
for (int i = 0; i < n_bubbles; i++) {
Bubble b = bubbles.get(i);
if (b.dragged) {
float x = event.getX(), y = event.getY();
float dt = (float) ((now - b.last_drag) / 1000000000.);
float dx = x - b.getPosX(), dy = y - b.getPosY();
b.last_drag = now;
b.setPos(event.getX(), event.getY());
b.speed.x = dx / dt;
b.speed.y = dy / dt;
// }
return true;
}
}
return true;
}
@Override
public void onShowPress(MotionEvent e) {
Log.d("Main", "onShowPress");
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
Log.d("Main", "onSingleTapUp");
return true;
}
}
}