Add Weather Content Provider [4/5]

Introduce CM Weather Manager and Weather Provider Services API.
The CM Weather Manager can be used by apps to request weather
updates.

The Weather Provider Services API allows a third party developer to
implement a weather service to process weather update requests and
update the CM Weather Content Provider data which can be consumed by
any other app holding the required permission.

Change-Id: Idcc80712ba92715109d3577d120f7fea85d6c996
This commit is contained in:
Luis Vidal 2016-03-21 11:56:40 -07:00
parent 46821304e9
commit 4195a1cf89
28 changed files with 3563 additions and 2 deletions

View File

@ -24,7 +24,7 @@ LOCAL_PATH := $(call my-dir)
cm_platform_res := APPS/org.cyanogenmod.platform-res_intermediates/src
# List of packages used in cm-api-stubs and cm-system-api-stubs
cm_stub_packages := cyanogenmod.alarmclock:cyanogenmod.app:cyanogenmod.content:cyanogenmod.externalviews:cyanogenmod.hardware:cyanogenmod.media:cyanogenmod.os:cyanogenmod.profiles:cyanogenmod.providers:cyanogenmod.platform:cyanogenmod.power:cyanogenmod.themes:cyanogenmod.util
cm_stub_packages := cyanogenmod.alarmclock:cyanogenmod.app:cyanogenmod.content:cyanogenmod.externalviews:cyanogenmod.hardware:cyanogenmod.media:cyanogenmod.os:cyanogenmod.profiles:cyanogenmod.providers:cyanogenmod.platform:cyanogenmod.power:cyanogenmod.themes:cyanogenmod.util:cyanogenmod.weather:cyanogenmod.weatherservice
# The CyanogenMod Platform Framework Library
# ============================================================

View File

