Android Open Source - DashClockWidget Weather Extension






From Project

Back to project page DashClockWidget.

License

The source code is released under:

Apache License

If you think the Android project DashClockWidget listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*
 * Copyright 2013 Google Inc.//from ww  w .j a  v  a  2 s  .  c  om
 *
 * 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.google.android.apps.dashclock.weather;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.text.TextUtils;

import com.google.android.apps.dashclock.LogUtils;
import com.google.android.apps.dashclock.api.DashClockExtension;
import com.google.android.apps.dashclock.api.ExtensionData;
import com.google.android.apps.dashclock.configuration.AppChooserPreference;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.location.LocationClient;
import com.google.android.gms.location.LocationRequest;

import net.nurik.roman.dashclock.BuildConfig;
import net.nurik.roman.dashclock.R;

import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Locale;

import static com.google.android.apps.dashclock.LogUtils.LOGD;
import static com.google.android.apps.dashclock.LogUtils.LOGE;
import static com.google.android.apps.dashclock.LogUtils.LOGW;
import static com.google.android.apps.dashclock.Utils.MINUTES_MILLIS;
import static com.google.android.apps.dashclock.Utils.MILLIS_NANOS;
import static com.google.android.apps.dashclock.Utils.SECONDS_MILLIS;
import static com.google.android.apps.dashclock.weather.YahooWeatherApiClient.LocationInfo;
import static com.google.android.apps.dashclock.weather.YahooWeatherApiClient.getLocationInfo;
import static com.google.android.apps.dashclock.weather.YahooWeatherApiClient.getWeatherForLocationInfo;
import static com.google.android.apps.dashclock.weather.YahooWeatherApiClient.setWeatherUnits;
import static com.google.android.gms.common.GooglePlayServicesClient.ConnectionCallbacks;
import static com.google.android.gms.common.GooglePlayServicesClient.OnConnectionFailedListener;

/**
 * A local weather and forecast extension.
 */
public class WeatherExtension extends DashClockExtension {
    private static final String TAG = LogUtils.makeLogTag(WeatherExtension.class);

    public static final String ACTION_RECEIVED_LOCATION
            = "com.google.android.apps.dashclock.action.RECEIVED_LOCATION";

    public static final String PREF_WEATHER_UNITS = "pref_weather_units";
    public static final String PREF_WEATHER_SHORTCUT = "pref_weather_shortcut";
    public static final String PREF_WEATHER_LOCATION = "pref_weather_location";

    public static final Intent DEFAULT_WEATHER_INTENT = new Intent(Intent.ACTION_VIEW,
            Uri.parse("https://www.google.com/search?q=weather"));

    public static final String STATE_WEATHER_LAST_BACKOFF_MILLIS
            = "state_weather_last_backoff_millis";
    public static final String STATE_WEATHER_LAST_UPDATE_ELAPSED_MILLIS
            = "state_weather_last_update_elapsed_millis";

    // At least 10 min b/w updates
    private static final int UPDATE_THROTTLE_MILLIS = 10 * MINUTES_MILLIS;

    private static final long STALE_LOCATION_NANOS = 10 * MINUTES_MILLIS * MILLIS_NANOS;

    // 30 seconds for first error retry
    private static final int INITIAL_BACKOFF_MILLIS = 30 * SECONDS_MILLIS;

    // 60 sec timeout for location attempt
    private static final int LOCATION_TIMEOUT_MILLIS = 60 * SECONDS_MILLIS;

    private static final Criteria sLocationCriteria;
    private LocationClient mPlayServicesLocationClient;

    private static String sWeatherUnits = "f";
    private static Intent sWeatherIntent;

    private Handler mServiceThreadHandler;
    private boolean mOneTimeLocationListenerActive = false;
    private Handler mTimeoutHandler = new Handler();

    static {
        sLocationCriteria = new Criteria();
        sLocationCriteria.setPowerRequirement(Criteria.POWER_LOW);
        sLocationCriteria.setAccuracy(Criteria.ACCURACY_COARSE);
        sLocationCriteria.setCostAllowed(false);
    }

    private void resetAndCancelRetries() {
        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
        sp.edit().remove(STATE_WEATHER_LAST_BACKOFF_MILLIS).apply();
        AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
        am.cancel(WeatherRetryReceiver.getPendingIntent(this));
    }

