Java tutorial
/** * Copyright (C) 2013 Romain Guefveneu. * * This file is part of naonedbus. * * Naonedbus is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Naonedbus is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package net.naonedbus.appwidget; import java.io.IOException; import java.util.Arrays; import java.util.List; import net.naonedbus.BuildConfig; import net.naonedbus.R; import net.naonedbus.activity.impl.HorairesActivity; import net.naonedbus.activity.impl.MainActivity; import net.naonedbus.activity.widgetconfigure.WidgetConfigureActivity; import net.naonedbus.bean.Arret; import net.naonedbus.bean.Favori; import net.naonedbus.bean.NextHoraireTask; import net.naonedbus.bean.horaire.Horaire; import net.naonedbus.intent.ParamIntent; import net.naonedbus.manager.impl.FavorisViewManager; import net.naonedbus.manager.impl.HoraireManager; import net.naonedbus.utils.ColorUtils; import net.naonedbus.utils.FormatUtils; import org.joda.time.DateMidnight; import org.joda.time.DateTime; import android.annotation.TargetApi; import android.app.AlarmManager; import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.drawable.GradientDrawable; import android.os.Build; import android.os.Bundle; import android.support.v4.app.TaskStackBuilder; import android.text.TextUtils; import android.text.format.DateFormat; import android.util.Log; import android.util.TypedValue; import android.view.View; import android.view.View.MeasureSpec; import android.widget.RemoteViews; import android.widget.TextView; /** * @author romain.guefveneu * */ public abstract class HoraireWidgetProvider extends AppWidgetProvider { private static final String LOG_TAG = "HoraireWidgetProvider"; private static final boolean DBG = BuildConfig.DEBUG; private static final String ACTION_APPWIDGET_UPDATE = "net.naonedbus.action.APPWIDGET_UPDATE"; private static final String ACTION_APPWIDGET_ON_CLICK = "net.naonedbus.action.APPWIDGET_ON_CLICK"; private static PendingIntent sPendingIntent; private static long sNextTimestamp = Long.MAX_VALUE; private static Integer sHoraireTextWidth = Integer.MIN_VALUE; private static Object sLock = new Object(); private final int mLayoutId; private final int mHoraireLimitRes; private int mHoraireLimit = -1; protected HoraireWidgetProvider(final int layoutId, final int horaireLimitRes) { mLayoutId = layoutId; mHoraireLimitRes = horaireLimitRes; } @Override public void onUpdate(final Context context, final AppWidgetManager appWidgetManager, final int[] appWidgetIds) { final int count = appWidgetIds.length; if (DBG) Log.i(LOG_TAG, Integer.toHexString(hashCode()) + " - " + "onUpdate"); final Thread update = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < count; i++) { updateAppWidget(context, appWidgetManager, appWidgetIds[i]); } } }); update.start(); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN) @Override public void onAppWidgetOptionsChanged(final Context context, final AppWidgetManager appWidgetManager, final int appWidgetId, final Bundle newOptions) { super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions); updateAppWidget(context, appWidgetManager, appWidgetId); } /** * Programmer le rafraichissement des widgets. * * @param context * @param timestamp */ private synchronized void scheduleUpdate(final Context context, final long timestamp) { final long now = new DateTime().getMillis(); final long triggerTimestamp = new DateTime(timestamp).plusMinutes(1).getMillis(); if (sNextTimestamp < now || triggerTimestamp < sNextTimestamp) { sNextTimestamp = triggerTimestamp; final AlarmManager m = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); if (sPendingIntent != null) { m.cancel(sPendingIntent); if (DBG) Log.i(LOG_TAG, Integer.toHexString(hashCode()) + " - " + "\tAnnulation de la prcdente alarme."); } final Intent intent = new Intent(ACTION_APPWIDGET_UPDATE); sPendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0); m.set(AlarmManager.RTC, sNextTimestamp, sPendingIntent); if (DBG) Log.i(LOG_TAG, Integer.toHexString(hashCode()) + " - " + "Programmation de l'alarme : " + new DateTime(sNextTimestamp).toString()); } } /** * Update the widget. */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public void updateAppWidget(final Context context, final AppWidgetManager appWidgetManager, final int appWidgetId) { final RemoteViews views = new RemoteViews(context.getPackageName(), this.mLayoutId); final int idFavori = WidgetConfigureActivity.getFavoriIdFromWidget(context, appWidgetId); final Favori favori = FavorisViewManager.getInstance().getSingle(context.getContentResolver(), idFavori); if (DBG) Log.i(LOG_TAG, Integer.toHexString(hashCode()) + " - " + appWidgetId + " - updateAppWidget " + favori); // Initialisation du nombre d'horaires if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { final Bundle bundle = appWidgetManager.getAppWidgetOptions(appWidgetId); mHoraireLimit = getHorairesCount(context, bundle); } else { if (mHoraireLimit == -1) { mHoraireLimit = context.getResources().getInteger(mHoraireLimitRes); } } if (favori != null) { prepareWidgetView(context, views, favori, appWidgetId); appWidgetManager.updateAppWidget(appWidgetId, views); } else { WidgetConfigureActivity.removeWidgetId(context, appWidgetId); } } /** * Prparer le widget, le mettre jour si ncessaire. */ protected void prepareWidgetView(final Context context, final RemoteViews views, final Favori favori, final Integer appWidgetId) { if (DBG) Log.d(LOG_TAG, Integer.toHexString(hashCode()) + " - " + appWidgetId + " - prepareWidgetView " + favori); final HoraireManager horaireManager = HoraireManager.getInstance(); final DateMidnight today = new DateMidnight(); // Initialisation du widget prepareWidgetViewInit(context, views, favori); setOnClickListener(context, views, favori); try { if (horaireManager.isInDB(context.getContentResolver(), favori, today, mHoraireLimit)) { // Les horaires sont en cache final List<Horaire> horaires = horaireManager.getNextSchedules(context.getContentResolver(), favori, today, mHoraireLimit); prepareWidgetViewHoraires(context, views, favori, horaires); } else { // Charger les prochains horaires prepareWidgetAndloadHoraires(context, views, favori, appWidgetId); } } catch (final IOException e) { if (DBG) Log.e(LOG_TAG, Integer.toHexString(hashCode()) + " - " + appWidgetId + " - Erreur lors du chargement des horaires", e); } } /** * Lancer le chargement des horaires. */ protected void prepareWidgetAndloadHoraires(final Context context, final RemoteViews views, final Favori favori, final Integer appWidgetId) { final NextHoraireTask horaireTask = new NextHoraireTask(); horaireTask.setContext(context); horaireTask.setArret(favori); horaireTask.setId(appWidgetId); horaireTask.setLimit(mHoraireLimit); horaireTask.setActionCallback(ACTION_APPWIDGET_UPDATE); // Lancer le chargement des horaires final HoraireManager horaireManager = HoraireManager.getInstance(); horaireManager.schedule(horaireTask); views.setTextViewText(R.id.itemTime, null); views.setViewVisibility(R.id.widgetProgress, View.VISIBLE); } /** * Prparer le widget avec les lments fixes (nom, ligne, sens...) */ protected void prepareWidgetViewInit(final Context context, final RemoteViews views, final Favori favori) { views.setTextViewText(R.id.itemSymbole, favori.getLettre()); // Uniquement pour 2.2 et sup if (Build.VERSION.SDK_INT > 7) { views.setTextColor(R.id.itemSymbole, favori.getCouleurTexte()); views.setTextColor(R.id.itemTitle, favori.getCouleurTexte()); views.setTextColor(R.id.itemDescription, favori.getCouleurTexte()); final GradientDrawable gradientDrawable = ColorUtils.getGradiant(favori.getCouleurBackground()); final Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); final Canvas canvas = new Canvas(bitmap); gradientDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); gradientDrawable.draw(canvas); views.setImageViewBitmap(R.id.widgetHeaderBackground, bitmap); } if (favori.getNomFavori() == null) { views.setTextViewText(R.id.itemTitle, favori.getNomArret()); views.setTextViewText(R.id.itemDescription, FormatUtils.formatSens(favori.getNomSens())); } else { views.setTextViewText(R.id.itemTitle, favori.getNomArret()); views.setTextViewText(R.id.itemDescription, FormatUtils.formatSens(favori.getNomArret(), favori.getNomSens())); } views.setViewVisibility(R.id.widgetLoading, View.GONE); } /** * Set pending intent in remote views. * * @param context * @param views * Content remote views * @param viewId * The view listening to click */ private void setOnClickListener(final Context context, final RemoteViews views, final Favori favori) { final Intent intent = new Intent(context, this.getClass()); intent.setAction(ACTION_APPWIDGET_ON_CLICK); intent.putExtra("favori", favori); final PendingIntent pendingIntent = PendingIntent.getBroadcast(context, favori.getId(), intent, PendingIntent.FLAG_UPDATE_CURRENT); views.setOnClickPendingIntent(R.id.horaireWidget, pendingIntent); } /** * Prparer le widget avec les horaires. * * @param views * @param favori */ protected void prepareWidgetViewHoraires(final Context context, final RemoteViews views, final Favori favori, final List<Horaire> nextHoraires) { final java.text.DateFormat timeFormat = DateFormat.getTimeFormat(context); CharSequence content = ""; if (nextHoraires.size() > 0) { // Programmer si besoin l'alarme scheduleUpdate(context, nextHoraires.get(0).getHoraire().getMillis()); for (int i = 0; i < nextHoraires.size(); i++) { final Horaire horaire = nextHoraires.get(i); content = TextUtils.concat(content, FormatUtils.formatTimeAmPm(context, timeFormat.format(horaire.getHoraire().toDate()))); if (i < nextHoraires.size() - 1) { content = TextUtils.concat(content, " \u2022 "); } } } else { content = context.getString(R.string.msg_aucun_depart_24h); } views.setTextViewText(R.id.itemTime, content); views.setViewVisibility(R.id.widgetProgress, View.GONE); } /** * Supprimer la configuration du widget. */ @Override public void onDeleted(final Context context, final int[] appWidgetIds) { if (DBG) Log.i(LOG_TAG, Integer.toHexString(hashCode()) + " - " + "onDeleted " + Arrays.toString(appWidgetIds)); final int count = appWidgetIds.length; for (int i = 0; i < count; i++) { WidgetConfigureActivity.removeWidgetId(context, appWidgetIds[i]); } } /** * Grer le signal de rafraichissement. */ @Override public void onReceive(final Context context, final Intent intent) { final String action = intent.getAction(); final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); if (DBG) Log.i(LOG_TAG, Integer.toHexString(hashCode()) + " - " + "onReceive " + action); final boolean updateWidget = ACTION_APPWIDGET_UPDATE.equals(action) || Intent.ACTION_USER_PRESENT.equals(action); if (updateWidget) { final int idExtra = intent.getIntExtra("id", -1); int[] ids; if (DBG) Log.i(LOG_TAG, Integer.toHexString(hashCode()) + " - " + "Rception de l'ordre de rafraichissement des widgets."); if (idExtra == -1) { ids = appWidgetManager.getAppWidgetIds(new ComponentName(context, this.getClass())); } else { ids = new int[] { idExtra }; } onUpdate(context, appWidgetManager, ids); } else if (HoraireWidgetProvider.ACTION_APPWIDGET_ON_CLICK.equals(action)) { final Arret arret = intent.getParcelableExtra("favori"); if (arret != null) { final ParamIntent startIntent = new ParamIntent(context, HorairesActivity.class); startIntent.putExtra(HorairesActivity.PARAM_ARRET, arret); startIntent.putExtra(HorairesActivity.PARAM_FROM_WIDGET, true); startIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); final TaskStackBuilder stackBuilder = TaskStackBuilder.create(context); stackBuilder.addParentStack(MainActivity.class); stackBuilder.addNextIntentWithParentStack(startIntent); stackBuilder.startActivities(); } } super.onReceive(context, intent); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN) private int getHorairesCount(final Context context, final Bundle bundle) { synchronized (sLock) { if (sHoraireTextWidth == Integer.MIN_VALUE) { final TextView horaireTextView = new TextView(context); horaireTextView.setTextAppearance(horaireTextView.getContext(), android.R.style.TextAppearance_Medium); final DateTime noon = new DateTime().withHourOfDay(12).withMinuteOfHour(00); final java.text.DateFormat timeFormat = DateFormat.getTimeFormat(context); horaireTextView.setText( FormatUtils.formatTimeAmPm(context, timeFormat.format(noon.toDate())) + " \u2022 "); final int specY = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); final int specX = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); horaireTextView.measure(specX, specY); sHoraireTextWidth = horaireTextView.getMeasuredWidth(); } } final Resources r = context.getResources(); final int padding = r.getDimensionPixelSize(R.dimen.padding_small); final int minWidth = bundle.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH); final int minWidthPixel = Math.round( TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, minWidth, r.getDisplayMetrics())) - padding; return (minWidthPixel / sHoraireTextWidth); } }