@ -595,6 +595,8 @@ package cyanogenmod.platform {
ctor public Manifest.permission();
field public static final java.lang.String ACCESS_APP_SUGGESTIONS = "cyanogenmod.permission.ACCESS_APP_SUGGESTIONS";
field public static final java.lang.String ACCESS_THEME_MANAGER = "cyanogenmod.permission.ACCESS_THEME_MANAGER";
field public static final java.lang.String ACCESS_WEATHER_MANAGER = "cyanogenmod.permission.ACCESS_WEATHER_MANAGER";
field public static final java.lang.String BIND_WEATHER_PROVIDER_SERVICE = "cyanogenmod.permission.BIND_WEATHER_PROVIDER_SERVICE";
field public static final java.lang.String HARDWARE_ABSTRACTION_ACCESS = "cyanogenmod.permission.HARDWARE_ABSTRACTION_ACCESS";
field public static final java.lang.String LIVE_LOCK_SCREEN_MANAGER_ACCESS = "cyanogenmod.permission.LIVE_LOCK_SCREEN_MANAGER_ACCESS";
field public static final java.lang.String MANAGE_ALARMS = "cyanogenmod.permission.MANAGE_ALARMS";
@ -609,11 +611,13 @@ package cyanogenmod.platform {
field public static final java.lang.String READ_ALARMS = "cyanogenmod.permission.READ_ALARMS";
field public static final java.lang.String READ_MSIM_PHONE_STATE = "cyanogenmod.permission.READ_MSIM_PHONE_STATE";
field public static final java.lang.String READ_THEMES = "cyanogenmod.permission.READ_THEMES";
field public static final java.lang.String READ_WEATHER = "cyanogenmod.permission.READ_WEATHER";
field public static final java.lang.String THIRD_PARTY_KEYGUARD = "android.permission.THIRD_PARTY_KEYGUARD";
field public static final java.lang.String WRITE_ALARMS = "cyanogenmod.permission.WRITE_ALARMS";
field public static final java.lang.String WRITE_SECURE_SETTINGS = "cyanogenmod.permission.WRITE_SECURE_SETTINGS";
field public static final java.lang.String WRITE_SETTINGS = "cyanogenmod.permission.WRITE_SETTINGS";
field public static final java.lang.String WRITE_THEMES = "cyanogenmod.permission.WRITE_THEMES";
field public static final java.lang.String WRITE_WEATHER = "cyanogenmod.permission.WRITE_WEATHER";
}
public final class R {
@ -1043,6 +1047,94 @@ package cyanogenmod.providers {
field public static final int UPDATING = 2; // 0x2
}
public class WeatherContract {
ctor public WeatherContract();
field public static final java.lang.String AUTHORITY = "com.cyanogenmod.weather";
field public static final android.net.Uri AUTHORITY_URI;
}
public static class WeatherContract.WeatherColumns {
ctor public WeatherContract.WeatherColumns();
field public static final android.net.Uri CONTENT_URI;
field public static final android.net.Uri CURRENT_AND_FORECAST_WEATHER_URI;
field public static final java.lang.String CURRENT_CITY = "city";
field public static final java.lang.String CURRENT_CITY_ID = "city_id";
field public static final java.lang.String CURRENT_CONDITION = "condition";
field public static final java.lang.String CURRENT_CONDITION_CODE = "condition_code";
field public static final java.lang.String CURRENT_HUMIDITY = "humidity";
field public static final java.lang.String CURRENT_TEMPERATURE = "temperature";
field public static final java.lang.String CURRENT_TEMPERATURE_UNIT = "temperature_unit";
field public static final java.lang.String CURRENT_TIMESTAMP = "timestamp";
field public static final android.net.Uri CURRENT_WEATHER_URI;
field public static final java.lang.String CURRENT_WIND_DIRECTION = "wind_direction";
field public static final java.lang.String CURRENT_WIND_SPEED = "wind_speed";
field public static final java.lang.String CURRENT_WIND_SPEED_UNIT = "wind_speed_unit";
field public static final java.lang.String FORECAST_CONDITION = "forecast_condition";
field public static final java.lang.String FORECAST_CONDITION_CODE = "forecast_condition_code";
field public static final java.lang.String FORECAST_HIGH = "forecast_high";
field public static final java.lang.String FORECAST_LOW = "forecast_low";
field public static final android.net.Uri FORECAST_WEATHER_URI;
}
public static final class WeatherContract.WeatherColumns.TempUnit {
field public static final int CELSIUS = 1; // 0x1
field public static final int FAHRENHEIT = 2; // 0x2
}
public static final class WeatherContract.WeatherColumns.WeatherCode {
field public static final int BLOWING_SNOW = 14; // 0xe
field public static final int BLUSTERY = 22; // 0x16
field public static final int CLEAR_NIGHT = 30; // 0x1e
field public static final int CLOUDY = 25; // 0x19
field public static final int COLD = 24; // 0x18
field public static final int DRIZZLE = 9; // 0x9
field public static final int DUST = 18; // 0x12
field public static final int FAIR_DAY = 33; // 0x21
field public static final int FAIR_NIGHT = 32; // 0x20
field public static final int FOGGY = 19; // 0x13
field public static final int FREEZING_DRIZZLE = 8; // 0x8
field public static final int FREEZING_RAIN = 10; // 0xa
field public static final int HAIL = 16; // 0x10
field public static final int HAZE = 20; // 0x14
field public static final int HEAVY_SNOW = 39; // 0x27
field public static final int HOT = 35; // 0x23
field public static final int HURRICANE = 2; // 0x2
field public static final int ISOLATED_THUNDERSHOWERS = 44; // 0x2c
field public static final int ISOLATED_THUNDERSTORMS = 36; // 0x24
field public static final int LIGHT_SNOW_SHOWERS = 13; // 0xd
field public static final int MIXED_RAIN_AND_HAIL = 34; // 0x22
field public static final int MIXED_RAIN_AND_SLEET = 6; // 0x6
field public static final int MIXED_RAIN_AND_SNOW = 5; // 0x5
field public static final int MIXED_SNOW_AND_SLEET = 7; // 0x7
field public static final int MOSTLY_CLOUDY_DAY = 27; // 0x1b
field public static final int MOSTLY_CLOUDY_NIGHT = 26; // 0x1a
field public static final int NOT_AVAILABLE = 3200; // 0xc80
field public static final int PARTLY_CLOUDY = 41; // 0x29
field public static final int PARTLY_CLOUDY_DAY = 29; // 0x1d
field public static final int PARTLY_CLOUDY_NIGHT = 28; // 0x1c
field public static final int SCATTERED_SHOWERS = 38; // 0x26
field public static final int SCATTERED_SNOW_SHOWERS = 40; // 0x28
field public static final int SCATTERED_THUNDERSTORMS = 37; // 0x25
field public static final int SEVERE_THUNDERSTORMS = 3; // 0x3
field public static final int SHOWERS = 11; // 0xb
field public static final int SLEET = 17; // 0x11
field public static final int SMOKY = 21; // 0x15
field public static final int SNOW = 15; // 0xf
field public static final int SNOW_FLURRIES = 12; // 0xc
field public static final int SNOW_SHOWERS = 43; // 0x2b
field public static final int SUNNY = 31; // 0x1f
field public static final int THUNDERSHOWER = 42; // 0x2a
field public static final int THUNDERSTORMS = 4; // 0x4
field public static final int TORNADO = 0; // 0x0
field public static final int TROPICAL_STORM = 1; // 0x1
field public static final int WINDY = 23; // 0x17
}
public static final class WeatherContract.WeatherColumns.WindSpeedUnit {
field public static final int KPH = 1; // 0x1
field public static final int MPH = 2; // 0x2
}
}
package cyanogenmod.themes {
@ -1155,3 +1247,147 @@ package cyanogenmod.util {
}
package cyanogenmod.weather {
public class CMWeatherManager {
method public java.lang.String getActiveWeatherServiceProviderLabel();
method public static cyanogenmod.weather.CMWeatherManager getInstance(android.content.Context);
method public void lookupCity(java.lang.String, cyanogenmod.weather.CMWeatherManager.LookupCityRequestListener);
method public void registerWeatherServiceProviderChangeListener(cyanogenmod.weather.CMWeatherManager.WeatherServiceProviderChangeListener);
method public void requestWeatherUpdate(android.location.Location, cyanogenmod.weather.CMWeatherManager.WeatherUpdateRequestListener);
method public void requestWeatherUpdate(cyanogenmod.weather.WeatherLocation, cyanogenmod.weather.CMWeatherManager.WeatherUpdateRequestListener);
method public void unregisterWeatherServiceProviderChangeListener(cyanogenmod.weather.CMWeatherManager.WeatherServiceProviderChangeListener);
field public static final int WEATHER_REQUEST_ALREADY_IN_PROGRESS = -3; // 0xfffffffd
field public static final int WEATHER_REQUEST_COMPLETED = 1; // 0x1
field public static final int WEATHER_REQUEST_FAILED = -2; // 0xfffffffe
field public static final int WEATHER_REQUEST_SUBMITTED_TOO_SOON = -1; // 0xffffffff
}
public static abstract interface CMWeatherManager.LookupCityRequestListener {
method public abstract void onLookupCityRequestCompleted(java.util.ArrayList<cyanogenmod.weather.WeatherLocation>);
}
public static abstract interface CMWeatherManager.WeatherServiceProviderChangeListener {
method public abstract void onWeatherServiceProviderChanged(java.lang.String);
}
public static abstract interface CMWeatherManager.WeatherUpdateRequestListener {
method public abstract void onWeatherRequestCompleted(int, cyanogenmod.weather.WeatherInfo);
}
public final class RequestInfo implements android.os.Parcelable {
method public int describeContents();
method public java.lang.String getCityName();
method public android.location.Location getLocation();
method public int getRequestType();
method public int getTemperatureUnit();
method public cyanogenmod.weather.WeatherLocation getWeatherLocation();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<cyanogenmod.weather.RequestInfo> CREATOR;
field public static final int TYPE_GEO_LOCATION_REQ = 1; // 0x1
field public static final int TYPE_LOOKUP_CITY_NAME_REQ = 3; // 0x3
field public static final int TYPE_WEATHER_LOCATION_REQ = 2; // 0x2
}
public final class WeatherInfo implements android.os.Parcelable {
method public int describeContents();
method public java.lang.String getCity();
method public java.lang.String getCityId();
method public int getConditionCode();
method public java.util.ArrayList<cyanogenmod.weather.WeatherInfo.DayForecast> getForecasts();
method public float getHumidity();
method public float getTemperature();
method public int getTemperatureUnit();
method public long getTimestamp();
method public float getWindDirection();
method public float getWindSpeed();
method public int getWindSpeedUnit();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<cyanogenmod.weather.WeatherInfo> CREATOR;
}
public static class WeatherInfo.Builder {
ctor public WeatherInfo.Builder(long);
method public cyanogenmod.weather.WeatherInfo build();
method public cyanogenmod.weather.WeatherInfo.Builder setCity(java.lang.String, java.lang.String);
method public cyanogenmod.weather.WeatherInfo.Builder setForecast(java.util.ArrayList<cyanogenmod.weather.WeatherInfo.DayForecast>);
method public cyanogenmod.weather.WeatherInfo.Builder setHumidity(float);
method public cyanogenmod.weather.WeatherInfo.Builder setTemperature(float, int);
method public cyanogenmod.weather.WeatherInfo.Builder setWeatherCondition(int);
method public cyanogenmod.weather.WeatherInfo.Builder setWind(float, float, int);
}
public static class WeatherInfo.DayForecast implements android.os.Parcelable {
method public int describeContents();
method public int getConditionCode();
method public float getHigh();
method public float getLow();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<cyanogenmod.weather.WeatherInfo.DayForecast> CREATOR;
}
public static class WeatherInfo.DayForecast.Builder {
ctor public WeatherInfo.DayForecast.Builder();
method public cyanogenmod.weather.WeatherInfo.DayForecast build();
method public cyanogenmod.weather.WeatherInfo.DayForecast.Builder setHigh(float);
method public cyanogenmod.weather.WeatherInfo.DayForecast.Builder setLow(float);
method public cyanogenmod.weather.WeatherInfo.DayForecast.Builder setWeatherCondition(int);
}
public final class WeatherLocation implements android.os.Parcelable {
method public int describeContents();
method public java.lang.String getCity();
method public java.lang.String getCityId();
method public java.lang.String getCountry();
method public java.lang.String getCountryId();
method public java.lang.String getPostalCode();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<cyanogenmod.weather.WeatherLocation> CREATOR;
}
public static class WeatherLocation.Builder {
ctor public WeatherLocation.Builder(java.lang.String, java.lang.String);
method public cyanogenmod.weather.WeatherLocation build();
method public cyanogenmod.weather.WeatherLocation.Builder setCountry(java.lang.String, java.lang.String);
method public cyanogenmod.weather.WeatherLocation.Builder setPostalCode(java.lang.String);
}
}
package cyanogenmod.weatherservice {
public final class ServiceRequest {
method public void complete(cyanogenmod.weatherservice.ServiceRequestResult);
method public void fail();
method public cyanogenmod.weather.RequestInfo getRequestInfo();
}
public final class ServiceRequestResult implements android.os.Parcelable {
method public int describeContents();
method public java.util.ArrayList<cyanogenmod.weather.WeatherLocation> getLocationLookupList();
method public cyanogenmod.weather.WeatherInfo getWeatherInfo();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<cyanogenmod.weatherservice.ServiceRequestResult> CREATOR;
}
public static class ServiceRequestResult.Builder {
ctor public ServiceRequestResult.Builder();
method public cyanogenmod.weatherservice.ServiceRequestResult build();
method public cyanogenmod.weatherservice.ServiceRequestResult.Builder setLocationLookupList(java.util.ArrayList<cyanogenmod.weather.WeatherLocation>);
method public cyanogenmod.weatherservice.ServiceRequestResult.Builder setWeatherInfo(cyanogenmod.weather.WeatherInfo);
}
public abstract class WeatherProviderService extends android.app.Service {
ctor public WeatherProviderService();
method protected final void attachBaseContext(android.content.Context);
method public final android.os.IBinder onBind(android.content.Intent);
method protected void onConnected();
method protected void onDisconnected();
method protected abstract void onRequestCancelled(cyanogenmod.weatherservice.ServiceRequest);
method protected abstract void onRequestSubmitted(cyanogenmod.weatherservice.ServiceRequest);
field public static final java.lang.String SERVICE_INTERFACE = "cyanogenmod.weatherservice.WeatherProviderService";
field public static final java.lang.String SERVICE_META_DATA = "cyanogenmod.weatherservice";
}
}

View File

@ -0,0 +1,545 @@
/*
* 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 org.cyanogenmod.platform.internal;
import android.app.AppGlobals;
import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Slog;
import com.android.internal.content.PackageMonitor;
import com.android.internal.os.BackgroundThread;
import com.android.server.SystemService;
import cyanogenmod.app.CMContextConstants;
import cyanogenmod.platform.Manifest;
import cyanogenmod.providers.CMSettings;
import cyanogenmod.providers.WeatherContract.WeatherColumns;
import cyanogenmod.weather.CMWeatherManager;
import cyanogenmod.weather.ICMWeatherManager;
import cyanogenmod.weather.IRequestInfoListener;
import cyanogenmod.weather.IWeatherServiceProviderChangeListener;
import cyanogenmod.weather.RequestInfo;
import cyanogenmod.weather.WeatherInfo;
import cyanogenmod.weatherservice.IWeatherProviderService;
import cyanogenmod.weatherservice.IWeatherProviderServiceClient;
import cyanogenmod.weatherservice.ServiceRequestResult;
import java.util.ArrayList;
import java.util.List;
public class CMWeatherManagerService extends SystemService{
private static final String TAG = CMWeatherManagerService.class.getSimpleName();
/**
* How long clients will have to wait until a new weather update request can be honored
* TODO Allow weather service providers to specify this threshold
*/
private static final long REQUEST_THRESHOLD_MILLIS = 1000L * 60L * 10L;
private IWeatherProviderService mWeatherProviderService;
private boolean mIsWeatherProviderServiceBound;
private long mLastWeatherUpdateRequestTimestamp = -REQUEST_THRESHOLD_MILLIS;
private boolean mIsProcessingRequest = false;
private Object mMutex = new Object();
private Context mContext;
private final RemoteCallbackList<IWeatherServiceProviderChangeListener> mProviderChangeListeners
= new RemoteCallbackList<>();
private volatile boolean mReconnectedDuePkgModified = false;
private final IWeatherProviderServiceClient mServiceClient
= new IWeatherProviderServiceClient.Stub() {
@Override
public void setServiceRequestState(RequestInfo requestInfo,
ServiceRequestResult result, int state) {
synchronized (mMutex) {
if (requestInfo == null) {
//Invalid request info object
mIsProcessingRequest = false;
return;
}
final IRequestInfoListener listener = requestInfo.getRequestListener();
final int requestType = requestInfo.getRequestType();
switch (requestType) {
case RequestInfo.TYPE_GEO_LOCATION_REQ:
case RequestInfo.TYPE_WEATHER_LOCATION_REQ:
if (!isValidRequestInfoState(requestType, state)) {
//We received an invalid state, silently disregard the request
mIsProcessingRequest = false;
return;
}
WeatherInfo weatherInfo = null;
if (state == CMWeatherManager.WEATHER_REQUEST_COMPLETED) {
weatherInfo = (result != null) ? result.getWeatherInfo() : null;
if (weatherInfo == null) {
//This should never happen! WEATHER_REQUEST_COMPLETED is set
//only if the weatherinfo object was not null when the request
//was marked as completed
state = CMWeatherManager.WEATHER_REQUEST_FAILED;
} else {
if (!requestInfo.isQueryOnlyWeatherRequest()) {
final long identity = Binder.clearCallingIdentity();
try {
updateWeatherInfoLocked(weatherInfo);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
}
if (isValidListener(listener)) {
try {
listener.onWeatherRequestCompleted(requestInfo, state, weatherInfo);
} catch (RemoteException e) {
}
}
break;
case RequestInfo.TYPE_LOOKUP_CITY_NAME_REQ:
if (isValidListener(listener)) {
try {
//Result might be null if the provider marked the request as failed
listener.onLookupCityRequestCompleted(requestInfo,
result != null ? result.getLocationLookupList() : null);
} catch (RemoteException e) {
}
}
break;
}
mIsProcessingRequest = false;
}
}
};
private boolean isValidRequestInfoState(int requestType, int state) {
switch (requestType) {
case RequestInfo.TYPE_GEO_LOCATION_REQ:
case RequestInfo.TYPE_WEATHER_LOCATION_REQ:
switch (state) {
case CMWeatherManager.WEATHER_REQUEST_COMPLETED:
case CMWeatherManager.WEATHER_REQUEST_SUBMITTED_TOO_SOON:
case CMWeatherManager.WEATHER_REQUEST_FAILED:
case CMWeatherManager.WEATHER_REQUEST_ALREADY_IN_PROGRESS:
return true;
default:
return false;
}
case RequestInfo.TYPE_LOOKUP_CITY_NAME_REQ:
switch (state) {
case CMWeatherManager.LOOKUP_REQUEST_COMPLETED:
case CMWeatherManager.LOOKUP_REQUEST_FAILED:
case CMWeatherManager.LOOKUP_REQUEST_NO_MATCH_FOUND:
return true;
default:
return false;
}
default:
return false;
}
}
private boolean isValidListener(IRequestInfoListener listener) {
return (listener != null && listener.asBinder().pingBinder());
}
private void enforcePermission() {
mContext.enforceCallingOrSelfPermission(
Manifest.permission.ACCESS_WEATHER_MANAGER, null);
}
private final IBinder mService = new ICMWeatherManager.Stub() {
@Override
public void updateWeather(RequestInfo info) {
enforcePermission();
processWeatherUpdateRequest(info);
}
@Override
public void lookupCity(RequestInfo info) {
enforcePermission();
processCityNameLookupRequest(info);
}
@Override
public void registerWeatherServiceProviderChangeListener(
IWeatherServiceProviderChangeListener listener) {
enforcePermission();
mProviderChangeListeners.register(listener);
}
@Override
public void unregisterWeatherServiceProviderChangeListener(
IWeatherServiceProviderChangeListener listener) {
enforcePermission();
mProviderChangeListeners.unregister(listener);
}
@Override
public String getActiveWeatherServiceProviderLabel() {
enforcePermission();
final long identity = Binder.clearCallingIdentity();
try {
String enabledProviderService = CMSettings.Secure.getString(
mContext.getContentResolver(), CMSettings.Secure.WEATHER_PROVIDER_SERVICE);
if (enabledProviderService != null) {
return getComponentLabel(
ComponentName.unflattenFromString(enabledProviderService));
}
} finally {
Binder.restoreCallingIdentity(identity);
}
return null;
}
};
private String getComponentLabel(ComponentName componentName) {
final PackageManager pm = mContext.getPackageManager();
Intent intent = new Intent().setComponent(componentName);
ResolveInfo resolveInfo = pm.resolveService(intent,
PackageManager.GET_SERVICES);
if (resolveInfo != null) {
return resolveInfo.loadLabel(pm).toString();
}
return null;
}
public CMWeatherManagerService(Context context) {
super(context);
mContext = context;
}
@Override
public void onStart() {
publishBinderService(CMContextConstants.CM_WEATHER_SERVICE, mService);
registerPackageMonitor();
registerSettingsObserver();
}
@Override
public void onBootPhase(int phase) {
if (phase == PHASE_ACTIVITY_MANAGER_READY) {
bindActiveWeatherProviderService();
}
}
private void bindActiveWeatherProviderService() {
String activeProviderService = CMSettings.Secure.getString(mContext.getContentResolver(),
CMSettings.Secure.WEATHER_PROVIDER_SERVICE);
if (activeProviderService != null) {
if (!getContext().bindServiceAsUser(new Intent().setComponent(
ComponentName.unflattenFromString(activeProviderService)),
mWeatherServiceProviderConnection, Context.BIND_AUTO_CREATE,
UserHandle.CURRENT)) {
Slog.w(TAG, "Failed to bind service " + activeProviderService);
}
}
}
private boolean canProcessWeatherUpdateRequest(RequestInfo info, long currentTimeMillis) {
final IRequestInfoListener listener = info.getRequestListener();
if ((mLastWeatherUpdateRequestTimestamp + REQUEST_THRESHOLD_MILLIS) > currentTimeMillis) {
if (listener != null && listener.asBinder().pingBinder()) {
try {
listener.onWeatherRequestCompleted(info,
CMWeatherManager.WEATHER_REQUEST_SUBMITTED_TOO_SOON, null);
} catch (RemoteException e) {
}
}
return false;
}
if (mIsProcessingRequest) {
if (listener != null && listener.asBinder().pingBinder()) {
try {
listener.onWeatherRequestCompleted(info,
CMWeatherManager.WEATHER_REQUEST_ALREADY_IN_PROGRESS, null);
} catch (RemoteException e) {
}
}
return false;
}
if (!mIsWeatherProviderServiceBound) {
if (listener != null && listener.asBinder().pingBinder()) {
try {
listener.onWeatherRequestCompleted(info,
CMWeatherManager.WEATHER_REQUEST_FAILED, null);
} catch (RemoteException e) {
}
}
return false;
}
return true;
}
private synchronized void processWeatherUpdateRequest(RequestInfo info) {
final long currentTimeMillis = SystemClock.elapsedRealtime();
if (!canProcessWeatherUpdateRequest(info, currentTimeMillis)) return;
mLastWeatherUpdateRequestTimestamp = currentTimeMillis;
mIsProcessingRequest = true;
try {
mWeatherProviderService.processWeatherUpdateRequest(info);
} catch (RemoteException e) {
}
}
private void processCityNameLookupRequest(RequestInfo info) {
if (!mIsWeatherProviderServiceBound) {
final IRequestInfoListener listener = info.getRequestListener();
if (listener != null && listener.asBinder().pingBinder()) {
try {
listener.onLookupCityRequestCompleted(info, null);
} catch (RemoteException e) {
}
}
return;
}
try {
mWeatherProviderService.processCityNameLookupRequest(info);
} catch(RemoteException e){
}
}
private ServiceConnection mWeatherServiceProviderConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mWeatherProviderService = IWeatherProviderService.Stub.asInterface(service);
mIsWeatherProviderServiceBound = true;
try {
mWeatherProviderService.setServiceClient(mServiceClient);
} catch(RemoteException e) {
}
if (!mReconnectedDuePkgModified) {
notifyProviderChanged(name);
}
mReconnectedDuePkgModified = false;
}
@Override
public void onServiceDisconnected(ComponentName name) {
mWeatherProviderService = null;
mIsWeatherProviderServiceBound = false;
//We can't talk to the current service anyway...
mIsProcessingRequest = false;
Slog.d(TAG, "Connection with " + name.flattenToString() + " has been closed");
}
};
private void notifyProviderChanged(ComponentName name) {
String providerName = null;
if (name != null) {
providerName = getComponentLabel(name);
}
int N = mProviderChangeListeners.beginBroadcast();
for (int indx = 0; indx < N; indx++) {
IWeatherServiceProviderChangeListener listener
= mProviderChangeListeners.getBroadcastItem(indx);
try {
listener.onWeatherServiceProviderChanged(providerName);
} catch (RemoteException e){
}
}
mProviderChangeListeners.finishBroadcast();
}
private boolean updateWeatherInfoLocked(WeatherInfo wi) {
final int size = wi.getForecasts().size() + 1;
List<ContentValues> contentValuesList = new ArrayList<>(size);
ContentValues contentValues = new ContentValues();
contentValues.put(WeatherColumns.CURRENT_CITY_ID, wi.getCityId());
contentValues.put(WeatherColumns.CURRENT_CITY, wi.getCity());
contentValues.put(WeatherColumns.CURRENT_CONDITION_CODE, wi.getConditionCode());
contentValues.put(WeatherColumns.CURRENT_HUMIDITY, wi.getHumidity());
contentValues.put(WeatherColumns.CURRENT_TEMPERATURE, wi.getTemperature());
contentValues.put(WeatherColumns.CURRENT_TEMPERATURE_UNIT, wi.getTemperatureUnit());
contentValues.put(WeatherColumns.CURRENT_TIMESTAMP, wi.getTimestamp());
contentValues.put(WeatherColumns.CURRENT_WIND_DIRECTION, wi.getWindDirection());
contentValues.put(WeatherColumns.CURRENT_WIND_SPEED, wi.getWindSpeed());
contentValues.put(WeatherColumns.CURRENT_WIND_SPEED_UNIT, wi.getWindSpeedUnit());
contentValuesList.add(contentValues);
for (WeatherInfo.DayForecast df : wi.getForecasts()) {
contentValues = new ContentValues();
contentValues.put(WeatherColumns.FORECAST_LOW, df.getLow());
contentValues.put(WeatherColumns.FORECAST_HIGH, df.getHigh());
contentValues.put(WeatherColumns.FORECAST_CONDITION_CODE, df.getConditionCode());
contentValuesList.add(contentValues);
}
ContentValues[] updateValues = new ContentValues[contentValuesList.size()];
if (size != getContext().getContentResolver().bulkInsert(
WeatherColumns.CURRENT_AND_FORECAST_WEATHER_URI,
contentValuesList.toArray(updateValues))) {
Slog.w(TAG, "Failed to update the weather content provider");
return false;
}
return true;
}
private void registerPackageMonitor() {
PackageMonitor monitor = new PackageMonitor() {
@Override
public void onPackageModified(String packageName) {
String enabledProviderService = CMSettings.Secure.getString(
mContext.getContentResolver(), CMSettings.Secure.WEATHER_PROVIDER_SERVICE);
if (enabledProviderService == null) return;
ComponentName cn = ComponentName.unflattenFromString(enabledProviderService);
if (!TextUtils.equals(packageName, cn.getPackageName())) return;
if (cn.getPackageName().equals(packageName) && !mIsWeatherProviderServiceBound) {
//We were disconnected because the whole package changed
//(most likely remove->install)
if (!getContext().bindServiceAsUser(new Intent().setComponent(cn),
mWeatherServiceProviderConnection, Context.BIND_AUTO_CREATE,
UserHandle.CURRENT)) {
CMSettings.Secure.putStringForUser( mContext.getContentResolver(),
CMSettings.Secure.WEATHER_PROVIDER_SERVICE, null,
getChangingUserId());
Slog.w(TAG, "Unable to rebind " + cn.flattenToString() + " after receiving"
+ " package modified notification. Settings updated.");
} else {
mReconnectedDuePkgModified = true;
}
}
}
@Override
public boolean onPackageChanged(String packageName, int uid, String[] components) {
String enabledProviderService = CMSettings.Secure.getString(
mContext.getContentResolver(), CMSettings.Secure.WEATHER_PROVIDER_SERVICE);
if (enabledProviderService == null) return false;
boolean packageChanged = false;
ComponentName cn = ComponentName.unflattenFromString(enabledProviderService);
for (String component : components) {
if (cn.getPackageName().equals(component)) {
packageChanged = true;
break;
}
}
if (packageChanged) {
try {
final IPackageManager pm = AppGlobals.getPackageManager();
final int enabled = pm.getApplicationEnabledSetting(packageName,
getChangingUserId());
if (enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|| enabled == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
return false;
} else {
disconnectClient();
//The package is not enabled so we can't use it anymore
CMSettings.Secure.putStringForUser(
mContext.getContentResolver(),
CMSettings.Secure.WEATHER_PROVIDER_SERVICE, null,
getChangingUserId());
Slog.w(TAG, "Active provider " + cn.flattenToString() + " disabled");
notifyProviderChanged(null);
}
} catch (IllegalArgumentException e) {
Slog.d(TAG, "Exception trying to look up app enabled settings ", e);
} catch (RemoteException e) {
// Really?
}
}
return false;
}
@Override
public void onPackageRemoved(String packageName, int uid) {
String enabledProviderService = CMSettings.Secure.getString(
mContext.getContentResolver(), CMSettings.Secure.WEATHER_PROVIDER_SERVICE);
if (enabledProviderService == null) return;
ComponentName cn = ComponentName.unflattenFromString(enabledProviderService);
if (!TextUtils.equals(packageName, cn.getPackageName())) return;
disconnectClient();
CMSettings.Secure.putStringForUser(
mContext.getContentResolver(), CMSettings.Secure.WEATHER_PROVIDER_SERVICE,
null, getChangingUserId());
notifyProviderChanged(null);
}
};
monitor.register(mContext, BackgroundThread.getHandler().getLooper(), UserHandle.ALL, true);
}
private void registerSettingsObserver() {
final Uri enabledWeatherProviderServiceUri = CMSettings.Secure.getUriFor(
CMSettings.Secure.WEATHER_PROVIDER_SERVICE);
ContentObserver observer = new ContentObserver(BackgroundThread.getHandler()) {
@Override
public void onChange(boolean selfChange, Uri uri, int userId) {
if (enabledWeatherProviderServiceUri.equals(uri)) {
String activeSrvc = CMSettings.Secure.getString(mContext.getContentResolver(),
CMSettings.Secure.WEATHER_PROVIDER_SERVICE);
disconnectClient();
if (activeSrvc != null) {
ComponentName cn = ComponentName.unflattenFromString(activeSrvc);
getContext().bindServiceAsUser(new Intent().setComponent(cn),
mWeatherServiceProviderConnection, Context.BIND_AUTO_CREATE,
UserHandle.CURRENT);
}
}
}
};
mContext.getContentResolver().registerContentObserver(enabledWeatherProviderServiceUri,
false, observer, UserHandle.USER_ALL);
}
private synchronized void disconnectClient() {
if (mIsWeatherProviderServiceBound) {
if (mIsProcessingRequest) {
try {
mWeatherProviderService.cancelOngoingRequests();
} catch (RemoteException e) {
}
mIsProcessingRequest = false;
}
try {
mWeatherProviderService.setServiceClient(null);
} catch (RemoteException e) {
}
getContext().unbindService(mWeatherServiceProviderConnection);
mIsWeatherProviderServiceBound = false;
}
}
}

View File

@ -192,6 +192,33 @@
android:description="@string/permdesc_accessLiveLockScreenServiceProvider"
android:protectionLevel="signature|privileged" />
<!-- Allows an application to read the weather content from the provider-->
<permission android:name="cyanogenmod.permission.READ_WEATHER"
android:label="@string/permlab_weather_read"
android:description="@string/permdesc_weather_read"
android:protectionLevel="normal"/>
<!-- Allows an application to update the content of the weather provider
<p>Not for use by third-party applications. -->
<permission android:name="cyanogenmod.permission.WRITE_WEATHER"
android:label="@string/permlab_weather_write"
android:description="@string/permdesc_weather_write"
android:protectionLevel="signature|privileged" />
<!-- Allows an application to be identified as a weather provider service -->
<permission android:name="cyanogenmod.permission.BIND_WEATHER_PROVIDER_SERVICE"
android:label="@string/permlab_weather_bind"
android:description="@string/permdesc_weather_bind"
android:protectionLevel="signature"/>
<!-- Allows an application to access the weather service.
<p>Although the protection is normal, this permission should be required ONLY by those apps
meant to do something meaningful with the data provided by the service (LockClock, SysUI)-->
<permission android:name="cyanogenmod.permission.ACCESS_WEATHER_MANAGER"
android:label="@string/permlab_weather_access_mgr"
android:description="@string/permdesc_weather_access_mgr"
android:protectionLevel="normal"/>
<application android:process="system"
android:persistent="true"
android:hasCode="false"

View File

@ -90,5 +90,6 @@
<item>org.cyanogenmod.platform.internal.ThemeManagerService</item>
<item>org.cyanogenmod.platform.internal.IconCacheManagerService</item>
<item>org.cyanogenmod.platform.internal.LiveLockScreenServiceBroker</item>
<item>org.cyanogenmod.platform.internal.CMWeatherManagerService</item>
</string-array>
</resources>

View File

@ -179,5 +179,13 @@
<!-- Live lock screen manager service provider permission description -->
<string name="permdesc_accessLiveLockScreenServiceProvider">Allows a service to provide the live lock screen manager service.</string>
<!-- Weather Service strings -->
<string name="permlab_weather_read">read weather</string>
<string name="permdesc_weather_read">Allows an app to read content from the weather provider</string>
<string name="permlab_weather_write">update weather provider</string>
<string name="permdesc_weather_write">Allows an app to update the content of the weather provider</string>
<string name="permlab_weather_bind">binds as a weather provider service</string>
<string name="permdesc_weather_bind">Allows an app to be identified as a weather provider service</string>
<string name="permlab_weather_access_mgr">access weather service</string>
<string name="permdesc_weather_access_mgr">Allows an app to access the weather service in the system. Should never be needed for normal apps</string>
</resources>

View File

@ -119,6 +119,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.
*/
@ -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";
}
}

