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