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 }