    private void scheduleRetry() {
        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
        int lastBackoffMillis = sp.getInt(STATE_WEATHER_LAST_BACKOFF_MILLIS, 0);
        int backoffMillis = (lastBackoffMillis > 0)
                ? lastBackoffMillis * 2
                : INITIAL_BACKOFF_MILLIS;
        sp.edit().putInt(STATE_WEATHER_LAST_BACKOFF_MILLIS, backoffMillis).apply();
        LOGD(TAG, "Scheduling weather retry in " + (backoffMillis / SECONDS_MILLIS) + " second(s)");
        AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
        am.set(AlarmManager.ELAPSED_REALTIME,
                SystemClock.elapsedRealtime() + backoffMillis,
                WeatherRetryReceiver.getPendingIntent(this));
    }

    @Override
    protected void onInitialize(boolean isReconnect) {
        super.onInitialize(isReconnect);
    }

    @Override
    protected void onUpdateData(int reason) {
        if (mServiceThreadHandler == null) {
            // Get handle to background thread
            mServiceThreadHandler = new Handler(Looper.myLooper());
        }

        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
        sWeatherUnits = sp.getString(PREF_WEATHER_UNITS, sWeatherUnits);
        sWeatherIntent = AppChooserPreference.getIntentValue(
                sp.getString(PREF_WEATHER_SHORTCUT, null), DEFAULT_WEATHER_INTENT);

        setWeatherUnits(sWeatherUnits);

        long lastUpdateElapsedMillis = sp.getLong(STATE_WEATHER_LAST_UPDATE_ELAPSED_MILLIS,
                -UPDATE_THROTTLE_MILLIS);
        long nowElapsedMillis = SystemClock.elapsedRealtime();
        if (reason != UPDATE_REASON_INITIAL && reason != UPDATE_REASON_MANUAL &&
                nowElapsedMillis < lastUpdateElapsedMillis + UPDATE_THROTTLE_MILLIS) {
            LOGD(TAG, "Throttling weather update attempt.");
            return;
        }

        LOGD(TAG, "Attempting weather update; reason=" + reason);

        NetworkInfo ni = ((ConnectivityManager) getSystemService(
                Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo();
        if (ni == null || !ni.isConnected()) {
            LOGD(TAG, "No network connection; not attempting to update weather.");
            return;
        }

        String manualLocationWoeid = WeatherLocationPreference.getWoeidFromValue(
                sp.getString(PREF_WEATHER_LOCATION, null));
        if (!TextUtils.isEmpty(manualLocationWoeid)) {
            // WOEIDs
            // Honolulu = 2423945
            // Paris = 615702
            // London = 44418
            // New York = 2459115
            // San Francisco = 2487956
            LocationInfo locationInfo = new LocationInfo();
            locationInfo.woeids = Arrays.asList(manualLocationWoeid);
            tryPublishWeatherUpdateFromLocationInfo(locationInfo);
            return;
        }

        // Get the user's location, then get the weather for that location.
        tryGooglePlayServicesGetLocationAndPublishWeatherUpdate(new Runnable() {
            @Override
            public void run() {
                // If there was an error with Play Services, try LocationManager
                tryLocationManagerGetLocationAndPublishWeatherUpdate();
            }
        });
    }

    private void tryGooglePlayServicesGetLocationAndPublishWeatherUpdate(
            final Runnable errorRunnable) {
        int playServicesResult = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
        if (playServicesResult != ConnectionResult.SUCCESS) {
            LOGW(TAG, "Google Play Services was unavailable (code " + playServicesResult + ").");
            if (errorRunnable != null) {
                errorRunnable.run();
            }
            return;
        }

        if (mPlayServicesLocationClient != null) {
            // Already trying to obtain a location. Don't call error runnable since this isn't
            // an error.
            return;
        }

        LOGD(TAG, "Getting location using Google Play Services.");
        mPlayServicesLocationClient = new LocationClient(this, new ConnectionCallbacks() {
            @Override
            public void onConnected(Bundle bundle) {
                if (mServiceThreadHandler == null) {
                    LOGW(TAG, "Service thread handler empty; can't use Play Services location.");
                    mPlayServicesLocationClient.disconnect();
                    mPlayServicesLocationClient = null;
                    if (errorRunnable != null) {
                        errorRunnable.run();
                    }
                    return;
                }

                mServiceThreadHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        onHasLocation();
                    }
                });
            }

            private void onHasLocation() {
                final Location lastLocation = mPlayServicesLocationClient.getLastLocation();
                if (lastLocation == null || (SystemClock.elapsedRealtimeNanos()
                        - lastLocation.getElapsedRealtimeNanos()) >= STALE_LOCATION_NANOS) {
                    LOGW(TAG, "Stale or missing last-known location; requesting single location "
                            + "update.");
                    LocationRequest request = LocationRequest.create()
                            .setExpirationDuration(LOCATION_TIMEOUT_MILLIS - 1000)
                            .setFastestInterval(0)
                            .setInterval(0)
                            .setNumUpdates(1)
                            .setSmallestDisplacement(0)
                            .setPriority(LocationRequest.PRIORITY_LOW_POWER);
                    mPlayServicesLocationClient.requestLocationUpdates(request,
                            PendingIntent.getService(WeatherExtension.this, 0,
                                    new Intent(WeatherExtension.this, WeatherExtension.class)
                                            .setAction(ACTION_RECEIVED_LOCATION),
                                    PendingIntent.FLAG_UPDATE_CURRENT));

                    // Schedule a retry if timing out. When the location request expires, updates
                    // will simply stop, and we won't get any notification of this, so handle it
                    // separately.
                    mTimeoutHandler.removeCallbacksAndMessages(null);
                    mTimeoutHandler.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            LOGE(TAG, "Play Services location request timed out.");
                            disableOneTimeLocationListener();
                            scheduleRetry();
                        }
                    }, LOCATION_TIMEOUT_MILLIS);
                } else {
                    tryPublishWeatherUpdateFromGeolocation(lastLocation);
                }

