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.android.protips; 18 19 import android.app.AlarmManager; 20 import android.app.PendingIntent; 21 import android.app.Service; 22 import android.appwidget.AppWidgetManager; 23 import android.appwidget.AppWidgetProvider; 24 import android.content.BroadcastReceiver; 25 import android.content.ComponentName; 26 import android.content.Context; 27 import android.content.SharedPreferences; 28 import android.content.Intent; 29 import android.content.res.Resources; 30 import android.content.res.TypedArray; 31 import android.graphics.drawable.Drawable; 32 import android.net.Uri; 33 import android.os.Bundle; 34 import android.os.Handler; 35 import android.os.HandlerThread; 36 import android.os.IBinder; 37 import android.os.Looper; 38 import android.os.Message; 39 import android.os.SystemClock; 40 import android.text.format.Time; 41 import android.text.Spannable; 42 import android.util.Log; 43 import android.view.View; 44 import android.widget.RemoteViews; 45 46 import java.util.regex.Matcher; 47 import java.util.regex.Pattern; 48 import java.util.Random; 49 50 /** Mister Widget appears on your home screen to provide helpful tips. */ 51 public class ProtipWidget extends AppWidgetProvider { 52 public static final String ACTION_NEXT_TIP = "com.android.misterwidget.NEXT_TIP"; 53 public static final String ACTION_POKE = "com.android.misterwidget.HEE_HEE"; 54 55 public static final String EXTRA_TIMES = "times"; 56 57 public static final String PREFS_NAME = "Protips"; 58 public static final String PREFS_TIP_NUMBER = "widget_tip"; 59 public static final String PREFS_TIP_SET = "widget_tip_set"; 60 61 private static Random sRNG = new Random(); 62 63 private static final Pattern sNewlineRegex = Pattern.compile(" *\\n *"); 64 private static final Pattern sDrawableRegex = Pattern.compile(" *@(drawable/[a-z0-9_]+) *"); 65 66 // initial appearance: eyes closed, no bubble 67 private int mIconRes = R.drawable.droidman_open; 68 private int mMessage = 0; 69 private int mTipSet = 0; 70 71 private AppWidgetManager mWidgetManager = null; 72 private int[] mWidgetIds; 73 private Context mContext; 74 75 private CharSequence[] mTips; 76 77 private void setup(Context context) { 78 mContext = context; 79 mWidgetManager = AppWidgetManager.getInstance(context); 80 mWidgetIds = mWidgetManager.getAppWidgetIds(new ComponentName(context, ProtipWidget.class)); 81 82 SharedPreferences pref = context.getSharedPreferences(PREFS_NAME, 0); 83 mMessage = pref.getInt(PREFS_TIP_NUMBER, 0); 84 mTipSet = pref.getInt(PREFS_TIP_SET, 0); 85 86 mTips = context.getResources().getTextArray(mTipSet == 1 ? R.array.tips2 : R.array.tips); 87 88 if (mTips != null) { 89 if (mMessage >= mTips.length) mMessage = 0; 90 } else { 91 mMessage = -1; 92 } 93 } 94 95 public void goodmorning() { 96 mMessage = -1; 97 try { 98 setIcon(R.drawable.droidman_down_closed); 99 Thread.sleep(500); 100 setIcon(R.drawable.droidman_down_open); 101 Thread.sleep(200); 102 setIcon(R.drawable.droidman_down_closed); 103 Thread.sleep(100); 104 setIcon(R.drawable.droidman_down_open); 105 Thread.sleep(600); 106 } catch (InterruptedException ex) { 107 } 108 mMessage = 0; 109 mIconRes = R.drawable.droidman_open; 110 refresh(); 111 } 112 113 @Override 114 public void onReceive(Context context, Intent intent) { 115 setup(context); 116 117 Resources res = mContext.getResources(); 118 mTips = res.getTextArray(mTipSet == 1 ? R.array.tips2 : R.array.tips); 119 120 if (intent.getAction().equals(ACTION_NEXT_TIP)) { 121 mMessage = getNextMessageIndex(); 122 SharedPreferences.Editor pref = context.getSharedPreferences(PREFS_NAME, 0).edit(); 123 pref.putInt(PREFS_TIP_NUMBER, mMessage); 124 pref.commit(); 125 refresh(); 126 } else if (intent.getAction().equals(ACTION_POKE)) { 127 blink(intent.getIntExtra(EXTRA_TIMES, 1)); 128 } else if (intent.getAction().equals(AppWidgetManager.ACTION_APPWIDGET_ENABLED)) { 129 goodmorning(); 130 } else if (intent.getAction().equals("android.provider.Telephony.SECRET_CODE")) { 131 Log.d("Protips", "ACHIEVEMENT UNLOCKED"); 132 mTipSet = 1 - mTipSet; 133 mMessage = 0; 134 135 SharedPreferences.Editor pref = context.getSharedPreferences(PREFS_NAME, 0).edit(); 136 pref.putInt(PREFS_TIP_NUMBER, mMessage); 137 pref.putInt(PREFS_TIP_SET, mTipSet); 138 pref.commit(); 139 140 mContext.startActivity( 141 new Intent(Intent.ACTION_MAIN) 142 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP) 143 .addCategory(Intent.CATEGORY_HOME)); 144 145 final Intent bcast = new Intent(context, ProtipWidget.class); 146 bcast.setAction(ACTION_POKE); 147 bcast.putExtra(EXTRA_TIMES, 3); 148 mContext.sendBroadcast(bcast); 149 } else { 150 mIconRes = R.drawable.droidman_open; 151 refresh(); 152 } 153 } 154 155 private void refresh() { 156 RemoteViews rv = buildUpdate(mContext); 157 for (int i : mWidgetIds) { 158 mWidgetManager.updateAppWidget(i, rv); 159 } 160 } 161 162 private void setIcon(int resId) { 163 mIconRes = resId; 164 refresh(); 165 } 166 167 private int getNextMessageIndex() { 168 return (mMessage + 1) % mTips.length; 169 } 170 171 private void blink(int blinks) { 172 // don't blink if no bubble showing or if goodmorning() is happening 173 if (mMessage < 0) return; 174 175 setIcon(R.drawable.droidman_closed); 176 try { 177 Thread.sleep(100); 178 while (0<--blinks) { 179 setIcon(R.drawable.droidman_open); 180 Thread.sleep(200); 181 setIcon(R.drawable.droidman_closed); 182 Thread.sleep(100); 183 } 184 } catch (InterruptedException ex) { } 185 setIcon(R.drawable.droidman_open); 186 } 187 188 public RemoteViews buildUpdate(Context context) { 189 RemoteViews updateViews = new RemoteViews( 190 context.getPackageName(), R.layout.widget); 191 192 // Action for tap on bubble 193 Intent bcast = new Intent(context, ProtipWidget.class); 194 bcast.setAction(ACTION_NEXT_TIP); 195 PendingIntent pending = PendingIntent.getBroadcast( 196 context, 0, bcast, PendingIntent.FLAG_UPDATE_CURRENT); 197 updateViews.setOnClickPendingIntent(R.id.tip_bubble, pending); 198 199 // Action for tap on android 200 bcast = new Intent(context, ProtipWidget.class); 201 bcast.setAction(ACTION_POKE); 202 bcast.putExtra(EXTRA_TIMES, 1); 203 pending = PendingIntent.getBroadcast( 204 context, 0, bcast, PendingIntent.FLAG_UPDATE_CURRENT); 205 updateViews.setOnClickPendingIntent(R.id.bugdroid, pending); 206 207 // Tip bubble text 208 if (mMessage >= 0) { 209 String[] parts = sNewlineRegex.split(mTips[mMessage], 2); 210 String title = parts[0]; 211 String text = parts.length > 1 ? parts[1] : ""; 212 213 // Look for a callout graphic referenced in the text 214 Matcher m = sDrawableRegex.matcher(text); 215 if (m.find()) { 216 String imageName = m.group(1); 217 int resId = context.getResources().getIdentifier( 218 219 imageName, null, context.getPackageName()); 220 updateViews.setImageViewResource(R.id.tip_callout, resId); 221 updateViews.setViewVisibility(R.id.tip_callout, View.VISIBLE); 222 text = m.replaceFirst(""); 223 } else { 224 updateViews.setImageViewResource(R.id.tip_callout, 0); 225 updateViews.setViewVisibility(R.id.tip_callout, View.GONE); 226 } 227 228 updateViews.setTextViewText(R.id.tip_message, 229 text); 230 updateViews.setTextViewText(R.id.tip_header, 231 title); 232 updateViews.setTextViewText(R.id.tip_footer, 233 context.getResources().getString( 234 R.string.pager_footer, 235 (1+mMessage), mTips.length)); 236 updateViews.setViewVisibility(R.id.tip_bubble, View.VISIBLE); 237 } else { 238 updateViews.setViewVisibility(R.id.tip_bubble, View.INVISIBLE); 239 } 240 241 updateViews.setImageViewResource(R.id.bugdroid, mIconRes); 242 243 return updateViews; 244 } 245 } 246