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