                mPlayServicesLocationClient.disconnect();
                mPlayServicesLocationClient = null;
            }

            @Override
            public void onDisconnected() {
                mPlayServicesLocationClient = null;
            }
        }, new OnConnectionFailedListener() {
            @Override
            public void onConnectionFailed(ConnectionResult connectionResult) {
                mPlayServicesLocationClient = null;
                if (errorRunnable != null) {
                    errorRunnable.run();
                }
            }
        });
        mPlayServicesLocationClient.connect();
    }

    private void tryLocationManagerGetLocationAndPublishWeatherUpdate() {
        LOGD(TAG, "Getting location using LocationManager");
        LocationManager lm = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
        String provider = lm.getBestProvider(sLocationCriteria, true);
        if (TextUtils.isEmpty(provider)) {
            publishErrorUpdate(new CantGetWeatherException(false, R.string.no_location_data,
                    "No available location providers matching criteria."));
            return;
        }

        final Location lastLocation = lm.getLastKnownLocation(provider);
        if (lastLocation == null ||
                (SystemClock.elapsedRealtimeNanos() - lastLocation.getElapsedRealtimeNanos())
                        >= STALE_LOCATION_NANOS) {
            LOGW(TAG, "Stale or missing last-known location; requesting single coarse location "
                    + "update.");
            disableOneTimeLocationListener();
            mOneTimeLocationListenerActive = true;
            lm.requestSingleUpdate(provider, mOneTimeLocationListener, null);

            // Time-out single location update request
            mTimeoutHandler.removeCallbacksAndMessages(null);
            mTimeoutHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    LOGE(TAG, "LocationManager location request timed out.");
                    disableOneTimeLocationListener();
                    scheduleRetry();
                }
            }, LOCATION_TIMEOUT_MILLIS);
        } else {
            tryPublishWeatherUpdateFromGeolocation(lastLocation);
        }
    }

    private void disableOneTimeLocationListener() {
        if (mOneTimeLocationListenerActive) {
            LocationManager lm = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
            lm.removeUpdates(mOneTimeLocationListener);
            mOneTimeLocationListenerActive = false;
        }
    }

    private LocationListener mOneTimeLocationListener = new LocationListener() {
        @Override
        public void onLocationChanged(Location location) {
            LOGD(TAG, "Got network location update");
            mTimeoutHandler.removeCallbacksAndMessages(null);
            tryPublishWeatherUpdateFromGeolocation(location);
            disableOneTimeLocationListener();
        }

        @Override
        public void onStatusChanged(String provider, int status, Bundle extras) {
            LOGD(TAG, "Network location provider status change: " + status);
            if (status == LocationProvider.TEMPORARILY_UNAVAILABLE) {
                scheduleRetry();
                disableOneTimeLocationListener();
            }
        }

        @Override
        public void onProviderEnabled(String provider) {
        }

        @Override
        public void onProviderDisabled(String provider) {
        }
    };

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (intent != null && ACTION_RECEIVED_LOCATION.equals(intent.getAction())) {
            final Location location = intent.getParcelableExtra(
                    LocationClient.KEY_LOCATION_CHANGED);
            if (mServiceThreadHandler == null) {
                LOGW(TAG, "Can't process location update because onUpdateData hasn't been called "
                        + "on this service instance.");
            } else if (location != null) {
                // A location update request succeeded; try publishing weather from here.
                LOGD(TAG, "Got a Play Services location update; trying weather update.");
                mTimeoutHandler.removeCallbacksAndMessages(null);
                mServiceThreadHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        tryPublishWeatherUpdateFromGeolocation(location);
                    }
                });
            }

            stopSelf(startId);
        }

        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mPlayServicesLocationClient != null) {
            mPlayServicesLocationClient.disconnect();
        }
        disableOneTimeLocationListener();
    }

    private void tryPublishWeatherUpdateFromGeolocation(Location location) {
        try {
            LOGD(TAG, "Using location: " + location.getLatitude() + "," + location.getLongitude());
            tryPublishWeatherUpdateFromLocationInfo(getLocationInfo(location));
        } catch (CantGetWeatherException e) {
            publishErrorUpdate(e);
            if (e.isRetryable()) {
                scheduleRetry();
            }
        }
    }

    private void tryPublishWeatherUpdateFromLocationInfo(LocationInfo locationInfo) {
        try {
            publishWeatherUpdate(getWeatherForLocationInfo(locationInfo));
        } catch (CantGetWeatherException e) {
            publishErrorUpdate(e);
            if (e.isRetryable()) {
                scheduleRetry();
            }
        }
    }

    private void publishErrorUpdate(CantGetWeatherException e) {
        LOGE(TAG, "Showing a weather extension error", e);
        publishUpdate(new ExtensionData()
                .visible(true)
                .clickIntent(sWeatherIntent)
                .icon(R.drawable.ic_weather_clear)
                .status(getString(R.string.status_none))
                .expandedBody(getString(e.getUserFacingErrorStringId())));
    }

    private void publishWeatherUpdate(WeatherData weatherData) {
        String temperature = (weatherData.temperature != WeatherData.INVALID_TEMPERATURE)
                ? getString(R.string.temperature_template, weatherData.temperature)
                : getString(R.string.status_none);
        StringBuilder expandedBody = new StringBuilder();

        if (weatherData.low != WeatherData.INVALID_TEMPERATURE
                && weatherData.high != WeatherData.INVALID_TEMPERATURE) {
            expandedBody.append(getString(R.string.weather_low_high_template,
                    getString(R.string.temperature_template, weatherData.low),
                    getString(R.string.temperature_template, weatherData.high)));
        }

        int conditionIconId = WeatherData.getConditionIconId(weatherData.conditionCode);
        if (WeatherData.getConditionIconId(weatherData.todayForecastConditionCode)
                == R.drawable.ic_weather_raining) {
            // Show rain if it will rain today.
            conditionIconId = R.drawable.ic_weather_raining;

            if (expandedBody.length() > 0) {
                expandedBody.append(", ");
            }
            expandedBody.append(
                    getString(R.string.later_forecast_template, weatherData.forecastText));
        }

        if (expandedBody.length() > 0) {
            expandedBody.append("\n");
        }
        expandedBody.append(weatherData.location);

        if (BuildConfig.DEBUG) {
            expandedBody.append("\n")
                    .append(SimpleDateFormat.getDateTimeInstance().format(new Date()));
        }

        publishUpdate(new ExtensionData()
                .visible(true)
                .clickIntent(sWeatherIntent)
                .status(temperature)
                .expandedTitle(getString(R.string.weather_expanded_title_template,
                        temperature + sWeatherUnits.toUpperCase(Locale.US),
                        weatherData.conditionText))
                .icon(conditionIconId)
                .expandedBody(expandedBody.toString()));

        // Mark that a successful weather update has been pushed
        resetAndCancelRetries();
        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
        sp.edit().putLong(STATE_WEATHER_LAST_UPDATE_ELAPSED_MILLIS,
                SystemClock.elapsedRealtime()).commit();
    }
}




