aboutsummaryrefslogtreecommitdiffstats
path: root/sdk/src/java/cyanogenmod/weather/CMWeatherManager.java
blob: 8292b5801002e3f8f73e585829caad960c8de3e1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
/*
 * 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 cyanogenmod.providers.CMSettings;
import cyanogenmod.providers.WeatherContract;

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


    /**
     * The different request statuses
     */
    public static class RequestStatus {
        /**
         * Request Successfully completed
         */
        public static final int COMPLETED = 1;
        /**
         * An error occurred while trying to honor the request.
         */
        public static final int FAILED = -1;
        /**
         * The request can't be processed at this time
         */
        public static final int SUBMITTED_TOO_SOON = -2;
        /**
         * Another request in already in progress
         */
        public static final int ALREADY_IN_PROGRESS = -3;
        /**
         * No match found for the query
         */
        public static final int NO_MATCH_FOUND = -4;
    }

    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
     * @return An integer that identifies the request submitted to the weather service
     * Note that this method might return -1 if an error occurred while trying to submit
     * the request.
     */
    public int requestWeatherUpdate(@NonNull Location location,
            @NonNull WeatherUpdateRequestListener listener) {
        if (sWeatherManagerService == null) {
            return -1;
        }

        try {
            int tempUnit = CMSettings.Global.getInt(mContext.getContentResolver(),
                    CMSettings.Global.WEATHER_TEMPERATURE_UNIT,
                        WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT);

            RequestInfo info = new RequestInfo
                    .Builder(mRequestInfoListener)
                    .setLocation(location)
                    .setTemperatureUnit(tempUnit)
                    .build();
            if (listener != null) mWeatherUpdateRequestListeners.put(info, listener);
            sWeatherManagerService.updateWeather(info);
            return info.hashCode();
        } catch (RemoteException e) {
            return -1;
        }
    }

    /**
     * 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
     * @return An integer that identifies the request submitted to the weather service.
     * Note that this method might return -1 if an error occurred while trying to submit
     * the request.
     */
    public int requestWeatherUpdate(@NonNull WeatherLocation weatherLocation,
            @NonNull WeatherUpdateRequestListener listener) {
        if (sWeatherManagerService == null) {
            return -1;
        }

        try {
            int tempUnit = CMSettings.Global.getInt(mContext.getContentResolver(),
                    CMSettings.Global.WEATHER_TEMPERATURE_UNIT,
                        WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT);

            RequestInfo info = new RequestInfo
                    .Builder(mRequestInfoListener)
                    .setWeatherLocation(weatherLocation)
                    .setTemperatureUnit(tempUnit)
                    .build();
            if (listener != null) mWeatherUpdateRequestListeners.put(info, listener);
            sWeatherManagerService.updateWeather(info);
            return info.hashCode();
        } catch (RemoteException e) {
            return -1;
        }
    }

    /**
     * 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
     * @return An integer that identifies the request submitted to the weather service.
     * Note that this method might return -1 if an error occurred while trying to submit
     * the request.
     */
    public int lookupCity(@NonNull String city, @NonNull LookupCityRequestListener listener) {
        if (sWeatherManagerService == null) {
            return -1;
        }
        try {
            RequestInfo info = new RequestInfo
                    .Builder(mRequestInfoListener)
                    .setCityName(city)
                    .build();
            if (listener != null) mLookupNameRequestListeners.put(info, listener);
            sWeatherManagerService.lookupCity(info);
            return info.hashCode();
        } catch (RemoteException e) {
            return -1;
        }
    }

    /**
     * Cancels a request that was previously submitted to the weather service.
     * @param requestId The ID that you received when the request was submitted
     */
    public void cancelRequest(int requestId) {
        if (sWeatherManagerService == null) {
            return;
        }

        try {
            sWeatherManagerService.cancelRequest(requestId);
        }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 status,
                final WeatherInfo weatherInfo) {
            final WeatherUpdateRequestListener listener
                    = mWeatherUpdateRequestListeners.remove(requestInfo);
            if (listener != null) {
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        listener.onWeatherRequestCompleted(status, weatherInfo);
                    }
                });
            }
        }

        @Override
        public void onLookupCityRequestCompleted(RequestInfo requestInfo, final int status,
            final List<WeatherLocation> weatherLocations) {

            final LookupCityRequestListener listener
                    = mLookupNameRequestListeners.remove(requestInfo);
            if (listener != null) {
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        listener.onLookupCityRequestCompleted(status, weatherLocations);
                    }
                });
            }
        }
    };

    /**
     * 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 status See {@link RequestStatus}
         *
         * @param weatherInfo A fully populated {@link WeatherInfo} if state is
         *                    {@link RequestStatus#COMPLETED}, null otherwise
         */
        void onWeatherRequestCompleted(int status, 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.
         *
         * @param status See {@link RequestStatus}
         *
         * @param locations A list of {@link WeatherLocation} if the status is
         * {@link RequestStatus#COMPLETED}, null otherwise
         */
        void onLookupCityRequestCompleted(int status, List<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);
    }
}