1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.example.android.apis.view;
18 
19 import com.example.android.apis.R;
20 
21 import android.content.ClipData;
22 import android.content.Context;
23 import android.content.res.TypedArray;
24 import android.graphics.*;
25 import android.os.SystemClock;
26 import android.text.TextPaint;
27 import android.util.AttributeSet;
28 import android.util.Log;
29 import android.view.DragEvent;
30 import android.view.View;
31 import android.widget.TextView;
32 
33 public class DraggableDot extends View {
34     static final String TAG = "DraggableDot";
35 
36     private boolean mDragInProgress;
37     private boolean mHovering;
38     private boolean mAcceptsDrag;
39     TextView mReportView;
40 
41     private Paint mPaint;
42     private TextPaint mLegendPaint;
43     private Paint mGlow;
44     private static final int NUM_GLOW_STEPS = 10;
45     private static final int GREEN_STEP = 0x0000FF00 / NUM_GLOW_STEPS;
46     private static final int WHITE_STEP = 0x00FFFFFF / NUM_GLOW_STEPS;
47     private static final int ALPHA_STEP = 0xFF000000 / NUM_GLOW_STEPS;
48 
49     int mRadius;
50     int mAnrType;
51     CharSequence mLegend;
52 
53     static final int ANR_NONE = 0;
54     static final int ANR_SHADOW = 1;
55     static final int ANR_DROP = 2;
56 
sleepSixSeconds()57     void sleepSixSeconds() {
58         // hang forever; good for producing ANRs
59         long start = SystemClock.uptimeMillis();
60         do {
61             try { Thread.sleep(1000); } catch (InterruptedException e) {}
62         } while (SystemClock.uptimeMillis() < start + 6000);
63     }
64 
65     // Shadow builder that can ANR if desired
66     class ANRShadowBuilder extends DragShadowBuilder {
67         boolean mDoAnr;
68 
ANRShadowBuilder(View view, boolean doAnr)69         public ANRShadowBuilder(View view, boolean doAnr) {
70             super(view);
71             mDoAnr = doAnr;
72         }
73 
74         @Override
onDrawShadow(Canvas canvas)75         public void onDrawShadow(Canvas canvas) {
76             if (mDoAnr) {
77                 sleepSixSeconds();
78             }
79             super.onDrawShadow(canvas);
80         }
81     }
82 
DraggableDot(Context context, AttributeSet attrs)83     public DraggableDot(Context context, AttributeSet attrs) {
84         super(context, attrs);
85 
86         setFocusable(true);
87         setClickable(true);
88 
89         mLegend = "";
90 
91         mPaint = new Paint();
92         mPaint.setAntiAlias(true);
93         mPaint.setStrokeWidth(6);
94         mPaint.setColor(0xFFD00000);
95 
96         mLegendPaint = new TextPaint();
97         mLegendPaint.setAntiAlias(true);
98         mLegendPaint.setTextAlign(Paint.Align.CENTER);
99         mLegendPaint.setColor(0xFFF0F0FF);
100 
101         mGlow = new Paint();
102         mGlow.setAntiAlias(true);
103         mGlow.setStrokeWidth(1);
104         mGlow.setStyle(Paint.Style.STROKE);
105 
106         // look up any layout-defined attributes
107         TypedArray a = context.obtainStyledAttributes(attrs,
108                 R.styleable.DraggableDot);
109 
110         final int N = a.getIndexCount();
111         for (int i = 0; i < N; i++) {
112             int attr = a.getIndex(i);
113             switch (attr) {
114             case R.styleable.DraggableDot_radius: {
115                 mRadius = a.getDimensionPixelSize(attr, 0);
116             } break;
117 
118             case R.styleable.DraggableDot_legend: {
119                 mLegend = a.getText(attr);
120             } break;
121 
122             case R.styleable.DraggableDot_anr: {
123                 mAnrType = a.getInt(attr, 0);
124             } break;
125             }
126         }
127 
128         Log.i(TAG, "DraggableDot @ " + this + " : radius=" + mRadius + " legend='" + mLegend
129                 + "' anr=" + mAnrType);
130 
131         setOnLongClickListener(new View.OnLongClickListener() {
132             public boolean onLongClick(View v) {
133                 ClipData data = ClipData.newPlainText("dot", "Dot : " + v.toString());
134                 v.startDrag(data, new ANRShadowBuilder(v, mAnrType == ANR_SHADOW),
135                         (Object)v, 0);
136                 return true;
137             }
138         });
139     }
140 
setReportView(TextView view)141     void setReportView(TextView view) {
142         mReportView = view;
143     }
144 
145     @Override
onDraw(Canvas canvas)146     protected void onDraw(Canvas canvas) {
147         float wf = getWidth();
148         float hf = getHeight();
149         final float cx = wf/2;
150         final float cy = hf/2;
151         wf -= getPaddingLeft() + getPaddingRight();
152         hf -= getPaddingTop() + getPaddingBottom();
153         float rad = (wf < hf) ? wf/2 : hf/2;
154         canvas.drawCircle(cx, cy, rad, mPaint);
155 
156         if (mLegend != null && mLegend.length() > 0) {
157             canvas.drawText(mLegend, 0, mLegend.length(),
158                     cx, cy + mLegendPaint.getFontSpacing()/2,
159                     mLegendPaint);
160         }
161 
162         // if we're in the middle of a drag, light up as a potential target
163         if (mDragInProgress && mAcceptsDrag) {
164             for (int i = NUM_GLOW_STEPS; i > 0; i--) {
165                 int color = (mHovering) ? WHITE_STEP : GREEN_STEP;
166                 color = i*(color | ALPHA_STEP);
167                 mGlow.setColor(color);
168                 canvas.drawCircle(cx, cy, rad, mGlow);
169                 rad -= 0.5f;
170                 canvas.drawCircle(cx, cy, rad, mGlow);
171                 rad -= 0.5f;
172             }
173         }
174     }
175 
176     @Override
onMeasure(int widthSpec, int heightSpec)177     protected void onMeasure(int widthSpec, int heightSpec) {
178         int totalDiameter = 2*mRadius + getPaddingLeft() + getPaddingRight();
179         setMeasuredDimension(totalDiameter, totalDiameter);
180     }
181 
182     /**
183      * Drag and drop
184      */
185     @Override
onDragEvent(DragEvent event)186     public boolean onDragEvent(DragEvent event) {
187         boolean result = false;
188         switch (event.getAction()) {
189         case DragEvent.ACTION_DRAG_STARTED: {
190             // claim to accept any dragged content
191             Log.i(TAG, "Drag started, event=" + event);
192             // cache whether we accept the drag to return for LOCATION events
193             mDragInProgress = true;
194             mAcceptsDrag = result = true;
195             // Redraw in the new visual state if we are a potential drop target
196             if (mAcceptsDrag) {
197                 invalidate();
198             }
199         } break;
200 
201         case DragEvent.ACTION_DRAG_ENDED: {
202             Log.i(TAG, "Drag ended.");
203             if (mAcceptsDrag) {
204                 invalidate();
205             }
206             mDragInProgress = false;
207             mHovering = false;
208         } break;
209 
210         case DragEvent.ACTION_DRAG_LOCATION: {
211             // we returned true to DRAG_STARTED, so return true here
212             Log.i(TAG, "... seeing drag locations ...");
213             result = mAcceptsDrag;
214         } break;
215 
216         case DragEvent.ACTION_DROP: {
217             Log.i(TAG, "Got a drop! dot=" + this + " event=" + event);
218             if (mAnrType == ANR_DROP) {
219                 sleepSixSeconds();
220             }
221             processDrop(event);
222             result = true;
223         } break;
224 
225         case DragEvent.ACTION_DRAG_ENTERED: {
226             Log.i(TAG, "Entered dot @ " + this);
227             mHovering = true;
228             invalidate();
229         } break;
230 
231         case DragEvent.ACTION_DRAG_EXITED: {
232             Log.i(TAG, "Exited dot @ " + this);
233             mHovering = false;
234             invalidate();
235         } break;
236 
237         default:
238             Log.i(TAG, "other drag event: " + event);
239             result = mAcceptsDrag;
240             break;
241         }
242 
243         return result;
244     }
245 
processDrop(DragEvent event)246     private void processDrop(DragEvent event) {
247         final ClipData data = event.getClipData();
248         final int N = data.getItemCount();
249         for (int i = 0; i < N; i++) {
250             ClipData.Item item = data.getItemAt(i);
251             Log.i(TAG, "Dropped item " + i + " : " + item);
252             if (mReportView != null) {
253                 String text = item.coerceToText(getContext()).toString();
254                 if (event.getLocalState() == (Object) this) {
255                     text += " : Dropped on self!";
256                 }
257                 mReportView.setText(text);
258             }
259         }
260     }
261 }