/*
* Copyright (C) 2014-2016 VersoBit
*
* This file is part of Weather Doge.
*
* Weather Doge 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.
*
* Weather Doge 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 Weather Doge. If not, see <http://www.gnu.org/licenses/>.
*/
package com.versobit.weatherdoge;
import android.app.IntentService;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.ContextCompat;
import android.text.format.DateFormat;
import android.util.Log;
import android.view.View;
import android.widget.RemoteViews;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public final class WidgetService extends IntentService implements LocationReceiver {
private static final String TAG = WidgetService.class.getSimpleName();
static final String ACTION_REFRESH_ALL = "refresh_all";
static final String ACTION_REFRESH_MULTIPLE = "refresh_multiple";
static final String ACTION_REFRESH_ONE = "refresh_one";
static final String EXTRA_WIDGET_ID = "widget_id";
static final int PERMISSION_NOTIFICATION_ID = 410;
private CountDownLatch locationLatch = new CountDownLatch(1);
private AppWidgetManager widgetManager;
private int[] widgets;
private PendingIntent pIntent;
public WidgetService() {
super(TAG);
}
@Override
protected void onHandleIntent(Intent intent) {
locationLatch = new CountDownLatch(1);
widgetManager = AppWidgetManager.getInstance(this);
if(ACTION_REFRESH_ALL.equals(intent.getAction())) {
widgets = widgetManager.getAppWidgetIds(new ComponentName(this, WidgetProvider.class));
} else if(ACTION_REFRESH_MULTIPLE.equals(intent.getAction())) {
widgets = intent.getIntArrayExtra(EXTRA_WIDGET_ID);
} else if(ACTION_REFRESH_ONE.equals(intent.getAction())) {
widgets = new int[] { intent.getIntExtra(EXTRA_WIDGET_ID, 0) };
} else {
Log.wtf(TAG, "Unknown action: " + intent.getAction());
return;
}
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
boolean forceMetric = prefs.getBoolean(OptionsActivity.PREF_FORCE_METRIC, false);
String forceLocation = prefs.getString(OptionsActivity.PREF_FORCE_LOCATION, "");
WeatherUtil.Source weatherSource = WeatherUtil.Source.OPEN_WEATHER_MAP;
if("1".equals(prefs.getString(OptionsActivity.PREF_WEATHER_SOURCE, "0"))) {
weatherSource = WeatherUtil.Source.YAHOO;
}
boolean tapToRefresh = prefs.getBoolean(OptionsActivity.PREF_WIDGET_TAP_TO_REFRESH, false);
boolean showWowText = prefs.getBoolean(OptionsActivity.PREF_WIDGET_SHOW_WOWTEXT, true);
boolean showDate = prefs.getBoolean(OptionsActivity.PREF_WIDGET_SHOW_DATE, false);
boolean backgroundFix = prefs.getBoolean(OptionsActivity.PREF_WIDGET_BACKGROUND_FIX, false);
if(tapToRefresh) {
pIntent = PendingIntent.getService(this, 0,
new Intent(this, WidgetService.class).setAction(ACTION_REFRESH_ALL),
PendingIntent.FLAG_UPDATE_CURRENT);
} else {
pIntent = PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class),
PendingIntent.FLAG_UPDATE_CURRENT);
}
setStatus(getString(R.string.loading));
if(forceLocation == null || forceLocation.isEmpty()) {
if(!LocationApi.isAvailable(this)) {
showError(BuildConfig.FLAVOR.equals(BuildConfig.FLAVOR_PLAY) ?
R.string.widget_error_no_gms : R.string.widget_error_location_settings);
return;
}
}
WeatherUtil.WeatherResult result = null;
WeatherUtil.WeatherData data;
String locationName = "";
if(forceLocation == null || forceLocation.isEmpty()) {
if (ContextCompat.checkSelfPermission(this, WeatherDoge.LOCATION_PERMISSION)
!= PackageManager.PERMISSION_GRANTED) {
showError(R.string.widget_error_permission);
showPermissionNotification();
return;
}
LocationApi locationApi = new LocationApi(this, this);
locationApi.connect();
try {
locationLatch.await(15, TimeUnit.SECONDS);
} catch (InterruptedException ex) {
Log.wtf(TAG, ex);
showError(R.string.widget_error_unknown);
return;
}
if(!locationApi.isConnected()) {
showError(R.string.widget_error_gms_connect);
return;
}
Location location = locationApi.getLocation();
locationApi.disconnect();
if(location == null) {
Log.e(TAG, "Unable to retrieve location. (null)");
showError(BuildConfig.FLAVOR.equals(BuildConfig.FLAVOR_PLAY) ?
R.string.widget_error_location : R.string.widget_error_location_settings);
return;
}
data = Cache.getWeatherData(this, location.getLatitude(),location.getLongitude());
if(data == null || data.source != weatherSource) {
result = WeatherUtil.getWeather(location.getLatitude(), location.getLongitude(),
weatherSource);
}
Geocoder geocoder = new Geocoder(this);
try {
List<Address> addresses = geocoder.getFromLocation(location.getLatitude(),
location.getLongitude(), 1);
if (addresses != null && addresses.size() > 0) {
locationName = addresses.get(0).getLocality();
}
} catch (IOException ex) {
Log.wtf(TAG, ex);
showError(R.string.widget_error_geocoder);
return;
}
} else {
locationName = forceLocation;
data = Cache.getWeatherData(this, forceLocation);
if(data == null || data.source != weatherSource) {
result = WeatherUtil.getWeather(forceLocation, weatherSource);
}
}
if(data == null || data.source != weatherSource) {
if(result == null) {
Log.wtf(TAG, "data: " + (data == null ? "null" : data) + ", data.source: " +
((data == null || data.source == null) ? "null" : data.source) +
", weatherSource: " + weatherSource);
showError(R.string.widget_error_unknown);
return;
}
switch (result.error) {
case WeatherUtil.WeatherResult.ERROR_NONE:
data = result.data;
Cache.putWeatherData(this, data);
break;
case WeatherUtil.WeatherResult.ERROR_API:
Log.e(TAG, "ERROR_API: " + (result.msg == null ? "null" : result.msg));
showError(R.string.widget_error_api);
return;
case WeatherUtil.WeatherResult.ERROR_THROWABLE:
Log.e(TAG, "ERROR_THROWABLE: " + (result.msg == null ? "null" : result.msg), result.throwable);
showError(R.string.widget_error_weather_util);
return;
default:
Log.wtf(TAG, "Unhandled WeatherResult: " + result.error);
showError(R.string.widget_error_unknown);
return;
}
}
double temp = data.temperature;
if(UnitLocale.getDefault() == UnitLocale.IMPERIAL && !forceMetric) {
temp = temp * 1.8d + 32d; // F
}
temp = Math.round(temp);
DecimalFormat tempFormat = new DecimalFormat();
tempFormat.setMaximumFractionDigits(0);
tempFormat.setDecimalSeparatorAlwaysShown(false);
tempFormat.setGroupingUsed(false);
String formattedTemp = tempFormat.format(temp) + "°";
int dogeImg = WeatherDoge.dogeSelect(data.image);
int skyImg = WeatherDoge.skySelect(data.image);
if(locationName == null || locationName.isEmpty()) {
locationName = data.place;
}
// Generate the common text bitmaps
formattedTemp = formattedTemp.isEmpty() ? " " : formattedTemp;
String condition = data.condition.isEmpty() ? " " : data.condition;
locationName = locationName.isEmpty() ? " " : locationName;
Date now = new Date();
StringBuilder time = new StringBuilder();
if(showDate) {
time.append(DateFormat.format(" MMM d ", now)).append(getString(R.string.widget_at));
}
time.append(" ").append(DateFormat.getTimeFormat(this).format(now)).append(" ");
Bitmap[] textBitmaps = WidgetProvider.getTextBitmaps(this,
formattedTemp, condition, locationName, time.toString());
for(int widget : widgets) {
RemoteViews views = new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.widget);
Bitmap sky = null;
Bitmap wowLayer = null;
boolean failed = false;
views.setOnClickPendingIntent(R.id.widget_root, pIntent);
views.setImageViewResource(R.id.widget_dogeimg, dogeImg);
views.setImageViewBitmap(R.id.widget_tempimg, textBitmaps[0]);
views.setImageViewBitmap(R.id.widget_descimg, textBitmaps[1]);
views.setImageViewBitmap(R.id.widget_locationimg, textBitmaps[2]);
views.setImageViewBitmap(R.id.widget_last_updated_img, textBitmaps[3]);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
try {
Bundle options = widgetManager.getAppWidgetOptions(widget);
if(!backgroundFix) {
sky = WidgetProvider.getSkyBitmap(this, options, skyImg);
views.setImageViewBitmap(R.id.widget_sky, sky);
views.setInt(R.id.widget_sky, "setVisibility", View.VISIBLE);
views.setInt(R.id.widget_sky_compat, "setVisibility", View.GONE);
}
if(showWowText) {
wowLayer = WidgetProvider.getWowLayer(this, options, data.image, (int)data.temperature);
views.setImageViewBitmap(R.id.widget_wowlayer, wowLayer);
} else {
views.setImageViewBitmap(R.id.widget_wowlayer, null);
}
} catch (Exception ex) {
Log.wtf(TAG, ex);
failed = true;
}
}
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN || failed || backgroundFix) {
views.setInt(R.id.widget_sky, "setVisibility", View.GONE);
views.setInt(R.id.widget_sky_compat, "setVisibility", View.VISIBLE);
views.setImageViewResource(R.id.widget_sky_compat, skyImg);
}
widgetManager.updateAppWidget(widget, views);
if(sky != null && !sky.isRecycled()) {
sky.recycle();
}
if(wowLayer != null && !wowLayer.isRecycled()) {
wowLayer.recycle();
}
}
for(Bitmap b : textBitmaps) {
if(b != null && !b.isRecycled()) {
b.recycle();
}
}
}
private void showError(final int resId) {
String error = getString(resId);
Log.e(TAG, error);
setStatus(error);
}
private void setStatus(String status) {
Bitmap loading = WidgetProvider.getStatusBitmap(this, status);
for(int widget : widgets) {
RemoteViews views = new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.widget);
views.setImageViewBitmap(R.id.widget_locationimg, loading);
views.setImageViewBitmap(R.id.widget_last_updated_img, null);
views.setOnClickPendingIntent(R.id.widget_root, pIntent);
widgetManager.partiallyUpdateAppWidget(widget, views);
}
loading.recycle();
}
private void showPermissionNotification() {
PendingIntent intent = PendingIntent.getActivity(this, 0,
new Intent(this, MainActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_launcher) // TODO: Needs a real icon
.setContentTitle(getString(R.string.widget_notification_permission_title))
.setContentText(getString(R.string.widget_notification_permission_body))
.setContentIntent(intent);
new NotificationCompat.BigTextStyle(builder)
.setBigContentTitle(builder.mContentTitle)
.bigText(builder.mContentText);
((NotificationManager)getSystemService(NOTIFICATION_SERVICE))
.notify(PERMISSION_NOTIFICATION_ID, builder.build());
}
@Override
public void onLocation(Location location) {
locationLatch.countDown();
}
@Override
public void onConnected() {
locationLatch.countDown();
}
}