blob: 077f1bf33267aa10b8ca36a39bf5dd19fddc6b03 [file] [log] [blame]
alisioncc7bb422013-06-06 15:31:39 -04001/*
2 * Copyright (C) 2004-2013 Savoir-Faire Linux Inc.
3 *
4 * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
5 * Adrien Béraud <adrien.beraud@gmail.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 *
21 * Additional permission under GNU GPL version 3 section 7:
22 *
23 * If you modify this program, or any covered work, by linking or
24 * combining it with the OpenSSL project's OpenSSL library (or a
25 * modified version of that library), containing parts covered by the
26 * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
27 * grants you additional permission to convey the resulting work.
28 * Corresponding Source for a non-source form of such a combination
29 * shall include the source code for the parts of OpenSSL used as well
30 * as that of the covered work.
31 */
32
Adrien Béraud04463092013-05-06 14:17:22 +100033package com.savoirfairelinux.sflphone.model;
alisionfe9cf712013-05-03 17:26:08 -040034
Adrien Béraud0c9bd8f2013-05-30 16:16:57 +100035import java.util.List;
36
alisionfe9cf712013-05-03 17:26:08 -040037import android.content.Context;
38import android.graphics.Canvas;
39import android.graphics.Color;
Adrien Béraud33268882013-05-18 03:41:15 +100040import android.graphics.Paint;
Adrien Béraud6bbce912013-05-24 00:48:13 +100041import android.graphics.Paint.Align;
Adrien Béraud04463092013-05-06 14:17:22 +100042import android.os.Handler;
43import android.os.Message;
alisionfe9cf712013-05-03 17:26:08 -040044import android.util.AttributeSet;
45import android.util.Log;
alision58356b72013-06-03 17:13:36 -040046import android.view.GestureDetector;
47import android.view.GestureDetector.OnGestureListener;
alisionfe9cf712013-05-03 17:26:08 -040048import android.view.MotionEvent;
Adrien Béraud04463092013-05-06 14:17:22 +100049import android.view.SurfaceHolder;
50import android.view.SurfaceView;
alisionfe9cf712013-05-03 17:26:08 -040051import android.view.View;
Adrien Béraud04463092013-05-06 14:17:22 +100052import android.view.View.OnTouchListener;
alision34673e62013-06-25 14:40:07 -040053import android.widget.Toast;
54
55import com.savoirfairelinux.sflphone.client.CallActivity;
56import com.savoirfairelinux.sflphone.fragments.CallFragment;
alisionfe9cf712013-05-03 17:26:08 -040057
alision2cb99562013-05-30 17:02:20 -040058public class BubblesView extends SurfaceView implements SurfaceHolder.Callback, OnTouchListener {
59 private static final String TAG = BubblesView.class.getSimpleName();
alisionfe9cf712013-05-03 17:26:08 -040060
alision2cb99562013-05-30 17:02:20 -040061 private BubblesThread thread = null;
62 private BubbleModel model;
alisionfe9cf712013-05-03 17:26:08 -040063
alision2cb99562013-05-30 17:02:20 -040064 private Paint attractor_paint = new Paint();
alision34673e62013-06-25 14:40:07 -040065 private Paint black_name_paint = new Paint(Paint.ANTI_ALIAS_FLAG);
66 private Paint white_name_paint = new Paint(Paint.ANTI_ALIAS_FLAG);
Adrien Béraud6bbce912013-05-24 00:48:13 +100067
alisione38001f2013-06-04 14:14:39 -040068 private GestureDetector gDetector;
69
alision2cb99562013-05-30 17:02:20 -040070 private float density;
71 private float textDensity;
Adrien Béraud33268882013-05-18 03:41:15 +100072
alision2cb99562013-05-30 17:02:20 -040073 private boolean dragging_bubble = false;
Adrien Béraudc9c424d2013-05-30 17:47:35 +100074
alision34673e62013-06-25 14:40:07 -040075 private CallFragment callback;
76
alision2cb99562013-05-30 17:02:20 -040077 public BubblesView(Context context, AttributeSet attrs) {
78 super(context, attrs);
alisionfe9cf712013-05-03 17:26:08 -040079
alision2cb99562013-05-30 17:02:20 -040080 density = getResources().getDisplayMetrics().density;
81 textDensity = getResources().getDisplayMetrics().scaledDensity;
Adrien Béraud6bbce912013-05-24 00:48:13 +100082
alision2cb99562013-05-30 17:02:20 -040083 SurfaceHolder holder = getHolder();
84 holder.addCallback(this);
alisionfe9cf712013-05-03 17:26:08 -040085
alision2cb99562013-05-30 17:02:20 -040086 // create thread only; it's started in surfaceCreated()
87 createThread();
alisionfe9cf712013-05-03 17:26:08 -040088
alision2cb99562013-05-30 17:02:20 -040089 setOnTouchListener(this);
90 setFocusable(true);
Adrien Béraud33268882013-05-18 03:41:15 +100091
alision2cb99562013-05-30 17:02:20 -040092 attractor_paint.setColor(Color.RED);
93 // attractor_paint.set
alision34673e62013-06-25 14:40:07 -040094 black_name_paint.setTextSize(18 * textDensity);
95 black_name_paint.setColor(0xFF303030);
96 black_name_paint.setTextAlign(Align.CENTER);
97
98 white_name_paint.setTextSize(18 * textDensity);
99 white_name_paint.setColor(0xFFEEEEEE);
100 white_name_paint.setTextAlign(Align.CENTER);
alision58356b72013-06-03 17:13:36 -0400101
102 gDetector = new GestureDetector(getContext(), new MyOnGestureListener());
alision2cb99562013-05-30 17:02:20 -0400103 }
alisionfe9cf712013-05-03 17:26:08 -0400104
alision2cb99562013-05-30 17:02:20 -0400105 private void createThread() {
106 if (thread != null)
107 return;
108 thread = new BubblesThread(getHolder(), getContext(), new Handler() {
109 @Override
110 public void handleMessage(Message m) {
111 /*
112 * mStatusText.setVisibility(m.getData().getInt("viz")); mStatusText.setText(m.getData().getString("text"));
113 */
114 }
115 });
116 if (model != null)
117 thread.setModel(model);
118 }
alisionfe9cf712013-05-03 17:26:08 -0400119
alision2cb99562013-05-30 17:02:20 -0400120 public void setModel(BubbleModel model) {
121 this.model = model;
122 thread.setModel(model);
123 }
alisionfe9cf712013-05-03 17:26:08 -0400124
alision2cb99562013-05-30 17:02:20 -0400125 /*
126 * @Override public void onWindowFocusChanged(boolean hasWindowFocus) { if (!hasWindowFocus) { thread.pause(); } }
127 */
alisionfe9cf712013-05-03 17:26:08 -0400128
alision2cb99562013-05-30 17:02:20 -0400129 @Override
130 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
131 Log.w(TAG, "surfaceChanged " + width + "-" + height);
132 thread.setSurfaceSize(width, height);
133 }
alisionfe9cf712013-05-03 17:26:08 -0400134
alision2cb99562013-05-30 17:02:20 -0400135 /*
136 * Callback invoked when the Surface has been created and is ready to be used.
137 */
138 @Override
139 public void surfaceCreated(SurfaceHolder holder) {
140 // start the thread here so that we don't busy-wait in run()
141 // waiting for the surface to be created
142 createThread();
alisionfe9cf712013-05-03 17:26:08 -0400143
alision2cb99562013-05-30 17:02:20 -0400144 Log.w(TAG, "surfaceCreated");
145 thread.setRunning(true);
146 thread.start();
147 }
alisionfe9cf712013-05-03 17:26:08 -0400148
alision2cb99562013-05-30 17:02:20 -0400149 /*
150 * Callback invoked when the Surface has been destroyed and must no longer be touched. WARNING: after this method returns, the Surface/Canvas must
151 * never be touched again!
152 */
153 @Override
154 public void surfaceDestroyed(SurfaceHolder holder) {
155 // we have to tell thread to shut down & wait for it to finish, or else
156 // it might touch the Surface after we return and explode
157 Log.w(TAG, "surfaceDestroyed");
158 boolean retry = true;
159 thread.setRunning(false);
alision1005ba12013-06-19 13:52:44 -0400160 thread.setPaused(false);
alision2cb99562013-05-30 17:02:20 -0400161 while (retry) {
162 try {
alision1005ba12013-06-19 13:52:44 -0400163 Log.w(TAG, "joining...");
alision2cb99562013-05-30 17:02:20 -0400164 thread.join();
165 retry = false;
166 } catch (InterruptedException e) {
167 }
168 }
alision1005ba12013-06-19 13:52:44 -0400169 Log.w(TAG, "done");
alision2cb99562013-05-30 17:02:20 -0400170 thread = null;
171 }
alisionfe9cf712013-05-03 17:26:08 -0400172
alision2cb99562013-05-30 17:02:20 -0400173 public boolean isDraggingBubble() {
174 return dragging_bubble;
175 }
Adrien Béraudc9c424d2013-05-30 17:47:35 +1000176
alision2cb99562013-05-30 17:02:20 -0400177 class BubblesThread extends Thread {
178 private boolean running = false;
179 private SurfaceHolder surfaceHolder;
alisione38001f2013-06-04 14:14:39 -0400180 public Boolean suspendFlag = false;
alisionfe9cf712013-05-03 17:26:08 -0400181
alision2cb99562013-05-30 17:02:20 -0400182 BubbleModel model = null;
alisionfe9cf712013-05-03 17:26:08 -0400183
alision2cb99562013-05-30 17:02:20 -0400184 public BubblesThread(SurfaceHolder holder, Context context, Handler handler) {
185 surfaceHolder = holder;
186 }
alisionfe9cf712013-05-03 17:26:08 -0400187
alision2cb99562013-05-30 17:02:20 -0400188 public void setModel(BubbleModel model) {
189 this.model = model;
190 }
alisionfe9cf712013-05-03 17:26:08 -0400191
alision2cb99562013-05-30 17:02:20 -0400192 @Override
193 public void run() {
194 while (running) {
195 Canvas c = null;
196 try {
Adrien Béraud7ed23dc2013-05-06 16:27:24 +1000197
alisione38001f2013-06-04 14:14:39 -0400198 if (suspendFlag) {
199 synchronized (this) {
200 while (suspendFlag) {
201 try {
202 wait();
203 } catch (InterruptedException e) {
204 // TODO Auto-generated catch block
205 e.printStackTrace();
206 }
207 }
208 }
209 } else {
alisione38001f2013-06-04 14:14:39 -0400210 c = surfaceHolder.lockCanvas(null);
Adrien Béraud7ed23dc2013-05-06 16:27:24 +1000211
alisione38001f2013-06-04 14:14:39 -0400212 // for the case the surface is destroyed while already in the loop
213 if (c == null || model == null)
214 continue;
215
216 synchronized (surfaceHolder) {
217 // Log.w(TAG, "Thread doDraw");
218 model.update();
219 doDraw(c);
220 }
alision2cb99562013-05-30 17:02:20 -0400221 }
alisione38001f2013-06-04 14:14:39 -0400222
alision2cb99562013-05-30 17:02:20 -0400223 } finally {
224 if (c != null)
225 surfaceHolder.unlockCanvasAndPost(c);
226 }
227 }
228 }
Adrien Béraud04463092013-05-06 14:17:22 +1000229
alisione38001f2013-06-04 14:14:39 -0400230 public void setPaused(boolean wantToPause) {
231 synchronized (this) {
232 suspendFlag = wantToPause;
233 notify();
234 }
235 }
236
alision2cb99562013-05-30 17:02:20 -0400237 public void setRunning(boolean b) {
238 running = b;
239 }
Adrien Béraud04463092013-05-06 14:17:22 +1000240
alision2cb99562013-05-30 17:02:20 -0400241 public void setSurfaceSize(int width, int height) {
242 synchronized (surfaceHolder) {
243 if (model != null) {
244 model.width = width;
245 model.height = height;
246 }
247 }
248 }
Adrien Béraud04463092013-05-06 14:17:22 +1000249
alision2cb99562013-05-30 17:02:20 -0400250 /**
251 * I got multiple IndexOutOfBoundsException, when switching calls. //FIXME
252 *
253 * @param canvas
254 */
255 private void doDraw(Canvas canvas) {
256 canvas.drawColor(Color.WHITE);
Adrien Béraud04463092013-05-06 14:17:22 +1000257
alision2cb99562013-05-30 17:02:20 -0400258 synchronized (model) {
259 List<Bubble> bubbles = model.getBubbles();
260 List<Attractor> attractors = model.getAttractors();
261 try {
Adrien Béraud0c9bd8f2013-05-30 16:16:57 +1000262
alision2cb99562013-05-30 17:02:20 -0400263 for (int i = 0, n = attractors.size(); i < n; i++) {
264 Attractor a = attractors.get(i);
265 canvas.drawBitmap(a.getBitmap(), null, a.getBounds(), null);
266 }
Adrien Béraud33268882013-05-18 03:41:15 +1000267
alision2cb99562013-05-30 17:02:20 -0400268 for (int i = 0, n = bubbles.size(); i < n; i++) {
269 Bubble b = bubbles.get(i);
270 canvas.drawBitmap(b.getBitmap(), null, b.getBounds(), null);
alision729b0a22013-07-02 11:57:33 -0400271 canvas.drawText(b.associated_call.getContact().getmDisplayName(), b.getPosX(),
272 (float) (b.getPosY() - b.getRetractedRadius() * 1.5 * density), getNamePaint(b));
alision34673e62013-06-25 14:40:07 -0400273 }
274 Bubble first_plan = getExpandedBubble();
275 if (first_plan != null) {
276 canvas.drawBitmap(first_plan.getBitmap(), null, first_plan.getBounds(), null);
alision729b0a22013-07-02 11:57:33 -0400277
278 canvas.drawText(first_plan.associated_call.getContact().getmDisplayName(), first_plan.getPosX(),
279 (float) (first_plan.getPosY() - first_plan.getRetractedRadius() * 1.5 * density), getNamePaint(first_plan));
280
281 canvas.drawText("Transfer", first_plan.getPosX(), (float) (first_plan.getPosY() + first_plan.getRetractedRadius() * 1.5
282 * density), getNamePaint(first_plan));
alisiondf1dac92013-06-27 17:35:53 -0400283
alision729b0a22013-07-02 11:57:33 -0400284 canvas.drawText(getResources().getString(first_plan.getHoldStatus()),
285 (float) (first_plan.getPosX() - first_plan.getRetractedRadius() * 1.5 * density), first_plan.getPosY(),
286 getNamePaint(first_plan));
287
288 canvas.drawText(getResources().getString(first_plan.getRecordStatus()),
289 (float) (first_plan.getPosX() + first_plan.getRetractedRadius() * 1.5 * density), first_plan.getPosY(),
290 getNamePaint(first_plan));
alisiondf1dac92013-06-27 17:35:53 -0400291
alision2cb99562013-05-30 17:02:20 -0400292 }
293
294 } catch (IndexOutOfBoundsException e) {
295 Log.e(TAG, e.toString());
296 }
297 }
298 }
alision34673e62013-06-25 14:40:07 -0400299
300 }
301
302 private Paint getNamePaint(Bubble b) {
alision50fa0722013-06-25 17:29:44 -0400303 if (b.expanded) {
304 white_name_paint.setTextSize(15 * b.target_scale * textDensity);
alision34673e62013-06-25 14:40:07 -0400305 return white_name_paint;
alision50fa0722013-06-25 17:29:44 -0400306 }
307 black_name_paint.setTextSize(18 * b.target_scale * textDensity);
alision34673e62013-06-25 14:40:07 -0400308 return black_name_paint;
alision2cb99562013-05-30 17:02:20 -0400309 }
Adrien Béraud04463092013-05-06 14:17:22 +1000310
alisione38001f2013-06-04 14:14:39 -0400311 @Override
312 public boolean onTouch(View v, MotionEvent event) {
313 Log.w(TAG, "onTouch " + event.getAction());
314
315 int action = event.getActionMasked();
316
317 if (action == MotionEvent.ACTION_UP) {
318 if (thread.suspendFlag) {
319 Log.i(TAG, "Relaunch drawing thread");
320 thread.setPaused(false);
321 }
322
323 List<Bubble> bubbles = model.getBubbles();
324 final int n_bubbles = bubbles.size();
325 for (int i = 0; i < n_bubbles; i++) {
326 Bubble b = bubbles.get(i);
327 if (b.dragged) {
328 b.dragged = false;
329 b.target_scale = 1.f;
330 }
331 }
332 dragging_bubble = false;
333 } else if (action != MotionEvent.ACTION_DOWN && !isDraggingBubble() && !thread.suspendFlag) {
334
335 Log.i(TAG, "Not dragging thread should be stopped");
336 thread.setPaused(true);
337 // thread.holdDrawing();
338 }
339
340 return gDetector.onTouchEvent(event);
341 }
342
alision34673e62013-06-25 14:40:07 -0400343 private Bubble getExpandedBubble() {
344 List<Bubble> bubbles = model.getBubbles();
345 final int n_bubbles = bubbles.size();
346 for (int i = 0; i < n_bubbles; i++) {
347 Bubble b = bubbles.get(i);
348 if (b.expanded) {
349 return b;
350 }
351 }
352 return null;
353 }
alision2ec64f92013-06-17 17:28:58 -0400354
alisiondf1dac92013-06-27 17:35:53 -0400355 public void restartDrawing() {
356 if (thread != null && thread.suspendFlag) {
357 Log.i(TAG, "Relaunch drawing thread");
358 thread.setPaused(false);
359 }
360 }
361
362 public void setFragment(CallFragment callFragment) {
363 callback = callFragment;
364
365 }
366
367 public void stopThread() {
368 if (thread != null && thread.suspendFlag) {
369 Log.i(TAG, "Stop drawing thread");
370 thread.setRunning(false);
371 thread.setPaused(false);
372 }
373
374 }
375
alision58356b72013-06-03 17:13:36 -0400376 class MyOnGestureListener implements OnGestureListener {
377 @Override
378 public boolean onDown(MotionEvent event) {
379 List<Bubble> bubbles = model.getBubbles();
380 final int n_bubbles = bubbles.size();
alision2ec64f92013-06-17 17:28:58 -0400381 Bubble expand = getExpandedBubble();
alision34673e62013-06-25 14:40:07 -0400382 if (expand != null) {
383 if (!expand.intersects(event.getX(), event.getY())) {
384 expand.retract();
385 } else {
386 Log.d("Main", "getAction");
alision50fa0722013-06-25 17:29:44 -0400387 switch (expand.getAction(event.getX(), event.getY())) {
388 case 0:
389 expand.retract();
390 break;
391 case 1:
alisiondf1dac92013-06-27 17:35:53 -0400392 if (expand.associated_call.isOnHold()) {
393 ((CallActivity) callback.getActivity()).onCallResumed(expand.associated_call);
394 } else {
395 ((CallActivity) callback.getActivity()).onCallSuspended(expand.associated_call);
396 }
397
alision50fa0722013-06-25 17:29:44 -0400398 break;
399 case 2:
400 Log.d("Main", "onRecordCall");
401 ((CallActivity) callback.getActivity()).onRecordCall(expand.associated_call);
402 break;
403 case 3:
404 Toast.makeText(getContext(), "Not implemented here", Toast.LENGTH_SHORT).show();
405 break;
alision34673e62013-06-25 14:40:07 -0400406 }
407 }
408 return true;
alision2ec64f92013-06-17 17:28:58 -0400409 }
alision58356b72013-06-03 17:13:36 -0400410 Log.d("Main", "onDown");
411 for (int i = 0; i < n_bubbles; i++) {
412 Bubble b = bubbles.get(i);
alision34673e62013-06-25 14:40:07 -0400413 if (b.intersects(event.getX(), event.getY()) && !b.expanded) {
alision58356b72013-06-03 17:13:36 -0400414 b.dragged = true;
415 b.last_drag = System.nanoTime();
416 b.setPos(event.getX(), event.getY());
417 b.target_scale = .8f;
418 dragging_bubble = true;
419 }
420 }
421 return true;
422 }
alision2ec64f92013-06-17 17:28:58 -0400423
alision58356b72013-06-03 17:13:36 -0400424 @Override
425 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
426 Log.d("Main", "onFling");
427 return true;
428 }
429
430 @Override
431 public void onLongPress(MotionEvent e) {
432 Log.d("Main", "onLongPress");
alision2ec64f92013-06-17 17:28:58 -0400433 if (isDraggingBubble()) {
434 Bubble b = getDraggedBubble(e);
alision729b0a22013-07-02 11:57:33 -0400435 b.expand();
alision2ec64f92013-06-17 17:28:58 -0400436 }
437 }
438
alision2ec64f92013-06-17 17:28:58 -0400439 private Bubble getDraggedBubble(MotionEvent e) {
440 List<Bubble> bubbles = model.getBubbles();
441 final int n_bubbles = bubbles.size();
442 for (int i = 0; i < n_bubbles; i++) {
443 Bubble b = bubbles.get(i);
444 if (b.intersects(e.getX(), e.getY())) {
445 return b;
446 }
447 }
448 return null;
alision58356b72013-06-03 17:13:36 -0400449 }
450
451 @Override
452 public boolean onScroll(MotionEvent e1, MotionEvent event, float distanceX, float distanceY) {
453 Log.d("Main", "onScroll");
454 List<Bubble> bubbles = model.getBubbles();
455 final int n_bubbles = bubbles.size();
456 long now = System.nanoTime();
457 for (int i = 0; i < n_bubbles; i++) {
458 Bubble b = bubbles.get(i);
459 if (b.dragged) {
460 float x = event.getX(), y = event.getY();
461 float dt = (float) ((now - b.last_drag) / 1000000000.);
462 float dx = x - b.getPosX(), dy = y - b.getPosY();
463 b.last_drag = now;
alision58356b72013-06-03 17:13:36 -0400464 b.setPos(event.getX(), event.getY());
alision58356b72013-06-03 17:13:36 -0400465 b.speed.x = dx / dt;
466 b.speed.y = dy / dt;
alision58356b72013-06-03 17:13:36 -0400467 // }
468 return true;
469 }
470 }
471 return true;
472 }
473
474 @Override
475 public void onShowPress(MotionEvent e) {
476 Log.d("Main", "onShowPress");
477
478 }
479
480 @Override
481 public boolean onSingleTapUp(MotionEvent e) {
482 Log.d("Main", "onSingleTapUp");
483 return true;
484 }
485 }
alisionfe9cf712013-05-03 17:26:08 -0400486}