aboutsummaryrefslogtreecommitdiffstats
path: root/cm
diff options
context:
space:
mode:
authorLuis Vidal <lvidal@cyngn.com>2016-03-21 11:56:40 -0700
committerLuis Vidal <lvidal@cyngn.com>2016-03-31 17:36:53 -0700
commit4195a1cf89d8c7bea024b6922bc2145ee05015f3 (patch)
tree9fc4228233537327cf54fe7c9c4820b57909e9d7 /cm
parent46821304e984bbf2ac3552ebb6f0a55440cf35e5 (diff)
downloadvendor_cmsdk-4195a1cf89d8c7bea024b6922bc2145ee05015f3.zip
vendor_cmsdk-4195a1cf89d8c7bea024b6922bc2145ee05015f3.tar.gz
vendor_cmsdk-4195a1cf89d8c7bea024b6922bc2145ee05015f3.tar.bz2
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
Diffstat (limited to 'cm')
-rw-r--r--cm/lib/main/java/org/cyanogenmod/platform/internal/CMWeatherManagerService.java545
-rw-r--r--cm/res/AndroidManifest.xml27
-rw-r--r--cm/res/res/values/config.xml1
-rw-r--r--cm/res/res/values/strings.xml10
4 files changed, 582 insertions, 1 deletions
diff --git a/cm/lib/main/java/org/cyanogenmod/platform/internal/CMWeatherManagerService.java b/cm/lib/main/java/org/cyanogenmod/platform/internal/CMWeatherManagerService.java
new file mode 100644
index 0000000..c5f78d6
--- /dev/null
+++ b/cm/lib/main/java/org/cyanogenmod/platform/internal/CMWeatherManagerService.java
@@ -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;
+ }
+ }
+}
diff --git a/cm/res/AndroidManifest.xml b/cm/res/AndroidManifest.xml
index 72c4c70..b403d1e 100644
--- a/cm/res/AndroidManifest.xml
+++ b/cm/res/AndroidManifest.xml
@@ -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"
diff --git a/cm/res/res/values/config.xml b/cm/res/res/values/config.xml
index 179ade2..3a5b7d2 100644
--- a/cm/res/res/values/config.xml
+++ b/cm/res/res/values/config.xml
@@ -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>
diff --git a/cm/res/res/values/strings.xml b/cm/res/res/values/strings.xml
index 00baff7..a86ca24 100644
--- a/cm/res/res/values/strings.xml
+++ b/cm/res/res/values/strings.xml
@@ -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>