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
17package com.android.protips;
18
19import android.app.PendingIntent;
20import android.appwidget.AppWidgetManager;
21import android.appwidget.AppWidgetProvider;
22import android.content.ComponentName;
23import android.content.Context;
24import android.content.SharedPreferences;
25import android.content.Intent;
26import android.content.res.Resources;
27import android.os.Handler;
28import android.os.HandlerThread;
29import android.util.Log;
30import android.view.View;
31import android.widget.RemoteViews;
32
33import java.util.regex.Matcher;
34import java.util.regex.Pattern;
35
36/** Mister Widget appears on your home screen to provide helpful tips. */
37public 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