PageRenderTime 60ms CodeModel.GetById 39ms app.highlight 17ms RepoModel.GetById 1ms app.codeStats 0ms

/Sky/Sky/src/org/jsharkey/sky/ConfigureActivity.java

http://android-sky.googlecode.com/
Java | 382 lines | 243 code | 55 blank | 84 comment | 40 complexity | e429bc2f40c224ad79ae675b5aeb687f MD5 | raw file
  1/*
  2 * Copyright (C) 2009 Jeff Sharkey, http://jsharkey.org/
  3 *
  4 * Licensed under the Apache License, Version 2.0 (the "License");
  5 * you may not use this file except in compliance with the License.
  6 * You may obtain a copy of the License at
  7 *
  8 *      http://www.apache.org/licenses/LICENSE-2.0
  9 *
 10 * Unless required by applicable law or agreed to in writing, software
 11 * distributed under the License is distributed on an "AS IS" BASIS,
 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13 * See the License for the specific language governing permissions and
 14 * limitations under the License.
 15 */
 16
 17package org.jsharkey.sky;
 18
 19import java.io.IOException;
 20import java.util.List;
 21
 22import org.jsharkey.sky.ForecastProvider.AppWidgets;
 23import org.jsharkey.sky.ForecastProvider.AppWidgetsColumns;
 24
 25import android.app.Activity;
 26import android.app.SearchManager;
 27import android.appwidget.AppWidgetManager;
 28import android.content.ContentResolver;
 29import android.content.ContentValues;
 30import android.content.Context;
 31import android.content.Intent;
 32import android.location.Address;
 33import android.location.Criteria;
 34import android.location.Geocoder;
 35import android.location.Location;
 36import android.location.LocationManager;
 37import android.net.Uri;
 38import android.os.AsyncTask;
 39import android.os.Bundle;
 40import android.provider.BaseColumns;
 41import android.text.TextUtils;
 42import android.util.Log;
 43import android.view.View;
 44import android.view.Window;
 45import android.widget.Button;
 46import android.widget.EditText;
 47import android.widget.RadioButton;
 48import android.widget.RadioGroup;
 49
 50/**
 51 * Activity to configure forecast widgets. Usually launched automatically by an
 52 * {@link AppWidgetHost} after the {@link AppWidgetManager#EXTRA_APPWIDGET_ID}
 53 * has been bound to a widget.
 54 */
 55public class ConfigureActivity extends Activity
 56        implements View.OnClickListener, RadioGroup.OnCheckedChangeListener {
 57    public static final String TAG = "ConfigureActivity";
 58
 59    private Button mMap;
 60    private Button mSave;
 61
 62    private EditText mTitle;
 63    private double mLat = Double.NaN;
 64    private double mLon = Double.NaN;
 65    private int mUnits = AppWidgetsColumns.UNITS_FAHRENHEIT;
 66
 67    /**
 68     * Default zoom level when showing map to verify location.
 69     */
 70    private static final int ZOOM_LEVEL = 10;
 71
 72    /**
 73     * Last found location fix, used when user selects "My current location."
 74     */
 75    private Location mLastFix;
 76
 77    private int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
 78
 79    /**
 80     * Spawn a reverse geocoding operation to find names for the given
 81     * {@link Location}. Will update GUI when finished.
 82     */
 83    private void startGeocode(Location location) {
 84        new GeocoderTask().execute(new GeocodeQuery(location));
 85    }
 86
 87    /**
 88     * Spawn a forward geocoding operation to find the location of a given name.
 89     * Will update GUI when finished.
 90     */
 91    private void startGeocode(String query) {
 92        new GeocoderTask().execute(new GeocodeQuery(query));
 93    }
 94
 95    /**
 96     * Background task to perform a geocoding operation. Will disable GUI
 97     * actions while running in the background, and then update GUI with results
 98     * when found.
 99     * <p>
100     * If no reverse geocoding results found, will still return original
101     * coordinates but will leave suggested title empty.
102     */
103    private class GeocoderTask extends AsyncTask<GeocodeQuery, Void, GeocodeQuery> {
104        private Geocoder mGeocoder;
105
106        private GeocoderTask() {
107            mGeocoder = new Geocoder(ConfigureActivity.this);
108        }
109
110        protected void onPreExecute() {
111            // Show progress spinner and disable buttons
112            setProgressBarIndeterminateVisibility(true);
113            setActionEnabled(false);
114        }
115
116        protected GeocodeQuery doInBackground(GeocodeQuery... args) {
117            GeocodeQuery query = args[0];
118            GeocodeQuery result = null;
119
120            try {
121                if (!TextUtils.isEmpty(query.name)) {
122                    // Forward geocode using query
123                    List<Address> results = mGeocoder.getFromLocationName(query.name, 1);
124                    if (results.size() > 0) {
125                        result = new GeocodeQuery(results.get(0));
126                    }
127                } else if (!Double.isNaN(query.lat) && !Double.isNaN(query.lon)) {
128                    // Reverse geocode using location
129                    List<Address> results = mGeocoder.getFromLocation(query.lat, query.lon, 1);
130                    if (results.size() > 0) {
131                        result = new GeocodeQuery(results.get(0));
132                        result.lat = query.lat;
133                        result.lon = query.lon;
134                    } else {
135                        result = query;
136                    }
137                }
138            } catch (IOException e) {
139                Log.e(TAG, "Problem using geocoder", e);
140            }
141
142            return result;
143        }
144
145        protected void onPostExecute(GeocodeQuery found) {
146            setProgressBarIndeterminateVisibility(false);
147
148            // Update GUI with resolved string
149            if (found == null) {
150                mLat = Double.NaN;
151                mLon = Double.NaN;
152                setActionEnabled(false);
153            } else {
154                mTitle.setText(found.name);
155                mLat = found.lat;
156                mLon = found.lon;
157                setActionEnabled(true);
158            }
159        }
160    }
161
162    /**
163     * Temporary object to hold geocoding query and/or results.
164     */
165    private static class GeocodeQuery {
166        String name = null;
167
168        double lat = Double.NaN;
169
170        double lon = Double.NaN;
171
172        public GeocodeQuery(String query) {
173            name = query;
174        }
175
176        public GeocodeQuery(Location location) {
177            lat = location.getLatitude();
178            lon = location.getLongitude();
179        }
180
181        /**
182         * Summarize details of the given {@link Address}, walking down a
183         * prioritized list of names until valid text is found to describe it.
184         */
185        public GeocodeQuery(Address address) {
186            name = address.getLocality();
187            if (name == null) {
188                name = address.getFeatureName();
189            }
190            if (name == null) {
191                name = address.getAdminArea();
192            }
193            if (name == null) {
194                name = address.getPostalCode();
195            }
196            if (name == null) {
197                name = address.getCountryName();
198            }
199
200            // Fill in coordinates, if given
201            if (address.hasLatitude() && address.hasLongitude()) {
202                lat = address.getLatitude();
203                lon = address.getLongitude();
204            }
205        }
206    }
207
208    /**
209     * Enable or disable any GUI actions, including text fields and buttons.
210     */
211    protected void setActionEnabled(boolean enabled) {
212        mTitle.setEnabled(enabled);
213        mMap.setEnabled(enabled);
214        mSave.setEnabled(enabled);
215    }
216
217    @Override
218    public void onCreate(Bundle savedInstanceState) {
219        super.onCreate(savedInstanceState);
220
221        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
222
223        setContentView(R.layout.configure);
224
225        mTitle = (EditText)findViewById(R.id.conf_title);
226
227        ((RadioGroup)findViewById(R.id.conf_units)).setOnCheckedChangeListener(this);
228
229        ((RadioButton)findViewById(R.id.conf_current)).setOnClickListener(this);
230        ((RadioButton)findViewById(R.id.conf_manual)).setOnClickListener(this);
231
232        mMap = (Button)findViewById(R.id.conf_map);
233        mSave = (Button)findViewById(R.id.conf_save);
234
235        mMap.setOnClickListener(this);
236        mSave.setOnClickListener(this);
237
238        // Read the appWidgetId to configure from the incoming intent
239        mAppWidgetId = getIntent().getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
240        setConfigureResult(Activity.RESULT_CANCELED);
241        if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
242            finish();
243            return;
244        }
245
246        // TODO: handle editing an existing widget by reading values
247
248        // If restoring, read location and units from bundle
249        if (savedInstanceState != null) {
250            mLat = savedInstanceState.getDouble(AppWidgetsColumns.LAT);
251            mLon = savedInstanceState.getDouble(AppWidgetsColumns.LON);
252            mUnits = savedInstanceState.getInt(AppWidgetsColumns.UNITS);
253            if (mUnits == AppWidgetsColumns.UNITS_FAHRENHEIT) {
254                ((RadioButton)findViewById(R.id.conf_units_f)).setSelected(true);
255            } else if (mUnits == AppWidgetsColumns.UNITS_CELSIUS) {
256                ((RadioButton)findViewById(R.id.conf_units_c)).setSelected(true);
257            }
258        }
259
260        // Start listener to find current location
261        LocationManager locationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
262        String provider = locationManager.getBestProvider(new Criteria(), true);
263
264        if (provider != null) {
265            // Fire off geocoding request for last fix, but only if not
266            // restoring
267            mLastFix = locationManager.getLastKnownLocation(provider);
268            if (mLastFix != null && savedInstanceState == null) {
269                startGeocode(mLastFix);
270            }
271        }
272
273        if (mLastFix == null) {
274            // No enabled providers found, so disable option
275            RadioButton radioCurrent = (RadioButton)findViewById(R.id.conf_current);
276            radioCurrent.setVisibility(View.GONE);
277
278            mTitle.setText(R.string.conf_nofix);
279        }
280    }
281
282    /**
283     * Handle any new intents wrapping around from {@link SearchManager}.
284     */
285    @Override
286    public void onNewIntent(Intent intent) {
287        final String action = intent.getAction();
288        if (Intent.ACTION_SEARCH.equals(action)) {
289            // Fire off geocoding request for given query
290            String query = intent.getStringExtra(SearchManager.QUERY);
291            startGeocode(query);
292        }
293    }
294
295    /**
296     * {@inheritDoc}
297     */
298    @Override
299    protected void onSaveInstanceState(Bundle outState) {
300        super.onSaveInstanceState(outState);
301
302        outState.putDouble(AppWidgetsColumns.LAT, mLat);
303        outState.putDouble(AppWidgetsColumns.LON, mLon);
304        outState.putInt(AppWidgetsColumns.UNITS, mUnits);
305    }
306
307    public void onClick(View v) {
308        switch (v.getId()) {
309            case R.id.conf_current: {
310                // Picked current location, start geocode to find location name
311                startGeocode(mLastFix);
312                break;
313            }
314            case R.id.conf_manual: {
315                // Picked manual search, so trigger search dialog
316                onSearchRequested();
317                break;
318            }
319            case R.id.conf_map: {
320                // Picked verify on map, so launch mapping intent
321                Uri mapUri = Uri.parse(String.format("geo:%f,%f?z=%d", mLat, mLon, ZOOM_LEVEL));
322
323                Intent mapIntent = new Intent(Intent.ACTION_VIEW);
324                mapIntent.setData(mapUri);
325
326                startActivity(mapIntent);
327                break;
328            }
329            case R.id.conf_save: {
330                // Picked save, so write values to backend
331                ContentValues values = new ContentValues();
332                String title = mTitle.getText().toString();
333                values.put(BaseColumns._ID, mAppWidgetId);
334                values.put(AppWidgetsColumns.TITLE, title);
335                values.put(AppWidgetsColumns.LAT, mLat);
336                values.put(AppWidgetsColumns.LON, mLon);
337                values.put(AppWidgetsColumns.UNITS, mUnits);
338                values.put(AppWidgetsColumns.LAST_UPDATED, -1);
339                values.put(AppWidgetsColumns.CONFIGURED, AppWidgetsColumns.CONFIGURED_TRUE);
340
341                // TODO: update instead of insert if editing an existing widget
342                ContentResolver resolver = getContentResolver();
343                resolver.insert(AppWidgets.CONTENT_URI, values);
344
345                // Trigger pushing a widget update to surface
346                UpdateService.requestUpdate(new int[] {
347                    mAppWidgetId
348                });
349                startService(new Intent(this, UpdateService.class));
350
351                setConfigureResult(Activity.RESULT_OK);
352                finish();
353
354                break;
355            }
356        }
357    }
358
359    /**
360     * Change {@link #mUnits} when requested by user.
361     */
362    public void onCheckedChanged(RadioGroup group, int checkedId) {
363        switch (checkedId) {
364            case R.id.conf_units_f:
365                mUnits = AppWidgetsColumns.UNITS_FAHRENHEIT;
366                break;
367            case R.id.conf_units_c:
368                mUnits = AppWidgetsColumns.UNITS_CELSIUS;
369                break;
370        }
371    }
372
373    /**
374     * Convenience method to always include {@link #mAppWidgetId} when setting
375     * the result {@link Intent}.
376     */
377    public void setConfigureResult(int resultCode) {
378        final Intent data = new Intent();
379        data.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
380        setResult(resultCode, data);
381    }
382}