blob: 0f1b33f0c292a18b553fd4b7b77f893930cc9941 [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;
alision2ec64f92013-06-17 17:28:58 -040042import android.graphics.PointF;
43import android.graphics.RectF;
Adrien Béraud04463092013-05-06 14:17:22 +100044import android.os.Handler;
45import android.os.Message;
alisionfe9cf712013-05-03 17:26:08 -040046import android.util.AttributeSet;
47import android.util.Log;
alision58356b72013-06-03 17:13:36 -040048import android.view.GestureDetector;
49import android.view.GestureDetector.OnGestureListener;
alisionfe9cf712013-05-03 17:26:08 -040050import android.view.MotionEvent;
Adrien Béraud04463092013-05-06 14:17:22 +100051import android.view.SurfaceHolder;
52import android.view.SurfaceView;
alisionfe9cf712013-05-03 17:26:08 -040053import android.view.View;
Adrien Béraud04463092013-05-06 14:17:22 +100054import android.view.View.OnTouchListener;
alisionfe9cf712013-05-03 17:26:08 -040055
alision2cb99562013-05-30 17:02:20 -040056public class BubblesView extends SurfaceView implements SurfaceHolder.Callback, OnTouchListener {
57 private static final String TAG = BubblesView.class.getSimpleName();
alisionfe9cf712013-05-03 17:26:08 -040058
alision2cb99562013-05-30 17:02:20 -040059 private BubblesThread thread = null;
60 private BubbleModel model;
alisionfe9cf712013-05-03 17:26:08 -040061
alision2cb99562013-05-30 17:02:20 -040062 private Paint attractor_paint = new Paint();
63 private Paint name_paint = new Paint(Paint.ANTI_ALIAS_FLAG);
Adrien Béraud6bbce912013-05-24 00:48:13 +100064
alisione38001f2013-06-04 14:14:39 -040065 private GestureDetector gDetector;
66
alision2cb99562013-05-30 17:02:20 -040067 private float density;
68 private float textDensity;
Adrien Béraud33268882013-05-18 03:41:15 +100069
alision2cb99562013-05-30 17:02:20 -040070 private boolean dragging_bubble = false;
Adrien Béraudc9c424d2013-05-30 17:47:35 +100071
alision2cb99562013-05-30 17:02:20 -040072 public BubblesView(Context context, AttributeSet attrs) {
73 super(context, attrs);
alisionfe9cf712013-05-03 17:26:08 -040074
alision2cb99562013-05-30 17:02:20 -040075 density = getResources().getDisplayMetrics().density;
76 textDensity = getResources().getDisplayMetrics().scaledDensity;
Adrien Béraud6bbce912013-05-24 00:48:13 +100077
alision2cb99562013-05-30 17:02:20 -040078 SurfaceHolder holder = getHolder();
79 holder.addCallback(this);
alisionfe9cf712013-05-03 17:26:08 -040080
alision2cb99562013-05-30 17:02:20 -040081 // create thread only; it's started in surfaceCreated()
82 createThread();
alisionfe9cf712013-05-03 17:26:08 -040083
alision2cb99562013-05-30 17:02:20 -040084 setOnTouchListener(this);
85 setFocusable(true);
Adrien Béraud33268882013-05-18 03:41:15 +100086
alision2cb99562013-05-30 17:02:20 -040087 attractor_paint.setColor(Color.RED);
88 // attractor_paint.set
89 name_paint.setTextSize(18 * textDensity);
90 name_paint.setColor(0xFF303030);
91 name_paint.setTextAlign(Align.CENTER);
alision58356b72013-06-03 17:13:36 -040092
93 gDetector = new GestureDetector(getContext(), new MyOnGestureListener());
alision2cb99562013-05-30 17:02:20 -040094 }
alisionfe9cf712013-05-03 17:26:08 -040095
alision2cb99562013-05-30 17:02:20 -040096 private void createThread() {
97 if (thread != null)
98 return;
99 thread = new BubblesThread(getHolder(), getContext(), new Handler() {
100 @Override
101 public void handleMessage(Message m) {
102 /*
103 * mStatusText.setVisibility(m.getData().getInt("viz")); mStatusText.setText(m.getData().getString("text"));
104 */
105 }
106 });
107 if (model != null)
108 thread.setModel(model);
109 }
alisionfe9cf712013-05-03 17:26:08 -0400110
alision2cb99562013-05-30 17:02:20 -0400111 public void setModel(BubbleModel model) {
112 this.model = model;
113 thread.setModel(model);
114 }
alisionfe9cf712013-05-03 17:26:08 -0400115
alision2cb99562013-05-30 17:02:20 -0400116 /*
117 * @Override public void onWindowFocusChanged(boolean hasWindowFocus) { if (!hasWindowFocus) { thread.pause(); } }
118 */
alisionfe9cf712013-05-03 17:26:08 -0400119
alision2cb99562013-05-30 17:02:20 -0400120 @Override
121 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
122 Log.w(TAG, "surfaceChanged " + width + "-" + height);
123 thread.setSurfaceSize(width, height);
124 }
alisionfe9cf712013-05-03 17:26:08 -0400125
alision2cb99562013-05-30 17:02:20 -0400126 /*
127 * Callback invoked when the Surface has been created and is ready to be used.
128 */
129 @Override
130 public void surfaceCreated(SurfaceHolder holder) {
131 // start the thread here so that we don't busy-wait in run()
132 // waiting for the surface to be created
133 createThread();
alisionfe9cf712013-05-03 17:26:08 -0400134
alision2cb99562013-05-30 17:02:20 -0400135 Log.w(TAG, "surfaceCreated");
136 thread.setRunning(true);
137 thread.start();
138 }
alisionfe9cf712013-05-03 17:26:08 -0400139
alision2cb99562013-05-30 17:02:20 -0400140 /*
141 * Callback invoked when the Surface has been destroyed and must no longer be touched. WARNING: after this method returns, the Surface/Canvas must
142 * never be touched again!
143 */
144 @Override
145 public void surfaceDestroyed(SurfaceHolder holder) {
146 // we have to tell thread to shut down & wait for it to finish, or else
147 // it might touch the Surface after we return and explode
148 Log.w(TAG, "surfaceDestroyed");
149 boolean retry = true;
150 thread.setRunning(false);
alision1005ba12013-06-19 13:52:44 -0400151 thread.setPaused(false);
alision2cb99562013-05-30 17:02:20 -0400152 while (retry) {
153 try {
alision1005ba12013-06-19 13:52:44 -0400154 Log.w(TAG, "joining...");
alision2cb99562013-05-30 17:02:20 -0400155 thread.join();
156 retry = false;
157 } catch (InterruptedException e) {
158 }
159 }
alision1005ba12013-06-19 13:52:44 -0400160 Log.w(TAG, "done");
alision2cb99562013-05-30 17:02:20 -0400161 thread = null;
162 }
alisionfe9cf712013-05-03 17:26:08 -0400163
alision2cb99562013-05-30 17:02:20 -0400164 public boolean isDraggingBubble() {
165 return dragging_bubble;
166 }
Adrien Béraudc9c424d2013-05-30 17:47:35 +1000167
alision2cb99562013-05-30 17:02:20 -0400168 class BubblesThread extends Thread {
169 private boolean running = false;
170 private SurfaceHolder surfaceHolder;
alisione38001f2013-06-04 14:14:39 -0400171 public Boolean suspendFlag = false;
alisionfe9cf712013-05-03 17:26:08 -0400172
alision2cb99562013-05-30 17:02:20 -0400173 BubbleModel model = null;
alisionfe9cf712013-05-03 17:26:08 -0400174
alision2cb99562013-05-30 17:02:20 -0400175 public BubblesThread(SurfaceHolder holder, Context context, Handler handler) {
176 surfaceHolder = holder;
177 }
alisionfe9cf712013-05-03 17:26:08 -0400178
alision2cb99562013-05-30 17:02:20 -0400179 public void setModel(BubbleModel model) {
180 this.model = model;
181 }
alisionfe9cf712013-05-03 17:26:08 -0400182
alision2cb99562013-05-30 17:02:20 -0400183 @Override
184 public void run() {
185 while (running) {
186 Canvas c = null;
187 try {
Adrien Béraud7ed23dc2013-05-06 16:27:24 +1000188
alisione38001f2013-06-04 14:14:39 -0400189 if (suspendFlag) {
190 synchronized (this) {
191 while (suspendFlag) {
192 try {
193 wait();
194 } catch (InterruptedException e) {
195 // TODO Auto-generated catch block
196 e.printStackTrace();
197 }
198 }
199 }
200 } else {
alisione38001f2013-06-04 14:14:39 -0400201 c = surfaceHolder.lockCanvas(null);
Adrien Béraud7ed23dc2013-05-06 16:27:24 +1000202
alisione38001f2013-06-04 14:14:39 -0400203 // for the case the surface is destroyed while already in the loop
204 if (c == null || model == null)
205 continue;
206
207 synchronized (surfaceHolder) {
208 // Log.w(TAG, "Thread doDraw");
209 model.update();
210 doDraw(c);
211 }
alision2cb99562013-05-30 17:02:20 -0400212 }
alisione38001f2013-06-04 14:14:39 -0400213
alision2cb99562013-05-30 17:02:20 -0400214 } finally {
215 if (c != null)
216 surfaceHolder.unlockCanvasAndPost(c);
217 }
218 }
219 }
Adrien Béraud04463092013-05-06 14:17:22 +1000220
alisione38001f2013-06-04 14:14:39 -0400221 public void setPaused(boolean wantToPause) {
222 synchronized (this) {
223 suspendFlag = wantToPause;
224 notify();
225 }
226 }
227
alision2cb99562013-05-30 17:02:20 -0400228 public void setRunning(boolean b) {
229 running = b;
230 }
Adrien Béraud04463092013-05-06 14:17:22 +1000231
alision2cb99562013-05-30 17:02:20 -0400232 public void setSurfaceSize(int width, int height) {
233 synchronized (surfaceHolder) {
234 if (model != null) {
235 model.width = width;
236 model.height = height;
237 }
238 }
239 }
Adrien Béraud04463092013-05-06 14:17:22 +1000240
alision2cb99562013-05-30 17:02:20 -0400241 /**
242 * I got multiple IndexOutOfBoundsException, when switching calls. //FIXME
243 *
244 * @param canvas
245 */
246 private void doDraw(Canvas canvas) {
247 canvas.drawColor(Color.WHITE);
Adrien Béraud04463092013-05-06 14:17:22 +1000248
alision2cb99562013-05-30 17:02:20 -0400249 synchronized (model) {
250 List<Bubble> bubbles = model.getBubbles();
251 List<Attractor> attractors = model.getAttractors();
252 try {
Adrien Béraud0c9bd8f2013-05-30 16:16:57 +1000253
alision2cb99562013-05-30 17:02:20 -0400254 for (int i = 0, n = attractors.size(); i < n; i++) {
255 Attractor a = attractors.get(i);
256 canvas.drawBitmap(a.getBitmap(), null, a.getBounds(), null);
257 }
Adrien Béraud33268882013-05-18 03:41:15 +1000258
alision2cb99562013-05-30 17:02:20 -0400259 for (int i = 0, n = bubbles.size(); i < n; i++) {
260 Bubble b = bubbles.get(i);
261 canvas.drawBitmap(b.getBitmap(), null, b.getBounds(), null);
alision806e18e2013-06-21 15:30:17 -0400262 canvas.drawText(b.associated_call.getContact().getmDisplayName(), b.getPosX(), b.getPosY() - 50 * density, name_paint);
alision2ec64f92013-06-17 17:28:58 -0400263
alision2cb99562013-05-30 17:02:20 -0400264 }
265
266 } catch (IndexOutOfBoundsException e) {
267 Log.e(TAG, e.toString());
268 }
269 }
270 }
271 }
Adrien Béraud04463092013-05-06 14:17:22 +1000272
alisione38001f2013-06-04 14:14:39 -0400273 @Override
274 public boolean onTouch(View v, MotionEvent event) {
275 Log.w(TAG, "onTouch " + event.getAction());
276
277 int action = event.getActionMasked();
278
279 if (action == MotionEvent.ACTION_UP) {
280 if (thread.suspendFlag) {
281 Log.i(TAG, "Relaunch drawing thread");
282 thread.setPaused(false);
283 }
284
285 List<Bubble> bubbles = model.getBubbles();
286 final int n_bubbles = bubbles.size();
287 for (int i = 0; i < n_bubbles; i++) {
288 Bubble b = bubbles.get(i);
289 if (b.dragged) {
290 b.dragged = false;
291 b.target_scale = 1.f;
292 }
293 }
294 dragging_bubble = false;
295 } else if (action != MotionEvent.ACTION_DOWN && !isDraggingBubble() && !thread.suspendFlag) {
296
297 Log.i(TAG, "Not dragging thread should be stopped");
298 thread.setPaused(true);
299 // thread.holdDrawing();
300 }
301
302 return gDetector.onTouchEvent(event);
303 }
304
alision2ec64f92013-06-17 17:28:58 -0400305 RectF intern;
306 RectF external;
307
alision58356b72013-06-03 17:13:36 -0400308 class MyOnGestureListener implements OnGestureListener {
309 @Override
310 public boolean onDown(MotionEvent event) {
311 List<Bubble> bubbles = model.getBubbles();
312 final int n_bubbles = bubbles.size();
alision2ec64f92013-06-17 17:28:58 -0400313 Bubble expand = getExpandedBubble();
314 if (expand != null && !expand.intersects(event.getX(), event.getY())) {
315
316 expand.retract();
317 }
alision58356b72013-06-03 17:13:36 -0400318 Log.d("Main", "onDown");
319 for (int i = 0; i < n_bubbles; i++) {
320 Bubble b = bubbles.get(i);
321 if (b.intersects(event.getX(), event.getY())) {
322 b.dragged = true;
323 b.last_drag = System.nanoTime();
324 b.setPos(event.getX(), event.getY());
325 b.target_scale = .8f;
326 dragging_bubble = true;
327 }
328 }
329 return true;
330 }
331
alision2ec64f92013-06-17 17:28:58 -0400332 private Bubble getExpandedBubble() {
333 List<Bubble> bubbles = model.getBubbles();
334 final int n_bubbles = bubbles.size();
335 for (int i = 0; i < n_bubbles; i++) {
336 Bubble b = bubbles.get(i);
337 if (b.expanded) {
338 return b;
339 }
340 }
341 return null;
342 }
343
alision58356b72013-06-03 17:13:36 -0400344 @Override
345 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
346 Log.d("Main", "onFling");
347 return true;
348 }
349
350 @Override
351 public void onLongPress(MotionEvent e) {
352 Log.d("Main", "onLongPress");
alision2ec64f92013-06-17 17:28:58 -0400353 if (isDraggingBubble()) {
354 Bubble b = getDraggedBubble(e);
355
356 calculateBounds(b.getPos(), b.getRadius());
357
358 b.expand(100);
359 }
360 }
361
362 private void calculateBounds(PointF pointF, float radius) {
363 // intern = new RectF();
364 // float half_square = (float) (radius / Math.sqrt(2));
365 // intern.set(pointF.x - half_square, pointF.y - half_square, pointF.x + half_square, pointF.y + half_square);
366
367 external = new RectF();
368 float large_half_square = 200;
369 external.set(pointF.x - large_half_square, pointF.y - large_half_square, pointF.x + large_half_square, pointF.y + large_half_square);
370
371 }
372
373 private Bubble getDraggedBubble(MotionEvent e) {
374 List<Bubble> bubbles = model.getBubbles();
375 final int n_bubbles = bubbles.size();
376 for (int i = 0; i < n_bubbles; i++) {
377 Bubble b = bubbles.get(i);
378 if (b.intersects(e.getX(), e.getY())) {
379 return b;
380 }
381 }
382 return null;
alision58356b72013-06-03 17:13:36 -0400383 }
384
385 @Override
386 public boolean onScroll(MotionEvent e1, MotionEvent event, float distanceX, float distanceY) {
387 Log.d("Main", "onScroll");
388 List<Bubble> bubbles = model.getBubbles();
389 final int n_bubbles = bubbles.size();
390 long now = System.nanoTime();
391 for (int i = 0; i < n_bubbles; i++) {
392 Bubble b = bubbles.get(i);
393 if (b.dragged) {
394 float x = event.getX(), y = event.getY();
395 float dt = (float) ((now - b.last_drag) / 1000000000.);
396 float dx = x - b.getPosX(), dy = y - b.getPosY();
397 b.last_drag = now;
398
399 b.setPos(event.getX(), event.getY());
400 /*
401 * int hn = event.getHistorySize() - 2; Log.w(TAG, "event.getHistorySize() : " + event.getHistorySize()); if(hn > 0) { float dx =
402 * x-event.getHistoricalX(hn); float dy = y-event.getHistoricalY(hn); float dt = event.getHistoricalEventTime(hn)/1000.f;
403 */
404 b.speed.x = dx / dt;
405 b.speed.y = dy / dt;
406 // Log.w(TAG, "onTouch dx:" + b.speed.x + " dy:" + b.speed.y);
407 // }
408 return true;
409 }
410 }
411 return true;
412 }
413
414 @Override
415 public void onShowPress(MotionEvent e) {
416 Log.d("Main", "onShowPress");
417
418 }
419
420 @Override
421 public boolean onSingleTapUp(MotionEvent e) {
422 Log.d("Main", "onSingleTapUp");
423 return true;
424 }
425 }
426
alisione38001f2013-06-04 14:14:39 -0400427 public void restartDrawing() {
428 if (thread != null && thread.suspendFlag) {
429 Log.i(TAG, "Relaunch drawing thread");
430 thread.setPaused(false);
431 }
432 }
433
alisionfe9cf712013-05-03 17:26:08 -0400434}