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