diff options
Diffstat (limited to 'src/java/cyanogenmod/weather')
-rw-r--r-- | src/java/cyanogenmod/weather/CMWeatherManager.java | 373 | ||||
-rw-r--r-- | src/java/cyanogenmod/weather/ICMWeatherManager.aidl | 31 | ||||
-rw-r--r-- | src/java/cyanogenmod/weather/IRequestInfoListener.aidl | 31 | ||||
-rw-r--r-- | src/java/cyanogenmod/weather/IWeatherServiceProviderChangeListener.aidl | 22 | ||||
-rw-r--r-- | src/java/cyanogenmod/weather/RequestInfo.aidl | 19 | ||||
-rw-r--r-- | src/java/cyanogenmod/weather/RequestInfo.java | 355 | ||||
-rw-r--r-- | src/java/cyanogenmod/weather/WeatherInfo.aidl | 19 | ||||
-rwxr-xr-x | src/java/cyanogenmod/weather/WeatherInfo.java | 525 | ||||
-rw-r--r-- | src/java/cyanogenmod/weather/WeatherLocation.aidl | 19 | ||||
-rw-r--r-- | src/java/cyanogenmod/weather/WeatherLocation.java | 172 | ||||
-rw-r--r-- | src/java/cyanogenmod/weather/util/WeatherUtils.java | 84 |
11 files changed, 1650 insertions, 0 deletions
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°F or XX°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; + } + } +} |