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