Java tutorial
/* * Copyright (C) 2013-2016 Scott Warner * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tortel.deploytrack.provider; import java.util.Date; import java.util.List; import org.joda.time.DateTime; import com.google.firebase.analytics.FirebaseAnalytics; import com.tortel.deploytrack.Analytics; import com.tortel.deploytrack.Log; import com.tortel.deploytrack.Prefs; import com.tortel.deploytrack.R; import com.tortel.deploytrack.data.*; import android.annotation.TargetApi; import android.app.AlarmManager; import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.RectF; import android.graphics.Region; import android.os.Build; import android.os.Bundle; import android.view.View; import android.widget.RemoteViews; /** * Class that manages updating the widgets */ public class WidgetProvider extends AppWidgetProvider { public static final String UPDATE_INTENT = "com.tortel.deploytrack.WIDGET_UPDATE"; public static final String KEY_SCREENSHOT_MODE = "screenshot"; private static final int DEFAULT_SIZE = 190; private static final float PADDING = 0.5f; private static final int SCREENSHOT_TIMEOUT = 3; private static final int MILIS_PER_MIN = 60000; private boolean mScreenShotMode = false; @Override public void onReceive(Context context, Intent intent) { Log.v("Got intent: " + intent.toString()); Log.v("Intent action: " + intent.getAction()); // The standard widget intents are handled in the super call if (UPDATE_INTENT.equals(intent.getAction())) { // Check if the database needs to be upgraded if (DatabaseUpgrader.needsUpgrade(context)) { DatabaseUpgrader.doDatabaseUpgrade(context); } mScreenShotMode = intent.getBooleanExtra(KEY_SCREENSHOT_MODE, false); Log.v("Update intent received"); AppWidgetManager widgetManager = AppWidgetManager.getInstance(context); onUpdate(context, widgetManager, new int[0]); return; } super.onReceive(context, intent); } @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { // Check if the database needs to be upgraded if (DatabaseUpgrader.needsUpgrade(context)) { DatabaseUpgrader.doDatabaseUpgrade(context); } updateAllWidgets(context, appWidgetManager); AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); if (mScreenShotMode) { // Time screenshot mode out in 3 min Intent cancelScreenShotMode = new Intent(UPDATE_INTENT); cancelScreenShotMode.putExtra(KEY_SCREENSHOT_MODE, false); PendingIntent screenshotPending = PendingIntent.getBroadcast(context, 0, cancelScreenShotMode, PendingIntent.FLAG_CANCEL_CURRENT); long triggerTime = new Date().getTime() + SCREENSHOT_TIMEOUT * MILIS_PER_MIN; if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2) { alarmManager.setExact(AlarmManager.RTC, triggerTime, screenshotPending); } else { alarmManager.set(AlarmManager.RTC, triggerTime, screenshotPending); } } else { //Schedule an update at midnight DateTime now = new DateTime(); DateTime tomorrow = new DateTime(now.plusDays(1)).withTimeAtStartOfDay(); PendingIntent pending = PendingIntent.getBroadcast(context, 0, new Intent(UPDATE_INTENT), PendingIntent.FLAG_CANCEL_CURRENT); //Adding 100msec to make sure its triggered after midnight Log.d("Scheduling update for " + tomorrow.getMillis() + 100); if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2) { alarmManager.setExact(AlarmManager.RTC, tomorrow.getMillis() + 100, pending); } else { alarmManager.set(AlarmManager.RTC, tomorrow.getMillis() + 100, pending); } } } /** * Update all widgets in the database * @param context * @param appWidgetManager */ private void updateAllWidgets(Context context, AppWidgetManager appWidgetManager) { List<WidgetInfo> infoList = DatabaseManager.getInstance(context).getAllWidgetInfo(); // Log how many widgets there are FirebaseAnalytics.getInstance(context).setUserProperty(Analytics.PROPERTY_WIDGET_COUNT, "" + infoList.size()); for (WidgetInfo info : infoList) { int widgetId = info.getId(); Log.d("Updating widget " + widgetId); Log.d("Widget " + info.getId() + " with deployment " + info.getDeployment().getUuid()); //Draw everything RemoteViews remoteViews = updateWidgetView(context, info, mScreenShotMode); //Update it try { appWidgetManager.updateAppWidget(widgetId, remoteViews); } catch (Exception e) { /* * Catching all exceptions, because I suspect that if a widget has been deleted, * yet not removed from the database, it will still try to update it and probably cause * some sort of exception. So Ill just go ahead and keep the app from crashing. */ Log.e("Uhoh!", e); // Show the error view showErrorView(context, appWidgetManager, widgetId); } } } /** * Show the error view for the widget ID * @param context * @param appWidgetManager * @param widgetId */ private void showErrorView(Context context, AppWidgetManager appWidgetManager, int widgetId) { // Show the error message RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_error); try { appWidgetManager.updateAppWidget(widgetId, remoteViews); } catch (Exception e) { // Ohwell. We tried Log.e("Uhoh showing error view", e); } } @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) { DatabaseManager db = DatabaseManager.getInstance(context); WidgetInfo widgetInfo = db.getWidgetInfo(appWidgetId); // Bail if the info is null if (widgetInfo == null) { return; } int minWidth = newOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH); int maxWidth = newOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH); int minHeight = newOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT); int maxHeight = newOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT); Log.v("Widget size: " + minWidth + "/" + maxWidth + " W, " + minHeight + "/" + maxHeight + "H"); widgetInfo.setMinWidth(minWidth); widgetInfo.setMaxWidth(maxWidth); widgetInfo.setMinHeight(minHeight); widgetInfo.setMaxHeight(maxHeight); // Save it db.saveWidgetInfo(widgetInfo); super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions); } @Override public void onDeleted(Context context, int[] appWidgetIds) { //Remove them from the database for (int id : appWidgetIds) { DatabaseManager.getInstance(context).deleteWidgetInfo(id); } super.onDeleted(context, appWidgetIds); } /** * Sets up and fills the RemoteViews with the data provided in the WidgetInfo class */ public static RemoteViews updateWidgetView(Context context, WidgetInfo info) { return updateWidgetView(context, info, false); } /** * Sets up and fills the RemoteViews with the data provided in the WidgetInfo class */ public static RemoteViews updateWidgetView(Context context, WidgetInfo info, boolean screenShotMode) { Deployment deployment = info.getDeployment(); Resources resources = context.getResources(); Prefs.load(context); Log.v("Updating widget info for " + deployment); // Check for a null name and no percentage - it probably means that the deployment was deleted, but // the widgetinfo object is still around if (deployment.getName() == null && deployment.getPercentage() == 0) { return new RemoteViews(context.getPackageName(), R.layout.widget_error); } RemoteViews remoteViews; if (info.isWide()) { Log.v("Using wide layout"); remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_wide_layout); } else { remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_layout); } if (screenShotMode) { // If screen shot mode is enabled, hide the details Log.v("Screen shot mode enabled, hiding detail views"); remoteViews.setViewVisibility(R.id.widget_percent, View.GONE); remoteViews.setViewVisibility(R.id.widget_name, View.GONE); remoteViews.setViewVisibility(R.id.widget_info, View.GONE); } else { // Set the text remoteViews.setTextViewText(R.id.widget_percent, deployment.getPercentage() + "%"); remoteViews.setTextViewText(R.id.widget_name, deployment.getName()); remoteViews.setTextViewText(R.id.widget_info, resources.getQuantityString(R.plurals.days_remaining, deployment.getRemaining(), deployment.getRemaining())); remoteViews.setViewVisibility(R.id.widget_name, View.VISIBLE); // Apply hide preferences if (Prefs.hideDate()) { remoteViews.setViewVisibility(R.id.widget_info, View.GONE); } else { remoteViews.setViewVisibility(R.id.widget_info, View.VISIBLE); } if (Prefs.hidePercent()) { remoteViews.setViewVisibility(R.id.widget_percent, View.GONE); } else { remoteViews.setViewVisibility(R.id.widget_percent, View.VISIBLE); } } int size = DEFAULT_SIZE; if (info.getMinHeight() > 0) { size = info.getMaxWidth(); Log.v("Using chart size " + size); } remoteViews.setImageViewBitmap(R.id.widget_pie, getChartBitmap(deployment, size)); // Apply text color if (info.isLightText()) { remoteViews.setTextColor(R.id.widget_info, Color.LTGRAY); remoteViews.setTextColor(R.id.widget_name, Color.LTGRAY); remoteViews.setTextColor(R.id.widget_percent, Color.LTGRAY); } else { remoteViews.setTextColor(R.id.widget_info, Color.DKGRAY); remoteViews.setTextColor(R.id.widget_name, Color.DKGRAY); remoteViews.setTextColor(R.id.widget_percent, Color.DKGRAY); } // Register an onClickListener Intent intent = new Intent(context, WidgetProvider.class); intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); int array[] = new int[1]; array[0] = info.getId(); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, array); PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); remoteViews.setOnClickPendingIntent(R.id.widget_pie, pendingIntent); return remoteViews; } public static Bitmap getChartBitmap(Deployment deployment, int size) { //Set up the pie chart image Bitmap.Config conf = Bitmap.Config.ARGB_8888; Bitmap bmp = Bitmap.createBitmap(size, size, conf); Canvas canvas = new Canvas(bmp); canvas.drawColor(Color.TRANSPARENT); Paint paint = new Paint(); paint.setAntiAlias(true); Path p = new Path(); RectF rect = new RectF(); Region region = new Region(); //Reset variables float currentAngle = 270; float currentSweep; float totalLength = deployment.getCompleted() + deployment.getRemaining(); int thickness = size / 3; float midX = size / 2f; float midY = size / 2f; float radius; if (midX < midY) { radius = midX; } else { radius = midY; } radius -= PADDING; float innerRadius = radius - thickness; // Draw completed if (deployment.getCompleted() > 0) { p.reset(); paint.setColor(deployment.getCompletedColor()); currentSweep = (deployment.getCompleted() / totalLength) * (360); rect.set(midX - radius, midY - radius, midX + radius, midY + radius); p.arcTo(rect, currentAngle + PADDING, currentSweep - PADDING); rect.set(midX - innerRadius, midY - innerRadius, midX + innerRadius, midY + innerRadius); p.arcTo(rect, (currentAngle + PADDING) + (currentSweep - PADDING), -(currentSweep - PADDING)); p.close(); region.set((int) (midX - radius), (int) (midY - radius), (int) (midX + radius), (int) (midY + radius)); canvas.drawPath(p, paint); currentAngle = currentAngle + currentSweep; } // Draw remaining if (deployment.getCompleted() > 0) { p.reset(); paint.setColor(deployment.getRemainingColor()); currentSweep = (deployment.getRemaining() / totalLength) * (360); rect.set(midX - radius, midY - radius, midX + radius, midY + radius); p.arcTo(rect, currentAngle + PADDING, currentSweep - PADDING); rect.set(midX - innerRadius, midY - innerRadius, midX + innerRadius, midY + innerRadius); p.arcTo(rect, (currentAngle + PADDING) + (currentSweep - PADDING), -(currentSweep - PADDING)); p.close(); region.set((int) (midX - radius), (int) (midY - radius), (int) (midX + radius), (int) (midY + radius)); canvas.drawPath(p, paint); } return bmp; } }