PageRenderTime 42ms CodeModel.GetById 18ms app.highlight 18ms RepoModel.GetById 2ms app.codeStats 0ms

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

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