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");
thr.start()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 
setup(Context context)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 
goodmorning()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
onReceive(final Context context, final Intent intent)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 
onReceiveAsync(Context context, Intent intent)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 
refresh()158     private void refresh() {
159         RemoteViews rv = buildUpdate(mContext);
160         for (int i : mWidgetIds) {
161             mWidgetManager.updateAppWidget(i, rv);
162         }
163     }
164 
setIcon(int resId)165     private void setIcon(int resId) {
166         mIconRes = resId;
167         refresh();
168     }
169 
getNextMessageIndex()170     private int getNextMessageIndex() {
171         return (mMessage + 1) % mTips.length;
172     }
173 
blink(int blinks)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 
buildUpdate(Context context)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