aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/java/cyanogenmod/app/CMContextConstants.java20
-rw-r--r--src/java/cyanogenmod/providers/CMSettings.java7
-rw-r--r--src/java/cyanogenmod/providers/WeatherContract.java240
-rw-r--r--src/java/cyanogenmod/weather/CMWeatherManager.java373
-rw-r--r--src/java/cyanogenmod/weather/ICMWeatherManager.aidl31
-rw-r--r--src/java/cyanogenmod/weather/IRequestInfoListener.aidl31
-rw-r--r--src/java/cyanogenmod/weather/IWeatherServiceProviderChangeListener.aidl22
-rw-r--r--src/java/cyanogenmod/weather/RequestInfo.aidl19
-rw-r--r--src/java/cyanogenmod/weather/RequestInfo.java355
-rw-r--r--src/java/cyanogenmod/weather/WeatherInfo.aidl19
-rwxr-xr-xsrc/java/cyanogenmod/weather/WeatherInfo.java525
-rw-r--r--src/java/cyanogenmod/weather/WeatherLocation.aidl19
-rw-r--r--src/java/cyanogenmod/weather/WeatherLocation.java172
-rw-r--r--src/java/cyanogenmod/weather/util/WeatherUtils.java84
-rw-r--r--src/java/cyanogenmod/weatherservice/IWeatherProviderService.aidl28
-rw-r--r--src/java/cyanogenmod/weatherservice/IWeatherProviderServiceClient.aidl26
-rw-r--r--src/java/cyanogenmod/weatherservice/ServiceRequest.java118
-rw-r--r--src/java/cyanogenmod/weatherservice/ServiceRequestResult.aidl19
-rw-r--r--src/java/cyanogenmod/weatherservice/ServiceRequestResult.java203
-rw-r--r--src/java/cyanogenmod/weatherservice/WeatherProviderService.java196
-rw-r--r--src/java/org/cyanogenmod/internal/logging/CMMetricsLogger.java1
21 files changed, 2508 insertions, 0 deletions
diff --git a/src/java/cyanogenmod/app/CMContextConstants.java b/src/java/cyanogenmod/app/CMContextConstants.java
index 6b2cb23..98171b8 100644
--- a/src/java/cyanogenmod/app/CMContextConstants.java
+++ b/src/java/cyanogenmod/app/CMContextConstants.java
@@ -120,6 +120,18 @@ public final class CMContextConstants {
public static final String CM_LIVE_LOCK_SCREEN_SERVICE = "cmlivelockscreen";
/**
+ * Use with {@link android.content.Context#getSystemService} to retrieve a
+ * {@link cyanogenmod.weather.CMWeatherManager} to manage the weather service
+ * settings and request weather updates
+ *
+ * @see android.content.Context#getSystemService
+ * @see cyanogenmod.weather.CMWeatherManager
+ *
+ * @hide
+ */
+ public static final String CM_WEATHER_SERVICE = "cmweather";
+
+ /**
* Features supported by the CMSDK.
*/
public static class Features {
@@ -194,5 +206,13 @@ public final class CMContextConstants {
*/
@SdkConstant(SdkConstant.SdkConstantType.FEATURE)
public static final String LIVE_LOCK_SCREEN = "org.cyanogenmod.livelockscreen";
+
+ /**
+ * Feature for {@link PackageManager#getSystemAvailableFeatures} and
+ * {@link PackageManager#hasSystemFeature}: The device includes the cm weather weather
+ * service utilized by the cmsdk.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.FEATURE)
+ public static final String WEATHER_SERVICES = "org.cyanogenmod.weather";
}
}
diff --git a/src/java/cyanogenmod/providers/CMSettings.java b/src/java/cyanogenmod/providers/CMSettings.java
index 3886c5e..1a0f9a9 100644
--- a/src/java/cyanogenmod/providers/CMSettings.java
+++ b/src/java/cyanogenmod/providers/CMSettings.java
@@ -2686,6 +2686,13 @@ public final class CMSettings {
*/
public static final String ENABLED_EVENT_LIVE_LOCKS_KEY = "live_lockscreens_events_enabled";
+ /**
+ * Current active & enabled Weather Provider Service
+ *
+ * @hide
+ */
+ public static final String WEATHER_PROVIDER_SERVICE = "weather_provider_service";
+
// endregion
/**
diff --git a/src/java/cyanogenmod/providers/WeatherContract.java b/src/java/cyanogenmod/providers/WeatherContract.java
new file mode 100644
index 0000000..e8e3726
--- /dev/null
+++ b/src/java/cyanogenmod/providers/WeatherContract.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * 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 cyanogenmod.providers;
+
+import android.net.Uri;
+
+/**
+ * The contract between the weather provider and applications.
+ */
+public class WeatherContract {
+
+ /**
+ * The authority of the weather content provider
+ */
+ public static final String AUTHORITY = "com.cyanogenmod.weather";
+
+ /**
+ * A content:// style uri to the authority for the weather provider
+ */
+ public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
+
+ public static class WeatherColumns {
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "weather");
+
+ public static final Uri CURRENT_AND_FORECAST_WEATHER_URI
+ = Uri.withAppendedPath(CONTENT_URI, "current_and_forecast");
+ public static final Uri CURRENT_WEATHER_URI
+ = Uri.withAppendedPath(CONTENT_URI, "current");
+ public static final Uri FORECAST_WEATHER_URI
+ = Uri.withAppendedPath(CONTENT_URI, "forecast");
+
+ /**
+ * A unique ID for the city. NOTE: this value fully depends on the implementation of the
+ * weather provider service and can potentially change when you switch providers.
+ * <P>Type: TEXT</P>
+ */
+ public static final String CURRENT_CITY_ID = "city_id";
+
+ /**
+ * The city name
+ * <P>Type: TEXT</P>
+ */
+ public static final String CURRENT_CITY = "city";
+
+ /**
+ * A Valid {@link WeatherCode}
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CURRENT_CONDITION_CODE = "condition_code";
+
+
+ /**
+ * A localized string mapped to the current weather condition code. Note that, if no
+ * locale is found, the string will be in english
+ * <P>Type: TEXT</P>
+ */
+ public static final String CURRENT_CONDITION = "condition";
+
+ /**
+ * The current weather temperature
+ * <P>Type: FLOAT</P>
+ */
+ public static final String CURRENT_TEMPERATURE = "temperature";
+
+ /**
+ * The unit in which current temperature is reported
+ * <P>Type: INTEGER</P>
+ * Can be one of the following:
+ * <ul>
+ * <li>{@link TempUnit#CELSIUS}</li>
+ * <li>{@link TempUnit#FAHRENHEIT}</li>
+ * </ul>
+ */
+ public static final String CURRENT_TEMPERATURE_UNIT = "temperature_unit";
+
+ /**
+ * The current weather humidity
+ * <P>Type: FLOAT</P>
+ */
+ public static final String CURRENT_HUMIDITY = "humidity";
+
+ /**
+ * The current wind direction (in degrees)
+ * <P>Type: FLOAT</P>
+ */
+ public static final String CURRENT_WIND_DIRECTION = "wind_direction";
+
+ /**
+ * The current wind speed
+ * <P>Type: FLOAT</P>
+ */
+ public static final String CURRENT_WIND_SPEED = "wind_speed";
+
+ /**
+ * The unit in which the wind speed is reported
+ * <P>Type: INTEGER</P>
+ * Can be one of the following:
+ * <ul>
+ * <li>{@link WindSpeedUnit#KPH}</li>
+ * <li>{@link WindSpeedUnit#MPH}</li>
+ * </ul>
+ */
+ public static final String CURRENT_WIND_SPEED_UNIT = "wind_speed_unit";
+
+ /**
+ * The timestamp when this weather was reported
+ * <P>Type: LONG</P>
+ */
+ public static final String CURRENT_TIMESTAMP = "timestamp";
+
+ /**
+ * The forecasted low temperature
+ * <P>Type: FLOAT</P>
+ */
+ public static final String FORECAST_LOW = "forecast_low";
+
+ /**
+ * The forecasted high temperature
+ * <P>Type: FLOAT</P>
+ */
+ public static final String FORECAST_HIGH = "forecast_high";
+
+ /**
+ * A localized string mapped to the forecasted weather condition code. Note that, if no
+ * locale is found, the string will be in english
+ * <P>Type: TEXT</P>
+ */
+ public static final String FORECAST_CONDITION = "forecast_condition";
+
+ /**
+ * The code identifying the forecasted weather condition.
+ * @see #CURRENT_CONDITION_CODE
+ */
+ public static final String FORECAST_CONDITION_CODE = "forecast_condition_code";
+
+ /**
+ * Temperature units
+ */
+ public static final class TempUnit {
+ private TempUnit() {}
+ public final static int CELSIUS = 1;
+ public final static int FAHRENHEIT = 2;
+ }
+
+ /**
+ * Wind speed units
+ */
+ public static final class WindSpeedUnit {
+ private WindSpeedUnit() {}
+ /**
+ * Kilometers per hour
+ */
+ public final static int KPH = 1;
+
+ /**
+ * Miles per hour
+ */
+ public final static int MPH = 2;
+ }
+
+ /**
+ * Weather condition codes
+ */
+ public static final class WeatherCode {
+ private WeatherCode() {}
+
+ /**
+ * @hide
+ */
+ public final static int WEATHER_CODE_MIN = 0;
+
+ public final static int TORNADO = 0;
+ public final static int TROPICAL_STORM = 1;
+ public final static int HURRICANE = 2;
+ public final static int SEVERE_THUNDERSTORMS = 3;
+ public final static int THUNDERSTORMS = 4;
+ public final static int MIXED_RAIN_AND_SNOW = 5;
+ public final static int MIXED_RAIN_AND_SLEET = 6;
+ public final static int MIXED_SNOW_AND_SLEET = 7;
+ public final static int FREEZING_DRIZZLE = 8;
+ public final static int DRIZZLE = 9;
+ public final static int FREEZING_RAIN = 10;
+ public final static int SHOWERS = 11;
+ public final static int SNOW_FLURRIES = 12;
+ public final static int LIGHT_SNOW_SHOWERS = 13;
+ public final static int BLOWING_SNOW = 14;
+ public final static int SNOW = 15;
+ public final static int HAIL = 16;
+ public final static int SLEET = 17;
+ public final static int DUST = 18;
+ public final static int FOGGY = 19;
+ public final static int HAZE = 20;
+ public final static int SMOKY = 21;
+ public final static int BLUSTERY = 22;
+ public final static int WINDY = 23;
+ public final static int COLD = 24;
+ public final static int CLOUDY = 25;
+ public final static int MOSTLY_CLOUDY_NIGHT = 26;
+ public final static int MOSTLY_CLOUDY_DAY = 27;
+ public final static int PARTLY_CLOUDY_NIGHT = 28;
+ public final static int PARTLY_CLOUDY_DAY = 29;
+ public final static int CLEAR_NIGHT = 30;
+ public final static int SUNNY = 31;
+ public final static int FAIR_NIGHT = 32;
+ public final static int FAIR_DAY = 33;
+ public final static int MIXED_RAIN_AND_HAIL = 34;
+ public final static int HOT = 35;
+ public final static int ISOLATED_THUNDERSTORMS = 36;
+ public final static int SCATTERED_THUNDERSTORMS = 37;
+ public final static int SCATTERED_SHOWERS = 38;
+ public final static int HEAVY_SNOW = 39;
+ public final static int SCATTERED_SNOW_SHOWERS = 40;
+ public final static int PARTLY_CLOUDY = 41;
+ public final static int THUNDERSHOWER = 42;
+ public final static int SNOW_SHOWERS = 43;
+ public final static int ISOLATED_THUNDERSHOWERS = 44;
+
+ /**
+ * @hide
+ */
+ public final static int WEATHER_CODE_MAX = 44;
+
+ public final static int NOT_AVAILABLE = 3200;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/java/cyanogenmod/weather/CMWeatherManager.java b/src/java/cyanogenmod/weather/CMWeatherManager.java
new file mode 100644
index 0000000..32eab52
--- /dev/null
+++ b/src/java/cyanogenmod/weather/CMWeatherManager.java
@@ -0,0 +1,373 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * 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 cyanogenmod.weather;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.location.Location;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.ArraySet;
+import cyanogenmod.app.CMContextConstants;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Provides access to the weather services in the device.
+ */
+public class CMWeatherManager {
+
+ private static ICMWeatherManager sWeatherManagerService;
+ private static CMWeatherManager sInstance;
+ private Context mContext;
+ private Map<RequestInfo,WeatherUpdateRequestListener> mWeatherUpdateRequestListeners
+ = Collections.synchronizedMap(new HashMap<RequestInfo,WeatherUpdateRequestListener>());
+ private Map<RequestInfo,LookupCityRequestListener> mLookupNameRequestListeners
+ = Collections.synchronizedMap(new HashMap<RequestInfo,LookupCityRequestListener>());
+ private Handler mHandler;
+ private Set<WeatherServiceProviderChangeListener> mProviderChangedListeners = new ArraySet<>();
+
+ private static final String TAG = CMWeatherManager.class.getSimpleName();
+
+ /**
+ * Weather update request state: Successfully completed
+ */
+ public static final int WEATHER_REQUEST_COMPLETED = 1;
+
+ /**
+ * Weather update request state: You need to wait a bit longer before requesting an update
+ * again.
+ * <p>Please bear in mind that the weather does not change very often. A threshold of 10 minutes
+ * is enforced by the system</p>
+ */
+ public static final int WEATHER_REQUEST_SUBMITTED_TOO_SOON = -1;
+
+ /**
+ * Weather update request state: An error occurred while trying to update the weather. You
+ * should wait before trying again, or your request will be rejected with
+ * {@link #WEATHER_REQUEST_SUBMITTED_TOO_SOON}
+ */
+ public static final int WEATHER_REQUEST_FAILED = -2;
+
+ /**
+ * Weather update request state: Only one update request can be processed at a given time.
+ */
+ public static final int WEATHER_REQUEST_ALREADY_IN_PROGRESS = -3;
+
+ /** @hide */
+ public static final int LOOKUP_REQUEST_COMPLETED = 100;
+
+ /** @hide */
+ public static final int LOOKUP_REQUEST_FAILED = -100;
+
+ /** @hide */
+ public static final int LOOKUP_REQUEST_NO_MATCH_FOUND = -101;
+
+
+ private CMWeatherManager(Context context) {
+ Context appContext = context.getApplicationContext();
+ mContext = (appContext != null) ? appContext : context;
+ sWeatherManagerService = getService();
+
+ if (context.getPackageManager().hasSystemFeature(
+ CMContextConstants.Features.WEATHER_SERVICES) && (sWeatherManagerService == null)) {
+ throw new RuntimeException("Unable to bind the CMWeatherManagerService");
+ }
+ mHandler = new Handler(appContext.getMainLooper());
+ }
+
+ /**
+ * Gets or creates an instance of the {@link cyanogenmod.weather.CMWeatherManager}
+ * @param context
+ * @return {@link CMWeatherManager}
+ */
+ public static CMWeatherManager getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new CMWeatherManager(context);
+ }
+ return sInstance;
+ }
+
+ /** @hide */
+ public static ICMWeatherManager getService() {
+ if (sWeatherManagerService != null) {
+ return sWeatherManagerService;
+ }
+ IBinder binder = ServiceManager.getService(CMContextConstants.CM_WEATHER_SERVICE);
+ if (binder != null) {
+ sWeatherManagerService = ICMWeatherManager.Stub.asInterface(binder);
+ return sWeatherManagerService;
+ }
+ return null;
+ }
+
+ /**
+ * Forces the weather service to request the latest available weather information for
+ * the supplied {@link android.location.Location} location.
+ *
+ * @param location The location you want to get the latest weather data from.
+ * @param listener {@link WeatherUpdateRequestListener} To be notified once the active weather
+ * service provider has finished
+ * processing your request
+ */
+ public void requestWeatherUpdate(@NonNull Location location,
+ @NonNull WeatherUpdateRequestListener listener) {
+ if (sWeatherManagerService == null) {
+ return;
+ }
+
+ try {
+ RequestInfo info = new RequestInfo
+ .Builder(mRequestInfoListener)
+ .setLocation(location)
+ .build();
+ if (listener != null) mWeatherUpdateRequestListeners.put(info, listener);
+ sWeatherManagerService.updateWeather(info);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Forces the weather service to request the latest weather information for the provided
+ * WeatherLocation. This is the preferred method for requesting a weather update.
+ *
+ * @param weatherLocation A {@link cyanogenmod.weather.WeatherLocation} that was previously
+ * obtained by calling
+ * {@link #lookupCity(String, LookupCityRequestListener)}
+ * @param listener {@link WeatherUpdateRequestListener} To be notified once the active weather
+ * service provider has finished
+ * processing your request
+ */
+ public void requestWeatherUpdate(@NonNull WeatherLocation weatherLocation,
+ @NonNull WeatherUpdateRequestListener listener) {
+ if (sWeatherManagerService == null) {
+ return;
+ }
+
+ try {
+ RequestInfo info = new RequestInfo
+ .Builder(mRequestInfoListener)
+ .setWeatherLocation(weatherLocation)
+ .build();
+ if (listener != null) mWeatherUpdateRequestListeners.put(info, listener);
+ sWeatherManagerService.updateWeather(info);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Request the active weather provider service to lookup the supplied city name.
+ *
+ * @param city The city name
+ * @param listener {@link LookupCityRequestListener} To be notified once the request has been
+ * completed. Upon success, a list of
+ * {@link cyanogenmod.weather.WeatherLocation}
+ * will be provided
+ */
+ public void lookupCity(@NonNull String city, @NonNull LookupCityRequestListener listener) {
+ if (sWeatherManagerService == null) {
+ return;
+ }
+ try {
+ RequestInfo info = new RequestInfo
+ .Builder(mRequestInfoListener)
+ .setCityName(city)
+ .build();
+ if (listener != null) mLookupNameRequestListeners.put(info, listener);
+ sWeatherManagerService.lookupCity(info);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Registers a {@link WeatherServiceProviderChangeListener} to be notified when a new weather
+ * service provider becomes active.
+ * @param listener {@link WeatherServiceProviderChangeListener} to register
+ */
+ public void registerWeatherServiceProviderChangeListener(
+ @NonNull WeatherServiceProviderChangeListener listener) {
+ synchronized (mProviderChangedListeners) {
+ if (mProviderChangedListeners.contains(listener)) {
+ throw new IllegalArgumentException("Listener already registered");
+ }
+ if (mProviderChangedListeners.size() == 0) {
+ try {
+ sWeatherManagerService.registerWeatherServiceProviderChangeListener(
+ mProviderChangeListener);
+ } catch (RemoteException e){
+ }
+ }
+ mProviderChangedListeners.add(listener);
+ }
+ }
+
+ /**
+ * Unregisters a listener
+ * @param listener A previously registered {@link WeatherServiceProviderChangeListener}
+ */
+ public void unregisterWeatherServiceProviderChangeListener(
+ @NonNull WeatherServiceProviderChangeListener listener) {
+ synchronized (mProviderChangedListeners) {
+ if (!mProviderChangedListeners.contains(listener)) {
+ throw new IllegalArgumentException("Listener was never registered");
+ }
+ mProviderChangedListeners.remove(listener);
+ if (mProviderChangedListeners.size() == 0) {
+ try {
+ sWeatherManagerService.unregisterWeatherServiceProviderChangeListener(
+ mProviderChangeListener);
+ } catch(RemoteException e){
+ }
+ }
+ }
+ }
+
+ /**
+ * Gets the service's label as declared by the active weather service provider in its manifest
+ * @return the service's label
+ */
+ public String getActiveWeatherServiceProviderLabel() {
+ try {
+ return sWeatherManagerService.getActiveWeatherServiceProviderLabel();
+ } catch(RemoteException e){
+ }
+ return null;
+ }
+
+ private final IWeatherServiceProviderChangeListener mProviderChangeListener =
+ new IWeatherServiceProviderChangeListener.Stub() {
+ @Override
+ public void onWeatherServiceProviderChanged(final String providerName) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mProviderChangedListeners) {
+ List<WeatherServiceProviderChangeListener> deadListeners
+ = new ArrayList<>();
+ for (WeatherServiceProviderChangeListener listener
+ : mProviderChangedListeners) {
+ try {
+ listener.onWeatherServiceProviderChanged(providerName);
+ } catch (Throwable e) {
+ deadListeners.add(listener);
+ }
+ }
+ if (deadListeners.size() > 0) {
+ for (WeatherServiceProviderChangeListener listener : deadListeners) {
+ mProviderChangedListeners.remove(listener);
+ }
+ }
+ }
+ }
+ });
+ }
+ };
+
+ private final IRequestInfoListener mRequestInfoListener = new IRequestInfoListener.Stub() {
+
+ @Override
+ public void onWeatherRequestCompleted(final RequestInfo requestInfo, final int state,
+ final WeatherInfo weatherInfo) {
+ final WeatherUpdateRequestListener listener
+ = mWeatherUpdateRequestListeners.remove(requestInfo);
+ if (listener != null) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ listener.onWeatherRequestCompleted(state, weatherInfo);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onLookupCityRequestCompleted(RequestInfo requestInfo,
+ final List<WeatherLocation> weatherLocations) {
+
+ final LookupCityRequestListener listener
+ = mLookupNameRequestListeners.remove(requestInfo);
+ if (listener != null) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ ArrayList<WeatherLocation> list = null;
+ if (weatherLocations != null) {
+ list = new ArrayList<>(weatherLocations);
+ }
+ listener.onLookupCityRequestCompleted(list);
+ }
+ });
+ }
+ }
+ };
+
+ /**
+ * Interface used to receive notifications upon completion of a weather update request
+ */
+ public interface WeatherUpdateRequestListener {
+ /**
+ * This method will be called when the weather service provider has finished processing the
+ * request
+ *
+ * @param state Any of the following values
+ * {@link #WEATHER_REQUEST_COMPLETED}
+ * {@link #WEATHER_REQUEST_ALREADY_IN_PROGRESS}
+ * {@link #WEATHER_REQUEST_SUBMITTED_TOO_SOON}
+ * {@link #WEATHER_REQUEST_FAILED}
+ *
+ * @param weatherInfo A fully populated {@link WeatherInfo} if state is
+ * {@link #WEATHER_REQUEST_COMPLETED}, null otherwise
+ */
+ void onWeatherRequestCompleted(int state, WeatherInfo weatherInfo);
+ }
+
+ /**
+ * Interface used to receive notifications upon completion of a request to lookup a city name
+ */
+ public interface LookupCityRequestListener {
+ /**
+ * This method will be called when the weather service provider has finished processing the
+ * request. The argument can be null if the provider couldn't find a match
+ *
+ * @param locations
+ */
+ void onLookupCityRequestCompleted(ArrayList<WeatherLocation> locations);
+ }
+
+ /**
+ * Interface used to be notified when the user changes the weather service provider
+ */
+ public interface WeatherServiceProviderChangeListener {
+ /**
+ * This method will be called when a new weather service provider becomes active in the
+ * system. The parameter can be null when
+ * <p>The user removed the active weather service provider from the system </p>
+ * <p>The active weather provider was disabled.</p>
+ *
+ * @param providerLabel The label as declared on the weather service provider manifest
+ */
+ void onWeatherServiceProviderChanged(String providerLabel);
+ }
+}
diff --git a/src/java/cyanogenmod/weather/ICMWeatherManager.aidl b/src/java/cyanogenmod/weather/ICMWeatherManager.aidl
new file mode 100644
index 0000000..21e1d26
--- /dev/null
+++ b/src/java/cyanogenmod/weather/ICMWeatherManager.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * 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 cyanogenmod.weather;
+
+import cyanogenmod.weather.IWeatherServiceProviderChangeListener;
+import cyanogenmod.weather.RequestInfo;
+
+/** @hide */
+interface ICMWeatherManager {
+ oneway void updateWeather(in RequestInfo info);
+ oneway void lookupCity(in RequestInfo info);
+ oneway void registerWeatherServiceProviderChangeListener(
+ in IWeatherServiceProviderChangeListener listener);
+ oneway void unregisterWeatherServiceProviderChangeListener(
+ in IWeatherServiceProviderChangeListener listener);
+ String getActiveWeatherServiceProviderLabel();
+} \ No newline at end of file
diff --git a/src/java/cyanogenmod/weather/IRequestInfoListener.aidl b/src/java/cyanogenmod/weather/IRequestInfoListener.aidl
new file mode 100644
index 0000000..553da71
--- /dev/null
+++ b/src/java/cyanogenmod/weather/IRequestInfoListener.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * 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 cyanogenmod.weather;
+
+import cyanogenmod.weather.RequestInfo;
+import cyanogenmod.weather.WeatherInfo;
+import cyanogenmod.weather.WeatherLocation;
+
+import java.util.List;
+
+/** @hide */
+oneway interface IRequestInfoListener {
+ void onWeatherRequestCompleted(in RequestInfo requestInfo, int state,
+ in WeatherInfo weatherInfo);
+ void onLookupCityRequestCompleted(in RequestInfo requestInfo,
+ in List<WeatherLocation> weatherLocation);
+} \ No newline at end of file
diff --git a/src/java/cyanogenmod/weather/IWeatherServiceProviderChangeListener.aidl b/src/java/cyanogenmod/weather/IWeatherServiceProviderChangeListener.aidl
new file mode 100644
index 0000000..7c823b6
--- /dev/null
+++ b/src/java/cyanogenmod/weather/IWeatherServiceProviderChangeListener.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * 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 cyanogenmod.weather;
+
+/** @hide */
+oneway interface IWeatherServiceProviderChangeListener {
+ void onWeatherServiceProviderChanged(String providerLabel);
+} \ No newline at end of file
diff --git a/src/java/cyanogenmod/weather/RequestInfo.aidl b/src/java/cyanogenmod/weather/RequestInfo.aidl
new file mode 100644
index 0000000..5e53b93
--- /dev/null
+++ b/src/java/cyanogenmod/weather/RequestInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * 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 cyanogenmod.weather;
+
+parcelable RequestInfo; \ No newline at end of file
diff --git a/src/java/cyanogenmod/weather/RequestInfo.java b/src/java/cyanogenmod/weather/RequestInfo.java
new file mode 100644
index 0000000..e99af98
--- /dev/null
+++ b/src/java/cyanogenmod/weather/RequestInfo.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * 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 cyanogenmod.weather;
+
+import android.location.Location;
+import android.os.Parcel;
+import android.os.Parcelable;
+import cyanogenmod.os.Build;
+import cyanogenmod.providers.WeatherContract;
+
+/**
+ * This class holds the information of a request submitted to the active weather provider service
+ */
+public final class RequestInfo implements Parcelable {
+
+ private Location mLocation;
+ private String mCityName;
+ private WeatherLocation mWeatherLocation;
+ private int mRequestType;
+ private IRequestInfoListener mListener;
+ private int mTempUnit;
+ private int mKey;
+ private boolean mIsQueryOnly;
+
+ /**
+ * A request to update the weather data using a geographical {@link android.location.Location}
+ */
+ public static final int TYPE_GEO_LOCATION_REQ = 1;
+ /**
+ * A request to update the weather data using a {@link WeatherLocation}
+ */
+ public static final int TYPE_WEATHER_LOCATION_REQ = 2;
+
+ /**
+ * A request to look up a city name
+ */
+ public static final int TYPE_LOOKUP_CITY_NAME_REQ = 3;
+
+ private RequestInfo() {}
+
+ /* package */ static class Builder {
+ private Location mLocation;
+ private String mCityName;
+ private WeatherLocation mWeatherLocation;
+ private int mRequestType;
+ private IRequestInfoListener mListener;
+ private int mTempUnit = WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT;
+ private boolean mIsQueryOnly = false;
+
+ public Builder(IRequestInfoListener listener) {
+ this.mListener = listener;
+ }
+
+ /**
+ * Sets the city name and identifies this request as a {@link #TYPE_LOOKUP_CITY_NAME_REQ}
+ * request. If set, will null out the location and weather location.
+ */
+ public Builder setCityName(String cityName) {
+ this.mCityName = cityName;
+ this.mRequestType = TYPE_LOOKUP_CITY_NAME_REQ;
+ this.mLocation = null;
+ this.mWeatherLocation = null;
+ return this;
+ }
+
+ /**
+ * Sets the Location and identifies this request as a {@link #TYPE_GEO_LOCATION_REQ}. If
+ * set, will null out the city name and weather location.
+ */
+ public Builder setLocation(Location location) {
+ this.mLocation = location;
+ this.mCityName = null;
+ this.mWeatherLocation = null;
+ this.mRequestType = TYPE_GEO_LOCATION_REQ;
+ return this;
+ }
+
+ /**
+ * Sets the weather location and identifies this request as a
+ * {@link #TYPE_WEATHER_LOCATION_REQ}. If set, will null out the location and city name
+ */
+ public Builder setWeatherLocation(WeatherLocation weatherLocation) {
+ this.mWeatherLocation = weatherLocation;
+ this.mLocation = null;
+ this.mCityName = null;
+ this.mRequestType = TYPE_WEATHER_LOCATION_REQ;
+ return this;
+ }
+
+ /**
+ * Sets the unit in which the temperature will be reported if the request is honored.
+ * Valid values are:
+ * <ul>
+ * {@link cyanogenmod.providers.WeatherContract.WeatherColumns.TempUnit#CELSIUS}
+ * {@link cyanogenmod.providers.WeatherContract.WeatherColumns.TempUnit#FAHRENHEIT}
+ * </ul>
+ * Any other value will generate an IllegalArgumentException. If the temperature unit is not
+ * set, the default will be degrees Fahrenheit
+ * @param unit A valid temperature unit
+ */
+ public Builder setTemperatureUnit(int unit) {
+ if (!isValidTempUnit(unit)) {
+ throw new IllegalArgumentException("Invalid temperature unit");
+ }
+ this.mTempUnit = unit;
+ return this;
+ }
+
+ /**
+ * If this is a weather request, marks the request as a query only, meaning that the
+ * content provider won't be updated after the active weather service has finished
+ * processing the request.
+ */
+ public Builder queryOnly() {
+ switch (mRequestType) {
+ case TYPE_GEO_LOCATION_REQ:
+ case TYPE_WEATHER_LOCATION_REQ:
+ this.mIsQueryOnly = true;
+ break;
+ default:
+ this.mIsQueryOnly = false;
+ break;
+ }
+ return this;
+ }
+
+ public RequestInfo build() {
+ RequestInfo info = new RequestInfo();
+ info.mListener = this.mListener;
+ info.mRequestType = this.mRequestType;
+ info.mCityName = this.mCityName;
+ info.mWeatherLocation = this.mWeatherLocation;
+ info.mLocation = this.mLocation;
+ info.mTempUnit = this.mTempUnit;
+ info.mIsQueryOnly = this.mIsQueryOnly;
+ info.mKey = this.hashCode();
+ return info;
+ }
+
+ private boolean isValidTempUnit(int unit) {
+ switch (unit) {
+ case WeatherContract.WeatherColumns.TempUnit.CELSIUS:
+ case WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ }
+
+ private RequestInfo(Parcel parcel) {
+ int parcelableVersion = parcel.readInt();
+ int parcelableSize = parcel.readInt();
+ int startPosition = parcel.dataPosition();
+ if (parcelableVersion >= Build.CM_VERSION_CODES.ELDERBERRY) {
+ mKey = parcel.readInt();
+ mRequestType = parcel.readInt();
+ switch (mRequestType) {
+ case TYPE_GEO_LOCATION_REQ:
+ mLocation = Location.CREATOR.createFromParcel(parcel);
+ mTempUnit = parcel.readInt();
+ break;
+ case TYPE_WEATHER_LOCATION_REQ:
+ mWeatherLocation = WeatherLocation.CREATOR.createFromParcel(parcel);
+ mTempUnit = parcel.readInt();
+ break;
+ case TYPE_LOOKUP_CITY_NAME_REQ:
+ mCityName = parcel.readString();
+ break;
+ }
+ mIsQueryOnly = (parcel.readInt() == 1);
+ mListener = IRequestInfoListener.Stub.asInterface(parcel.readStrongBinder());
+ }
+ parcel.setDataPosition(startPosition + parcelableSize);
+ }
+
+
+ /**
+ * @return The request type
+ */
+ public int getRequestType() {
+ return mRequestType;
+ }
+
+ /**
+ * @return the {@link android.location.Location} if this is a request by location, null
+ * otherwise
+ */
+ public Location getLocation() {
+ return mLocation;
+ }
+
+ /**
+ * @return the {@link cyanogenmod.weather.WeatherLocation} if this is a request by weather
+ * location, null otherwise
+ */
+ public WeatherLocation getWeatherLocation() {
+ return mWeatherLocation;
+ }
+
+ /**
+ * @hide
+ */
+ public IRequestInfoListener getRequestListener() {
+ return mListener;
+ }
+
+ /**
+ * @return the city name if this is a lookup request, null otherwise
+ */
+ public String getCityName() {
+ return mCityName;
+ }
+
+ /**
+ * @return the temperature unit if this is a weather request, -1 otherwise
+ */
+ public int getTemperatureUnit() {
+ switch (mRequestType) {
+ case TYPE_GEO_LOCATION_REQ:
+ case TYPE_WEATHER_LOCATION_REQ:
+ return mTempUnit;
+ default:
+ return -1;
+ }
+ }
+
+ /**
+ * @return if this is a weather request, whether the request will update the content provider.
+ * False for other kind of requests
+ * @hide
+ */
+ public boolean isQueryOnlyWeatherRequest() {
+ switch (mRequestType) {
+ case TYPE_GEO_LOCATION_REQ:
+ case TYPE_WEATHER_LOCATION_REQ:
+ return mIsQueryOnly;
+ default:
+ return false;
+ }
+ }
+
+ public static final Creator<RequestInfo> CREATOR = new Creator<RequestInfo>() {
+ @Override
+ public RequestInfo createFromParcel(Parcel in) {
+ return new RequestInfo(in);
+ }
+
+ @Override
+ public RequestInfo[] newArray(int size) {
+ return new RequestInfo[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(Build.PARCELABLE_VERSION);
+
+ int sizePosition = dest.dataPosition();
+ dest.writeInt(0);
+ int startPosition = dest.dataPosition();
+
+ // ==== ELDERBERRY =====
+ dest.writeInt(mKey);
+ dest.writeInt(mRequestType);
+ switch (mRequestType) {
+ case TYPE_GEO_LOCATION_REQ:
+ mLocation.writeToParcel(dest, 0);
+ dest.writeInt(mTempUnit);
+ break;
+ case TYPE_WEATHER_LOCATION_REQ:
+ mWeatherLocation.writeToParcel(dest, 0);
+ dest.writeInt(mTempUnit);
+ break;
+ case TYPE_LOOKUP_CITY_NAME_REQ:
+ dest.writeString(mCityName);
+ break;
+ }
+ dest.writeInt(mIsQueryOnly == true ? 1 : 0);
+ dest.writeStrongBinder(mListener.asBinder());
+
+ int parcelableSize = dest.dataPosition() - startPosition;
+ dest.setDataPosition(sizePosition);
+ dest.writeInt(parcelableSize);
+ dest.setDataPosition(startPosition + parcelableSize);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("{ Request for ");
+ switch (mRequestType) {
+ case TYPE_GEO_LOCATION_REQ:
+ builder.append("Location: ").append(mLocation);
+ builder.append(" Temp Unit: ");
+ if (mTempUnit == WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT) {
+ builder.append("Fahrenheit");
+ } else {
+ builder.append(" Celsius");
+ }
+ break;
+ case TYPE_WEATHER_LOCATION_REQ:
+ builder.append("WeatherLocation: ").append(mWeatherLocation);
+ builder.append(" Temp Unit: ");
+ if (mTempUnit == WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT) {
+ builder.append("Fahrenheit");
+ } else {
+ builder.append(" Celsius");
+ }
+ break;
+ case TYPE_LOOKUP_CITY_NAME_REQ:
+ builder.append("Lookup City: ").append(mCityName);
+ break;
+ }
+ return builder.append(" }").toString();
+ }
+
+ @Override
+ public int hashCode() {
+ //The hashcode of this object was stored when it was built. This is an
+ //immutable object but we need to preserve the hashcode since this object is parcelable and
+ //it's reconstructed over IPC, and clients of this object might want to store it in a
+ //collection that relies on this code to identify the object
+ return mKey;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof RequestInfo) {
+ RequestInfo info = (RequestInfo) obj;
+ return (info.hashCode() == this.mKey);
+ }
+ return false;
+ }
+}
diff --git a/src/java/cyanogenmod/weather/WeatherInfo.aidl b/src/java/cyanogenmod/weather/WeatherInfo.aidl
new file mode 100644
index 0000000..ffff02c
--- /dev/null
+++ b/src/java/cyanogenmod/weather/WeatherInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2016 The CyanongenMod Project
+ *
+ * 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 cyanogenmod.weather;
+
+parcelable WeatherInfo; \ No newline at end of file
diff --git a/src/java/cyanogenmod/weather/WeatherInfo.java b/src/java/cyanogenmod/weather/WeatherInfo.java
new file mode 100755
index 0000000..3fd2eac
--- /dev/null
+++ b/src/java/cyanogenmod/weather/WeatherInfo.java
@@ -0,0 +1,525 @@
+/*
+ * Copyright (C) 2016 The CyanongenMod Project
+ *
+ * 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 cyanogenmod.weather;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import cyanogenmod.os.Build;
+import cyanogenmod.providers.WeatherContract;
+import cyanogenmod.weatherservice.ServiceRequest;
+import cyanogenmod.weatherservice.ServiceRequestResult;
+
+import java.util.ArrayList;
+
+/**
+ * This class represents the weather information that a
+ * {@link cyanogenmod.weatherservice.WeatherProviderService} will use to update the weather content
+ * provider. A weather provider service will be called by the system to process an update
+ * request at any time. If the service successfully processes the request, then the weather provider
+ * service is responsible of calling
+ * {@link ServiceRequest#complete(ServiceRequestResult)} to notify the
+ * system that the request was completed and that the weather content provider should be updated
+ * with the supplied weather information.
+ */
+public final class WeatherInfo implements Parcelable {
+
+ private String mCityId;
+ private String mCity;
+ private int mConditionCode;
+ private float mTemperature;
+ private int mTempUnit;
+ private float mHumidity;
+ private float mWindSpeed;
+ private float mWindDirection;
+ private int mWindSpeedUnit;
+ private long mTimestamp;
+ private ArrayList<DayForecast> mForecastList;
+ int mKey;
+
+ private WeatherInfo() {}
+
+ public static class Builder {
+ private String mCityId;
+ private String mCity;
+ private int mConditionCode;
+ private float mTemperature;
+ private int mTempUnit;
+ private float mHumidity;
+ private float mWindSpeed;
+ private float mWindDirection;
+ private int mWindSpeedUnit;
+ private long mTimestamp;
+ private ArrayList<DayForecast> mForecastList;
+
+ public Builder(long timestamp) {
+ mTimestamp = timestamp;
+ }
+
+ public Builder setCity(String cityId, @NonNull String cityName) {
+ if (cityName == null || cityId == null) {
+ throw new IllegalArgumentException("City name and id can't be null");
+ }
+ mCityId = cityId;
+ mCity = cityName;
+ return this;
+ }
+
+ public Builder setTemperature(float temperature, int tempUnit) {
+ if (!isValidTempUnit(tempUnit)) {
+ throw new IllegalArgumentException("Invalid temperature unit");
+ }
+
+ if (Float.isNaN(temperature)) {
+ throw new IllegalArgumentException("Invalid temperature value");
+ }
+
+ mTemperature = temperature;
+ mTempUnit = tempUnit;
+ return this;
+ }
+
+ public Builder setHumidity(float humidity) {
+ if (Float.isNaN(humidity)) {
+ throw new IllegalArgumentException("Invalid humidity value");
+ }
+
+ mHumidity = humidity;
+ return this;
+ }
+
+ public Builder setWind(float windSpeed, float windDirection, int windSpeedUnit) {
+ if (Float.isNaN(windSpeed)) {
+ throw new IllegalArgumentException("Invalid wind speed value");
+ }
+ if (Float.isNaN(windDirection)) {
+ throw new IllegalArgumentException("Invalid wind direction value");
+ }
+ if (!isValidWindSpeedUnit(windSpeedUnit)) {
+ throw new IllegalArgumentException("Invalid speed unit");
+ }
+ mWindSpeed = windSpeed;
+ mWindSpeedUnit = windSpeedUnit;
+ mWindDirection = windDirection;
+ return this;
+ }
+
+ public Builder setWeatherCondition(int conditionCode) {
+ if (!isValidWeatherCode(conditionCode)) {
+ throw new IllegalArgumentException("Invalid weather condition code");
+ }
+ mConditionCode = conditionCode;
+ return this;
+ }
+
+ public Builder setForecast(@NonNull ArrayList<DayForecast> forecasts) {
+ if (forecasts == null) {
+ throw new IllegalArgumentException("Forecast list can't be null");
+ }
+ mForecastList = forecasts;
+ return this;
+ }
+
+ public WeatherInfo build() {
+ WeatherInfo info = new WeatherInfo();
+ info.mCityId = this.mCityId;
+ info.mCity = this.mCity;
+ info.mConditionCode = this.mConditionCode;
+ info.mTemperature = this.mTemperature;
+ info.mTempUnit = this.mTempUnit;
+ info.mHumidity = this.mHumidity;
+ info.mWindSpeed = this.mWindSpeed;
+ info.mWindDirection = this.mWindDirection;
+ info.mWindSpeedUnit = this.mWindSpeedUnit;
+ info.mTimestamp = this.mTimestamp;
+ info.mForecastList = this.mForecastList;
+ info.mKey = this.hashCode();
+ return info;
+ }
+
+ private boolean isValidTempUnit(int unit) {
+ switch (unit) {
+ case WeatherContract.WeatherColumns.TempUnit.CELSIUS:
+ case WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private boolean isValidWindSpeedUnit(int unit) {
+ switch (unit) {
+ case WeatherContract.WeatherColumns.WindSpeedUnit.KPH:
+ case WeatherContract.WeatherColumns.WindSpeedUnit.MPH:
+ return true;
+ default:
+ return false;
+ }
+ }
+ }
+
+
+ private static boolean isValidWeatherCode(int code) {
+ if (code < WeatherContract.WeatherColumns.WeatherCode.WEATHER_CODE_MIN
+ || code > WeatherContract.WeatherColumns.WeatherCode.WEATHER_CODE_MAX) {
+ if (code != WeatherContract.WeatherColumns.WeatherCode.NOT_AVAILABLE) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * @return city id
+ */
+ public String getCityId() {
+ return mCityId;
+ }
+
+ /**
+ * @return city name
+ */
+ public String getCity() {
+ return mCity;
+ }
+
+ /**
+ * @return An implementation specific weather condition code
+ */
+ public int getConditionCode() {
+ return mConditionCode;
+ }
+
+ /**
+ * @return humidity
+ */
+ public float getHumidity() {
+ return mHumidity;
+ }
+
+ /**
+ * @return time stamp when the request was processed
+ */
+ public long getTimestamp() {
+ return mTimestamp;
+ }
+
+ /**
+ * @return wind direction (degrees)
+ */
+ public float getWindDirection() {
+ return mWindDirection;
+ }
+
+ /**
+ * @return wind speed
+ */
+ public float getWindSpeed() {
+ return mWindSpeed;
+ }
+
+ /**
+ * @return wind speed unit
+ */
+ public int getWindSpeedUnit() {
+ return mWindSpeedUnit;
+ }
+
+ /**
+ * @return current temperature
+ */
+ public float getTemperature() {
+ return mTemperature;
+ }
+
+ /**
+ * @return temperature unit
+ */
+ public int getTemperatureUnit() {
+ return mTempUnit;
+ }
+
+ /**
+ * @return List of {@link cyanogenmod.weather.WeatherInfo.DayForecast}
+ */
+ public ArrayList<DayForecast> getForecasts() {
+ return mForecastList;
+ }
+
+ private WeatherInfo(Parcel parcel) {
+ int parcelableVersion = parcel.readInt();
+ int parcelableSize = parcel.readInt();
+ int startPosition = parcel.dataPosition();
+ if (parcelableVersion >= Build.CM_VERSION_CODES.ELDERBERRY) {
+ mKey = parcel.readInt();
+ mCityId = parcel.readString();
+ mCity = parcel.readString();
+ mConditionCode = parcel.readInt();
+ mTemperature = parcel.readFloat();
+ mTempUnit = parcel.readInt();
+ mHumidity = parcel.readFloat();
+ mWindSpeed = parcel.readFloat();
+ mWindDirection = parcel.readFloat();
+ mWindSpeedUnit = parcel.readInt();
+ mTimestamp = parcel.readLong();
+ int forecastListSize = parcel.readInt();
+ mForecastList = new ArrayList<>();
+ while (forecastListSize > 0) {
+ mForecastList.add(DayForecast.CREATOR.createFromParcel(parcel));
+ forecastListSize--;
+ }
+ }
+ parcel.setDataPosition(startPosition + parcelableSize);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(Build.PARCELABLE_VERSION);
+
+ int sizePosition = dest.dataPosition();
+ dest.writeInt(0);
+ int startPosition = dest.dataPosition();
+
+ // ==== ELDERBERRY =====
+ dest.writeInt(mKey);
+ dest.writeString(mCityId);
+ dest.writeString(mCity);
+ dest.writeInt(mConditionCode);
+ dest.writeFloat(mTemperature);
+ dest.writeInt(mTempUnit);
+ dest.writeFloat(mHumidity);
+ dest.writeFloat(mWindSpeed);
+ dest.writeFloat(mWindDirection);
+ dest.writeInt(mWindSpeedUnit);
+ dest.writeLong(mTimestamp);
+ dest.writeInt(mForecastList.size());
+ for (DayForecast dayForecast : mForecastList) {
+ dayForecast.writeToParcel(dest, 0);
+ }
+
+ int parcelableSize = dest.dataPosition() - startPosition;
+ dest.setDataPosition(sizePosition);
+ dest.writeInt(parcelableSize);
+ dest.setDataPosition(startPosition + parcelableSize);
+ }
+
+ public static final Parcelable.Creator<WeatherInfo> CREATOR =
+ new Parcelable.Creator<WeatherInfo>() {
+
+ @Override
+ public WeatherInfo createFromParcel(Parcel source) {
+ return new WeatherInfo(source);
+ }
+
+ @Override
+ public WeatherInfo[] newArray(int size) {
+ return new WeatherInfo[size];
+ }
+ };
+
+ /**
+ * This class represents the weather forecast for a given day
+ */
+ public static class DayForecast implements Parcelable{
+ float mLow;
+ float mHigh;
+ int mConditionCode;
+ int mKey;
+
+ private DayForecast() {}
+
+ public static class Builder {
+ float mLow;
+ float mHigh;
+ int mConditionCode;
+
+ public Builder() {}
+ public Builder setHigh(float high) {
+ if (Float.isNaN(high)) {
+ throw new IllegalArgumentException("Invalid high forecast temperature");
+ }
+ mHigh = high;
+ return this;
+ }
+ public Builder setLow(float low) {
+ if (Float.isNaN(low)) {
+ throw new IllegalArgumentException("Invalid low forecast temperature");
+ }
+ mLow = low;
+ return this;
+ }
+
+ public Builder setWeatherCondition(int code) {
+ if (!isValidWeatherCode(code)) {
+ throw new IllegalArgumentException("Invalid weather condition code");
+ }
+ mConditionCode = code;
+ return this;
+ }
+
+ public DayForecast build() {
+ DayForecast forecast = new DayForecast();
+ forecast.mLow = this.mLow;
+ forecast.mHigh = this.mHigh;
+ forecast.mConditionCode = this.mConditionCode;
+ forecast.mKey = this.hashCode();
+ return forecast;
+ }
+ }
+
+ /**
+ * @return forecasted low temperature
+ */
+ public float getLow() {
+ return mLow;
+ }
+
+ /**
+ * @return not what you think. Returns the forecasted high temperature
+ */
+ public float getHigh() {
+ return mHigh;
+ }
+
+ /**
+ * @return forecasted weather condition code. Implementation specific
+ */
+ public int getConditionCode() {
+ return mConditionCode;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(Build.PARCELABLE_VERSION);
+
+ int sizePosition = dest.dataPosition();
+ dest.writeInt(0);
+ int startPosition = dest.dataPosition();
+
+ // ==== ELDERBERRY =====
+ dest.writeInt(mKey);
+ dest.writeFloat(mLow);
+ dest.writeFloat(mHigh);
+ dest.writeInt(mConditionCode);
+
+ int parcelableSize = dest.dataPosition() - startPosition;
+ dest.setDataPosition(sizePosition);
+ dest.writeInt(parcelableSize);
+ dest.setDataPosition(startPosition + parcelableSize);
+ }
+
+ public static final Parcelable.Creator<DayForecast> CREATOR =
+ new Parcelable.Creator<DayForecast>() {
+ @Override
+ public DayForecast createFromParcel(Parcel source) {
+ return new DayForecast(source);
+ }
+
+ @Override
+ public DayForecast[] newArray(int size) {
+ return new DayForecast[size];
+ }
+ };
+
+ private DayForecast(Parcel parcel) {
+ int parcelableVersion = parcel.readInt();
+ int parcelableSize = parcel.readInt();
+ int startPosition = parcel.dataPosition();
+ if (parcelableVersion >= Build.CM_VERSION_CODES.ELDERBERRY) {
+ mKey = parcel.readInt();
+ mLow = parcel.readFloat();
+ mHigh = parcel.readFloat();
+ mConditionCode = parcel.readInt();
+ }
+ parcel.setDataPosition(startPosition + parcelableSize);
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append("{Low temp: ").append(mLow)
+ .append(" High temp: ").append(mHigh)
+ .append(" Condition code: ").append(mConditionCode)
+ .append("}").toString();
+ }
+
+ @Override
+ public int hashCode() {
+ //The hashcode of this object was stored when it was built. This is an
+ //immutable object but we need to preserve the hashcode since this object is parcelable
+ //and it's reconstructed over IPC, and clients of this object might want to store it in
+ //a collection that relies on this code to identify the object
+ return mKey;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof DayForecast) {
+ DayForecast forecast = (DayForecast) obj;
+ return (forecast.hashCode() == this.mKey);
+ }
+ return false;
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder()
+ .append("{CityId: ").append(mCityId)
+ .append(" City Name: ").append(mCity)
+ .append(" Condition Code: ").append(mConditionCode)
+ .append(" Temperature: ").append(mTemperature)
+ .append(" Temperature Unit: ").append(mTempUnit)
+ .append(" Humidity: ").append(mHumidity)
+ .append(" Wind speed: ").append(mWindSpeed)
+ .append(" Wind direction: ").append(mWindDirection)
+ .append(" Wind Speed Unit: ").append(mWindSpeedUnit)
+ .append(" Timestamp: ").append(mTimestamp).append(" Forecasts: [");
+ for (DayForecast dayForecast : mForecastList) {
+ builder.append(dayForecast.toString());
+ }
+ return builder.append("]}").toString();
+ }
+
+ @Override
+ public int hashCode() {
+ //The hashcode of this object was stored when it was built. This is an
+ //immutable object but we need to preserve the hashcode since this object is parcelable and
+ //it's reconstructed over IPC, and clients of this object might want to store it in a
+ //collection that relies on this code to identify the object
+ return mKey;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof WeatherInfo) {
+ WeatherInfo info = (WeatherInfo) obj;
+ return (info.hashCode() == this.mKey);
+ }
+ return false;
+ }
+} \ No newline at end of file
diff --git a/src/java/cyanogenmod/weather/WeatherLocation.aidl b/src/java/cyanogenmod/weather/WeatherLocation.aidl
new file mode 100644
index 0000000..23f7b24
--- /dev/null
+++ b/src/java/cyanogenmod/weather/WeatherLocation.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * 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 cyanogenmod.weather;
+
+parcelable WeatherLocation; \ No newline at end of file
diff --git a/src/java/cyanogenmod/weather/WeatherLocation.java b/src/java/cyanogenmod/weather/WeatherLocation.java
new file mode 100644
index 0000000..b7827e9
--- /dev/null
+++ b/src/java/cyanogenmod/weather/WeatherLocation.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * 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 cyanogenmod.weather;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import cyanogenmod.os.Build;
+
+/**
+ * A class representing a geographical location that a weather service provider can use to
+ * get weather data from. Each service provider will potentially populate objects of this class
+ * with different content, so make sure you don't preserve the values when a service provider
+ * is changed
+ */
+public final class WeatherLocation implements Parcelable{
+ private String mCityId;
+ private String mCity;
+ private String mPostal;
+ private String mCountryId;
+ private String mCountry;
+ private int mKey;
+
+ private WeatherLocation() {}
+
+ public static class Builder {
+ String mCityId;
+ String mCity;
+ String mPostal;
+ String mCountryId;
+ String mCountry;
+
+ public Builder(String cityId, String cityName) {
+ this.mCityId = cityId;
+ this.mCity = cityName;
+ }
+
+ public Builder setCountry(String countyId, String country) {
+ this.mCountryId = countyId;
+ this.mCountry = country;
+ return this;
+ }
+
+ public Builder setPostalCode(String postalCode) {
+ this.mPostal = postalCode;
+ return this;
+ }
+
+ public WeatherLocation build() {
+ WeatherLocation weatherLocation = new WeatherLocation();
+ weatherLocation.mCityId = this.mCityId;
+ weatherLocation.mCity = this.mCity;
+ weatherLocation.mPostal = this.mPostal;
+ weatherLocation.mCountryId = this.mCountryId;
+ weatherLocation.mCountry = this.mCountry;
+ weatherLocation.mKey = this.hashCode();
+ return weatherLocation;
+ }
+ }
+
+ public String getCityId() {
+ return mCityId;
+ }
+
+ public String getCity() {
+ return mCity;
+ }
+
+ public String getPostalCode() {
+ return mPostal;
+ }
+
+ public String getCountryId() {
+ return mCountryId;
+ }
+
+ public String getCountry() {
+ return mCountry;
+ }
+
+ private WeatherLocation(Parcel in) {
+ int parcelableVersion = in.readInt();
+ int parcelableSize = in.readInt();
+ int startPosition = in.dataPosition();
+ if (parcelableVersion >= Build.CM_VERSION_CODES.ELDERBERRY) {
+ mKey = in.readInt();
+ mCityId = in.readString();
+ mCity = in.readString();
+ mPostal = in.readString();
+ mCountryId = in.readString();
+ mCountry = in.readString();
+ }
+ in.setDataPosition(startPosition + parcelableSize);
+ }
+
+ public static final Creator<WeatherLocation> CREATOR = new Creator<WeatherLocation>() {
+ @Override
+ public WeatherLocation createFromParcel(Parcel in) {
+ return new WeatherLocation(in);
+ }
+
+ @Override
+ public WeatherLocation[] newArray(int size) {
+ return new WeatherLocation[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(Build.PARCELABLE_VERSION);
+
+ int sizePosition = dest.dataPosition();
+ dest.writeInt(0);
+ int startPosition = dest.dataPosition();
+
+ // ==== ELDERBERRY =====
+ dest.writeInt(mKey);
+ dest.writeString(mCityId);
+ dest.writeString(mCity);
+ dest.writeString(mPostal);
+ dest.writeString(mCountryId);
+ dest.writeString(mCountry);
+
+ int parcelableSize = dest.dataPosition() - startPosition;
+ dest.setDataPosition(sizePosition);
+ dest.writeInt(parcelableSize);
+ dest.setDataPosition(startPosition + parcelableSize);
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append("{ City ID: ").append(mCityId)
+ .append(" City: ").append(mCity)
+ .append(" Postal Code: ").append(mPostal)
+ .append(" Country Id: ").append(mCountryId)
+ .append(" Country: ").append(mCountry).append("}")
+ .toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return mKey;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof WeatherLocation) {
+ WeatherLocation info = (WeatherLocation) obj;
+ return (info.hashCode() == this.mKey);
+ }
+ return false;
+ }
+}
diff --git a/src/java/cyanogenmod/weather/util/WeatherUtils.java b/src/java/cyanogenmod/weather/util/WeatherUtils.java
new file mode 100644
index 0000000..c89213b
--- /dev/null
+++ b/src/java/cyanogenmod/weather/util/WeatherUtils.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * 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 cyanogenmod.weather.util;
+
+
+import cyanogenmod.providers.WeatherContract;
+
+import java.text.DecimalFormat;
+
+/**
+ * Helper class to perform operations and formatting of weather data
+ */
+public class WeatherUtils {
+
+ /**
+ * Converts a temperature expressed in degrees Celsius to degrees Fahrenheit
+ * @param celsius temperature in Celsius
+ * @return the temperature in degrees Fahrenheit
+ */
+ public static float celsiusToFahrenheit(float celsius) {
+ return ((celsius * (9f/5f)) + 32f);
+ }
+
+ /**
+ * Converts a temperature expressed in degrees Fahrenheit to degrees Celsius
+ * @param fahrenheit temperature in Fahrenheit
+ * @return the temperature in degrees Celsius
+ */
+ public static float fahrenheitToCelsius(float fahrenheit) {
+ return ((fahrenheit - 32f) * (5f/9f));
+ }
+
+ /**
+ * Returns a string representation of the temperature and unit supplied. The temperature value
+ * will be half-even rounded.
+ * @param temperature the temperature value
+ * @param tempUnit A valid {@link WeatherContract.WeatherColumns.TempUnit}
+ * @return A string with the format XX&deg;F or XX&deg;C (where XX is the temperature)
+ * depending on the temperature unit that was provided or null if an invalid unit is supplied
+ */
+ public static String formatTemperature(float temperature, int tempUnit) {
+ if (!isValidTempUnit(tempUnit)) return null;
+ if (Float.isNaN(temperature)) return "-";
+
+ DecimalFormat noDigitsFormat = new DecimalFormat("0");
+ String noDigitsTemp = noDigitsFormat.format(temperature);
+ if (noDigitsTemp.equals("-0")) {
+ noDigitsTemp = "0";
+ }
+
+ StringBuilder formatted = new StringBuilder()
+ .append(noDigitsTemp).append("\u00b0");
+ if (tempUnit == WeatherContract.WeatherColumns.TempUnit.CELSIUS) {
+ formatted.append("C");
+ } else if (tempUnit == WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT) {
+ formatted.append("F");
+ }
+ return formatted.toString();
+ }
+
+ private static boolean isValidTempUnit(int unit) {
+ switch (unit) {
+ case WeatherContract.WeatherColumns.TempUnit.CELSIUS:
+ case WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT:
+ return true;
+ default:
+ return false;
+ }
+ }
+}
diff --git a/src/java/cyanogenmod/weatherservice/IWeatherProviderService.aidl b/src/java/cyanogenmod/weatherservice/IWeatherProviderService.aidl
new file mode 100644
index 0000000..d9eceb3
--- /dev/null
+++ b/src/java/cyanogenmod/weatherservice/IWeatherProviderService.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * 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 cyanogenmod.weatherservice;
+
+import cyanogenmod.weatherservice.IWeatherProviderServiceClient;
+import cyanogenmod.weather.RequestInfo;
+
+/** @hide */
+oneway interface IWeatherProviderService {
+ void processWeatherUpdateRequest(in RequestInfo request);
+ void processCityNameLookupRequest(in RequestInfo request);
+ void setServiceClient(in IWeatherProviderServiceClient client);
+ void cancelOngoingRequests();
+} \ No newline at end of file
diff --git a/src/java/cyanogenmod/weatherservice/IWeatherProviderServiceClient.aidl b/src/java/cyanogenmod/weatherservice/IWeatherProviderServiceClient.aidl
new file mode 100644
index 0000000..a4baa4c
--- /dev/null
+++ b/src/java/cyanogenmod/weatherservice/IWeatherProviderServiceClient.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * 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 cyanogenmod.weatherservice;
+
+import cyanogenmod.weather.RequestInfo;
+import cyanogenmod.weatherservice.ServiceRequestResult;
+
+/** @hide */
+oneway interface IWeatherProviderServiceClient {
+ void setServiceRequestState(in RequestInfo requestInfo, in ServiceRequestResult result,
+ int state);
+} \ No newline at end of file
diff --git a/src/java/cyanogenmod/weatherservice/ServiceRequest.java b/src/java/cyanogenmod/weatherservice/ServiceRequest.java
new file mode 100644
index 0000000..e43218d
--- /dev/null
+++ b/src/java/cyanogenmod/weatherservice/ServiceRequest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * 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 cyanogenmod.weatherservice;
+
+import android.annotation.NonNull;
+import android.os.RemoteException;
+import cyanogenmod.weather.CMWeatherManager;
+import cyanogenmod.weather.RequestInfo;
+
+/**
+ * This class represents a request submitted by the system to the active weather provider service
+ */
+public final class ServiceRequest {
+
+ private final RequestInfo mInfo;
+ private final IWeatherProviderServiceClient mClient;
+
+ /**
+ * If a request is marked as cancelled, it means the client does not want to know anything about
+ * this request anymore
+ */
+ private volatile boolean mCancelled;
+
+ /* package */ ServiceRequest(RequestInfo info, IWeatherProviderServiceClient client) {
+ mInfo = info;
+ mClient = client;
+ }
+
+ /**
+ * Obtains the request information
+ * @return {@link cyanogenmod.weather.RequestInfo}
+ */
+ public RequestInfo getRequestInfo() {
+ return mInfo;
+ }
+
+ /**
+ * This method should be called once the request has been completed
+ */
+ public void complete(@NonNull ServiceRequestResult result) {
+ if (!mCancelled) {
+ try {
+ final int requestType = mInfo.getRequestType();
+ switch (requestType) {
+ case RequestInfo.TYPE_GEO_LOCATION_REQ:
+ case RequestInfo.TYPE_WEATHER_LOCATION_REQ:
+ if (result.getWeatherInfo() == null) {
+ throw new IllegalStateException("The service request result does not"
+ + " contain a valid WeatherInfo object");
+ }
+ mClient.setServiceRequestState(mInfo, result,
+ CMWeatherManager.WEATHER_REQUEST_COMPLETED);
+ break;
+ case RequestInfo.TYPE_LOOKUP_CITY_NAME_REQ:
+ if (result.getLocationLookupList() == null) {
+ //In case the user decided to mark this request as completed with an
+ //empty list. It's not necessarily a failure
+ mClient.setServiceRequestState(mInfo, null,
+ CMWeatherManager.LOOKUP_REQUEST_NO_MATCH_FOUND);
+ } else {
+ mClient.setServiceRequestState(mInfo, result,
+ CMWeatherManager.LOOKUP_REQUEST_COMPLETED);
+ }
+ break;
+ }
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ /**
+ * This method should be called if the service failed to process the request
+ * (no internet connection, time out, etc.)
+ */
+ public void fail() {
+ if (!mCancelled) {
+ try {
+ final int requestType = mInfo.getRequestType();
+ switch (requestType) {
+ case RequestInfo.TYPE_GEO_LOCATION_REQ:
+ case RequestInfo.TYPE_WEATHER_LOCATION_REQ:
+ mClient.setServiceRequestState(mInfo, null,
+ CMWeatherManager.WEATHER_REQUEST_FAILED);
+ break;
+ case RequestInfo.TYPE_LOOKUP_CITY_NAME_REQ:
+ mClient.setServiceRequestState(mInfo, null,
+ CMWeatherManager.LOOKUP_REQUEST_FAILED);
+ break;
+ }
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ /**
+ * Called by the WeatherProviderService base class to notify we don't want this request anymore.
+ * The service implementing the WeatherProviderService will be notified of this action
+ * via onRequestCancelled()
+ * @hide
+ */
+ public void cancel() {
+ mCancelled = true;
+ }
+}
diff --git a/src/java/cyanogenmod/weatherservice/ServiceRequestResult.aidl b/src/java/cyanogenmod/weatherservice/ServiceRequestResult.aidl
new file mode 100644
index 0000000..669ece5
--- /dev/null
+++ b/src/java/cyanogenmod/weatherservice/ServiceRequestResult.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * 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 cyanogenmod.weatherservice;
+
+parcelable ServiceRequestResult; \ No newline at end of file
diff --git a/src/java/cyanogenmod/weatherservice/ServiceRequestResult.java b/src/java/cyanogenmod/weatherservice/ServiceRequestResult.java
new file mode 100644
index 0000000..2e962a9
--- /dev/null
+++ b/src/java/cyanogenmod/weatherservice/ServiceRequestResult.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * 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 cyanogenmod.weatherservice;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import cyanogenmod.os.Build;
+import cyanogenmod.weather.WeatherLocation;
+import cyanogenmod.weather.WeatherInfo;
+
+import java.util.ArrayList;
+
+/**
+ * Use this class to build a request result.
+ */
+public final class ServiceRequestResult implements Parcelable {
+
+ private WeatherInfo mWeatherInfo;
+ private ArrayList<WeatherLocation> mLocationLookupList;
+ private int mKey;
+
+ private ServiceRequestResult() {}
+
+ private ServiceRequestResult(Parcel in) {
+ int parcelableVersion = in.readInt();
+ int parcelableSize = in.readInt();
+ int startPosition = in.dataPosition();
+ if (parcelableVersion >= Build.CM_VERSION_CODES.ELDERBERRY) {
+ mKey = in.readInt();
+ int hasWeatherInfo = in.readInt();
+ if (hasWeatherInfo == 1) {
+ mWeatherInfo = WeatherInfo.CREATOR.createFromParcel(in);
+ }
+ int hasLocationLookupList = in.readInt();
+ if (hasLocationLookupList == 1) {
+ mLocationLookupList = new ArrayList<>();
+ int listSize = in.readInt();
+ while (listSize > 0) {
+ mLocationLookupList.add(WeatherLocation.CREATOR.createFromParcel(in));
+ listSize--;
+ }
+ }
+ }
+ in.setDataPosition(startPosition + parcelableSize);
+ }
+
+ public static final Creator<ServiceRequestResult> CREATOR
+ = new Creator<ServiceRequestResult>() {
+ @Override
+ public ServiceRequestResult createFromParcel(Parcel in) {
+ return new ServiceRequestResult(in);
+ }
+
+ @Override
+ public ServiceRequestResult[] newArray(int size) {
+ return new ServiceRequestResult[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(Build.PARCELABLE_VERSION);
+
+ int sizePosition = dest.dataPosition();
+ dest.writeInt(0);
+ int startPosition = dest.dataPosition();
+
+ // ==== ELDERBERRY =====
+ dest.writeInt(mKey);
+ if (mWeatherInfo != null) {
+ dest.writeInt(1);
+ mWeatherInfo.writeToParcel(dest, 0);
+ } else {
+ dest.writeInt(0);
+ }
+ if (mLocationLookupList != null) {
+ dest.writeInt(1);
+ dest.writeInt(mLocationLookupList.size());
+ for (WeatherLocation lookup : mLocationLookupList) {
+ lookup.writeToParcel(dest, 0);
+ }
+ } else {
+ dest.writeInt(0);
+ }
+
+ int parcelableSize = dest.dataPosition() - startPosition;
+ dest.setDataPosition(sizePosition);
+ dest.writeInt(parcelableSize);
+ dest.setDataPosition(startPosition + parcelableSize);
+ }
+
+ public static class Builder {
+ private WeatherInfo mBuilderWeatherInfo;
+ private ArrayList<WeatherLocation> mBuilderLocationLookupList;
+ public Builder() {
+ this.mBuilderWeatherInfo = null;
+ this.mBuilderLocationLookupList = null;
+ }
+
+ /**
+ * Add the supplied weather information to the result. Attempting to add a WeatherLocation
+ * list to the same builder will cause the system to throw IllegalArgumentException
+ *
+ * @param weatherInfo The WeatherInfo object holding the data that will be used to update
+ * the weather content provider
+ */
+ public Builder setWeatherInfo(@NonNull WeatherInfo weatherInfo) {
+ if (mBuilderLocationLookupList != null) {
+ throw new IllegalArgumentException("Can't add weather information when you have"
+ + " already added a WeatherLocation list");
+ }
+
+ if (weatherInfo == null) {
+ throw new IllegalArgumentException("WeatherInfo can't be null");
+ }
+
+ mBuilderWeatherInfo = weatherInfo;
+ return this;
+ }
+
+ /**
+ * Add the supplied list of WeatherLocation objects to the result. Attempting to add a
+ * WeatherInfo object to the same builder will cause the system to throw
+ * IllegalArgumentException
+ *
+ * @param locations The list of WeatherLocation objects. The list should not be null
+ */
+ public Builder setLocationLookupList(@NonNull ArrayList<WeatherLocation> locations) {
+ if (mBuilderWeatherInfo != null) {
+ throw new IllegalArgumentException("Can't add a WeatherLocation list when you have"
+ + " already added weather information");
+ }
+
+ mBuilderLocationLookupList = locations;
+ return this;
+ }
+
+ /**
+ * Creates a {@link ServiceRequest} with the arguments
+ * supplied to this builder
+ * @return {@link ServiceRequestResult}
+ */
+ public ServiceRequestResult build() {
+ ServiceRequestResult result = new ServiceRequestResult();
+ result.mWeatherInfo = this.mBuilderWeatherInfo;
+ result.mLocationLookupList = this.mBuilderLocationLookupList;
+ result.mKey = this.hashCode();
+ return result;
+ }
+ }
+
+ /**
+ * @return The WeatherInfo object supplied by the weather provider service
+ */
+ public WeatherInfo getWeatherInfo() {
+ return mWeatherInfo;
+ }
+
+ /**
+ * @return The list of WeatherLocation objects supplied by the weather provider service
+ */
+ public ArrayList<WeatherLocation> getLocationLookupList() {
+ return mLocationLookupList;
+ }
+
+ @Override
+ public int hashCode() {
+ //The hashcode of this object was stored when it was built. This is an
+ //immutable object but we need to preserve the hashcode since this object is parcelable and
+ //it's reconstructed over IPC, and clients of this object might want to store it in a
+ //collection that relies on this code to identify the object
+ return mKey;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof ServiceRequestResult) {
+ ServiceRequestResult request = (ServiceRequestResult) obj;
+ return (request.hashCode() == this.mKey);
+ }
+ return false;
+ }
+}
diff --git a/src/java/cyanogenmod/weatherservice/WeatherProviderService.java b/src/java/cyanogenmod/weatherservice/WeatherProviderService.java
new file mode 100644
index 0000000..759d5fc
--- /dev/null
+++ b/src/java/cyanogenmod/weatherservice/WeatherProviderService.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * 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 cyanogenmod.weatherservice;
+
+import android.annotation.SdkConstant;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import cyanogenmod.weather.RequestInfo;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+/**
+ * This is the base class for implementing a weather provider service. A weather provider service
+ * can handle weather update requests and update the weather content provider data by processing
+ * a {@link ServiceRequest}
+ *
+ * A print service is declared as any other service in an AndroidManifest.xml but it must also
+ * specify that in handles the {@link android.content.Intent} with action
+ * {@link #SERVICE_INTERFACE cyanogenmod.weatherservice.WeatherProviderService}. Failure to declare
+ * this intent will cause the system to ignore the weather provider service. Additionally, a
+ * weather provider service must request the
+ * {@link cyanogenmod.platform.Manifest.permission#BIND_WEATHER_PROVIDER_SERVICE} permission to
+ * ensure that only the system can bind to it. Failure to request this permission will cause the
+ * system to ignore this weather provider service. Following is an example declaration:
+ *
+ * <pre>
+ * &lt;service android:name=".MyWeatherProviderService"
+ * android:permission="cyanogenmod.permission.BIND_WEATHER_PROVIDER_SERVICE"&gt;
+ * &lt;intent-filter&gt;
+ * &lt;action android:name="cyanogenmod.weatherservice.WeatherProviderService" /&gt;
+ * &lt;intent-filter&gt;
+ * . . .
+ * &lt;/service&gt;
+ * </pre>
+ *
+ */
+public abstract class WeatherProviderService extends Service {
+
+ private Handler mHandler;
+ private IWeatherProviderServiceClient mClient;
+ private Set<ServiceRequest> mWeakRequestsSet
+ = Collections.newSetFromMap(new WeakHashMap<ServiceRequest, Boolean>());
+
+ /**
+ * The {@link android.content.Intent} action that must be declared as handled by a service in
+ * its manifest for the system to recognize it as a weather provider service
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE
+ = "cyanogenmod.weatherservice.WeatherProviderService";
+
+ /**
+ * Name under which a {@link WeatherProviderService} publishes information about itself.
+ * This meta-data must reference an XML resource containing
+ * a <code>&lt;weather-provider-service&gt;</code>
+ * tag.
+ */
+ public static final String SERVICE_META_DATA = "cyanogenmod.weatherservice";
+
+ @Override
+ protected final void attachBaseContext(Context base) {
+ super.attachBaseContext(base);
+ mHandler = new ServiceHandler(base.getMainLooper());
+ }
+
+ @Override
+ public final IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+ private final IWeatherProviderService.Stub mBinder = new IWeatherProviderService.Stub() {
+
+ @Override
+ public void processWeatherUpdateRequest(final RequestInfo info) {
+ mHandler.obtainMessage(ServiceHandler.MSG_ON_NEW_REQUEST, info).sendToTarget();
+ }
+
+ @Override
+ public void processCityNameLookupRequest(final RequestInfo info) {
+ mHandler.obtainMessage(ServiceHandler.MSG_ON_NEW_REQUEST, info).sendToTarget();
+ }
+
+ @Override
+ public void setServiceClient(IWeatherProviderServiceClient client) {
+ mHandler.obtainMessage(ServiceHandler.MSG_SET_CLIENT, client).sendToTarget();
+ }
+
+ @Override
+ public void cancelOngoingRequests() {
+ mHandler.obtainMessage(ServiceHandler.MSG_CANCEL_ONGOING_REQUESTS).sendToTarget();
+ }
+ };
+
+ private class ServiceHandler extends Handler {
+
+ public ServiceHandler(Looper looper) {
+ super(looper);
+ }
+ public static final int MSG_SET_CLIENT = 1;
+ public static final int MSG_ON_NEW_REQUEST = 2;
+ public static final int MSG_CANCEL_ONGOING_REQUESTS = 3;
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_SET_CLIENT: {
+ mClient = (IWeatherProviderServiceClient) msg.obj;
+ if (mClient != null) {
+ onConnected();
+ } else {
+ onDisconnected();
+ }
+ return;
+ }
+ case MSG_ON_NEW_REQUEST: {
+ RequestInfo info = (RequestInfo) msg.obj;
+ if (info != null) {
+ ServiceRequest request = new ServiceRequest(info, mClient);
+ synchronized (mWeakRequestsSet) {
+ mWeakRequestsSet.add(request);
+ }
+ onRequestSubmitted(request);
+ }
+ return;
+ }
+ case MSG_CANCEL_ONGOING_REQUESTS: {
+ synchronized (mWeakRequestsSet) {
+ for (final ServiceRequest request : mWeakRequestsSet) {
+ if (request != null) {
+ request.cancel();
+ mWeakRequestsSet.remove(request);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ onRequestCancelled(request);
+ }
+ });
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * The system has connected to this service.
+ */
+ protected void onConnected() {
+ /* Do nothing */
+ }
+
+ /**
+ * The system has disconnected from this service.
+ */
+ protected void onDisconnected() {
+ /* Do nothing */
+ }
+
+ /**
+ * A new request has been submitted to this service
+ * @param request The service request to be processed by this service
+ */
+ protected abstract void onRequestSubmitted(ServiceRequest request);
+
+ /**
+ * Called when the system is not interested on this request anymore. Note that the service
+ * <b>has marked the request as cancelled</b> and you must stop any ongoing operation
+ * (such as pulling data from internet) that this service could've been performing to honor the
+ * request.
+ *
+ * @param request The request cancelled by the system
+ */
+ protected abstract void onRequestCancelled(ServiceRequest request);
+} \ No newline at end of file
diff --git a/src/java/org/cyanogenmod/internal/logging/CMMetricsLogger.java b/src/java/org/cyanogenmod/internal/logging/CMMetricsLogger.java
index 26d7fe3..e3303d5 100644
--- a/src/java/org/cyanogenmod/internal/logging/CMMetricsLogger.java
+++ b/src/java/org/cyanogenmod/internal/logging/CMMetricsLogger.java
@@ -66,4 +66,5 @@ public class CMMetricsLogger extends MetricsLogger {
public static final int TILE_HEADS_UP = BASE + 39;
public static final int TILE_BATTERY_SAVER = BASE + 40;
public static final int TILE_CAFFEINE = BASE + 41;
+ public static final int WEATHER_SETTINGS = BASE + 42;
}