View File

@ -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
/**

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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();
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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();
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -595,6 +595,8 @@ package cyanogenmod.platform {
ctor public Manifest.permission();
field public static final java.lang.String ACCESS_APP_SUGGESTIONS = "cyanogenmod.permission.ACCESS_APP_SUGGESTIONS";
field public static final java.lang.String ACCESS_THEME_MANAGER = "cyanogenmod.permission.ACCESS_THEME_MANAGER";
field public static final java.lang.String ACCESS_WEATHER_MANAGER = "cyanogenmod.permission.ACCESS_WEATHER_MANAGER";
field public static final java.lang.String BIND_WEATHER_PROVIDER_SERVICE = "cyanogenmod.permission.BIND_WEATHER_PROVIDER_SERVICE";
field public static final java.lang.String HARDWARE_ABSTRACTION_ACCESS = "cyanogenmod.permission.HARDWARE_ABSTRACTION_ACCESS";
field public static final java.lang.String LIVE_LOCK_SCREEN_MANAGER_ACCESS = "cyanogenmod.permission.LIVE_LOCK_SCREEN_MANAGER_ACCESS";
field public static final java.lang.String MANAGE_ALARMS = "cyanogenmod.permission.MANAGE_ALARMS";
@ -609,11 +611,13 @@ package cyanogenmod.platform {
field public static final java.lang.String READ_ALARMS = "cyanogenmod.permission.READ_ALARMS";
field public static final java.lang.String READ_MSIM_PHONE_STATE = "cyanogenmod.permission.READ_MSIM_PHONE_STATE";
field public static final java.lang.String READ_THEMES = "cyanogenmod.permission.READ_THEMES";
field public static final java.lang.String READ_WEATHER = "cyanogenmod.permission.READ_WEATHER";
field public static final java.lang.String THIRD_PARTY_KEYGUARD = "android.permission.THIRD_PARTY_KEYGUARD";
field public static final java.lang.String WRITE_ALARMS = "cyanogenmod.permission.WRITE_ALARMS";
field public static final java.lang.String WRITE_SECURE_SETTINGS = "cyanogenmod.permission.WRITE_SECURE_SETTINGS";
field public static final java.lang.String WRITE_SETTINGS = "cyanogenmod.permission.WRITE_SETTINGS";
field public static final java.lang.String WRITE_THEMES = "cyanogenmod.permission.WRITE_THEMES";
field public static final java.lang.String WRITE_WEATHER = "cyanogenmod.permission.WRITE_WEATHER";
}
public final class R {
@ -1043,6 +1047,94 @@ package cyanogenmod.providers {
field public static final int UPDATING = 2; // 0x2
}
public class WeatherContract {
ctor public WeatherContract();
field public static final java.lang.String AUTHORITY = "com.cyanogenmod.weather";
field public static final android.net.Uri AUTHORITY_URI;
}
public static class WeatherContract.WeatherColumns {
ctor public WeatherContract.WeatherColumns();
field public static final android.net.Uri CONTENT_URI;
field public static final android.net.Uri CURRENT_AND_FORECAST_WEATHER_URI;
field public static final java.lang.String CURRENT_CITY = "city";
field public static final java.lang.String CURRENT_CITY_ID = "city_id";
field public static final java.lang.String CURRENT_CONDITION = "condition";
field public static final java.lang.String CURRENT_CONDITION_CODE = "condition_code";
field public static final java.lang.String CURRENT_HUMIDITY = "humidity";
field public static final java.lang.String CURRENT_TEMPERATURE = "temperature";
field public static final java.lang.String CURRENT_TEMPERATURE_UNIT = "temperature_unit";
field public static final java.lang.String CURRENT_TIMESTAMP = "timestamp";
field public static final android.net.Uri CURRENT_WEATHER_URI;
field public static final java.lang.String CURRENT_WIND_DIRECTION = "wind_direction";
field public static final java.lang.String CURRENT_WIND_SPEED = "wind_speed";
field public static final java.lang.String CURRENT_WIND_SPEED_UNIT = "wind_speed_unit";
field public static final java.lang.String FORECAST_CONDITION = "forecast_condition";
field public static final java.lang.String FORECAST_CONDITION_CODE = "forecast_condition_code";
field public static final java.lang.String FORECAST_HIGH = "forecast_high";
field public static final java.lang.String FORECAST_LOW = "forecast_low";
field public static final android.net.Uri FORECAST_WEATHER_URI;
}
public static final class WeatherContract.WeatherColumns.TempUnit {
field public static final int CELSIUS = 1; // 0x1
field public static final int FAHRENHEIT = 2; // 0x2
}
public static final class WeatherContract.WeatherColumns.WeatherCode {
field public static final int BLOWING_SNOW = 14; // 0xe
field public static final int BLUSTERY = 22; // 0x16
field public static final int CLEAR_NIGHT = 30; // 0x1e
field public static final int CLOUDY = 25; // 0x19
field public static final int COLD = 24; // 0x18
field public static final int DRIZZLE = 9; // 0x9
field public static final int DUST = 18; // 0x12
field public static final int FAIR_DAY = 33; // 0x21
field public static final int FAIR_NIGHT = 32; // 0x20
field public static final int FOGGY = 19; // 0x13
field public static final int FREEZING_DRIZZLE = 8; // 0x8
field public static final int FREEZING_RAIN = 10; // 0xa
field public static final int HAIL = 16; // 0x10
field public static final int HAZE = 20; // 0x14
field public static final int HEAVY_SNOW = 39; // 0x27
field public static final int HOT = 35; // 0x23
field public static final int HURRICANE = 2; // 0x2
field public static final int ISOLATED_THUNDERSHOWERS = 44; // 0x2c
field public static final int ISOLATED_THUNDERSTORMS = 36; // 0x24
field public static final int LIGHT_SNOW_SHOWERS = 13; // 0xd
field public static final int MIXED_RAIN_AND_HAIL = 34; // 0x22
field public static final int MIXED_RAIN_AND_SLEET = 6; // 0x6
field public static final int MIXED_RAIN_AND_SNOW = 5; // 0x5
field public static final int MIXED_SNOW_AND_SLEET = 7; // 0x7
field public static final int MOSTLY_CLOUDY_DAY = 27; // 0x1b
field public static final int MOSTLY_CLOUDY_NIGHT = 26; // 0x1a
field public static final int NOT_AVAILABLE = 3200; // 0xc80
field public static final int PARTLY_CLOUDY = 41; // 0x29
field public static final int PARTLY_CLOUDY_DAY = 29; // 0x1d
field public static final int PARTLY_CLOUDY_NIGHT = 28; // 0x1c
field public static final int SCATTERED_SHOWERS = 38; // 0x26
field public static final int SCATTERED_SNOW_SHOWERS = 40; // 0x28
field public static final int SCATTERED_THUNDERSTORMS = 37; // 0x25
field public static final int SEVERE_THUNDERSTORMS = 3; // 0x3
field public static final int SHOWERS = 11; // 0xb
field public static final int SLEET = 17; // 0x11
field public static final int SMOKY = 21; // 0x15
field public static final int SNOW = 15; // 0xf
field public static final int SNOW_FLURRIES = 12; // 0xc
field public static final int SNOW_SHOWERS = 43; // 0x2b
field public static final int SUNNY = 31; // 0x1f
field public static final int THUNDERSHOWER = 42; // 0x2a
field public static final int THUNDERSTORMS = 4; // 0x4
field public static final int TORNADO = 0; // 0x0
field public static final int TROPICAL_STORM = 1; // 0x1
field public static final int WINDY = 23; // 0x17
}
public static final class WeatherContract.WeatherColumns.WindSpeedUnit {
field public static final int KPH = 1; // 0x1
field public static final int MPH = 2; // 0x2
}
}
package cyanogenmod.themes {
@ -1155,3 +1247,147 @@ package cyanogenmod.util {
}
package cyanogenmod.weather {
public class CMWeatherManager {
method public java.lang.String getActiveWeatherServiceProviderLabel();
method public static cyanogenmod.weather.CMWeatherManager getInstance(android.content.Context);
method public void lookupCity(java.lang.String, cyanogenmod.weather.CMWeatherManager.LookupCityRequestListener);
method public void registerWeatherServiceProviderChangeListener(cyanogenmod.weather.CMWeatherManager.WeatherServiceProviderChangeListener);
method public void requestWeatherUpdate(android.location.Location, cyanogenmod.weather.CMWeatherManager.WeatherUpdateRequestListener);
method public void requestWeatherUpdate(cyanogenmod.weather.WeatherLocation, cyanogenmod.weather.CMWeatherManager.WeatherUpdateRequestListener);
method public void unregisterWeatherServiceProviderChangeListener(cyanogenmod.weather.CMWeatherManager.WeatherServiceProviderChangeListener);
field public static final int WEATHER_REQUEST_ALREADY_IN_PROGRESS = -3; // 0xfffffffd
field public static final int WEATHER_REQUEST_COMPLETED = 1; // 0x1
field public static final int WEATHER_REQUEST_FAILED = -2; // 0xfffffffe
field public static final int WEATHER_REQUEST_SUBMITTED_TOO_SOON = -1; // 0xffffffff
}
public static abstract interface CMWeatherManager.LookupCityRequestListener {
method public abstract void onLookupCityRequestCompleted(java.util.ArrayList<cyanogenmod.weather.WeatherLocation>);
}
public static abstract interface CMWeatherManager.WeatherServiceProviderChangeListener {
method public abstract void onWeatherServiceProviderChanged(java.lang.String);
}
public static abstract interface CMWeatherManager.WeatherUpdateRequestListener {
method public abstract void onWeatherRequestCompleted(int, cyanogenmod.weather.WeatherInfo);
}
public final class RequestInfo implements android.os.Parcelable {
method public int describeContents();
method public java.lang.String getCityName();
method public android.location.Location getLocation();
method public int getRequestType();
method public int getTemperatureUnit();
method public cyanogenmod.weather.WeatherLocation getWeatherLocation();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<cyanogenmod.weather.RequestInfo> CREATOR;
field public static final int TYPE_GEO_LOCATION_REQ = 1; // 0x1
field public static final int TYPE_LOOKUP_CITY_NAME_REQ = 3; // 0x3
field public static final int TYPE_WEATHER_LOCATION_REQ = 2; // 0x2
}
public final class WeatherInfo implements android.os.Parcelable {
method public int describeContents();
method public java.lang.String getCity();
method public java.lang.String getCityId();
method public int getConditionCode();
method public java.util.ArrayList<cyanogenmod.weather.WeatherInfo.DayForecast> getForecasts();
method public float getHumidity();
method public float getTemperature();
method public int getTemperatureUnit();
method public long getTimestamp();
method public float getWindDirection();
method public float getWindSpeed();
method public int getWindSpeedUnit();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<cyanogenmod.weather.WeatherInfo> CREATOR;
}
public static class WeatherInfo.Builder {
ctor public WeatherInfo.Builder(long);
method public cyanogenmod.weather.WeatherInfo build();
method public cyanogenmod.weather.WeatherInfo.Builder setCity(java.lang.String, java.lang.String);
method public cyanogenmod.weather.WeatherInfo.Builder setForecast(java.util.ArrayList<cyanogenmod.weather.WeatherInfo.DayForecast>);
method public cyanogenmod.weather.WeatherInfo.Builder setHumidity(float);
method public cyanogenmod.weather.WeatherInfo.Builder setTemperature(float, int);
method public cyanogenmod.weather.WeatherInfo.Builder setWeatherCondition(int);
method public cyanogenmod.weather.WeatherInfo.Builder setWind(float, float, int);
}
public static class WeatherInfo.DayForecast implements android.os.Parcelable {
method public int describeContents();
method public int getConditionCode();
method public float getHigh();
method public float getLow();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<cyanogenmod.weather.WeatherInfo.DayForecast> CREATOR;
}
public static class WeatherInfo.DayForecast.Builder {
ctor public WeatherInfo.DayForecast.Builder();
method public cyanogenmod.weather.WeatherInfo.DayForecast build();
method public cyanogenmod.weather.WeatherInfo.DayForecast.Builder setHigh(float);
method public cyanogenmod.weather.WeatherInfo.DayForecast.Builder setLow(float);
method public cyanogenmod.weather.WeatherInfo.DayForecast.Builder setWeatherCondition(int);
}
public final class WeatherLocation implements android.os.Parcelable {
method public int describeContents();
method public java.lang.String getCity();
method public java.lang.String getCityId();
method public java.lang.String getCountry();
method public java.lang.String getCountryId();
method public java.lang.String getPostalCode();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<cyanogenmod.weather.WeatherLocation> CREATOR;
}
public static class WeatherLocation.Builder {
ctor public WeatherLocation.Builder(java.lang.String, java.lang.String);
method public cyanogenmod.weather.WeatherLocation build();
method public cyanogenmod.weather.WeatherLocation.Builder setCountry(java.lang.String, java.lang.String);
method public cyanogenmod.weather.WeatherLocation.Builder setPostalCode(java.lang.String);
}
}
package cyanogenmod.weatherservice {
public final class ServiceRequest {
method public void complete(cyanogenmod.weatherservice.ServiceRequestResult);
method public void fail();
method public cyanogenmod.weather.RequestInfo getRequestInfo();
}
public final class ServiceRequestResult implements android.os.Parcelable {
method public int describeContents();
method public java.util.ArrayList<cyanogenmod.weather.WeatherLocation> getLocationLookupList();
method public cyanogenmod.weather.WeatherInfo getWeatherInfo();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<cyanogenmod.weatherservice.ServiceRequestResult> CREATOR;
}
public static class ServiceRequestResult.Builder {
ctor public ServiceRequestResult.Builder();
method public cyanogenmod.weatherservice.ServiceRequestResult build();
method public cyanogenmod.weatherservice.ServiceRequestResult.Builder setLocationLookupList(java.util.ArrayList<cyanogenmod.weather.WeatherLocation>);
method public cyanogenmod.weatherservice.ServiceRequestResult.Builder setWeatherInfo(cyanogenmod.weather.WeatherInfo);
}
public abstract class WeatherProviderService extends android.app.Service {
ctor public WeatherProviderService();
method protected final void attachBaseContext(android.content.Context);
method public final android.os.IBinder onBind(android.content.Intent);
method protected void onConnected();
method protected void onDisconnected();
method protected abstract void onRequestCancelled(cyanogenmod.weatherservice.ServiceRequest);
method protected abstract void onRequestSubmitted(cyanogenmod.weatherservice.ServiceRequest);
field public static final java.lang.String SERVICE_INTERFACE = "cyanogenmod.weatherservice.WeatherProviderService";
field public static final java.lang.String SERVICE_META_DATA = "cyanogenmod.weatherservice";
}
}