1/* 2 * Copyright (C) 2008-2009 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17package com.android.inputmethod.latin; 18 19import android.content.Context; 20import android.graphics.Rect; 21import android.graphics.drawable.Drawable; 22import android.os.Handler; 23import android.os.Message; 24import android.text.Layout; 25import android.text.SpannableStringBuilder; 26import android.text.StaticLayout; 27import android.view.Gravity; 28import android.view.LayoutInflater; 29import android.view.MotionEvent; 30import android.view.View; 31import android.view.View.OnTouchListener; 32import android.widget.PopupWindow; 33import android.widget.TextView; 34 35import java.util.ArrayList; 36import java.util.List; 37 38public class Tutorial implements OnTouchListener { 39 40 private List<Bubble> mBubbles = new ArrayList<Bubble>(); 41 private long mStartTime; 42 private static final long MINIMUM_TIME = 6000; 43 private static final long MAXIMUM_TIME = 20000; 44 private View mInputView; 45 private LatinIME mIme; 46 private int[] mLocation = new int[2]; 47 private int mBubblePointerOffset; 48 49 private static final int MSG_SHOW_BUBBLE = 0; 50 51 private int mBubbleIndex; 52 53 Handler mHandler = new Handler() { 54 @Override 55 public void handleMessage(Message msg) { 56 switch (msg.what) { 57 case MSG_SHOW_BUBBLE: 58 Bubble bubba = (Bubble) msg.obj; 59 bubba.show(mLocation[0], mLocation[1]); 60 break; 61 } 62 } 63 }; 64 65 class Bubble { 66 Drawable bubbleBackground; 67 int x; 68 int y; 69 int width; 70 int gravity; 71 CharSequence text; 72 boolean dismissOnTouch; 73 boolean dismissOnClose; 74 PopupWindow window; 75 TextView textView; 76 View inputView; 77 78 Bubble(Context context, View inputView, 79 int backgroundResource, int bx, int by, int textResource1, int textResource2) { 80 bubbleBackground = context.getResources().getDrawable(backgroundResource); 81 x = bx; 82 y = by; 83 width = (int) (inputView.getWidth() * 0.9); 84 this.gravity = Gravity.TOP | Gravity.LEFT; 85 text = new SpannableStringBuilder() 86 .append(context.getResources().getText(textResource1)) 87 .append("\n") 88 .append(context.getResources().getText(textResource2)); 89 this.dismissOnTouch = true; 90 this.dismissOnClose = false; 91 this.inputView = inputView; 92 window = new PopupWindow(context); 93 window.setBackgroundDrawable(null); 94 LayoutInflater inflate = 95 (LayoutInflater) context 96 .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 97 textView = (TextView) inflate.inflate(R.layout.bubble_text, null); 98 textView.setBackgroundDrawable(bubbleBackground); 99 textView.setText(text); 100 //textView.setText(textResource1); 101 window.setContentView(textView); 102 window.setFocusable(false); 103 window.setTouchable(true); 104 window.setOutsideTouchable(false); 105 } 106 107 private int chooseSize(PopupWindow pop, View parentView, CharSequence text, TextView tv) { 108 int wid = tv.getPaddingLeft() + tv.getPaddingRight(); 109 int ht = tv.getPaddingTop() + tv.getPaddingBottom(); 110 111 /* 112 * Figure out how big the text would be if we laid it out to the 113 * full width of this view minus the border. 114 */ 115 int cap = width - wid; 116 117 Layout l = new StaticLayout(text, tv.getPaint(), cap, 118 Layout.Alignment.ALIGN_NORMAL, 1, 0, true); 119 float max = 0; 120 for (int i = 0; i < l.getLineCount(); i++) { 121 max = Math.max(max, l.getLineWidth(i)); 122 } 123 124 /* 125 * Now set the popup size to be big enough for the text plus the border. 126 */ 127 pop.setWidth(width); 128 pop.setHeight(ht + l.getHeight()); 129 return l.getHeight(); 130 } 131 132 void show(int offx, int offy) { 133 int textHeight = chooseSize(window, inputView, text, textView); 134 offy -= textView.getPaddingTop() + textHeight; 135 if (inputView.getVisibility() == View.VISIBLE 136 && inputView.getWindowVisibility() == View.VISIBLE) { 137 try { 138 if ((gravity & Gravity.BOTTOM) == Gravity.BOTTOM) offy -= window.getHeight(); 139 if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) offx -= window.getWidth(); 140 textView.setOnTouchListener(new View.OnTouchListener() { 141 public boolean onTouch(View view, MotionEvent me) { 142 Tutorial.this.next(); 143 return true; 144 } 145 }); 146 window.showAtLocation(inputView, Gravity.NO_GRAVITY, x + offx, y + offy); 147 } catch (Exception e) { 148 // Input view is not valid 149 } 150 } 151 } 152 153 void hide() { 154 if (window.isShowing()) { 155 textView.setOnTouchListener(null); 156 window.dismiss(); 157 } 158 } 159 160 boolean isShowing() { 161 return window.isShowing(); 162 } 163 } 164 165 public Tutorial(LatinIME ime, LatinKeyboardView inputView) { 166 Context context = inputView.getContext(); 167 mIme = ime; 168 int inputWidth = inputView.getWidth(); 169 final int x = inputWidth / 20; // Half of 1/10th 170 mBubblePointerOffset = inputView.getContext().getResources() 171 .getDimensionPixelOffset(R.dimen.bubble_pointer_offset); 172 Bubble bWelcome = new Bubble(context, inputView, 173 R.drawable.dialog_bubble_step02, x, 0, 174 R.string.tip_to_open_keyboard, R.string.touch_to_continue); 175 mBubbles.add(bWelcome); 176 Bubble bAccents = new Bubble(context, inputView, 177 R.drawable.dialog_bubble_step02, x, 0, 178 R.string.tip_to_view_accents, R.string.touch_to_continue); 179 mBubbles.add(bAccents); 180 Bubble b123 = new Bubble(context, inputView, 181 R.drawable.dialog_bubble_step07, x, 0, 182 R.string.tip_to_open_symbols, R.string.touch_to_continue); 183 mBubbles.add(b123); 184 Bubble bABC = new Bubble(context, inputView, 185 R.drawable.dialog_bubble_step07, x, 0, 186 R.string.tip_to_close_symbols, R.string.touch_to_continue); 187 mBubbles.add(bABC); 188 Bubble bSettings = new Bubble(context, inputView, 189 R.drawable.dialog_bubble_step07, x, 0, 190 R.string.tip_to_launch_settings, R.string.touch_to_continue); 191 mBubbles.add(bSettings); 192 Bubble bDone = new Bubble(context, inputView, 193 R.drawable.dialog_bubble_step02, x, 0, 194 R.string.tip_to_start_typing, R.string.touch_to_finish); 195 mBubbles.add(bDone); 196 mInputView = inputView; 197 } 198 199 void start() { 200 mInputView.getLocationInWindow(mLocation); 201 mBubbleIndex = -1; 202 mInputView.setOnTouchListener(this); 203 next(); 204 } 205 206 boolean next() { 207 if (mBubbleIndex >= 0) { 208 // If the bubble is not yet showing, don't move to the next. 209 if (!mBubbles.get(mBubbleIndex).isShowing()) { 210 return true; 211 } 212 // Hide all previous bubbles as well, as they may have had a delayed show 213 for (int i = 0; i <= mBubbleIndex; i++) { 214 mBubbles.get(i).hide(); 215 } 216 } 217 mBubbleIndex++; 218 if (mBubbleIndex >= mBubbles.size()) { 219 mInputView.setOnTouchListener(null); 220 mIme.sendDownUpKeyEvents(-1); // Inform the setupwizard that tutorial is in last bubble 221 mIme.tutorialDone(); 222 return false; 223 } 224 if (mBubbleIndex == 3 || mBubbleIndex == 4) { 225 mIme.mKeyboardSwitcher.toggleSymbols(); 226 } 227 mHandler.sendMessageDelayed( 228 mHandler.obtainMessage(MSG_SHOW_BUBBLE, mBubbles.get(mBubbleIndex)), 500); 229 return true; 230 } 231 232 void hide() { 233 for (int i = 0; i < mBubbles.size(); i++) { 234 mBubbles.get(i).hide(); 235 } 236 mInputView.setOnTouchListener(null); 237 } 238 239 boolean close() { 240 mHandler.removeMessages(MSG_SHOW_BUBBLE); 241 hide(); 242 return true; 243 } 244 245 public boolean onTouch(View v, MotionEvent event) { 246 if (event.getAction() == MotionEvent.ACTION_DOWN) { 247 next(); 248 } 249 return true; 250 } 251} 252