Java Source Code List

com.example.dashclock.exampleextension.ExampleExtension.java
com.example.dashclock.exampleextension.ExampleSettingsActivity.java
com.google.android.apps.dashclock.BackupAgent.java
com.google.android.apps.dashclock.DashClockService.java
com.google.android.apps.dashclock.DaydreamService.java
com.google.android.apps.dashclock.ExtensionHost.java
com.google.android.apps.dashclock.ExtensionManager.java
com.google.android.apps.dashclock.ExtensionPackageChangeReceiver.java
com.google.android.apps.dashclock.HelpUtils.java
com.google.android.apps.dashclock.LogAttachmentProvider.java
com.google.android.apps.dashclock.LogUtils.java
com.google.android.apps.dashclock.PeriodicExtensionRefreshReceiver.java
com.google.android.apps.dashclock.Utils.java
com.google.android.apps.dashclock.WidgetClickProxyActivity.java
com.google.android.apps.dashclock.WidgetProvider.java
com.google.android.apps.dashclock.api.DashClockExtension.java
com.google.android.apps.dashclock.api.ExtensionData.java
com.google.android.apps.dashclock.api.VisibleExtension.java
com.google.android.apps.dashclock.api.package-info.java
com.google.android.apps.dashclock.calendar.CalendarExtension.java
com.google.android.apps.dashclock.calendar.CalendarSelectionPreference.java
com.google.android.apps.dashclock.calendar.CalendarSettingsActivity.java
com.google.android.apps.dashclock.configuration.AppChooserPreference.java
com.google.android.apps.dashclock.configuration.AppearanceConfig.java
com.google.android.apps.dashclock.configuration.BaseSettingsActivity.java
com.google.android.apps.dashclock.configuration.ColorPreference.java
com.google.android.apps.dashclock.configuration.ConfigurationActivity.java
com.google.android.apps.dashclock.configuration.ConfigureAdvancedFragment.java
com.google.android.apps.dashclock.configuration.ConfigureAppearanceFragment.java
com.google.android.apps.dashclock.configuration.ConfigureDaydreamFragment.java
com.google.android.apps.dashclock.configuration.ConfigureExtensionsFragment.java
com.google.android.apps.dashclock.configuration.DaydreamProxyActivity.java
com.google.android.apps.dashclock.gmail.GmailContract.java
com.google.android.apps.dashclock.gmail.GmailExtension.java
com.google.android.apps.dashclock.gmail.GmailSettingsActivity.java
com.google.android.apps.dashclock.nextalarm.NextAlarmExtension.java
com.google.android.apps.dashclock.nextalarm.NextAlarmSettingsActivity.java
com.google.android.apps.dashclock.phone.MissedCallsExtension.java
com.google.android.apps.dashclock.phone.SmsExtension.java
com.google.android.apps.dashclock.phone.TelephonyProviderConstants.java
com.google.android.apps.dashclock.render.DashClockRenderer.java
com.google.android.apps.dashclock.render.SimpleRenderer.java
com.google.android.apps.dashclock.render.SimpleViewBuilder.java
com.google.android.apps.dashclock.render.ViewBuilder.java
com.google.android.apps.dashclock.render.WidgetRemoteViewsFactoryService.java
com.google.android.apps.dashclock.render.WidgetRenderer.java
com.google.android.apps.dashclock.render.WidgetViewBuilder.java
com.google.android.apps.dashclock.ui.DragGripView.java
com.google.android.apps.dashclock.ui.EdgeEffectUtil.java
com.google.android.apps.dashclock.ui.PagerPositionStrip.java
com.google.android.apps.dashclock.ui.SimplePagedTabsHelper.java
com.google.android.apps.dashclock.ui.SwipeDismissListViewTouchListener.java
com.google.android.apps.dashclock.ui.UndoBarController.java
com.google.android.apps.dashclock.weather.CantGetWeatherException.java
com.google.android.apps.dashclock.weather.WeatherData.java
com.google.android.apps.dashclock.weather.WeatherExtension.java
com.google.android.apps.dashclock.weather.WeatherLocationPreference.java
com.google.android.apps.dashclock.weather.WeatherRetryReceiver.java
com.google.android.apps.dashclock.weather.WeatherSettingsActivity.java
com.google.android.apps.dashclock.weather.YahooWeatherApiClient.java
com.google.android.apps.dashclock.weather.YahooWeatherApiConfig.java