├── .gitignore ├── LICENSE.md ├── Readme.txt ├── android-protips-location-clean.iml ├── app ├── app.iml ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── radioactiveyak │ │ └── location_best_practices │ │ ├── PlacesApplication.java │ │ ├── PlacesBackupAgent.java │ │ ├── PlacesConstants.java │ │ ├── UI │ │ ├── PlaceActivity.java │ │ └── fragments │ │ │ ├── CheckinFragment.java │ │ │ ├── PlaceDetailFragment.java │ │ │ └── PlaceListFragment.java │ │ ├── content_providers │ │ ├── PlaceDetailsContentProvider.java │ │ ├── PlacesContentProvider.java │ │ └── QueuedCheckinsContentProvider.java │ │ ├── receivers │ │ ├── BootReceiver.java │ │ ├── ConnectivityChangedReceiver.java │ │ ├── LocationChangedReceiver.java │ │ ├── NewCheckinReceiver.java │ │ ├── PassiveLocationChangedReceiver.java │ │ └── PowerStateChangedReceiver.java │ │ ├── services │ │ ├── CheckinNotificationService.java │ │ ├── EclairPlacesUpdateService.java │ │ ├── PlaceCheckinService.java │ │ ├── PlaceDetailsUpdateService.java │ │ └── PlacesUpdateService.java │ │ └── utils │ │ ├── FroyoLocationUpdateRequester.java │ │ ├── FroyoSharedPreferenceSaver.java │ │ ├── GingerbreadLastLocationFinder.java │ │ ├── GingerbreadLocationUpdateRequester.java │ │ ├── GingerbreadSharedPreferenceSaver.java │ │ ├── HoneycombStrictMode.java │ │ ├── LegacyLastLocationFinder.java │ │ ├── LegacyLocationUpdateRequester.java │ │ ├── LegacySharedPreferenceSaver.java │ │ ├── LegacyStrictMode.java │ │ ├── PlatformSpecificImplementationFactory.java │ │ └── base │ │ ├── ILastLocationFinder.java │ │ ├── IStrictMode.java │ │ ├── LocationUpdateRequester.java │ │ └── SharedPreferenceSaver.java │ └── res │ ├── anim-v11 │ ├── slide_in_left.xml │ └── slide_out_right.xml │ ├── drawable-hdpi │ ├── icon.png │ ├── powered_by_google_on_black.png │ └── powered_by_google_on_white.png │ ├── drawable-ldpi │ ├── icon.png │ ├── powered_by_google_on_black.png │ └── powered_by_google_on_white.png │ ├── drawable-mdpi │ ├── icon.png │ ├── powered_by_google_on_black.png │ └── powered_by_google_on_white.png │ ├── layout-port │ └── main.xml │ ├── layout-xlarge-port │ └── main.xml │ ├── layout │ ├── checkin_box.xml │ ├── main.xml │ └── place_detail.xml │ ├── menu │ └── main_menu.xml │ ├── values-v8 │ └── booleans.xml │ └── values │ ├── booleans.xml │ └── strings.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ └── gradle-wrapper.properties ├── local.properties └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Android template 3 | # Built application files 4 | *.apk 5 | *.ap_ 6 | 7 | # Files for the ART/Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | 18 | # Gradle files 19 | .gradle/ 20 | build/ 21 | 22 | # Local configuration file (sdk path, etc) 23 | local.properties 24 | 25 | # Proguard folder generated by Eclipse 26 | proguard/ 27 | 28 | # Log Files 29 | *.log 30 | 31 | # Android Studio Navigation editor temp files 32 | .navigation/ 33 | 34 | # Android Studio captures folder 35 | captures/ 36 | 37 | # Intellij 38 | *.iml 39 | .idea/workspace.xml 40 | 41 | # Keystore files 42 | *.jks 43 | 44 | .gitignore 45 | .idea/ 46 | app/build/ 47 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Readme.txt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. 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 | 17 | This project is using outdated Android APIs and is in the process of being updated. 18 | 19 | ** QUICK START GUIDE ** 20 | 21 | 1) Make sure you've downloaded and installed the Android Compatibility Library: 22 | http://developer.android.com/sdk/compatibility-library.html 23 | 2) Obtain a Google Places API key from: 24 | http://code.google.com/apis/maps/documentation/places/#Limits 25 | And assign it to the MY_API_KEY static constant in PlacesConstants.java 26 | 3) Obtain a Backup Manager API key from: 27 | http://code.google.com/android/backup/signup.html 28 | And assign it to the backup_manager_key value in res/values/strings.xml 29 | 30 | ** About this Project ** 31 | 32 | Project Home Page: 33 | https://github.com/retomeier/android-protips-location/ 34 | 35 | Maintained by: 36 | Reto Meier 37 | http://www.twitter.com/retomeier 38 | http://blog.radioactiveyak.com 39 | 40 | Uses the Google Places API to mimic the core functionality of apps that use 41 | your current location to provide a list of nearby points of interest, allow you 42 | to drill down into the details, and then checkin / rate/ review. 43 | 44 | It is an open-source reference implementation of a location-based app that 45 | incorporates several tips, tricks, best practices, and cheats for creating 46 | high quality apps. 47 | 48 | Particular attention has been paid to reducing the time between opening an app 49 | and seeing an up-to-date list of nearby venues and providing a reasonable level 50 | of offline support. 51 | 52 | The code implements all of the location best-practices for reducing latency and 53 | battery consumption as detailed in my Google I/O 2011 session, 54 | Android Protips: Advanced Topics for Expert Android Developers: 55 | http://www.google.com/events/io/2011/sessions/android-protips-advanced-topics-for-expert-android-app-developers.html 56 | -------------------------------------------------------------------------------- /android-protips-location-clean.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 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 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | android { 3 | compileSdkVersion 23 4 | buildToolsVersion "24.0.0" 5 | 6 | useLibrary 'org.apache.http.legacy' 7 | 8 | defaultConfig { 9 | applicationId "com.radioactiveyak.location_best_practices" 10 | minSdkVersion 4 11 | targetSdkVersion 11 12 | } 13 | 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile 'com.android.support:support-v4:24.0.0' 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 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 | -------------------------------------------------------------------------------- /app/src/main/java/com/radioactiveyak/location_best_practices/PlacesApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. 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 | 17 | package com.radioactiveyak.location_best_practices; 18 | 19 | import android.app.Application; 20 | 21 | import com.radioactiveyak.location_best_practices.utils.PlatformSpecificImplementationFactory; 22 | import com.radioactiveyak.location_best_practices.utils.base.IStrictMode; 23 | 24 | public class PlacesApplication extends Application { 25 | 26 | // TODO Please Insert your Google Places API into MY_API_KEY in PlacesConstants.java 27 | // TODO Insert your Backup Manager API into res/values/strings.xml : backup_manager_key 28 | 29 | @Override 30 | public final void onCreate() { 31 | super.onCreate(); 32 | 33 | if (PlacesConstants.DEVELOPER_MODE) { 34 | IStrictMode strictMode = PlatformSpecificImplementationFactory.getStrictMode(); 35 | if (strictMode != null) 36 | strictMode.enableStrictMode(); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/radioactiveyak/location_best_practices/PlacesBackupAgent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. 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 | 17 | package com.radioactiveyak.location_best_practices; 18 | 19 | import android.app.backup.BackupAgentHelper; 20 | import android.app.backup.SharedPreferencesBackupHelper; 21 | 22 | /** 23 | * A class that specifies which of the shared preferences you want to backup 24 | * to the Google Backup Service. 25 | */ 26 | public class PlacesBackupAgent extends BackupAgentHelper { 27 | @Override 28 | public void onCreate() { 29 | SharedPreferencesBackupHelper helper = new SharedPreferencesBackupHelper(this, PlacesConstants.SHARED_PREFERENCE_FILE); 30 | addHelper(PlacesConstants.SP_KEY_FOLLOW_LOCATION_CHANGES, helper); 31 | // TODO Add additional helpers for each of the preferences you want to backup. 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/radioactiveyak/location_best_practices/PlacesConstants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. 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 | 17 | package com.radioactiveyak.location_best_practices; 18 | 19 | import android.app.AlarmManager; 20 | 21 | public class PlacesConstants { 22 | 23 | /** 24 | * TODO **P1** You must put your Google Places API key here. 25 | * You can get your API key from: 26 | * {@link http://code.google.com/apis/maps/documentation/places/#Limits} 27 | */ 28 | private static String MY_API_KEY = ""; 29 | 30 | public static String PLACES_API_KEY = "&key=" + MY_API_KEY; 31 | 32 | /** 33 | * You'll need to modify these values to suit your own app. 34 | */ 35 | // TODO Turn off when deploying your app. 36 | public static boolean DEVELOPER_MODE = true; 37 | 38 | // TODO Point these at your data sources. 39 | public static String PLACES_LIST_BASE_URI = "https://maps.googleapis.com/maps/api/place/search/xml?sensor=true"; 40 | public static String PLACES_DETAIL_BASE_URI = "https://maps.googleapis.com/maps/api/place/details/xml?sensor=true&reference="; 41 | public static String PLACES_CHECKIN_URI = "https://maps.googleapis.com/maps/api/place/check-in/xml?sensor=true"; 42 | public static String PLACES_CHECKIN_OK_STATUS = "OK"; 43 | 44 | /** 45 | * These values control the user experience of your app. You should 46 | * modify them to provide the best experience based on how your 47 | * app will actually be used. 48 | * TODO Update these values for your app. 49 | */ 50 | // The default search radius when searching for places nearby. 51 | public static int DEFAULT_RADIUS = 150; 52 | // The maximum distance the user should travel between location updates. 53 | public static int MAX_DISTANCE = DEFAULT_RADIUS/2; 54 | // The maximum time that should pass before the user gets a location update. 55 | public static long MAX_TIME = AlarmManager.INTERVAL_FIFTEEN_MINUTES; 56 | 57 | // You will generally want passive location updates to occur less frequently 58 | // than active updates. You need to balance location freshness with battery life. 59 | // The location update distance for passive updates. 60 | public static int PASSIVE_MAX_DISTANCE = MAX_DISTANCE; 61 | // The location update time for passive updates 62 | public static long PASSIVE_MAX_TIME = MAX_TIME; 63 | // Use the GPS (fine location provider) when the Activity is visible? 64 | public static boolean USE_GPS_WHEN_ACTIVITY_VISIBLE = true; 65 | //When the user exits via the back button, do you want to disable 66 | // passive background updates. 67 | public static boolean DISABLE_PASSIVE_LOCATION_WHEN_USER_EXIT = false; 68 | 69 | // Maximum latency before you force a cached detail page to be updated. 70 | public static long MAX_DETAILS_UPDATE_LATENCY = AlarmManager.INTERVAL_DAY; 71 | 72 | // Prefetching place details is useful but potentially expensive. The following 73 | // values lets you disable prefetching when on mobile data or low battery conditions. 74 | // Only prefetch on WIFI? 75 | public static boolean PREFETCH_ON_WIFI_ONLY = false; 76 | // Disable prefetching when battery is low? 77 | public static boolean DISABLE_PREFETCH_ON_LOW_BATTERY = true; 78 | 79 | // How long to wait before retrying failed checkins. 80 | public static long CHECKIN_RETRY_INTERVAL = AlarmManager.INTERVAL_FIFTEEN_MINUTES; 81 | 82 | // The maximum number of locations to prefetch for each update. 83 | public static int PREFETCH_LIMIT = 5; 84 | 85 | 86 | /** 87 | * These values are constants used for intents, exteas, and shared preferences. 88 | * You shouldn't need to modify them. 89 | */ 90 | public static String SHARED_PREFERENCE_FILE = "SHARED_PREFERENCE_FILE"; 91 | public static String SP_KEY_FOLLOW_LOCATION_CHANGES = "SP_KEY_FOLLOW_LOCATION_CHANGES"; 92 | public static String SP_KEY_LAST_LIST_UPDATE_TIME = "SP_KEY_LAST_LIST_UPDATE_TIME"; 93 | public static String SP_KEY_LAST_LIST_UPDATE_LAT = "SP_KEY_LAST_LIST_UPDATE_LAT"; 94 | public static String SP_KEY_LAST_LIST_UPDATE_LNG = "SP_KEY_LAST_LIST_UPDATE_LNG"; 95 | public static String SP_KEY_LAST_CHECKIN_ID = "SP_KEY_LAST_CHECKIN_ID"; 96 | public static String SP_KEY_LAST_CHECKIN_TIMESTAMP = "SP_KEY_LAST_CHECKIN_TIMESTAMP"; 97 | public static String SP_KEY_RUN_ONCE = "SP_KEY_RUN_ONCE"; 98 | 99 | public static String EXTRA_KEY_REFERENCE = "reference"; 100 | public static String EXTRA_KEY_ID = "id"; 101 | public static String EXTRA_KEY_LOCATION = "location"; 102 | public static String EXTRA_KEY_RADIUS = "radius"; 103 | public static String EXTRA_KEY_TIME_STAMP = "time_stamp"; 104 | public static String EXTRA_KEY_FORCEREFRESH = "force_refresh"; 105 | public static String EXTRA_KEY_IN_BACKGROUND = "EXTRA_KEY_IN_BACKGROUND"; 106 | 107 | public static String ARGUMENTS_KEY_REFERENCE = "reference"; 108 | public static String ARGUMENTS_KEY_ID = "id"; 109 | 110 | public static String NEW_CHECKIN_ACTION = "com.radioactiveyak.places.NEW_CHECKIN_ACTION"; 111 | public static String RETRY_QUEUED_CHECKINS_ACTION = "com.radioactiveyak.places.retry_queued_checkins"; 112 | public static String ACTIVE_LOCATION_UPDATE_PROVIDER_DISABLED = "com.radioactiveyak.places.active_location_update_provider_disabled"; 113 | 114 | public static boolean SUPPORTS_GINGERBREAD = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.GINGERBREAD; 115 | public static boolean SUPPORTS_HONEYCOMB = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB; 116 | public static boolean SUPPORTS_FROYO = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.FROYO; 117 | public static boolean SUPPORTS_ECLAIR = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.ECLAIR; 118 | 119 | public static String CONSTRUCTED_LOCATION_PROVIDER = "CONSTRUCTED_LOCATION_PROVIDER"; 120 | 121 | public static int CHECKIN_NOTIFICATION = 0; 122 | } 123 | -------------------------------------------------------------------------------- /app/src/main/java/com/radioactiveyak/location_best_practices/UI/fragments/CheckinFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. 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 | 17 | package com.radioactiveyak.location_best_practices.UI.fragments; 18 | 19 | import android.app.Activity; 20 | import android.database.Cursor; 21 | import android.os.Bundle; 22 | import android.os.Handler; 23 | import android.support.v4.app.Fragment; 24 | import android.support.v4.app.LoaderManager.LoaderCallbacks; 25 | import android.support.v4.content.CursorLoader; 26 | import android.support.v4.content.Loader; 27 | import android.view.LayoutInflater; 28 | import android.view.View; 29 | import android.view.ViewGroup; 30 | import android.widget.TextView; 31 | 32 | import com.radioactiveyak.location_best_practices.PlacesConstants; 33 | import com.radioactiveyak.location_best_practices.R; 34 | import com.radioactiveyak.location_best_practices.content_providers.PlaceDetailsContentProvider; 35 | 36 | // TODO Update this UI with details related to your checkin. That might include point / rewards 37 | // TODO Or incentives to review or rate the establishment. 38 | 39 | /** 40 | * UI Fragment to display which venue we are currently checked in to. 41 | */ 42 | public class CheckinFragment extends Fragment implements LoaderCallbacks { 43 | 44 | /** 45 | * Factory that return a new instance of the {@link CheckinFragment} 46 | * populated with the details corresponding to the passed in venue 47 | * identifier. 48 | * @param id Identifier of the venue checked in to 49 | * @return A new CheckinFragment 50 | */ 51 | public static CheckinFragment newInstance(String id) { 52 | CheckinFragment f = new CheckinFragment(); 53 | 54 | // Supply id input as an argument. 55 | Bundle args = new Bundle(); 56 | args.putString(PlacesConstants.ARGUMENTS_KEY_ID, id); 57 | f.setArguments(args); 58 | 59 | return f; 60 | } 61 | 62 | protected String placeId = null; 63 | protected Handler handler = new Handler(); 64 | protected Activity activity; 65 | protected TextView checkPlaceNameTextView; 66 | 67 | public CheckinFragment() { 68 | super(); 69 | } 70 | 71 | /** 72 | * Change the venue checked in to. 73 | * @param id Identifier of the venue checked in to 74 | */ 75 | public void setPlaceId(String id) { 76 | // Update the place ID and restart the loader to update the UI. 77 | placeId = id; 78 | if (placeId != null) 79 | getLoaderManager().restartLoader(0, null, this); 80 | } 81 | 82 | public void onActivityCreated(Bundle savedInstanceState) { 83 | super.onActivityCreated(savedInstanceState); 84 | activity = getActivity(); 85 | 86 | // Populate the UI by initiating the loader to retrieve the 87 | // details of the venue from the underlying Place Content Provider. 88 | if (placeId != null) 89 | getLoaderManager().initLoader(0, null, this); 90 | } 91 | 92 | @Override 93 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 94 | View view = inflater.inflate(R.layout.checkin_box, container, false); 95 | checkPlaceNameTextView = (TextView)view.findViewById(R.id.checkin_place_name); 96 | 97 | if (getArguments() != null) 98 | placeId = getArguments().getString(PlacesConstants.ARGUMENTS_KEY_ID); 99 | 100 | return view; 101 | } 102 | 103 | @Override 104 | public void onResume() { 105 | super.onResume(); 106 | } 107 | 108 | /** 109 | * {@inheritDoc} 110 | * This loader queries the {@link PlaceContentProvider} to extract the name of 111 | * the venue that has been checked in to. 112 | */ 113 | public Loader onCreateLoader(int id, Bundle args) { 114 | String[] projection = new String[] {PlaceDetailsContentProvider.KEY_NAME}; 115 | 116 | String selection = PlaceDetailsContentProvider.KEY_ID + "='" + placeId + "'"; 117 | 118 | return new CursorLoader(activity, PlaceDetailsContentProvider.CONTENT_URI, 119 | projection, selection, null, null); 120 | } 121 | 122 | /** 123 | * {@inheritDoc} 124 | * When this load has finished, update the UI with the name of the venue. 125 | */ 126 | public void onLoadFinished(Loader loader, Cursor data) { 127 | if (data.moveToFirst()) { 128 | final String venueName = data.getString(data.getColumnIndex(PlaceDetailsContentProvider.KEY_NAME)); 129 | handler.post(new Runnable () { 130 | public void run() { 131 | checkPlaceNameTextView.setText(venueName); 132 | } 133 | }); 134 | } 135 | } 136 | 137 | /** 138 | * {@inheritDoc} 139 | */ 140 | public void onLoaderReset(Loader loader) { 141 | handler.post(new Runnable () { 142 | public void run() { 143 | checkPlaceNameTextView.setText(""); 144 | } 145 | }); 146 | } 147 | } -------------------------------------------------------------------------------- /app/src/main/java/com/radioactiveyak/location_best_practices/UI/fragments/PlaceDetailFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. 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 | 17 | package com.radioactiveyak.location_best_practices.UI.fragments; 18 | 19 | // TODO Create a richer UI to display places Details. This should include images, 20 | // TODO ratings, reviews, other people checked in here, etc. 21 | 22 | import android.app.Activity; 23 | import android.content.Context; 24 | import android.content.Intent; 25 | import android.content.SharedPreferences; 26 | import android.database.Cursor; 27 | import android.os.Bundle; 28 | import android.os.Handler; 29 | import android.support.v4.app.Fragment; 30 | import android.support.v4.app.LoaderManager.LoaderCallbacks; 31 | import android.support.v4.content.CursorLoader; 32 | import android.support.v4.content.Loader; 33 | import android.util.Log; 34 | import android.view.LayoutInflater; 35 | import android.view.View; 36 | import android.view.ViewGroup; 37 | import android.view.View.OnClickListener; 38 | import android.widget.Button; 39 | import android.widget.TextView; 40 | 41 | import com.radioactiveyak.location_best_practices.PlacesConstants; 42 | import com.radioactiveyak.location_best_practices.R; 43 | import com.radioactiveyak.location_best_practices.content_providers.PlaceDetailsContentProvider; 44 | import com.radioactiveyak.location_best_practices.services.PlaceCheckinService; 45 | import com.radioactiveyak.location_best_practices.services.PlaceDetailsUpdateService; 46 | 47 | /** 48 | * UI Fragment to display the details for a selected venue. 49 | */ 50 | public class PlaceDetailFragment extends Fragment implements LoaderCallbacks { 51 | 52 | /** 53 | * Factory that produces a new {@link PlaceDetailFragment} populated with 54 | * details corresponding to the reference / ID of the venue passed in. 55 | * @param reference Venue Reference 56 | * @param id Venue Unique ID 57 | * @return {@link PlaceDetailFragment} 58 | */ 59 | public static PlaceDetailFragment newInstance(String reference, String id) { 60 | PlaceDetailFragment f = new PlaceDetailFragment(); 61 | 62 | // Supply reference and ID inputs as arguments. 63 | Bundle args = new Bundle(); 64 | args.putString(PlacesConstants.ARGUMENTS_KEY_REFERENCE, reference); 65 | args.putString(PlacesConstants.ARGUMENTS_KEY_ID, id); 66 | f.setArguments(args); 67 | 68 | return f; 69 | } 70 | 71 | protected static String TAG = "PlaceDetailFragment"; 72 | 73 | protected String placeReference = null; 74 | protected String placeId = null; 75 | 76 | protected Handler handler = new Handler(); 77 | protected Activity activity; 78 | protected TextView nameTextView; 79 | protected TextView phoneTextView; 80 | protected TextView addressTextView; 81 | protected TextView ratingTextView; 82 | protected TextView urlTextView; 83 | protected Button checkinButton; 84 | protected TextView checkedInText; 85 | 86 | public PlaceDetailFragment() { 87 | super(); 88 | } 89 | 90 | public void onActivityCreated(Bundle savedInstanceState) { 91 | super.onActivityCreated(savedInstanceState); 92 | activity = getActivity(); 93 | 94 | // Query the PlacesDetails Content Provider using a Loader to find 95 | // the details for the selected venue. 96 | if (placeId != null) 97 | getLoaderManager().initLoader(0, null, this); 98 | 99 | // Query the Shared Preferences to find the ID of the last venue checked in to. 100 | SharedPreferences sp = activity.getSharedPreferences(PlacesConstants.SHARED_PREFERENCE_FILE, Context.MODE_PRIVATE); 101 | String lastCheckin = sp.getString(PlacesConstants.SP_KEY_LAST_CHECKIN_ID, null); 102 | if (lastCheckin != null ) 103 | checkedIn(lastCheckin); 104 | } 105 | 106 | @Override 107 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 108 | View view = inflater.inflate(R.layout.place_detail, container, false); 109 | nameTextView = (TextView)view.findViewById(R.id.detail_name); 110 | phoneTextView = (TextView)view.findViewById(R.id.detail_phone); 111 | addressTextView = (TextView)view.findViewById(R.id.detail_address); 112 | ratingTextView = (TextView)view.findViewById(R.id.detail_rating); 113 | urlTextView = (TextView)view.findViewById(R.id.detail_url); 114 | checkinButton = (Button)view.findViewById(R.id.checkin_button); 115 | checkedInText = (TextView)view.findViewById(R.id.detail_checkin_text); 116 | 117 | checkinButton.setOnClickListener(checkinButtonOnClickListener); 118 | 119 | if (getArguments() != null) { 120 | placeReference = getArguments().getString(PlacesConstants.ARGUMENTS_KEY_REFERENCE); 121 | placeId = getArguments().getString(PlacesConstants.ARGUMENTS_KEY_ID); 122 | } 123 | return view; 124 | } 125 | 126 | @Override 127 | public void onResume() { 128 | super.onResume(); 129 | 130 | // Always refresh the details on resume, but don't force 131 | // a refresh to minimize the network usage. Forced updates 132 | // are unnecessary as we force an update when a venue 133 | // is selected in the Place List Activity. 134 | if (placeReference != null && placeId != null) 135 | updatePlace(placeReference, placeId, false); 136 | } 137 | 138 | /** 139 | * Start the {@link PlaceDetailsUpdateService} to refresh the details for the 140 | * selected venue. 141 | * @param reference Reference 142 | * @param id Unique Identifier 143 | * @param forceUpdate Force an update 144 | */ 145 | protected void updatePlace(String reference, String id, boolean forceUpdate) { 146 | if (placeReference != null && placeId != null) { 147 | // Start the PlaceDetailsUpdate Service to query the server for details 148 | // on the specified venue. A "forced update" will ignore the caching latency 149 | // rules and query the server. 150 | Intent updateServiceIntent = new Intent(activity, PlaceDetailsUpdateService.class); 151 | updateServiceIntent.putExtra(PlacesConstants.EXTRA_KEY_REFERENCE, reference); 152 | updateServiceIntent.putExtra(PlacesConstants.EXTRA_KEY_ID, id); 153 | updateServiceIntent.putExtra(PlacesConstants.EXTRA_KEY_FORCEREFRESH, forceUpdate); 154 | activity.startService(updateServiceIntent); 155 | } 156 | } 157 | 158 | /** 159 | * {@inheritDoc} 160 | * Query the {@link PlaceDetailsContentProvider} for the Phone, Address, Rating, Reference, and Url 161 | * of the selected venue. 162 | * TODO Expand the projection to include any other details you are recording in the Place Detail Content Provider. 163 | */ 164 | public Loader onCreateLoader(int id, Bundle args) { 165 | String[] projection = new String[] {PlaceDetailsContentProvider.KEY_NAME, 166 | PlaceDetailsContentProvider.KEY_PHONE, 167 | PlaceDetailsContentProvider.KEY_ADDRESS, 168 | PlaceDetailsContentProvider.KEY_RATING, 169 | PlaceDetailsContentProvider.KEY_REFERENCE, 170 | PlaceDetailsContentProvider.KEY_URL}; 171 | 172 | String selection = PlaceDetailsContentProvider.KEY_ID + "='" + placeId + "'"; 173 | 174 | return new CursorLoader(activity, PlaceDetailsContentProvider.CONTENT_URI, 175 | projection, selection, null, null); 176 | } 177 | 178 | /** 179 | * {@inheritDoc} 180 | * When the Loader has completed, schedule an update of the Fragment UI on the main application thread. 181 | */ 182 | public void onLoadFinished(Loader loader, Cursor data) { 183 | if (data.moveToFirst()) { 184 | final String name = data.getString(data.getColumnIndex(PlaceDetailsContentProvider.KEY_NAME)); 185 | final String phone = data.getString(data.getColumnIndex(PlaceDetailsContentProvider.KEY_PHONE)); 186 | final String address = data.getString(data.getColumnIndex(PlaceDetailsContentProvider.KEY_ADDRESS)); 187 | final String rating = data.getString(data.getColumnIndex(PlaceDetailsContentProvider.KEY_RATING)); 188 | final String url = data.getString(data.getColumnIndex(PlaceDetailsContentProvider.KEY_URL)); 189 | 190 | // If we don't have a place reference passed in, we need to look it up and update our details 191 | // accordingly. 192 | if (placeReference == null) { 193 | placeReference = data.getString(data.getColumnIndex(PlaceDetailsContentProvider.KEY_REFERENCE)); 194 | updatePlace(placeReference, placeId, true); 195 | } 196 | 197 | handler.post(new Runnable () { 198 | public void run() { 199 | nameTextView.setText(name); 200 | phoneTextView.setText(phone); 201 | addressTextView.setText(address); 202 | ratingTextView.setText(rating); 203 | urlTextView.setText(url); 204 | } 205 | }); 206 | } 207 | } 208 | 209 | /** 210 | * {@inheritDoc} 211 | */ 212 | public void onLoaderReset(Loader loader) { 213 | handler.post(new Runnable () { 214 | public void run() { 215 | nameTextView.setText(""); 216 | phoneTextView.setText(""); 217 | addressTextView.setText(""); 218 | ratingTextView.setText(""); 219 | urlTextView.setText(""); 220 | } 221 | }); 222 | } 223 | 224 | /** 225 | * When the Checkin Button is clicked start the {@link PlaceCheckinService} to checkin. 226 | */ 227 | protected OnClickListener checkinButtonOnClickListener = new OnClickListener() { 228 | public void onClick(View view) { 229 | // TODO Pass in additional parameters to your checkin / rating / review service as appropriate 230 | // TODO In some cases you may prefer to open a new Activity with checkin details before initiating the Service. 231 | Intent checkinServiceIntent = new Intent(getActivity(), PlaceCheckinService.class); 232 | checkinServiceIntent.putExtra(PlacesConstants.EXTRA_KEY_REFERENCE, placeReference); 233 | checkinServiceIntent.putExtra(PlacesConstants.EXTRA_KEY_ID, placeId); 234 | checkinServiceIntent.putExtra(PlacesConstants.EXTRA_KEY_TIME_STAMP, System.currentTimeMillis()); 235 | getActivity().startService(checkinServiceIntent); 236 | } 237 | }; 238 | 239 | /** 240 | * Checks to see if the currently displayed venue is the last place checked in to. 241 | * IF it is, it disables the checkin button and update the UI accordingly. 242 | * @param id Checked-in place ID 243 | */ 244 | public void checkedIn(String id) { 245 | if (placeId == null) 246 | Log.e(TAG, "Place ID = null"); 247 | boolean checkedIn = id != null && placeId != null && placeId.equals(id); 248 | checkinButton.setEnabled(!checkedIn); 249 | checkedInText.setVisibility(checkedIn ? View.VISIBLE : View.INVISIBLE); 250 | } 251 | } -------------------------------------------------------------------------------- /app/src/main/java/com/radioactiveyak/location_best_practices/UI/fragments/PlaceListFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. 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 | 17 | package com.radioactiveyak.location_best_practices.UI.fragments; 18 | 19 | import com.radioactiveyak.location_best_practices.PlacesConstants; 20 | import com.radioactiveyak.location_best_practices.UI.PlaceActivity; 21 | import com.radioactiveyak.location_best_practices.content_providers.PlacesContentProvider; 22 | import com.radioactiveyak.location_best_practices.services.PlaceDetailsUpdateService; 23 | 24 | import android.content.Intent; 25 | import android.database.Cursor; 26 | import android.os.Bundle; 27 | import android.support.v4.app.ListFragment; 28 | import android.support.v4.app.LoaderManager.LoaderCallbacks; 29 | import android.support.v4.content.CursorLoader; 30 | import android.support.v4.content.Loader; 31 | import android.support.v4.widget.SimpleCursorAdapter; 32 | import android.view.View; 33 | import android.widget.ListView; 34 | 35 | // TODO Update this UI to show a better list of available venues. This could include 36 | // TODO pictures, direction, more detailed text, etc. You will likely want to define 37 | // TODO your own List Item Layout. 38 | 39 | /** 40 | * UI Fragment to show a list of venues near to the users current location. 41 | */ 42 | public class PlaceListFragment extends ListFragment implements LoaderCallbacks { 43 | 44 | protected Cursor cursor = null; 45 | protected SimpleCursorAdapter adapter; 46 | protected PlaceActivity activity; 47 | 48 | @Override 49 | public void onActivityCreated(Bundle savedInstanceState) { 50 | super.onActivityCreated(savedInstanceState); 51 | 52 | activity = (PlaceActivity)getActivity(); 53 | 54 | // Create a new SimpleCursorAdapter that displays the name of each nearby 55 | // venue and the current distance to it. 56 | adapter = new SimpleCursorAdapter( 57 | activity, 58 | android.R.layout.two_line_list_item, 59 | cursor, 60 | new String[] {PlacesContentProvider.KEY_NAME, PlacesContentProvider.KEY_DISTANCE}, 61 | new int[] {android.R.id.text1, android.R.id.text2}, 62 | 0); 63 | // Allocate the adapter to the List displayed within this fragment. 64 | setListAdapter(adapter); 65 | 66 | // Populate the adapter / list using a Cursor Loader. 67 | getLoaderManager().initLoader(0, null, this); 68 | } 69 | 70 | /** 71 | * {@inheritDoc} 72 | * When a venue is clicked, fetch the details from your server and display the detail page. 73 | */ 74 | @Override 75 | public void onListItemClick(ListView l, View v, int position, long theid) { 76 | super.onListItemClick(l, v, position, theid); 77 | 78 | // Find the ID and Reference of the selected venue. 79 | // These are needed to perform a lookup in our cache and the Google Places API server respectively. 80 | Cursor c = adapter.getCursor(); 81 | c.moveToPosition(position); 82 | String reference = c.getString(c.getColumnIndex(PlacesContentProvider.KEY_REFERENCE)); 83 | String id = c.getString(c.getColumnIndex(PlacesContentProvider.KEY_ID)); 84 | 85 | // Initiate a lookup of the venue details usign the PlacesDetailsUpdateService. 86 | // Because this is a user initiated action (rather than a prefetch) we request 87 | // that the Service force a refresh. 88 | Intent serviceIntent = new Intent(activity, PlaceDetailsUpdateService.class); 89 | serviceIntent.putExtra(PlacesConstants.EXTRA_KEY_REFERENCE, reference); 90 | serviceIntent.putExtra(PlacesConstants.EXTRA_KEY_ID, id); 91 | serviceIntent.putExtra(PlacesConstants.EXTRA_KEY_FORCEREFRESH, true); 92 | activity.startService(serviceIntent); 93 | 94 | // Request the parent Activity display the venue detail UI. 95 | activity.selectDetail(reference, id); 96 | } 97 | 98 | /** 99 | * {@inheritDoc} 100 | * This loader will return the ID, Reference, Name, and Distance of all the venues 101 | * currently stored in the {@link PlacesContentProvider}. 102 | */ 103 | public Loader onCreateLoader(int id, Bundle args) { 104 | String[] projection = new String[] {PlacesContentProvider.KEY_ID,PlacesContentProvider.KEY_NAME, PlacesContentProvider.KEY_DISTANCE, PlacesContentProvider.KEY_REFERENCE}; 105 | 106 | return new CursorLoader(activity, PlacesContentProvider.CONTENT_URI, 107 | projection, null, null, null); 108 | } 109 | 110 | /** 111 | * {@inheritDoc} 112 | * When the loading has completed, assign the cursor to the adapter / list. 113 | */ 114 | public void onLoadFinished(Loader loader, Cursor data) { 115 | adapter.swapCursor(data); 116 | } 117 | 118 | /** 119 | * {@inheritDoc} 120 | */ 121 | public void onLoaderReset(Loader loader) { 122 | adapter.swapCursor(null); 123 | } 124 | } -------------------------------------------------------------------------------- /app/src/main/java/com/radioactiveyak/location_best_practices/content_providers/PlaceDetailsContentProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. 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 | 17 | package com.radioactiveyak.location_best_practices.content_providers; 18 | 19 | import android.content.ContentProvider; 20 | import android.content.ContentUris; 21 | import android.content.ContentValues; 22 | import android.content.Context; 23 | import android.content.UriMatcher; 24 | import android.database.Cursor; 25 | import android.database.SQLException; 26 | import android.database.sqlite.SQLiteDatabase; 27 | import android.database.sqlite.SQLiteException; 28 | import android.database.sqlite.SQLiteOpenHelper; 29 | import android.database.sqlite.SQLiteQueryBuilder; 30 | import android.database.sqlite.SQLiteDatabase.CursorFactory; 31 | import android.net.Uri; 32 | import android.text.TextUtils; 33 | import android.util.Log; 34 | 35 | /** 36 | * Content Provider and database for storing the details for 37 | * places whose details we've either viewed or prefetched. 38 | */ 39 | public class PlaceDetailsContentProvider extends ContentProvider { 40 | 41 | private static final String TAG = "PlacesDetailsContentProvider"; 42 | 43 | /** The underlying database */ 44 | private SQLiteDatabase placesDB; 45 | private static final String DATABASE_NAME = "placedetails.db"; 46 | private static final int DATABASE_VERSION = 3; 47 | private static final String PLACEDETAILS_TABLE = "placedetails"; 48 | 49 | // TODO Replace the columns names and database creation SQL with values for your own app. 50 | // Column Names 51 | public static final String KEY_ID = "_id"; 52 | public static final String KEY_NAME = "name"; 53 | public static final String KEY_VICINITY = "vicinity"; 54 | public static final String KEY_LOCATION_LAT = "latitude"; 55 | public static final String KEY_LOCATION_LNG = "longitude"; 56 | public static final String KEY_TYPES = "types"; 57 | public static final String KEY_VIEWPORT = "viewport"; 58 | public static final String KEY_ICON = "icon"; 59 | public static final String KEY_REFERENCE = "reference"; 60 | public static final String KEY_DISTANCE = "distance"; 61 | public static final String KEY_PHONE = "phone"; 62 | public static final String KEY_ADDRESS = "address"; 63 | public static final String KEY_RATING = "rating"; 64 | public static final String KEY_URL = "url"; 65 | public static final String KEY_LAST_UPDATE_TIME = "lastupdatetime"; 66 | public static final String KEY_FORCE_CACHE = "forcecache"; 67 | 68 | // TODO Replace this URI with something unique to your own application. 69 | public static final Uri CONTENT_URI = Uri.parse("content://com.radioactiveyak.provider.placedetails/places"); 70 | 71 | //Create the constants used to differentiate between the different URI requests. 72 | private static final int PLACES = 1; 73 | private static final int PLACE_ID = 2; 74 | 75 | //Allocate the UriMatcher object, where a URI ending in 'places' will 76 | //correspond to a request for all places, and 'places' with a trailing '/[Unique ID]' will represent a single place details row. 77 | private static final UriMatcher uriMatcher; 78 | static { 79 | uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); 80 | uriMatcher.addURI("com.radioactiveyak.provider.placedetails", "places", PLACES); 81 | uriMatcher.addURI("com.radioactiveyak.provider.placedetails", "places/*", PLACE_ID); 82 | } 83 | 84 | @Override 85 | public boolean onCreate() { 86 | Context context = getContext(); 87 | 88 | PlacesDatabaseHelper dbHelper = new PlacesDatabaseHelper(context, DATABASE_NAME, null, DATABASE_VERSION); 89 | try { 90 | placesDB = dbHelper.getWritableDatabase(); 91 | } catch (SQLiteException e) { 92 | placesDB = null; 93 | Log.e(TAG, "Database Opening exception"); 94 | } 95 | 96 | return (placesDB == null) ? false : true; 97 | } 98 | 99 | @Override 100 | public String getType(Uri uri) { 101 | switch (uriMatcher.match(uri)) { 102 | case PLACES: return "vnd.android.cursor.dir/vnd.radioativeyak.placedetail"; 103 | case PLACE_ID: return "vnd.android.cursor.item/vnd.radioactiveyak.placedetail"; 104 | default: throw new IllegalArgumentException("Unsupported URI: " + uri); 105 | } 106 | } 107 | 108 | 109 | @Override 110 | public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sort) { 111 | SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 112 | qb.setTables(PLACEDETAILS_TABLE); 113 | 114 | // If this is a row query, limit the result set to the passed in row. 115 | switch (uriMatcher.match(uri)) { 116 | case PLACE_ID: qb.appendWhere(KEY_ID + "='" + uri.getPathSegments().get(1) + "'"); 117 | break; 118 | default : break; 119 | } 120 | 121 | // If no sort order is specified sort by date / time 122 | String orderBy; 123 | if (TextUtils.isEmpty(sort)) { 124 | orderBy = KEY_DISTANCE + " ASC"; 125 | } else { 126 | orderBy = sort; 127 | } 128 | 129 | // Apply the query to the underlying database. 130 | Cursor c = qb.query(placesDB, 131 | projection, 132 | selection, selectionArgs, 133 | null, null, orderBy); 134 | 135 | // Register the contexts ContentResolver to be notified if 136 | // the cursor result set changes. 137 | c.setNotificationUri(getContext().getContentResolver(), uri); 138 | 139 | // Return a cursor to the query result. 140 | return c; 141 | } 142 | 143 | @Override 144 | public Uri insert(Uri _uri, ContentValues _initialValues) { 145 | // Insert the new row, will return the row number if successful. 146 | long rowID = placesDB.insert(PLACEDETAILS_TABLE, "not_null", _initialValues); 147 | 148 | // Return a URI to the newly inserted row on success. 149 | if (rowID > 0) { 150 | Uri uri = ContentUris.withAppendedId(CONTENT_URI, rowID); 151 | getContext().getContentResolver().notifyChange(uri, null); 152 | return uri; 153 | } 154 | throw new SQLException("Failed to insert row into " + _uri); 155 | } 156 | 157 | @Override 158 | public int delete(Uri uri, String where, String[] whereArgs) { 159 | int count; 160 | 161 | switch (uriMatcher.match(uri)) { 162 | case PLACES: 163 | count = placesDB.delete(PLACEDETAILS_TABLE, where, whereArgs); 164 | break; 165 | 166 | case PLACE_ID: 167 | String segment = uri.getPathSegments().get(1); 168 | count = placesDB.delete(PLACEDETAILS_TABLE, KEY_ID + "=" 169 | + segment 170 | + (!TextUtils.isEmpty(where) ? " AND (" 171 | + where + ')' : ""), whereArgs); 172 | break; 173 | 174 | default: throw new IllegalArgumentException("Unsupported URI: " + uri); 175 | } 176 | 177 | getContext().getContentResolver().notifyChange(uri, null); 178 | return count; 179 | } 180 | 181 | @Override 182 | public int update(Uri uri, ContentValues values, String where, String[] whereArgs) { 183 | int count; 184 | switch (uriMatcher.match(uri)) { 185 | case PLACES: count = placesDB.update(PLACEDETAILS_TABLE, values, where, whereArgs); 186 | break; 187 | 188 | case PLACE_ID: String segment = uri.getPathSegments().get(1); 189 | count = placesDB.update(PLACEDETAILS_TABLE, values, KEY_ID 190 | + "=" + segment 191 | + (!TextUtils.isEmpty(where) ? " AND (" 192 | + where + ')' : ""), whereArgs); 193 | break; 194 | 195 | default: throw new IllegalArgumentException("Unknown URI " + uri); 196 | } 197 | 198 | getContext().getContentResolver().notifyChange(uri, null); 199 | return count; 200 | } 201 | 202 | // Helper class for opening, creating, and managing database version control 203 | private static class PlacesDatabaseHelper extends SQLiteOpenHelper { 204 | private static final String DATABASE_CREATE = 205 | "create table " + PLACEDETAILS_TABLE + " (" 206 | + KEY_ID + " TEXT primary key, " 207 | + KEY_NAME + " TEXT, " 208 | + KEY_VICINITY + " TEXT, " 209 | + KEY_LOCATION_LAT + " FLOAT, " 210 | + KEY_LOCATION_LNG + " FLOAT, " 211 | + KEY_TYPES + " TEXT, " 212 | + KEY_VIEWPORT + " TEXT, " 213 | + KEY_ICON + " TEXT, " 214 | + KEY_REFERENCE + " TEXT, " 215 | + KEY_DISTANCE + " FLOAT, " 216 | + KEY_PHONE + " TEXT, " 217 | + KEY_ADDRESS + " TEXT, " 218 | + KEY_RATING + " FLOAT, " 219 | + KEY_URL + " TEXT, " 220 | + KEY_LAST_UPDATE_TIME + " LONG, " 221 | + KEY_FORCE_CACHE + " BOOLEAN); "; 222 | 223 | public PlacesDatabaseHelper(Context context, String name, CursorFactory factory, int version) { 224 | super(context, name, factory, version); 225 | } 226 | 227 | @Override 228 | public void onCreate(SQLiteDatabase db) { 229 | db.execSQL(DATABASE_CREATE); 230 | } 231 | 232 | @Override 233 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 234 | Log.w(TAG, "Upgrading database from version " + oldVersion + " to " 235 | + newVersion + ", which will destroy all old data"); 236 | 237 | db.execSQL("DROP TABLE IF EXISTS " + PLACEDETAILS_TABLE); 238 | onCreate(db); 239 | } 240 | } 241 | } -------------------------------------------------------------------------------- /app/src/main/java/com/radioactiveyak/location_best_practices/content_providers/PlacesContentProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. 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 | 17 | package com.radioactiveyak.location_best_practices.content_providers; 18 | 19 | import android.content.ContentProvider; 20 | import android.content.ContentUris; 21 | import android.content.ContentValues; 22 | import android.content.Context; 23 | import android.content.UriMatcher; 24 | import android.database.Cursor; 25 | import android.database.SQLException; 26 | import android.database.sqlite.SQLiteDatabase; 27 | import android.database.sqlite.SQLiteException; 28 | import android.database.sqlite.SQLiteOpenHelper; 29 | import android.database.sqlite.SQLiteQueryBuilder; 30 | import android.database.sqlite.SQLiteDatabase.CursorFactory; 31 | import android.net.Uri; 32 | import android.text.TextUtils; 33 | import android.util.Log; 34 | 35 | /** 36 | * Content Provider and database for storing the list of 37 | * places nearby our current location 38 | */ 39 | public class PlacesContentProvider extends ContentProvider { 40 | 41 | /** The underlying database */ 42 | private SQLiteDatabase placesDB; 43 | 44 | private static final String TAG = "PlacesContentProvider"; 45 | private static final String DATABASE_NAME = "places.db"; 46 | private static final int DATABASE_VERSION = 6; 47 | private static final String PLACES_TABLE = "places"; 48 | 49 | // TODO Replace the columns names and database creation SQL with values for your own app. 50 | // Column Names 51 | public static final String KEY_ID = "_id"; 52 | public static final String KEY_NAME = "name"; 53 | public static final String KEY_VICINITY = "vicinity"; 54 | public static final String KEY_LOCATION_LAT = "latitude"; 55 | public static final String KEY_LOCATION_LNG = "longitude"; 56 | public static final String KEY_TYPES = "types"; 57 | public static final String KEY_VIEWPORT = "viewport"; 58 | public static final String KEY_ICON = "icon"; 59 | public static final String KEY_REFERENCE = "reference"; 60 | public static final String KEY_DISTANCE = "distance"; 61 | public static final String KEY_LAST_UPDATE_TIME = "lastupdatetime"; 62 | 63 | // TODO Replace this URI with something unique to your own application. 64 | public static final Uri CONTENT_URI = Uri.parse("content://com.radioactiveyak.provider.places/places"); 65 | 66 | //Create the constants used to differentiate between the different URI requests. 67 | private static final int PLACES = 1; 68 | private static final int PLACE_ID = 2; 69 | 70 | //Allocate the UriMatcher object, where a URI ending in 'places' will 71 | //correspond to a request for all places, and 'places' with a trailing '/[Unique ID]' will represent a single place details row. 72 | private static final UriMatcher uriMatcher; 73 | static { 74 | uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); 75 | uriMatcher.addURI("com.radioactiveyak.provider.places", "places", PLACES); 76 | uriMatcher.addURI("com.radioactiveyak.provider.places", "places/*", PLACE_ID); 77 | } 78 | 79 | @Override 80 | public boolean onCreate() { 81 | Context context = getContext(); 82 | 83 | PlacesDatabaseHelper dbHelper = new PlacesDatabaseHelper(context, DATABASE_NAME, 84 | null, DATABASE_VERSION); 85 | try { 86 | placesDB = dbHelper.getWritableDatabase(); 87 | } catch (SQLiteException e) { 88 | placesDB = null; 89 | Log.d(TAG, "Database Opening exception"); 90 | } 91 | 92 | return (placesDB == null) ? false : true; 93 | } 94 | 95 | @Override 96 | public String getType(Uri uri) { 97 | switch (uriMatcher.match(uri)) { 98 | case PLACES: return "vnd.android.cursor.dir/vnd.radioativeyak.place"; 99 | case PLACE_ID: return "vnd.android.cursor.item/vnd.radioactiveyak.place"; 100 | default: throw new IllegalArgumentException("Unsupported URI: " + uri); 101 | } 102 | } 103 | 104 | 105 | @Override 106 | public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sort) { 107 | SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 108 | qb.setTables(PLACES_TABLE); 109 | 110 | // If this is a row query, limit the result set to the passed in row. 111 | switch (uriMatcher.match(uri)) { 112 | case PLACE_ID: qb.appendWhere(KEY_ID + "=" + uri.getPathSegments().get(1)); 113 | break; 114 | default : break; 115 | } 116 | 117 | // If no sort order is specified sort by date / time 118 | String orderBy; 119 | if (TextUtils.isEmpty(sort)) { 120 | orderBy = KEY_DISTANCE + " ASC"; 121 | } else { 122 | orderBy = sort; 123 | } 124 | 125 | // Apply the query to the underlying database. 126 | Cursor c = qb.query(placesDB, 127 | projection, 128 | selection, selectionArgs, 129 | null, null, orderBy); 130 | 131 | // Register the contexts ContentResolver to be notified if 132 | // the cursor result set changes. 133 | c.setNotificationUri(getContext().getContentResolver(), uri); 134 | 135 | // Return a cursor to the query result. 136 | return c; 137 | } 138 | 139 | @Override 140 | public Uri insert(Uri _uri, ContentValues _initialValues) { 141 | // Insert the new row, will return the row number if successful. 142 | long rowID = placesDB.insert(PLACES_TABLE, "nullhack", _initialValues); 143 | 144 | // Return a URI to the newly inserted row on success. 145 | if (rowID > 0) { 146 | Uri uri = ContentUris.withAppendedId(CONTENT_URI, rowID); 147 | getContext().getContentResolver().notifyChange(uri, null); 148 | return uri; 149 | } 150 | throw new SQLException("Failed to insert row into " + _uri); 151 | } 152 | 153 | @Override 154 | public int delete(Uri uri, String where, String[] whereArgs) { 155 | int count; 156 | 157 | switch (uriMatcher.match(uri)) { 158 | case PLACES: 159 | count = placesDB.delete(PLACES_TABLE, where, whereArgs); 160 | break; 161 | 162 | case PLACE_ID: 163 | String segment = uri.getPathSegments().get(1); 164 | count = placesDB.delete(PLACES_TABLE, KEY_ID + "=" 165 | + segment 166 | + (!TextUtils.isEmpty(where) ? " AND (" 167 | + where + ')' : ""), whereArgs); 168 | break; 169 | 170 | default: throw new IllegalArgumentException("Unsupported URI: " + uri); 171 | } 172 | 173 | getContext().getContentResolver().notifyChange(uri, null); 174 | return count; 175 | } 176 | 177 | @Override 178 | public int update(Uri uri, ContentValues values, String where, String[] whereArgs) { 179 | int count; 180 | switch (uriMatcher.match(uri)) { 181 | case PLACES: count = placesDB.update(PLACES_TABLE, values, where, whereArgs); 182 | break; 183 | 184 | case PLACE_ID: String segment = uri.getPathSegments().get(1); 185 | count = placesDB.update(PLACES_TABLE, values, KEY_ID 186 | + "=" + segment 187 | + (!TextUtils.isEmpty(where) ? " AND (" 188 | + where + ')' : ""), whereArgs); 189 | break; 190 | 191 | default: throw new IllegalArgumentException("Unknown URI " + uri); 192 | } 193 | 194 | getContext().getContentResolver().notifyChange(uri, null); 195 | return count; 196 | } 197 | 198 | 199 | // Helper class for opening, creating, and managing database version control 200 | private static class PlacesDatabaseHelper extends SQLiteOpenHelper { 201 | private static final String DATABASE_CREATE = 202 | "create table " + PLACES_TABLE + " (" 203 | + KEY_ID + " TEXT primary key, " 204 | + KEY_NAME + " TEXT, " 205 | + KEY_VICINITY + " TEXT, " 206 | + KEY_LOCATION_LAT + " FLOAT, " 207 | + KEY_LOCATION_LNG + " FLOAT, " 208 | + KEY_TYPES + " TEXT, " 209 | + KEY_VIEWPORT + " TEXT, " 210 | + KEY_ICON + " TEXT, " 211 | + KEY_REFERENCE + " TEXT, " 212 | + KEY_DISTANCE + " FLOAT, " 213 | + KEY_LAST_UPDATE_TIME + " LONG); "; 214 | 215 | public PlacesDatabaseHelper(Context context, String name, CursorFactory factory, int version) { 216 | super(context, name, factory, version); 217 | } 218 | 219 | @Override 220 | public void onCreate(SQLiteDatabase db) { 221 | db.execSQL(DATABASE_CREATE); 222 | } 223 | 224 | @Override 225 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 226 | Log.w(TAG, "Upgrading database from version " + oldVersion + " to " 227 | + newVersion + ", which will destroy all old data"); 228 | 229 | db.execSQL("DROP TABLE IF EXISTS " + PLACES_TABLE); 230 | onCreate(db); 231 | } 232 | } 233 | } -------------------------------------------------------------------------------- /app/src/main/java/com/radioactiveyak/location_best_practices/content_providers/QueuedCheckinsContentProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. 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 | 17 | package com.radioactiveyak.location_best_practices.content_providers; 18 | 19 | import android.content.ContentProvider; 20 | import android.content.ContentUris; 21 | import android.content.ContentValues; 22 | import android.content.Context; 23 | import android.content.UriMatcher; 24 | import android.database.Cursor; 25 | import android.database.SQLException; 26 | import android.database.sqlite.SQLiteDatabase; 27 | import android.database.sqlite.SQLiteException; 28 | import android.database.sqlite.SQLiteOpenHelper; 29 | import android.database.sqlite.SQLiteQueryBuilder; 30 | import android.database.sqlite.SQLiteDatabase.CursorFactory; 31 | import android.net.Uri; 32 | import android.text.TextUtils; 33 | import android.util.Log; 34 | 35 | /** 36 | * Content Provider and database for storing checkins which 37 | * have not yet successfully been reported to the server. 38 | * Checkins will be added be added and removed by the checkin Service. 39 | */ 40 | public class QueuedCheckinsContentProvider extends ContentProvider { 41 | /** The underlying database */ 42 | private SQLiteDatabase checkinsDB; 43 | 44 | private static final String TAG = "QueuedCheckinsContentProvider"; 45 | private static final String DATABASE_NAME = "checkins.db"; 46 | private static final int DATABASE_VERSION = 3; 47 | private static final String CHECKINS_TABLE = "checkins"; 48 | 49 | // TODO Update the columns and SQL Create statement with the data you 50 | // TODO will be sending to your online checkin service. 51 | // Column Names 52 | public static final String KEY_REFERENCE = "_id"; 53 | public static final String KEY_ID = "id"; 54 | public static final String KEY_TIME_STAMP = "timestamp"; 55 | 56 | public static final Uri CONTENT_URI = Uri.parse("content://com.radioactiveyak.provider.checkins/checkins"); 57 | 58 | //Create the constants used to differentiate between the different URI requests. 59 | private static final int CHECKINS = 1; 60 | private static final int CHECKIN_ID = 2; 61 | 62 | //Allocate the UriMatcher object, where a URI ending in 'checkins' will 63 | //correspond to a request for all earthquakes, and 'checkins' with a trailing '/[Unique ID]' will represent a single earthquake row. 64 | private static final UriMatcher uriMatcher; 65 | static { 66 | uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); 67 | uriMatcher.addURI("com.radioactiveyak.provider.checkins", "checkins", CHECKINS); 68 | uriMatcher.addURI("com.radioactiveyak.provider.checkins", "checkins/*", CHECKIN_ID); 69 | } 70 | 71 | @Override 72 | public boolean onCreate() { 73 | Context context = getContext(); 74 | 75 | CheckinsDatabaseHelper dbHelper = new CheckinsDatabaseHelper(context, DATABASE_NAME, null, DATABASE_VERSION); 76 | try { 77 | checkinsDB = dbHelper.getWritableDatabase(); 78 | } catch (SQLiteException e) { 79 | checkinsDB = null; 80 | Log.d(TAG, "Database Opening exception"); 81 | } 82 | 83 | return (checkinsDB == null) ? false : true; 84 | } 85 | 86 | @Override 87 | public String getType(Uri uri) { 88 | switch (uriMatcher.match(uri)) { 89 | case CHECKINS: return "vnd.android.cursor.dir/vnd.radioativeyak.checkin"; 90 | case CHECKIN_ID: return "vnd.android.cursor.item/vnd.radioactiveyak.checkin"; 91 | default: throw new IllegalArgumentException("Unsupported URI: " + uri); 92 | } 93 | } 94 | 95 | 96 | @Override 97 | public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sort) { 98 | SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 99 | qb.setTables(CHECKINS_TABLE); 100 | 101 | // If this is a row query, limit the result set to the passed in row. 102 | switch (uriMatcher.match(uri)) { 103 | case CHECKIN_ID: qb.appendWhere(KEY_REFERENCE + "=" + uri.getPathSegments().get(1)); 104 | break; 105 | default : break; 106 | } 107 | 108 | // If no sort order is specified sort by date / time 109 | String orderBy; 110 | if (TextUtils.isEmpty(sort)) { 111 | orderBy = KEY_TIME_STAMP + " ASC"; 112 | } else { 113 | orderBy = sort; 114 | } 115 | 116 | // Apply the query to the underlying database. 117 | Cursor c = qb.query(checkinsDB, 118 | projection, 119 | selection, selectionArgs, 120 | null, null, orderBy); 121 | 122 | // Register the contexts ContentResolver to be notified if 123 | // the cursor result set changes. 124 | c.setNotificationUri(getContext().getContentResolver(), uri); 125 | 126 | // Return a cursor to the query result. 127 | return c; 128 | } 129 | 130 | @Override 131 | public Uri insert(Uri _uri, ContentValues _initialValues) { 132 | // Insert the new row, will return the row number if successful. 133 | long rowID = checkinsDB.insert(CHECKINS_TABLE, "checkin", _initialValues); 134 | 135 | // Return a URI to the newly inserted row on success. 136 | if (rowID > 0) { 137 | Uri uri = ContentUris.withAppendedId(CONTENT_URI, rowID); 138 | getContext().getContentResolver().notifyChange(uri, null); 139 | return uri; 140 | } 141 | throw new SQLException("Failed to insert row into " + _uri); 142 | } 143 | 144 | @Override 145 | public int delete(Uri uri, String where, String[] whereArgs) { 146 | int count; 147 | 148 | switch (uriMatcher.match(uri)) { 149 | case CHECKINS: 150 | count = checkinsDB.delete(CHECKINS_TABLE, where, whereArgs); 151 | break; 152 | 153 | case CHECKIN_ID: 154 | String segment = uri.getPathSegments().get(1); 155 | count = checkinsDB.delete(CHECKINS_TABLE, KEY_REFERENCE + "=" 156 | + segment 157 | + (!TextUtils.isEmpty(where) ? " AND (" 158 | + where + ')' : ""), whereArgs); 159 | break; 160 | 161 | default: throw new IllegalArgumentException("Unsupported URI: " + uri); 162 | } 163 | 164 | getContext().getContentResolver().notifyChange(uri, null); 165 | return count; 166 | } 167 | 168 | @Override 169 | public int update(Uri uri, ContentValues values, String where, String[] whereArgs) { 170 | int count; 171 | switch (uriMatcher.match(uri)) { 172 | case CHECKINS: count = checkinsDB.update(CHECKINS_TABLE, values, where, whereArgs); 173 | break; 174 | 175 | case CHECKIN_ID: String segment = uri.getPathSegments().get(1); 176 | count = checkinsDB.update(CHECKINS_TABLE, values, KEY_REFERENCE 177 | + "=" + segment 178 | + (!TextUtils.isEmpty(where) ? " AND (" 179 | + where + ')' : ""), whereArgs); 180 | break; 181 | 182 | default: throw new IllegalArgumentException("Unknown URI " + uri); 183 | } 184 | 185 | getContext().getContentResolver().notifyChange(uri, null); 186 | return count; 187 | } 188 | 189 | // Helper class for opening, creating, and managing database version control 190 | private static class CheckinsDatabaseHelper extends SQLiteOpenHelper { 191 | private static final String DATABASE_CREATE = 192 | "create table " + CHECKINS_TABLE + " (" 193 | + KEY_REFERENCE + " TEXT primary key, " 194 | + KEY_ID + " TEXT, " 195 | + KEY_TIME_STAMP + " LONG); "; 196 | 197 | public CheckinsDatabaseHelper(Context context, String name, CursorFactory factory, int version) { 198 | super(context, name, factory, version); 199 | } 200 | 201 | @Override 202 | public void onCreate(SQLiteDatabase db) { 203 | db.execSQL(DATABASE_CREATE); 204 | } 205 | 206 | @Override 207 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 208 | Log.w(TAG, "Upgrading database from version " + oldVersion + " to " 209 | + newVersion + ", which will destroy all old data"); 210 | 211 | db.execSQL("DROP TABLE IF EXISTS " + CHECKINS_TABLE); 212 | onCreate(db); 213 | } 214 | } 215 | } -------------------------------------------------------------------------------- /app/src/main/java/com/radioactiveyak/location_best_practices/receivers/BootReceiver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. 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 | 17 | package com.radioactiveyak.location_best_practices.receivers; 18 | 19 | import android.app.PendingIntent; 20 | import android.content.BroadcastReceiver; 21 | import android.content.Context; 22 | import android.content.Intent; 23 | import android.content.SharedPreferences; 24 | import android.location.LocationManager; 25 | 26 | import com.radioactiveyak.location_best_practices.PlacesConstants; 27 | import com.radioactiveyak.location_best_practices.utils.PlatformSpecificImplementationFactory; 28 | import com.radioactiveyak.location_best_practices.utils.base.LocationUpdateRequester; 29 | 30 | /** 31 | * This Receiver class is designed to listen for system boot. 32 | * 33 | * If the app has been run at least once, the passive location 34 | * updates should be enabled after a reboot. 35 | */ 36 | public class BootReceiver extends BroadcastReceiver { 37 | @Override 38 | public void onReceive(Context context, Intent intent) { 39 | SharedPreferences prefs = context.getSharedPreferences(PlacesConstants.SHARED_PREFERENCE_FILE, Context.MODE_PRIVATE); 40 | boolean runOnce = prefs.getBoolean(PlacesConstants.SP_KEY_RUN_ONCE, false); 41 | 42 | if (runOnce) { 43 | LocationManager locationManager = (LocationManager)context.getSystemService(Context.LOCATION_SERVICE); 44 | 45 | // Instantiate a Location Update Requester class based on the available platform version. 46 | // This will be used to request location updates. 47 | LocationUpdateRequester locationUpdateRequester = PlatformSpecificImplementationFactory.getLocationUpdateRequester(locationManager); 48 | 49 | // Check the Shared Preferences to see if we are updating location changes. 50 | boolean followLocationChanges = prefs.getBoolean(PlacesConstants.SP_KEY_FOLLOW_LOCATION_CHANGES, true); 51 | 52 | if (followLocationChanges) { 53 | // Passive location updates from 3rd party apps when the Activity isn't visible. 54 | Intent passiveIntent = new Intent(context, PassiveLocationChangedReceiver.class); 55 | PendingIntent locationListenerPassivePendingIntent = PendingIntent.getActivity(context, 0, passiveIntent, PendingIntent.FLAG_UPDATE_CURRENT); 56 | locationUpdateRequester.requestPassiveLocationUpdates(PlacesConstants.PASSIVE_MAX_TIME, PlacesConstants.PASSIVE_MAX_DISTANCE, locationListenerPassivePendingIntent); 57 | } 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /app/src/main/java/com/radioactiveyak/location_best_practices/receivers/ConnectivityChangedReceiver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. 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 | 17 | package com.radioactiveyak.location_best_practices.receivers; 18 | 19 | import android.content.BroadcastReceiver; 20 | import android.content.ComponentName; 21 | import android.content.Context; 22 | import android.content.Intent; 23 | import android.content.pm.PackageManager; 24 | import android.net.ConnectivityManager; 25 | import android.net.NetworkInfo; 26 | 27 | import com.radioactiveyak.location_best_practices.services.PlaceCheckinService; 28 | 29 | /** 30 | * This Receiver class is designed to listen for changes in connectivity. 31 | * 32 | * When we lose connectivity the relevant Service classes will automatically 33 | * disable passive Location updates and queue pending checkins. 34 | * 35 | * This class will restart the checkin service to retry pending checkins 36 | * and re-enables passive location updates. 37 | */ 38 | public class ConnectivityChangedReceiver extends BroadcastReceiver { 39 | @Override 40 | public void onReceive(Context context, Intent intent) { 41 | ConnectivityManager cm = (ConnectivityManager)context. 42 | getSystemService(Context.CONNECTIVITY_SERVICE); 43 | 44 | // Check if we are connected to an active data network. 45 | NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); 46 | boolean isConnected = activeNetwork != null && activeNetwork.isConnectedOrConnecting(); 47 | 48 | if (isConnected) { 49 | PackageManager pm = context.getPackageManager(); 50 | 51 | ComponentName connectivityReceiver = new ComponentName(context, ConnectivityChangedReceiver.class); 52 | ComponentName locationReceiver = new ComponentName(context, LocationChangedReceiver.class); 53 | ComponentName passiveLocationReceiver = new ComponentName(context, PassiveLocationChangedReceiver.class); 54 | 55 | // The default state for this Receiver is disabled. it is only 56 | // enabled when a Service disables updates pending connectivity. 57 | pm.setComponentEnabledSetting(connectivityReceiver, 58 | PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, 59 | PackageManager.DONT_KILL_APP); 60 | 61 | // The default state for the Location Receiver is enabled. it is only 62 | // disabled when a Service disables updates pending connectivity. 63 | pm.setComponentEnabledSetting(locationReceiver, 64 | PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, 65 | PackageManager.DONT_KILL_APP); 66 | 67 | // The default state for the Location Receiver is enabled. it is only 68 | // disabled when a Service disables updates pending connectivity. 69 | pm.setComponentEnabledSetting(passiveLocationReceiver, 70 | PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, 71 | PackageManager.DONT_KILL_APP); 72 | 73 | // Commit any queued checkins now that we have connectivity 74 | Intent checkinServiceIntent = new Intent(context, PlaceCheckinService.class); 75 | context.startService(checkinServiceIntent); 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /app/src/main/java/com/radioactiveyak/location_best_practices/receivers/LocationChangedReceiver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. 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 | 17 | package com.radioactiveyak.location_best_practices.receivers; 18 | 19 | import android.content.BroadcastReceiver; 20 | import android.content.Context; 21 | import android.content.Intent; 22 | import android.location.Location; 23 | import android.location.LocationManager; 24 | import android.util.Log; 25 | 26 | import com.radioactiveyak.location_best_practices.PlacesConstants; 27 | import com.radioactiveyak.location_best_practices.services.EclairPlacesUpdateService; 28 | import com.radioactiveyak.location_best_practices.services.PlacesUpdateService; 29 | 30 | /** 31 | * This Receiver class is used to listen for Broadcast Intents that announce 32 | * that a location change has occurred. This is used instead of a LocationListener 33 | * within an Activity is our only action is to start a service. 34 | */ 35 | public class LocationChangedReceiver extends BroadcastReceiver { 36 | 37 | protected static String TAG = "LocationChangedReceiver"; 38 | 39 | /** 40 | * When a new location is received, extract it from the Intent and use 41 | * it to start the Service used to update the list of nearby places. 42 | * 43 | * This is the Active receiver, used to receive Location updates when 44 | * the Activity is visible. 45 | */ 46 | @Override 47 | public void onReceive(Context context, Intent intent) { 48 | String locationKey = LocationManager.KEY_LOCATION_CHANGED; 49 | String providerEnabledKey = LocationManager.KEY_PROVIDER_ENABLED; 50 | if (intent.hasExtra(providerEnabledKey)) { 51 | if (!intent.getBooleanExtra(providerEnabledKey, true)) { 52 | Intent providerDisabledIntent = new Intent(PlacesConstants.ACTIVE_LOCATION_UPDATE_PROVIDER_DISABLED); 53 | context.sendBroadcast(providerDisabledIntent); 54 | } 55 | } 56 | if (intent.hasExtra(locationKey)) { 57 | Location location = (Location)intent.getExtras().get(locationKey); 58 | Log.d(TAG, "Actively Updating place list"); 59 | Intent updateServiceIntent = new Intent(context, PlacesConstants.SUPPORTS_ECLAIR ? EclairPlacesUpdateService.class : PlacesUpdateService.class); 60 | updateServiceIntent.putExtra(PlacesConstants.EXTRA_KEY_LOCATION, location); 61 | updateServiceIntent.putExtra(PlacesConstants.EXTRA_KEY_RADIUS, PlacesConstants.DEFAULT_RADIUS); 62 | updateServiceIntent.putExtra(PlacesConstants.EXTRA_KEY_FORCEREFRESH, true); 63 | context.startService(updateServiceIntent); 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /app/src/main/java/com/radioactiveyak/location_best_practices/receivers/NewCheckinReceiver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. 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 | 17 | package com.radioactiveyak.location_best_practices.receivers; 18 | 19 | import com.radioactiveyak.location_best_practices.PlacesConstants; 20 | import com.radioactiveyak.location_best_practices.services.CheckinNotificationService; 21 | 22 | import android.content.BroadcastReceiver; 23 | import android.content.Context; 24 | import android.content.Intent; 25 | 26 | /** 27 | * Manifest Receiver that listens for broadcasts announcing a successful checkin. 28 | * This class starts the CheckinNotification Service that will trigger a notification 29 | * announcing the successful checkin. We don't want notifications for this app to 30 | * be announced while the app is running, so this receiver is disabled whenever the 31 | * main Activity is visible. 32 | */ 33 | public class NewCheckinReceiver extends BroadcastReceiver { 34 | 35 | protected static String TAG = "NewCheckinReceiver"; 36 | 37 | /** 38 | * When a successful checkin is announced, extract the unique ID of the place 39 | * that's been checked in to, and pass this value to the CheckinNotification Service 40 | * when you start it. 41 | */ 42 | @Override 43 | public void onReceive(Context context, Intent intent) { 44 | String id = intent.getStringExtra(PlacesConstants.EXTRA_KEY_ID); 45 | if (id != null) { 46 | Intent serviceIntent = new Intent(context, CheckinNotificationService.class); 47 | serviceIntent.putExtra(PlacesConstants.EXTRA_KEY_ID, id); 48 | context.startService(serviceIntent); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /app/src/main/java/com/radioactiveyak/location_best_practices/receivers/PassiveLocationChangedReceiver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. 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 | 17 | package com.radioactiveyak.location_best_practices.receivers; 18 | 19 | import com.radioactiveyak.location_best_practices.PlacesConstants; 20 | import com.radioactiveyak.location_best_practices.services.EclairPlacesUpdateService; 21 | import com.radioactiveyak.location_best_practices.services.PlacesUpdateService; 22 | import com.radioactiveyak.location_best_practices.utils.LegacyLastLocationFinder; 23 | 24 | import android.content.BroadcastReceiver; 25 | import android.content.Context; 26 | import android.content.Intent; 27 | import android.content.SharedPreferences; 28 | import android.location.Location; 29 | import android.location.LocationManager; 30 | import android.util.Log; 31 | 32 | /** 33 | * This Receiver class is used to listen for Broadcast Intents that announce 34 | * that a location change has occurred while this application isn't visible. 35 | * 36 | * Where possible, this is triggered by a Passive Location listener. 37 | */ 38 | public class PassiveLocationChangedReceiver extends BroadcastReceiver { 39 | 40 | protected static String TAG = "PassiveLocationChangedReceiver"; 41 | 42 | /** 43 | * When a new location is received, extract it from the Intent and use 44 | * it to start the Service used to update the list of nearby places. 45 | * 46 | * This is the Passive receiver, used to receive Location updates from 47 | * third party apps when the Activity is not visible. 48 | */ 49 | @Override 50 | public void onReceive(Context context, Intent intent) { 51 | String key = LocationManager.KEY_LOCATION_CHANGED; 52 | Location location = null; 53 | 54 | if (intent.hasExtra(key)) { 55 | // This update came from Passive provider, so we can extract the location 56 | // directly. 57 | location = (Location)intent.getExtras().get(key); 58 | } 59 | else { 60 | // This update came from a recurring alarm. We need to determine if there 61 | // has been a more recent Location received than the last location we used. 62 | 63 | // Get the best last location detected from the providers. 64 | LegacyLastLocationFinder lastLocationFinder = new LegacyLastLocationFinder(context); 65 | location = lastLocationFinder.getLastBestLocation(PlacesConstants.MAX_DISTANCE, System.currentTimeMillis()-PlacesConstants.MAX_TIME); 66 | SharedPreferences prefs = context.getSharedPreferences(PlacesConstants.SHARED_PREFERENCE_FILE, Context.MODE_PRIVATE); 67 | 68 | // Get the last location we used to get a listing. 69 | long lastTime = prefs.getLong(PlacesConstants.SP_KEY_LAST_LIST_UPDATE_TIME, Long.MIN_VALUE); 70 | long lastLat = prefs.getLong(PlacesConstants.SP_KEY_LAST_LIST_UPDATE_LAT, Long.MIN_VALUE); 71 | long lastLng = prefs.getLong(PlacesConstants.SP_KEY_LAST_LIST_UPDATE_LNG, Long.MIN_VALUE); 72 | Location lastLocation = new Location(PlacesConstants.CONSTRUCTED_LOCATION_PROVIDER); 73 | lastLocation.setLatitude(lastLat); 74 | lastLocation.setLongitude(lastLng); 75 | 76 | // Check if the last location detected from the providers is either too soon, or too close to the last 77 | // value we used. If it is within those thresholds we set the location to null to prevent the update 78 | // Service being run unnecessarily (and spending battery on data transfers). 79 | if ((lastTime > System.currentTimeMillis()-PlacesConstants.MAX_TIME) || 80 | (lastLocation.distanceTo(location) < PlacesConstants.MAX_DISTANCE)) 81 | location = null; 82 | } 83 | 84 | // Start the Service used to find nearby points of interest based on the last detected location. 85 | if (location != null) { 86 | Log.d(TAG, "Passivly updating place list."); 87 | Intent updateServiceIntent = new Intent(context, PlacesConstants.SUPPORTS_ECLAIR ? EclairPlacesUpdateService.class : PlacesUpdateService.class); 88 | updateServiceIntent.putExtra(PlacesConstants.EXTRA_KEY_LOCATION, location); 89 | updateServiceIntent.putExtra(PlacesConstants.EXTRA_KEY_RADIUS, PlacesConstants.DEFAULT_RADIUS); 90 | updateServiceIntent.putExtra(PlacesConstants.EXTRA_KEY_FORCEREFRESH, false); 91 | context.startService(updateServiceIntent); 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /app/src/main/java/com/radioactiveyak/location_best_practices/receivers/PowerStateChangedReceiver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. 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 | 17 | package com.radioactiveyak.location_best_practices.receivers; 18 | 19 | import android.content.BroadcastReceiver; 20 | import android.content.ComponentName; 21 | import android.content.Context; 22 | import android.content.Intent; 23 | import android.content.pm.PackageManager; 24 | 25 | /** 26 | * The manifest Receiver is used to detect changes in battery state. 27 | * When the system broadcasts a "Battery Low" warning we turn off 28 | * the passive location updates to conserve battery when the app is 29 | * in the background. 30 | * 31 | * When the system broadcasts "Battery OK" to indicate the battery 32 | * has returned to an okay state, the passive location updates are 33 | * resumed. 34 | */ 35 | public class PowerStateChangedReceiver extends BroadcastReceiver { 36 | @Override 37 | public void onReceive(Context context, Intent intent) { 38 | boolean batteryLow = intent.getAction().equals(Intent.ACTION_BATTERY_LOW); 39 | 40 | PackageManager pm = context.getPackageManager(); 41 | ComponentName passiveLocationReceiver = new ComponentName(context, PassiveLocationChangedReceiver.class); 42 | 43 | // Disable the passive location update receiver when the battery state is low. 44 | // Disabling the Receiver will prevent the app from initiating the background 45 | // downloads of nearby locations. 46 | pm.setComponentEnabledSetting(passiveLocationReceiver, 47 | batteryLow ? PackageManager.COMPONENT_ENABLED_STATE_DISABLED : PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, 48 | PackageManager.DONT_KILL_APP); 49 | } 50 | } -------------------------------------------------------------------------------- /app/src/main/java/com/radioactiveyak/location_best_practices/services/CheckinNotificationService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. 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 | 17 | package com.radioactiveyak.location_best_practices.services; 18 | 19 | import android.app.IntentService; 20 | import android.app.Notification; 21 | import android.app.NotificationManager; 22 | import android.app.PendingIntent; 23 | import android.content.ContentResolver; 24 | import android.content.Context; 25 | import android.content.Intent; 26 | import android.database.Cursor; 27 | import android.net.Uri; 28 | 29 | import com.radioactiveyak.location_best_practices.PlacesConstants; 30 | import com.radioactiveyak.location_best_practices.R; 31 | import com.radioactiveyak.location_best_practices.UI.PlaceActivity; 32 | import com.radioactiveyak.location_best_practices.content_providers.PlaceDetailsContentProvider; 33 | 34 | /** 35 | * Service that handles background checkin notifications. 36 | * This Service will be started by the {@link NewCheckinReceiver} 37 | * when the Application isn't visible and trigger a Notification 38 | * telling the user that they have been checked in to a venue. 39 | * This typically happens if an earlier checkin has failed 40 | * (due to lack of connectivity, server error, etc.). 41 | * 42 | * If your app lets users post reviews / ratings / etc. This 43 | * Service can be used to notify them once they have been successfully 44 | * posted. 45 | * 46 | * TODO Update the Notification to display a richer payload. 47 | * TODO Create a variation of this Notification for Honeycomb+ devices. 48 | */ 49 | public class CheckinNotificationService extends IntentService { 50 | 51 | protected static String TAG = "CheckinNotificationService"; 52 | 53 | protected ContentResolver contentResolver; 54 | protected NotificationManager notificationManager; 55 | protected String[] projection; 56 | 57 | public CheckinNotificationService() { 58 | super(TAG); 59 | } 60 | 61 | @Override 62 | public void onCreate() { 63 | super.onCreate(); 64 | contentResolver = getContentResolver(); 65 | notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE); 66 | 67 | projection = new String[] {PlaceDetailsContentProvider.KEY_ID, PlaceDetailsContentProvider.KEY_NAME}; 68 | } 69 | 70 | /** 71 | * {@inheritDoc} 72 | * Extract the name of the venue based on the ID specified in the broadcast Checkin Intent 73 | * and use it to display a Notification. 74 | */ 75 | @Override 76 | protected void onHandleIntent(Intent intent) { 77 | String id = intent.getStringExtra(PlacesConstants.EXTRA_KEY_ID); 78 | 79 | // Query the PlaceDetailsContentProvider for the specified venue. 80 | Uri uri = Uri.withAppendedPath(PlaceDetailsContentProvider.CONTENT_URI, id); 81 | Cursor cursor = contentResolver.query(uri, projection, null, null, null); 82 | 83 | if (cursor.moveToFirst()) { 84 | // Construct a Pending Intent for the Notification. This will ensure that when 85 | // the notification is clicked, the application will open and the venue we've 86 | // checked in to will be displayed. 87 | Intent contentIntent = new Intent(this, PlaceActivity.class); 88 | contentIntent.putExtra(PlacesConstants.EXTRA_KEY_ID, id); 89 | PendingIntent contentPendingIntent = PendingIntent.getActivity(this, 0, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT); 90 | 91 | // Construct the notification. 92 | String checkinText = getResources().getText(R.string.checkin_text).toString(); 93 | String placeName = cursor.getString(cursor.getColumnIndex(PlaceDetailsContentProvider.KEY_NAME)); 94 | String tickerText = checkinText + placeName; 95 | Notification notification = new Notification(R.drawable.icon, tickerText, System.currentTimeMillis()); 96 | // TODO Update this with new API. 97 | // notification.setLatestEventInfo(this, checkinText, placeName, contentPendingIntent); 98 | 99 | // Trigger the notification. 100 | notificationManager.notify(PlacesConstants.CHECKIN_NOTIFICATION, notification); 101 | } 102 | } 103 | } -------------------------------------------------------------------------------- /app/src/main/java/com/radioactiveyak/location_best_practices/services/EclairPlacesUpdateService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. 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 | 17 | package com.radioactiveyak.location_best_practices.services; 18 | 19 | /** 20 | * {@inheritDoc} 21 | * Extends the {@link PlacesUpdateService} to force Intent redelivery 22 | * on Eclaire+ devices (where this defaults to false). 23 | */ 24 | public class EclairPlacesUpdateService extends PlacesUpdateService { 25 | @Override 26 | protected void setIntentRedeliveryMode(boolean enable) { 27 | setIntentRedelivery(true); 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/main/java/com/radioactiveyak/location_best_practices/services/PlaceCheckinService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. 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 | 17 | package com.radioactiveyak.location_best_practices.services; 18 | 19 | import java.io.IOException; 20 | import java.net.URI; 21 | import java.net.URISyntaxException; 22 | import java.net.URLEncoder; 23 | import java.util.ArrayList; 24 | 25 | // TODO Update this with new API 26 | import org.apache.http.HttpResponse; 27 | import org.apache.http.client.ClientProtocolException; 28 | import org.apache.http.client.HttpClient; 29 | import org.apache.http.client.methods.HttpPost; 30 | import org.apache.http.entity.StringEntity; 31 | import org.apache.http.impl.client.DefaultHttpClient; 32 | 33 | import android.app.AlarmManager; 34 | import android.app.IntentService; 35 | import android.app.PendingIntent; 36 | import android.content.ComponentName; 37 | import android.content.ContentResolver; 38 | import android.content.ContentValues; 39 | import android.content.Context; 40 | import android.content.Intent; 41 | import android.content.SharedPreferences; 42 | import android.content.SharedPreferences.Editor; 43 | import android.content.pm.PackageManager; 44 | import android.database.Cursor; 45 | import android.net.ConnectivityManager; 46 | import android.net.NetworkInfo; 47 | import android.util.Log; 48 | 49 | import com.radioactiveyak.location_best_practices.PlacesConstants; 50 | import com.radioactiveyak.location_best_practices.content_providers.QueuedCheckinsContentProvider; 51 | import com.radioactiveyak.location_best_practices.receivers.ConnectivityChangedReceiver; 52 | import com.radioactiveyak.location_best_practices.utils.PlatformSpecificImplementationFactory; 53 | import com.radioactiveyak.location_best_practices.utils.base.SharedPreferenceSaver; 54 | 55 | /** 56 | * Service that notifies the underlying web service to Checkin to the specified venue. 57 | * TODO Replace or augment with a Service that performs ratings / reviews / etc. 58 | */ 59 | public class PlaceCheckinService extends IntentService { 60 | 61 | protected static String TAG = "PlaceCheckinService"; 62 | 63 | protected ContentResolver contentResolver; 64 | protected ConnectivityManager cm; 65 | protected AlarmManager alarmManager; 66 | protected PendingIntent retryQueuedCheckinsPendingIntent; 67 | protected SharedPreferences sharedPreferences; 68 | protected Editor sharedPreferencesEditor; 69 | protected SharedPreferenceSaver sharedPreferenceSaver; 70 | 71 | public PlaceCheckinService() { 72 | super(TAG); 73 | } 74 | 75 | @Override 76 | public void onCreate() { 77 | super.onCreate(); 78 | contentResolver = getContentResolver(); 79 | cm = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE); 80 | alarmManager = (AlarmManager)getSystemService(Context.ALARM_SERVICE); 81 | 82 | sharedPreferences = getSharedPreferences(PlacesConstants.SHARED_PREFERENCE_FILE, Context.MODE_PRIVATE); 83 | sharedPreferencesEditor = sharedPreferences.edit(); 84 | sharedPreferenceSaver = PlatformSpecificImplementationFactory.getSharedPreferenceSaver(this); 85 | 86 | Intent retryIntent = new Intent(PlacesConstants.RETRY_QUEUED_CHECKINS_ACTION); 87 | retryQueuedCheckinsPendingIntent = PendingIntent.getBroadcast(this, 0, retryIntent, PendingIntent.FLAG_UPDATE_CURRENT); 88 | } 89 | 90 | /** 91 | * {@inheritDoc} 92 | * Perform a checkin the specified venue. If the checkin fails, add it to the queue and 93 | * set an alarm to retry. 94 | * 95 | * Query the checkin queue to see if there are pending checkins to be retried. 96 | */ 97 | @Override 98 | protected void onHandleIntent(Intent intent) { 99 | // Retrieve the details for the checkin to perform. 100 | String reference = intent.getStringExtra(PlacesConstants.EXTRA_KEY_REFERENCE); 101 | String id = intent.getStringExtra(PlacesConstants.EXTRA_KEY_ID); 102 | long timeStamp = intent.getLongExtra(PlacesConstants.EXTRA_KEY_TIME_STAMP, 0); 103 | 104 | // Check if we're running in the foreground, if not, check if 105 | // we have permission to do background updates. 106 | boolean backgroundAllowed = cm.getBackgroundDataSetting(); 107 | boolean inBackground = sharedPreferences.getBoolean(PlacesConstants.EXTRA_KEY_IN_BACKGROUND, true); 108 | 109 | if (reference != null && !backgroundAllowed && inBackground) { 110 | addToQueue(timeStamp, reference, id); 111 | return; 112 | } 113 | 114 | // Check to see if we are connected to a data network. 115 | NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); 116 | boolean isConnected = activeNetwork != null && activeNetwork.isConnectedOrConnecting(); 117 | 118 | // If we're not connected then disable the retry Alarm, enable the Connectivity Changed Receiver 119 | // and add the new checkin directly to the queue. The Connectivity Changed Receiver will listen 120 | // for when we connect to a network and start this service to retry the checkins. 121 | if (!isConnected) { 122 | // No connection so no point triggering an alarm to retry until we're connected. 123 | alarmManager.cancel(retryQueuedCheckinsPendingIntent); 124 | 125 | // Enable the Connectivity Changed Receiver to listen for connection to a network 126 | // so we can commit the pending checkins. 127 | PackageManager pm = getPackageManager(); 128 | ComponentName connectivityReceiver = new ComponentName(this, ConnectivityChangedReceiver.class); 129 | pm.setComponentEnabledSetting(connectivityReceiver, 130 | PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 131 | PackageManager.DONT_KILL_APP); 132 | 133 | // Add this checkin to the queue. 134 | addToQueue(timeStamp, reference, id); 135 | } 136 | else { 137 | // Execute the checkin. If it fails, add it to the retry queue. 138 | if (reference != null) { 139 | if (!checkin(timeStamp, reference, id)) 140 | addToQueue(timeStamp, reference, id); 141 | } 142 | 143 | // Retry the queued checkins. 144 | ArrayList successfulCheckins = new ArrayList(); 145 | Cursor queuedCheckins = contentResolver.query(QueuedCheckinsContentProvider.CONTENT_URI, null, null, null, null); 146 | try { 147 | // Retry each checkin. 148 | while (queuedCheckins.moveToNext()) { 149 | long queuedTimeStamp = queuedCheckins.getLong(queuedCheckins.getColumnIndex(QueuedCheckinsContentProvider.KEY_TIME_STAMP)); 150 | String queuedReference = queuedCheckins.getString(queuedCheckins.getColumnIndex(QueuedCheckinsContentProvider.KEY_REFERENCE)); 151 | String queuedId = queuedCheckins.getString(queuedCheckins.getColumnIndex(QueuedCheckinsContentProvider.KEY_ID)); 152 | if (queuedReference == null || checkin(queuedTimeStamp, queuedReference, queuedId)) 153 | successfulCheckins.add(queuedReference); 154 | } 155 | 156 | // Delete the queued checkins that were successful. 157 | if (successfulCheckins.size() > 0) { 158 | StringBuilder sb = new StringBuilder("("+QueuedCheckinsContentProvider.KEY_REFERENCE + "='" + successfulCheckins.get(0) + "'"); 159 | for (int i = 1; i < successfulCheckins.size(); i++) 160 | sb.append(" OR " + QueuedCheckinsContentProvider.KEY_REFERENCE + " = '" + successfulCheckins.get(i) + "'"); 161 | sb.append(")"); 162 | int deleteCount = contentResolver.delete(QueuedCheckinsContentProvider.CONTENT_URI, sb.toString(), null); 163 | Log.d(TAG, "Deleted: " + deleteCount); 164 | } 165 | 166 | // If there are still queued checkins then set a non-waking alarm to retry them. 167 | queuedCheckins.requery(); 168 | if (queuedCheckins.getCount() > 0) { 169 | long triggerAtTime = System.currentTimeMillis() + PlacesConstants.CHECKIN_RETRY_INTERVAL; 170 | alarmManager.set(AlarmManager.ELAPSED_REALTIME, triggerAtTime, retryQueuedCheckinsPendingIntent); 171 | } 172 | else 173 | alarmManager.cancel(retryQueuedCheckinsPendingIntent); 174 | } 175 | finally { 176 | queuedCheckins.close(); 177 | } 178 | } 179 | } 180 | 181 | /** 182 | * Performs a checkin for the specified venue at the specified time. 183 | * TODO Add additional checkin / ratings / review details as appropriate for your service. 184 | * @param timeStamp Checkin timestamp 185 | * @param reference Checkin venue reference 186 | * @param id Checkin venue unique identifier 187 | * @return Successfully checked in 188 | */ 189 | protected boolean checkin(long timeStamp, String reference, String id) { 190 | if (reference != null) { 191 | try { 192 | // Construct the URI required to perform a checkin. 193 | // TODO Replace this with the checkin URI for your own web service. 194 | URI uri = new URI(PlacesConstants.PLACES_CHECKIN_URI + PlacesConstants.PLACES_API_KEY); 195 | 196 | // Construct the payload 197 | // TODO Replace with your own payload 198 | StringBuilder postData = new StringBuilder("\n"); 199 | postData.append(" "); 200 | postData.append(URLEncoder.encode(reference, "UTF-8")); 201 | postData.append("\n"); 202 | postData.append(""); 203 | 204 | // Construct and execute the HTTP Post 205 | HttpClient httpClient = new DefaultHttpClient(); 206 | HttpPost httppost = new HttpPost(uri); 207 | httppost.setEntity(new StringEntity(postData.toString())); 208 | HttpResponse response = httpClient.execute(httppost); 209 | 210 | // If the post was successful, check if this is the newest checkin, and if so save it and broadcast 211 | // an Intent to notify the application. 212 | if (response.getStatusLine().getReasonPhrase().equals(PlacesConstants.PLACES_CHECKIN_OK_STATUS)) { 213 | long lastCheckin = sharedPreferences.getLong(PlacesConstants.SP_KEY_LAST_CHECKIN_TIMESTAMP, 0); 214 | if (timeStamp > lastCheckin) { 215 | sharedPreferencesEditor.putLong(PlacesConstants.SP_KEY_LAST_CHECKIN_TIMESTAMP, timeStamp); 216 | sharedPreferencesEditor.putString(PlacesConstants.SP_KEY_LAST_CHECKIN_ID, id); 217 | sharedPreferenceSaver.savePreferences(sharedPreferencesEditor, false); 218 | Intent intent = new Intent(PlacesConstants.NEW_CHECKIN_ACTION); 219 | intent.putExtra(PlacesConstants.EXTRA_KEY_ID, id); 220 | sendBroadcast(intent); 221 | } 222 | return true; 223 | } 224 | // If the checkin fails return false. 225 | else 226 | return false; 227 | } catch (ClientProtocolException e) { 228 | Log.e(TAG, e.getMessage()); 229 | } catch (IOException e) { 230 | Log.e(TAG, e.getMessage()); 231 | } catch (URISyntaxException e) { 232 | Log.e(TAG, e.getMessage()); 233 | } 234 | } 235 | return false; 236 | } 237 | 238 | /** 239 | * Adds a checkin to the {@link QueuedCheckinsContentProvider} to be retried. 240 | * @param timeStamp Checkin timestamp 241 | * @param reference Checkin venue reference 242 | * @param id Checkin venue unique identifier 243 | * @return Successfully added to the queue 244 | */ 245 | protected boolean addToQueue(long timeStamp, String reference, String id) { 246 | // Construct the new / updated row. 247 | ContentValues values = new ContentValues(); 248 | values.put(QueuedCheckinsContentProvider.KEY_REFERENCE, reference); 249 | values.put(QueuedCheckinsContentProvider.KEY_ID, id); 250 | values.put(QueuedCheckinsContentProvider.KEY_TIME_STAMP, timeStamp); 251 | 252 | String where = QueuedCheckinsContentProvider.KEY_REFERENCE + " = '" + reference + "'"; 253 | 254 | // Update the existing checkin for this venue or add the venue to the queue. 255 | try { 256 | if (contentResolver.update(QueuedCheckinsContentProvider.CONTENT_URI, values, where, null) == 0) { 257 | if (contentResolver.insert(QueuedCheckinsContentProvider.CONTENT_URI, values) != null) 258 | return true; 259 | return true; 260 | } 261 | } 262 | catch (Exception ex) { 263 | Log.e(TAG, "Queuing checkin for " + reference + " failed."); 264 | } 265 | 266 | return false; 267 | } 268 | } -------------------------------------------------------------------------------- /app/src/main/java/com/radioactiveyak/location_best_practices/services/PlaceDetailsUpdateService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. 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 | 17 | package com.radioactiveyak.location_best_practices.services; 18 | 19 | import java.io.IOException; 20 | import java.io.InputStream; 21 | import java.net.HttpURLConnection; 22 | import java.net.MalformedURLException; 23 | import java.net.URL; 24 | import java.net.URLConnection; 25 | 26 | import javax.net.ssl.HttpsURLConnection; 27 | 28 | import org.xmlpull.v1.XmlPullParser; 29 | import org.xmlpull.v1.XmlPullParserException; 30 | import org.xmlpull.v1.XmlPullParserFactory; 31 | 32 | import android.app.IntentService; 33 | import android.content.ContentResolver; 34 | import android.content.ContentValues; 35 | import android.content.Context; 36 | import android.content.Intent; 37 | import android.content.SharedPreferences; 38 | import android.database.Cursor; 39 | import android.location.Location; 40 | import android.net.ConnectivityManager; 41 | import android.net.Uri; 42 | import android.util.Log; 43 | 44 | import com.radioactiveyak.location_best_practices.PlacesConstants; 45 | import com.radioactiveyak.location_best_practices.content_providers.PlaceDetailsContentProvider; 46 | 47 | /** 48 | * Service that queries the underlying web service to retrieve the full 49 | * details for the specified place / venue. 50 | * This Service is called by the {@link PlacesUpdateService} to prefetch details 51 | * for the nearby venues, or by the {@link PlacesActivity} and {@link PlaceDetailsFragment} 52 | * to retrieve the details for the selected place. 53 | * TODO Replace the URL and XML parsing to match the details available from your service. 54 | */ 55 | public class PlaceDetailsUpdateService extends IntentService { 56 | 57 | protected static String TAG = "PlaceDetailsIntentService"; 58 | 59 | protected ContentResolver contentResolver; 60 | protected String[] projection; 61 | protected SharedPreferences prefs; 62 | protected ConnectivityManager cm; 63 | 64 | public PlaceDetailsUpdateService() { 65 | super(TAG); 66 | } 67 | 68 | @Override 69 | public void onCreate() { 70 | super.onCreate(); 71 | contentResolver = getContentResolver(); 72 | projection = new String[] {PlaceDetailsContentProvider.KEY_LAST_UPDATE_TIME, PlaceDetailsContentProvider.KEY_FORCE_CACHE}; 73 | cm = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE); 74 | prefs = getSharedPreferences(PlacesConstants.SHARED_PREFERENCE_FILE, Context.MODE_PRIVATE); 75 | } 76 | 77 | /** 78 | * {@inheritDoc} 79 | * Check to see if we already have these details, and if so, whether or not we should update them. 80 | * Initiates an update where appropriate. 81 | */ 82 | @Override 83 | protected void onHandleIntent(Intent intent) { 84 | // Check if we're running in the foreground, if not, check if 85 | // we have permission to do background updates. 86 | boolean backgroundAllowed = cm.getBackgroundDataSetting(); 87 | boolean inBackground = prefs.getBoolean(PlacesConstants.EXTRA_KEY_IN_BACKGROUND, true); 88 | 89 | if (!backgroundAllowed && inBackground) return; 90 | 91 | // Extract the identifiers for the place to refresh the detail for. 92 | String reference = intent.getStringExtra(PlacesConstants.EXTRA_KEY_REFERENCE); 93 | String id = intent.getStringExtra(PlacesConstants.EXTRA_KEY_ID); 94 | 95 | // If this is a forced refresh (typically because the details UI is being displayed 96 | // then do an update and force this into the cache. 97 | boolean forceCache = intent.getBooleanExtra(PlacesConstants.EXTRA_KEY_FORCEREFRESH, false); 98 | boolean doUpdate = id == null || forceCache; 99 | 100 | // Check to see if the latency since the last update is sufficient to perform a refresh. 101 | if (!doUpdate) { 102 | Uri uri = Uri.withAppendedPath(PlaceDetailsContentProvider.CONTENT_URI, id); 103 | Cursor cursor = contentResolver.query(uri, projection, null, null, null); 104 | 105 | try { 106 | doUpdate = true; 107 | if (cursor.moveToFirst()) { 108 | if (cursor.getLong(cursor.getColumnIndex(PlaceDetailsContentProvider.KEY_LAST_UPDATE_TIME)) > System.currentTimeMillis()-PlacesConstants.MAX_DETAILS_UPDATE_LATENCY) 109 | doUpdate = false; 110 | } 111 | } 112 | finally { 113 | cursor.close(); 114 | } 115 | } 116 | 117 | // Hit the server for an update / refresh. 118 | if (doUpdate) { 119 | refreshPlaceDetails(reference, forceCache); 120 | } 121 | } 122 | 123 | /** 124 | * Request details for this place from the underlying web Service. 125 | * TODO Replace the URL and XML parsing with whatever is necessary for your service. 126 | * @param reference Reference 127 | * @param forceCache Force Cache 128 | */ 129 | protected void refreshPlaceDetails(String reference, boolean forceCache) { 130 | URL url; 131 | try { 132 | // TODO Replace with your web service URL schema. 133 | String placesFeed = PlacesConstants.PLACES_DETAIL_BASE_URI + reference + PlacesConstants.PLACES_API_KEY; 134 | 135 | // Make the web query. 136 | url = new URL(placesFeed); 137 | URLConnection connection = url.openConnection(); 138 | HttpsURLConnection httpConnection = (HttpsURLConnection)connection; 139 | int responseCode = httpConnection.getResponseCode(); 140 | 141 | if (responseCode == HttpURLConnection.HTTP_OK) { 142 | InputStream in = httpConnection.getInputStream(); 143 | 144 | // Extract the details from the returned feed. 145 | XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); 146 | factory.setNamespaceAware(true); 147 | XmlPullParser xpp = factory.newPullParser(); 148 | 149 | xpp.setInput(in, null); 150 | int eventType = xpp.getEventType(); 151 | while (eventType != XmlPullParser.END_DOCUMENT) { 152 | if (eventType == XmlPullParser.START_TAG && xpp.getName().equals("result")) { 153 | eventType = xpp.next(); 154 | String id = ""; 155 | String name = ""; 156 | String vicinity = ""; 157 | String types = ""; 158 | String locationLat = ""; 159 | String locationLng = ""; 160 | String viewport = ""; 161 | String icon = ""; 162 | String phone = ""; 163 | String address = ""; 164 | float rating = 0; 165 | String placeurl = ""; 166 | 167 | while (!(eventType == XmlPullParser.END_TAG && xpp.getName().equals("result"))) { 168 | if (eventType == XmlPullParser.START_TAG && xpp.getName().equals("name")) 169 | name = xpp.nextText(); 170 | else if (eventType == XmlPullParser.START_TAG && xpp.getName().equals("vicinity")) 171 | vicinity = xpp.nextText(); 172 | else if (eventType == XmlPullParser.START_TAG && xpp.getName().equals("type")) 173 | types = types == "" ? xpp.nextText() : types + " " + xpp.nextText(); 174 | else if (eventType == XmlPullParser.START_TAG && xpp.getName().equals("lat")) 175 | locationLat = xpp.nextText(); 176 | else if (eventType == XmlPullParser.START_TAG && xpp.getName().equals("lng")) 177 | locationLng = xpp.nextText(); 178 | else if (eventType == XmlPullParser.START_TAG && xpp.getName().equals("icon")) 179 | icon = xpp.nextText(); 180 | else if (eventType == XmlPullParser.START_TAG && xpp.getName().equals("id")) 181 | id = xpp.nextText(); 182 | else if (eventType == XmlPullParser.START_TAG && xpp.getName().equals("formatted_phone_number")) 183 | phone = xpp.nextText(); 184 | else if (eventType == XmlPullParser.START_TAG && xpp.getName().equals("formatted_address")) 185 | address = xpp.nextText(); 186 | else if (eventType == XmlPullParser.START_TAG && xpp.getName().equals("url")) 187 | placeurl = xpp.nextText(); 188 | else if (eventType == XmlPullParser.START_TAG && xpp.getName().equals("rating")) 189 | rating = Float.parseFloat(xpp.nextText()); 190 | 191 | eventType = xpp.next(); 192 | } 193 | Location placeLocation = new Location("XML"); 194 | try { 195 | placeLocation.setLatitude(Double.valueOf(locationLat)); 196 | placeLocation.setLongitude(Double.valueOf(locationLng)); 197 | } 198 | catch (NumberFormatException e) { 199 | Log.d(TAG, e.getMessage()); 200 | } 201 | // Add the new place to the Content Provider 202 | addPlaceDetail(id, name, vicinity, types, placeLocation, viewport, icon, reference, phone, address, rating, placeurl, forceCache); 203 | } 204 | eventType = xpp.next(); 205 | } 206 | } 207 | else 208 | Log.e(TAG, responseCode + ": " + httpConnection.getResponseMessage()); 209 | 210 | } catch (MalformedURLException e) { 211 | Log.e(TAG, e.getMessage()); 212 | } catch (IOException e) { 213 | Log.e(TAG, e.getMessage()); 214 | } catch (XmlPullParserException e) { 215 | Log.e(TAG, e.getMessage()); 216 | } 217 | finally { 218 | } 219 | } 220 | 221 | /** 222 | * Add details for a place / venue to the {@link PlaceDetailsContentProvider}. 223 | * TODO Update this with the details corresponding to what's available from your server. 224 | * @param id Unique identifier 225 | * @param name Name 226 | * @param vicinity Vicinity 227 | * @param types Types 228 | * @param location Location 229 | * @param viewport Viewport 230 | * @param icon Icon 231 | * @param reference Reference 232 | * @param phone Phone 233 | * @param address Address 234 | * @param rating Rating 235 | * @param url Url 236 | * @param forceCache Save to the persistent cache (do not delete) 237 | * @return Place detail has been updates or added 238 | */ 239 | protected boolean addPlaceDetail(String id, String name, String vicinity, String types, Location location, String viewport, String icon, String reference, String phone, String address, float rating, String url, boolean forceCache) { 240 | 241 | // Construct the new row. 242 | ContentValues values = new ContentValues(); 243 | values.put(PlaceDetailsContentProvider.KEY_ID, id); 244 | values.put(PlaceDetailsContentProvider.KEY_NAME, name); 245 | double lat = location.getLatitude(); 246 | double lng = location.getLongitude(); 247 | values.put(PlaceDetailsContentProvider.KEY_LOCATION_LAT, lat); 248 | values.put(PlaceDetailsContentProvider.KEY_LOCATION_LNG, lng); 249 | values.put(PlaceDetailsContentProvider.KEY_VICINITY, vicinity); 250 | values.put(PlaceDetailsContentProvider.KEY_TYPES, types); 251 | values.put(PlaceDetailsContentProvider.KEY_VIEWPORT, viewport); 252 | values.put(PlaceDetailsContentProvider.KEY_ICON, icon); 253 | values.put(PlaceDetailsContentProvider.KEY_REFERENCE, reference); 254 | values.put(PlaceDetailsContentProvider.KEY_PHONE, phone); 255 | values.put(PlaceDetailsContentProvider.KEY_ADDRESS, address); 256 | values.put(PlaceDetailsContentProvider.KEY_RATING, rating); 257 | values.put(PlaceDetailsContentProvider.KEY_URL, url); 258 | values.put(PlaceDetailsContentProvider.KEY_LAST_UPDATE_TIME, System.currentTimeMillis()); 259 | values.put(PlaceDetailsContentProvider.KEY_FORCE_CACHE, forceCache); 260 | 261 | // Update the existing listing, or add a new listing. 262 | String where = PlaceDetailsContentProvider.KEY_ID + " = '" + id + "'"; 263 | try { 264 | if (contentResolver.update(PlaceDetailsContentProvider.CONTENT_URI, values, where, null) == 0) { 265 | if (contentResolver.insert(PlaceDetailsContentProvider.CONTENT_URI, values) != null) 266 | return true; 267 | return true; 268 | } 269 | } 270 | catch (Exception ex) { 271 | Log.e(TAG, "Adding Detail for " + name + " failed."); 272 | } 273 | 274 | return false; 275 | } 276 | } -------------------------------------------------------------------------------- /app/src/main/java/com/radioactiveyak/location_best_practices/services/PlacesUpdateService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. 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 | 17 | package com.radioactiveyak.location_best_practices.services; 18 | 19 | import java.io.IOException; 20 | import java.io.InputStream; 21 | import java.net.HttpURLConnection; 22 | import java.net.MalformedURLException; 23 | import java.net.URL; 24 | import java.net.URLConnection; 25 | 26 | import javax.net.ssl.HttpsURLConnection; 27 | 28 | import org.xmlpull.v1.XmlPullParser; 29 | import org.xmlpull.v1.XmlPullParserException; 30 | import org.xmlpull.v1.XmlPullParserFactory; 31 | 32 | import android.app.IntentService; 33 | import android.content.ComponentName; 34 | import android.content.ContentResolver; 35 | import android.content.ContentValues; 36 | import android.content.Context; 37 | import android.content.Intent; 38 | import android.content.IntentFilter; 39 | import android.content.SharedPreferences; 40 | import android.content.SharedPreferences.Editor; 41 | import android.content.pm.PackageManager; 42 | import android.location.Location; 43 | import android.net.ConnectivityManager; 44 | import android.net.NetworkInfo; 45 | import android.os.BatteryManager; 46 | import android.os.Bundle; 47 | import android.util.Log; 48 | 49 | import com.radioactiveyak.location_best_practices.PlacesConstants; 50 | import com.radioactiveyak.location_best_practices.content_providers.PlaceDetailsContentProvider; 51 | import com.radioactiveyak.location_best_practices.content_providers.PlacesContentProvider; 52 | import com.radioactiveyak.location_best_practices.receivers.ConnectivityChangedReceiver; 53 | import com.radioactiveyak.location_best_practices.receivers.LocationChangedReceiver; 54 | import com.radioactiveyak.location_best_practices.receivers.PassiveLocationChangedReceiver; 55 | 56 | /** 57 | * Service that requests a list of nearby locations from the underlying web service. 58 | * TODO Update the URL and XML parsing to correspond with your underlying web service. 59 | */ 60 | public class PlacesUpdateService extends IntentService { 61 | 62 | protected static String TAG = "PlacesUpdateService"; 63 | 64 | protected ContentResolver contentResolver; 65 | protected SharedPreferences prefs; 66 | protected Editor prefsEditor; 67 | protected ConnectivityManager cm; 68 | protected boolean lowBattery = false; 69 | protected boolean mobileData = false; 70 | protected int prefetchCount = 0; 71 | 72 | public PlacesUpdateService() { 73 | super(TAG); 74 | setIntentRedeliveryMode(false); 75 | } 76 | 77 | /** 78 | * Set the Intent Redelivery mode to true to ensure the Service starts "Sticky" 79 | * Defaults to "true" on legacy devices. 80 | */ 81 | protected void setIntentRedeliveryMode(boolean enable) {} 82 | 83 | /** 84 | * Returns battery status. True if less than 10% remaining. 85 | * @param battery Battery Intent 86 | * @return Battery is low 87 | */ 88 | protected boolean getIsLowBattery(Intent battery) { 89 | float pctLevel = (float)battery.getIntExtra(BatteryManager.EXTRA_LEVEL, 1) / 90 | battery.getIntExtra(BatteryManager.EXTRA_SCALE, 1); 91 | return pctLevel < 0.15; 92 | } 93 | 94 | @Override 95 | public void onCreate() { 96 | super.onCreate(); 97 | cm = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE); 98 | contentResolver = getContentResolver(); 99 | prefs = getSharedPreferences(PlacesConstants.SHARED_PREFERENCE_FILE, Context.MODE_PRIVATE); 100 | prefsEditor = prefs.edit(); 101 | } 102 | 103 | /** 104 | * {@inheritDoc} 105 | * Checks the battery and connectivity state before removing stale venues 106 | * and initiating a server poll for new venues around the specified 107 | * location within the given radius. 108 | */ 109 | @Override 110 | protected void onHandleIntent(Intent intent) { 111 | // Check if we're running in the foreground, if not, check if 112 | // we have permission to do background updates. 113 | boolean backgroundAllowed = cm.getBackgroundDataSetting(); 114 | boolean inBackground = prefs.getBoolean(PlacesConstants.EXTRA_KEY_IN_BACKGROUND, true); 115 | 116 | if (!backgroundAllowed && inBackground) return; 117 | 118 | // Extract the location and radius around which to conduct our search. 119 | Location location = new Location(PlacesConstants.CONSTRUCTED_LOCATION_PROVIDER); 120 | int radius = PlacesConstants.DEFAULT_RADIUS; 121 | 122 | Bundle extras = intent.getExtras(); 123 | if (intent.hasExtra(PlacesConstants.EXTRA_KEY_LOCATION)) { 124 | location = (Location)(extras.get(PlacesConstants.EXTRA_KEY_LOCATION)); 125 | radius = extras.getInt(PlacesConstants.EXTRA_KEY_RADIUS, PlacesConstants.DEFAULT_RADIUS); 126 | } 127 | 128 | // Check if we're in a low battery situation. 129 | IntentFilter batIntentFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); 130 | Intent battery = registerReceiver(null, batIntentFilter); 131 | lowBattery = getIsLowBattery(battery); 132 | 133 | // Check if we're connected to a data network, and if so - if it's a mobile network. 134 | NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); 135 | boolean isConnected = activeNetwork != null && activeNetwork.isConnectedOrConnecting(); 136 | mobileData = activeNetwork != null && activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE; 137 | 138 | // If we're not connected, enable the connectivity receiver and disable the location receiver. 139 | // There's no point trying to poll the server for updates if we're not connected, and the 140 | // connectivity receiver will turn the location-based updates back on once we have a connection. 141 | if (!isConnected) { 142 | PackageManager pm = getPackageManager(); 143 | 144 | ComponentName connectivityReceiver = new ComponentName(this, ConnectivityChangedReceiver.class); 145 | ComponentName locationReceiver = new ComponentName(this, LocationChangedReceiver.class); 146 | ComponentName passiveLocationReceiver = new ComponentName(this, PassiveLocationChangedReceiver.class); 147 | 148 | pm.setComponentEnabledSetting(connectivityReceiver, 149 | PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 150 | PackageManager.DONT_KILL_APP); 151 | 152 | pm.setComponentEnabledSetting(locationReceiver, 153 | PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 154 | PackageManager.DONT_KILL_APP); 155 | 156 | pm.setComponentEnabledSetting(passiveLocationReceiver, 157 | PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 158 | PackageManager.DONT_KILL_APP); 159 | } 160 | else { 161 | // If we are connected check to see if this is a forced update (typically triggered 162 | // when the location has changed). 163 | boolean doUpdate = intent.getBooleanExtra(PlacesConstants.EXTRA_KEY_FORCEREFRESH, false); 164 | 165 | // If it's not a forced update (for example from the Activity being restarted) then 166 | // check to see if we've moved far enough, or there's been a long enough delay since 167 | // the last update and if so, enforce a new update. 168 | if (!doUpdate) { 169 | // Retrieve the last update time and place. 170 | long lastTime = prefs.getLong(PlacesConstants.SP_KEY_LAST_LIST_UPDATE_TIME, Long.MIN_VALUE); 171 | long lastLat = prefs.getLong(PlacesConstants.SP_KEY_LAST_LIST_UPDATE_LAT, Long.MIN_VALUE); 172 | long lastLng = prefs.getLong(PlacesConstants.SP_KEY_LAST_LIST_UPDATE_LNG, Long.MIN_VALUE); 173 | Location lastLocation = new Location(PlacesConstants.CONSTRUCTED_LOCATION_PROVIDER); 174 | lastLocation.setLatitude(lastLat); 175 | lastLocation.setLongitude(lastLng); 176 | 177 | // If update time and distance bounds have been passed, do an update. 178 | if ((lastTime < System.currentTimeMillis()-PlacesConstants.MAX_TIME) || 179 | (lastLocation.distanceTo(location) > PlacesConstants.MAX_DISTANCE)) 180 | doUpdate = true; 181 | } 182 | 183 | if (doUpdate) { 184 | // Refresh the prefetch count for each new location. 185 | prefetchCount = 0; 186 | // Remove the old locations 187 | removeOldLocations(location, radius); 188 | // Hit the server for new venues for the current location. 189 | refreshPlaces(location, radius); 190 | } 191 | else 192 | Log.d(TAG, "Place List is fresh: Not refreshing"); 193 | 194 | // Retry any queued checkins. 195 | Intent checkinServiceIntent = new Intent(this, PlaceCheckinService.class); 196 | startService(checkinServiceIntent); 197 | } 198 | Log.d(TAG, "Place List Download Service Complete"); 199 | } 200 | 201 | /** 202 | * Polls the underlying service to return a list of places within the specified 203 | * radius of the specified Location. 204 | * @param location Location 205 | * @param radius Radius 206 | */ 207 | protected void refreshPlaces(Location location, int radius) { 208 | // Log to see if we'll be prefetching the details page of each new place. 209 | if (mobileData) { 210 | Log.d(TAG, "Not prefetching due to being on mobile"); 211 | } else if (lowBattery) { 212 | Log.d(TAG, "Not prefetching due to low battery"); 213 | } 214 | 215 | long currentTime = System.currentTimeMillis(); 216 | URL url; 217 | 218 | try { 219 | // TODO Replace this with a URI to your own service. 220 | String locationStr = location.getLatitude() + "," + location.getLongitude(); 221 | String baseURI = PlacesConstants.PLACES_LIST_BASE_URI; 222 | String placesFeed = baseURI + "&location=" + locationStr + "&radius=" + radius + PlacesConstants.PLACES_API_KEY; 223 | url = new URL(placesFeed); 224 | 225 | // Open the connection 226 | URLConnection connection = url.openConnection(); 227 | HttpsURLConnection httpConnection = (HttpsURLConnection)connection; 228 | int responseCode = httpConnection.getResponseCode(); 229 | 230 | if (responseCode == HttpURLConnection.HTTP_OK) { 231 | // Use the XML Pull Parser to extract each nearby location. 232 | // TODO Replace the XML parsing to extract your own place list. 233 | InputStream in = httpConnection.getInputStream(); 234 | XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); 235 | factory.setNamespaceAware(true); 236 | XmlPullParser xpp = factory.newPullParser(); 237 | 238 | xpp.setInput(in, null); 239 | int eventType = xpp.getEventType(); 240 | while (eventType != XmlPullParser.END_DOCUMENT) { 241 | if (eventType == XmlPullParser.START_TAG && xpp.getName().equals("result")) { 242 | eventType = xpp.next(); 243 | String id = ""; 244 | String name = ""; 245 | String vicinity = ""; 246 | String types = ""; 247 | String locationLat = ""; 248 | String locationLng = ""; 249 | String viewport = ""; 250 | String icon = ""; 251 | String reference = ""; 252 | while (!(eventType == XmlPullParser.END_TAG && xpp.getName().equals("result"))) { 253 | if (eventType == XmlPullParser.START_TAG && xpp.getName().equals("name")) 254 | name = xpp.nextText(); 255 | else if (eventType == XmlPullParser.START_TAG && xpp.getName().equals("vicinity")) 256 | vicinity = xpp.nextText(); 257 | else if (eventType == XmlPullParser.START_TAG && xpp.getName().equals("type")) 258 | types = types == "" ? xpp.nextText() : types + " " + xpp.nextText(); 259 | else if (eventType == XmlPullParser.START_TAG && xpp.getName().equals("lat")) 260 | locationLat = xpp.nextText(); 261 | else if (eventType == XmlPullParser.START_TAG && xpp.getName().equals("lng")) 262 | locationLng = xpp.nextText(); 263 | else if (eventType == XmlPullParser.START_TAG && xpp.getName().equals("icon")) 264 | icon = xpp.nextText(); 265 | else if (eventType == XmlPullParser.START_TAG && xpp.getName().equals("reference")) 266 | reference = xpp.nextText(); 267 | else if (eventType == XmlPullParser.START_TAG && xpp.getName().equals("id")) 268 | id = xpp.nextText(); 269 | eventType = xpp.next(); 270 | } 271 | Location placeLocation = new Location(PlacesConstants.CONSTRUCTED_LOCATION_PROVIDER); 272 | placeLocation.setLatitude(Double.valueOf(locationLat)); 273 | placeLocation.setLongitude(Double.valueOf(locationLng)); 274 | 275 | // Add each new place to the Places Content Provider 276 | addPlace(location, id, name, vicinity, types, placeLocation, viewport, icon, reference, currentTime); 277 | } 278 | eventType = xpp.next(); 279 | } 280 | 281 | // Remove places from the PlacesContentProviderlist that aren't from this updte. 282 | String where = PlaceDetailsContentProvider.KEY_LAST_UPDATE_TIME + " < " + currentTime; 283 | contentResolver.delete(PlacesContentProvider.CONTENT_URI, where, null); 284 | 285 | // Save the last update time and place to the Shared Preferences. 286 | prefsEditor.putLong(PlacesConstants.SP_KEY_LAST_LIST_UPDATE_LAT, (long) location.getLatitude()); 287 | prefsEditor.putLong(PlacesConstants.SP_KEY_LAST_LIST_UPDATE_LNG, (long) location.getLongitude()); 288 | prefsEditor.putLong(PlacesConstants.SP_KEY_LAST_LIST_UPDATE_TIME, System.currentTimeMillis()); 289 | prefsEditor.commit(); 290 | } 291 | else 292 | Log.e(TAG, responseCode + ": " + httpConnection.getResponseMessage()); 293 | 294 | } catch (MalformedURLException e) { 295 | Log.e(TAG, e.getMessage()); 296 | } catch (IOException e) { 297 | Log.e(TAG, e.getMessage()); 298 | } catch (XmlPullParserException e) { 299 | Log.e(TAG, e.getMessage()); 300 | } 301 | finally { 302 | } 303 | } 304 | 305 | /** 306 | * Adds the new place to the {@link PlacesContentProvider} using the values passed in. 307 | * TODO Update this method to accept and persist the place information your service provides. 308 | * @param currentLocation Current location 309 | * @param id Unique identifier 310 | * @param name Name 311 | * @param vicinity Vicinity 312 | * @param types Types 313 | * @param location Location 314 | * @param viewport Viewport 315 | * @param icon Icon 316 | * @param reference Reference 317 | * @param currentTime Current time 318 | * @return Successfully added 319 | */ 320 | protected boolean addPlace(Location currentLocation, String id, String name, String vicinity, String types, Location location, String viewport, String icon, String reference, long currentTime) { 321 | // Contruct the Content Values 322 | ContentValues values = new ContentValues(); 323 | values.put(PlacesContentProvider.KEY_ID, id); 324 | values.put(PlacesContentProvider.KEY_NAME, name); 325 | double lat = location.getLatitude(); 326 | double lng = location.getLongitude(); 327 | values.put(PlacesContentProvider.KEY_LOCATION_LAT, lat); 328 | values.put(PlacesContentProvider.KEY_LOCATION_LNG, lng); 329 | values.put(PlacesContentProvider.KEY_VICINITY, vicinity); 330 | values.put(PlacesContentProvider.KEY_TYPES, types); 331 | values.put(PlacesContentProvider.KEY_VIEWPORT, viewport); 332 | values.put(PlacesContentProvider.KEY_ICON, icon); 333 | values.put(PlacesContentProvider.KEY_REFERENCE, reference); 334 | values.put(PlacesContentProvider.KEY_LAST_UPDATE_TIME, currentTime); 335 | 336 | // Calculate the distance between the current location and the venue's location 337 | float distance = 0; 338 | if (currentLocation != null && location != null) 339 | distance = currentLocation.distanceTo(location); 340 | values.put(PlacesContentProvider.KEY_DISTANCE, distance); 341 | 342 | // Update or add the new place to the PlacesContentProvider 343 | String where = PlacesContentProvider.KEY_ID + " = '" + id + "'"; 344 | boolean result = false; 345 | try { 346 | if (contentResolver.update(PlacesContentProvider.CONTENT_URI, values, where, null) == 0) { 347 | if (contentResolver.insert(PlacesContentProvider.CONTENT_URI, values) != null) 348 | result = true; 349 | } 350 | else 351 | result = true; 352 | } 353 | catch (Exception ex) { 354 | Log.e("PLACES", "Adding " + name + " failed."); 355 | } 356 | 357 | // If we haven't yet reached our prefetching limit, and we're either 358 | // on WiFi or don't have a WiFi-only prefetching restriction, and we 359 | // either don't have low batter or don't have a low battery prefetching 360 | // restriction, then prefetch the details for this newly added place. 361 | if ((prefetchCount < PlacesConstants.PREFETCH_LIMIT) && 362 | (!PlacesConstants.PREFETCH_ON_WIFI_ONLY || !mobileData) && 363 | (!PlacesConstants.DISABLE_PREFETCH_ON_LOW_BATTERY || !lowBattery)) { 364 | prefetchCount++; 365 | 366 | // Start the PlaceDetailsUpdateService to prefetch the details for this place. 367 | // As we're prefetching, don't force the refresh if we already have data. 368 | Intent updateServiceIntent = new Intent(this, PlaceDetailsUpdateService.class); 369 | updateServiceIntent.putExtra(PlacesConstants.EXTRA_KEY_REFERENCE, reference); 370 | updateServiceIntent.putExtra(PlacesConstants.EXTRA_KEY_ID, id); 371 | updateServiceIntent.putExtra(PlacesConstants.EXTRA_KEY_FORCEREFRESH, false); 372 | startService(updateServiceIntent); 373 | } 374 | 375 | return result; 376 | } 377 | 378 | /** 379 | * Remove stale place detail records unless we've set the persistent cache flag to true. 380 | * This is typically the case where a place has actually been viewed rather than prefetched. 381 | * @param location Location 382 | * @param radius Radius 383 | */ 384 | protected void removeOldLocations(Location location, int radius) { 385 | // Stale Detail Pages 386 | long minTime = System.currentTimeMillis()-PlacesConstants.MAX_DETAILS_UPDATE_LATENCY; 387 | String where = PlaceDetailsContentProvider.KEY_LAST_UPDATE_TIME + " < " + minTime + " AND " + 388 | PlaceDetailsContentProvider.KEY_FORCE_CACHE + " = 0"; 389 | contentResolver.delete(PlaceDetailsContentProvider.CONTENT_URI, where, null); 390 | } 391 | } -------------------------------------------------------------------------------- /app/src/main/java/com/radioactiveyak/location_best_practices/utils/FroyoLocationUpdateRequester.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. 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 | 17 | package com.radioactiveyak.location_best_practices.utils; 18 | 19 | import android.app.PendingIntent; 20 | import android.location.LocationManager; 21 | 22 | import com.radioactiveyak.location_best_practices.PlacesConstants; 23 | import com.radioactiveyak.location_best_practices.utils.base.LocationUpdateRequester; 24 | 25 | /** 26 | * Provides support for initiating active and passive location updates 27 | * optimized for the Froyo release. Includes use of the Passive Location Provider. 28 | * 29 | * Uses broadcast Intents to notify the app of location changes. 30 | */ 31 | public class FroyoLocationUpdateRequester extends LocationUpdateRequester{ 32 | 33 | public FroyoLocationUpdateRequester(LocationManager locationManager) { 34 | super(locationManager); 35 | } 36 | 37 | /** 38 | * {@inheritDoc} 39 | */ 40 | @Override 41 | public void requestPassiveLocationUpdates(long minTime, long minDistance, PendingIntent pendingIntent) { 42 | // Froyo introduced the Passive Location Provider, which receives updates whenever a 3rd party app 43 | // receives location updates. 44 | locationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, PlacesConstants.MAX_TIME, PlacesConstants.MAX_DISTANCE, pendingIntent); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/radioactiveyak/location_best_practices/utils/FroyoSharedPreferenceSaver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. 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 | 17 | package com.radioactiveyak.location_best_practices.utils; 18 | 19 | import android.app.backup.BackupManager; 20 | import android.content.Context; 21 | import android.content.SharedPreferences; 22 | import android.content.SharedPreferences.Editor; 23 | 24 | /** 25 | * Save {@link SharedPreferences} and provide the option to notify 26 | * the BackupManager to initiate a backup. 27 | */ 28 | public class FroyoSharedPreferenceSaver extends LegacySharedPreferenceSaver { 29 | 30 | protected BackupManager backupManager; 31 | 32 | public FroyoSharedPreferenceSaver(Context context) { 33 | super(context); 34 | backupManager = new BackupManager(context); 35 | } 36 | 37 | /** 38 | * {@inheritDoc} 39 | */ 40 | @Override 41 | public void savePreferences(Editor editor, boolean backup) { 42 | editor.commit(); 43 | backupManager.dataChanged(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/com/radioactiveyak/location_best_practices/utils/GingerbreadLastLocationFinder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. 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 | 17 | package com.radioactiveyak.location_best_practices.utils; 18 | 19 | import java.util.List; 20 | 21 | import android.app.PendingIntent; 22 | import android.content.BroadcastReceiver; 23 | import android.content.Context; 24 | import android.content.Intent; 25 | import android.content.IntentFilter; 26 | import android.location.Criteria; 27 | import android.location.Location; 28 | import android.location.LocationListener; 29 | import android.location.LocationManager; 30 | 31 | import com.radioactiveyak.location_best_practices.utils.base.ILastLocationFinder; 32 | 33 | /** 34 | * Optimized implementation of Last Location Finder for devices running Gingerbread 35 | * and above. 36 | * 37 | * This class let's you find the "best" (most accurate and timely) previously 38 | * detected location using whatever providers are available. 39 | * 40 | * Where a timely / accurate previous location is not detected it will 41 | * return the newest location (where one exists) and setup a oneshot 42 | * location update to find the current location. 43 | */ 44 | public class GingerbreadLastLocationFinder implements ILastLocationFinder { 45 | 46 | protected static String TAG = "LastLocationFinder"; 47 | protected static String SINGLE_LOCATION_UPDATE_ACTION = "com.radioactiveyak.places.SINGLE_LOCATION_UPDATE_ACTION"; 48 | 49 | protected PendingIntent singleUpatePI; 50 | protected LocationListener locationListener; 51 | protected LocationManager locationManager; 52 | protected Context context; 53 | protected Criteria criteria; 54 | 55 | /** 56 | * Construct a new Gingerbread Last Location Finder. 57 | * @param context Context 58 | */ 59 | public GingerbreadLastLocationFinder(Context context) { 60 | this.context = context; 61 | locationManager = (LocationManager)context.getSystemService(Context.LOCATION_SERVICE); 62 | // Coarse accuracy is specified here to get the fastest possible result. 63 | // The calling Activity will likely (or have already) request ongoing 64 | // updates using the Fine location provider. 65 | criteria = new Criteria(); 66 | criteria.setAccuracy(Criteria.ACCURACY_LOW); 67 | 68 | // Construct the Pending Intent that will be broadcast by the oneshot 69 | // location update. 70 | Intent updateIntent = new Intent(SINGLE_LOCATION_UPDATE_ACTION); 71 | singleUpatePI = PendingIntent.getBroadcast(context, 0, updateIntent, PendingIntent.FLAG_UPDATE_CURRENT); 72 | } 73 | 74 | /** 75 | * Returns the most accurate and timely previously detected location. 76 | * Where the last result is beyond the specified maximum distance or 77 | * latency a one-off location update is returned via the {@link LocationListener} 78 | * specified in {@link setChangedLocationListener}. 79 | * @param minDistance Minimum distance before we require a location update. 80 | * @param minTime Minimum time required between location updates. 81 | * @return The most accurate and / or timely previously detected location. 82 | */ 83 | public Location getLastBestLocation(int minDistance, long minTime) { 84 | Location bestResult = null; 85 | float bestAccuracy = Float.MAX_VALUE; 86 | long bestTime = Long.MIN_VALUE; 87 | 88 | // Iterate through all the providers on the system, keeping 89 | // note of the most accurate result within the acceptable time limit. 90 | // If no result is found within maxTime, return the newest Location. 91 | List matchingProviders = locationManager.getAllProviders(); 92 | for (String provider: matchingProviders) { 93 | Location location = locationManager.getLastKnownLocation(provider); 94 | if (location != null) { 95 | float accuracy = location.getAccuracy(); 96 | long time = location.getTime(); 97 | 98 | if ((time > minTime && accuracy < bestAccuracy)) { 99 | bestResult = location; 100 | bestAccuracy = accuracy; 101 | bestTime = time; 102 | } 103 | else if (time < minTime && bestAccuracy == Float.MAX_VALUE && time > bestTime) { 104 | bestResult = location; 105 | bestTime = time; 106 | } 107 | } 108 | } 109 | 110 | // If the best result is beyond the allowed time limit, or the accuracy of the 111 | // best result is wider than the acceptable maximum distance, request a single update. 112 | // This check simply implements the same conditions we set when requesting regular 113 | // location updates every [minTime] and [minDistance]. 114 | if (locationListener != null && (bestTime < minTime || bestAccuracy > minDistance)) { 115 | IntentFilter locIntentFilter = new IntentFilter(SINGLE_LOCATION_UPDATE_ACTION); 116 | context.registerReceiver(singleUpdateReceiver, locIntentFilter); 117 | locationManager.requestSingleUpdate(criteria, singleUpatePI); 118 | } 119 | 120 | return bestResult; 121 | } 122 | 123 | /** 124 | * This {@link BroadcastReceiver} listens for a single location 125 | * update before unregistering itself. 126 | * The oneshot location update is returned via the {@link LocationListener} 127 | * specified in {@link setChangedLocationListener}. 128 | */ 129 | protected BroadcastReceiver singleUpdateReceiver = new BroadcastReceiver() { 130 | @Override 131 | public void onReceive(Context context, Intent intent) { 132 | context.unregisterReceiver(singleUpdateReceiver); 133 | 134 | String key = LocationManager.KEY_LOCATION_CHANGED; 135 | Location location = (Location)intent.getExtras().get(key); 136 | 137 | if (locationListener != null && location != null) 138 | locationListener.onLocationChanged(location); 139 | 140 | locationManager.removeUpdates(singleUpatePI); 141 | } 142 | }; 143 | 144 | /** 145 | * {@inheritDoc} 146 | */ 147 | public void setChangedLocationListener(LocationListener l) { 148 | locationListener = l; 149 | } 150 | 151 | /** 152 | * {@inheritDoc} 153 | */ 154 | public void cancel() { 155 | locationManager.removeUpdates(singleUpatePI); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /app/src/main/java/com/radioactiveyak/location_best_practices/utils/GingerbreadLocationUpdateRequester.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. 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 | 17 | package com.radioactiveyak.location_best_practices.utils; 18 | 19 | import android.app.PendingIntent; 20 | import android.location.Criteria; 21 | import android.location.LocationManager; 22 | 23 | /** 24 | * Provides support for initiating active and passive location updates 25 | * optimized for the Gingerbread release. Includes use of the Passive Location Provider. 26 | * 27 | * Uses broadcast Intents to notify the app of location changes. 28 | */ 29 | public class GingerbreadLocationUpdateRequester extends FroyoLocationUpdateRequester{ 30 | 31 | public GingerbreadLocationUpdateRequester(LocationManager locationManager) { 32 | super(locationManager); 33 | } 34 | 35 | /** 36 | * {@inheritDoc} 37 | */ 38 | @Override 39 | public void requestLocationUpdates(long minTime, long minDistance, Criteria criteria, PendingIntent pendingIntent) { 40 | // Gingerbread supports a location update request that accepts criteria directly. 41 | // Note that we aren't monitoring this provider to check if it becomes disabled - this is handled by the calling Activity. 42 | locationManager.requestLocationUpdates(minTime, minDistance, criteria, pendingIntent); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/radioactiveyak/location_best_practices/utils/GingerbreadSharedPreferenceSaver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. 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 | 17 | package com.radioactiveyak.location_best_practices.utils; 18 | 19 | import android.content.Context; 20 | import android.content.SharedPreferences; 21 | 22 | /** 23 | * Save {@link SharedPreferences} using the asynchronous apply method available 24 | * in Gingerbread, and provide the option to notify the BackupManager to 25 | * initiate a backup. 26 | */ 27 | public class GingerbreadSharedPreferenceSaver extends FroyoSharedPreferenceSaver { 28 | 29 | public GingerbreadSharedPreferenceSaver(Context context) { 30 | super(context); 31 | } 32 | 33 | /** 34 | * {@inheritDoc} 35 | */ 36 | @Override 37 | public void savePreferences(SharedPreferences.Editor editor, boolean backup) { 38 | editor.apply(); 39 | backupManager.dataChanged(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/radioactiveyak/location_best_practices/utils/HoneycombStrictMode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. 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 | 17 | package com.radioactiveyak.location_best_practices.utils; 18 | 19 | import android.os.StrictMode; 20 | 21 | import com.radioactiveyak.location_best_practices.utils.base.IStrictMode; 22 | 23 | /** 24 | * Implementation that supports the Strict Mode functionality 25 | * available Honeycomb. 26 | */ 27 | public class HoneycombStrictMode implements IStrictMode { 28 | protected static String TAG = "HoneycombStrictMode"; 29 | 30 | /** 31 | * Enable {@link StrictMode} 32 | * TODO Set your preferred Strict Mode features. 33 | */ 34 | public void enableStrictMode() { 35 | StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() 36 | .detectDiskReads() 37 | .detectDiskWrites() 38 | .detectNetwork() 39 | .penaltyLog() 40 | .penaltyFlashScreen() 41 | .build()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/radioactiveyak/location_best_practices/utils/LegacyLastLocationFinder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. 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 | 17 | package com.radioactiveyak.location_best_practices.utils; 18 | 19 | import java.util.List; 20 | 21 | import android.content.Context; 22 | import android.location.Criteria; 23 | import android.location.Location; 24 | import android.location.LocationListener; 25 | import android.location.LocationManager; 26 | import android.os.Bundle; 27 | import android.util.Log; 28 | 29 | import com.radioactiveyak.location_best_practices.utils.base.ILastLocationFinder; 30 | 31 | /** 32 | * Legacy implementation of Last Location Finder for all Android platforms 33 | * down to Android 1.6. 34 | * 35 | * This class let's you find the "best" (most accurate and timely) previously 36 | * detected location using whatever providers are available. 37 | * 38 | * Where a timely / accurate previous location is not detected it will 39 | * return the newest location (where one exists) and setup a one-off 40 | * location update to find the current location. 41 | */ 42 | public class LegacyLastLocationFinder implements ILastLocationFinder { 43 | 44 | protected static String TAG = "PreGingerbreadLastLocationFinder"; 45 | 46 | protected LocationListener locationListener; 47 | protected LocationManager locationManager; 48 | protected Criteria criteria; 49 | protected Context context; 50 | 51 | /** 52 | * Construct a new Legacy Last Location Finder. 53 | * @param context Context 54 | */ 55 | public LegacyLastLocationFinder(Context context) { 56 | locationManager = (LocationManager)context.getSystemService(Context.LOCATION_SERVICE); 57 | criteria = new Criteria(); 58 | // Coarse accuracy is specified here to get the fastest possible result. 59 | // The calling Activity will likely (or have already) request ongoing 60 | // updates using the Fine location provider. 61 | criteria.setAccuracy(Criteria.ACCURACY_COARSE); 62 | this.context = context; 63 | } 64 | 65 | 66 | /** 67 | * Returns the most accurate and timely previously detected location. 68 | * Where the last result is beyond the specified maximum distance or 69 | * latency a one-off location update is returned via the {@link LocationListener} 70 | * specified in {@link setChangedLocationListener}. 71 | * @param minDistance Minimum distance before we require a location update. 72 | * @param minTime Minimum time required between location updates. 73 | * @return The most accurate and / or timely previously detected location. 74 | */ 75 | public Location getLastBestLocation(int minDistance, long minTime) { 76 | Location bestResult = null; 77 | float bestAccuracy = Float.MAX_VALUE; 78 | long bestTime = Long.MAX_VALUE; 79 | 80 | // Iterate through all the providers on the system, keeping 81 | // note of the most accurate result within the acceptable time limit. 82 | // If no result is found within maxTime, return the newest Location. 83 | List matchingProviders = locationManager.getAllProviders(); 84 | for (String provider: matchingProviders) { 85 | Location location = locationManager.getLastKnownLocation(provider); 86 | if (location != null) { 87 | float accuracy = location.getAccuracy(); 88 | long time = location.getTime(); 89 | 90 | if ((time < minTime && accuracy < bestAccuracy)) { 91 | bestResult = location; 92 | bestAccuracy = accuracy; 93 | bestTime = time; 94 | } 95 | else if (time > minTime && bestAccuracy == Float.MAX_VALUE && time < bestTime) { 96 | bestResult = location; 97 | bestTime = time; 98 | } 99 | } 100 | } 101 | 102 | // If the best result is beyond the allowed time limit, or the accuracy of the 103 | // best result is wider than the acceptable maximum distance, request a single update. 104 | // This check simply implements the same conditions we set when requesting regular 105 | // location updates every [minTime] and [minDistance]. 106 | // Prior to Gingerbread "one-shot" updates weren't available, so we need to implement 107 | // this manually. 108 | if (locationListener != null && (bestTime > minTime || bestAccuracy > minDistance)) { 109 | String provider = locationManager.getBestProvider(criteria, true); 110 | if (provider != null) 111 | locationManager.requestLocationUpdates(provider, 0, 0, singeUpdateListener, context.getMainLooper()); 112 | } 113 | 114 | return bestResult; 115 | } 116 | 117 | /** 118 | * This one-off {@link LocationListener} simply listens for a single location 119 | * update before unregistering itself. 120 | * The one-off location update is returned via the {@link LocationListener} 121 | * specified in {@link setChangedLocationListener}. 122 | */ 123 | protected LocationListener singeUpdateListener = new LocationListener() { 124 | public void onLocationChanged(Location location) { 125 | Log.d(TAG, "Single Location Update Received: " + location.getLatitude() + "," + location.getLongitude()); 126 | if (locationListener != null && location != null) 127 | locationListener.onLocationChanged(location); 128 | locationManager.removeUpdates(singeUpdateListener); 129 | } 130 | 131 | public void onStatusChanged(String provider, int status, Bundle extras) {} 132 | public void onProviderEnabled(String provider) {} 133 | public void onProviderDisabled(String provider) {} 134 | }; 135 | 136 | /** 137 | * {@inheritDoc} 138 | */ 139 | public void setChangedLocationListener(LocationListener l) { 140 | locationListener = l; 141 | } 142 | 143 | /** 144 | * {@inheritDoc} 145 | */ 146 | public void cancel() { 147 | locationManager.removeUpdates(singeUpdateListener); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /app/src/main/java/com/radioactiveyak/location_best_practices/utils/LegacyLocationUpdateRequester.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. 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 | 17 | package com.radioactiveyak.location_best_practices.utils; 18 | 19 | import com.radioactiveyak.location_best_practices.PlacesConstants; 20 | import com.radioactiveyak.location_best_practices.utils.base.LocationUpdateRequester; 21 | 22 | import android.app.AlarmManager; 23 | import android.app.PendingIntent; 24 | import android.location.Criteria; 25 | import android.location.LocationManager; 26 | 27 | /** 28 | * Provides support for initiating active and passive location updates 29 | * for all Android platforms from Android 1.6. 30 | * 31 | * Uses broadcast Intents to notify the app of location changes. 32 | */ 33 | public class LegacyLocationUpdateRequester extends LocationUpdateRequester{ 34 | 35 | protected AlarmManager alarmManager; 36 | 37 | protected LegacyLocationUpdateRequester(LocationManager locationManager, AlarmManager alarmManager) { 38 | super(locationManager); 39 | this.alarmManager = alarmManager; 40 | } 41 | 42 | /** 43 | * {@inheritDoc} 44 | */ 45 | @Override 46 | public void requestLocationUpdates(long minTime, long minDistance, Criteria criteria, PendingIntent pendingIntent) { 47 | // Prior to Gingerbread we needed to find the best provider manually. 48 | // Note that we aren't monitoring this provider to check if it becomes disabled - this is handled by the calling Activity. 49 | String provider = locationManager.getBestProvider(criteria, true); 50 | if (provider != null) 51 | locationManager.requestLocationUpdates(provider, minTime, minDistance, pendingIntent); 52 | } 53 | 54 | /** 55 | * {@inheritDoc} 56 | */ 57 | @Override 58 | public void requestPassiveLocationUpdates(long minTime, long minDistance, PendingIntent pendingIntent) { 59 | // Pre-Froyo there was no Passive Location Provider, so instead we will set an inexact repeating, non-waking alarm 60 | // that will trigger once the minimum time between passive updates has expired. This is potentially more expensive 61 | // than simple passive alarms, however the Receiver will ensure we've transitioned beyond the minimum time and 62 | // distance before initiating a background nearby loction update. 63 | alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, System.currentTimeMillis()+PlacesConstants.MAX_TIME, PlacesConstants.MAX_TIME, pendingIntent); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/com/radioactiveyak/location_best_practices/utils/LegacySharedPreferenceSaver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. 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 | 17 | package com.radioactiveyak.location_best_practices.utils; 18 | 19 | import android.content.Context; 20 | import android.content.SharedPreferences; 21 | import android.content.SharedPreferences.Editor; 22 | 23 | import com.radioactiveyak.location_best_practices.utils.base.SharedPreferenceSaver; 24 | 25 | /** 26 | * Save {@link SharedPreferences} in a way compatible with Android 1.6. 27 | */ 28 | public class LegacySharedPreferenceSaver extends SharedPreferenceSaver { 29 | 30 | public LegacySharedPreferenceSaver(Context context) { 31 | super(context); 32 | } 33 | 34 | /** 35 | * {@inheritDoc} 36 | */ 37 | @Override 38 | public void savePreferences(Editor editor, boolean backup) { 39 | editor.commit(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/radioactiveyak/location_best_practices/utils/LegacyStrictMode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. 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 | 17 | package com.radioactiveyak.location_best_practices.utils; 18 | 19 | import com.radioactiveyak.location_best_practices.utils.base.IStrictMode; 20 | 21 | import android.os.StrictMode; 22 | 23 | /** 24 | * Implementation that supports the Strict Mode functionality 25 | * available for the first platform release that supported Strict Mode. 26 | */ 27 | public class LegacyStrictMode implements IStrictMode { 28 | 29 | /** 30 | * Enable {@link StrictMode} 31 | * TODO Set your preferred Strict Mode features. 32 | */ 33 | public void enableStrictMode() { 34 | StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() 35 | .detectDiskReads() 36 | .detectDiskWrites() 37 | .detectNetwork() 38 | .penaltyLog() 39 | .build()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/radioactiveyak/location_best_practices/utils/PlatformSpecificImplementationFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. 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 | 17 | package com.radioactiveyak.location_best_practices.utils; 18 | 19 | import android.content.Context; 20 | import android.location.LocationManager; 21 | 22 | import com.radioactiveyak.location_best_practices.PlacesConstants; 23 | import com.radioactiveyak.location_best_practices.utils.base.ILastLocationFinder; 24 | import com.radioactiveyak.location_best_practices.utils.base.IStrictMode; 25 | import com.radioactiveyak.location_best_practices.utils.base.LocationUpdateRequester; 26 | import com.radioactiveyak.location_best_practices.utils.base.SharedPreferenceSaver; 27 | 28 | /** 29 | * Factory class to create the correct instances 30 | * of a variety of classes with platform specific 31 | * implementations. 32 | * 33 | */ 34 | public class PlatformSpecificImplementationFactory { 35 | 36 | /** 37 | * Create a new LastLocationFinder instance 38 | * @param context Context 39 | * @return LastLocationFinder 40 | */ 41 | public static ILastLocationFinder getLastLocationFinder(Context context) { 42 | return PlacesConstants.SUPPORTS_GINGERBREAD ? new GingerbreadLastLocationFinder(context) : new LegacyLastLocationFinder(context); 43 | } 44 | 45 | /** 46 | * Create a new StrictMode instance. 47 | * @return StrictMode 48 | */ 49 | public static IStrictMode getStrictMode() { 50 | if (PlacesConstants.SUPPORTS_HONEYCOMB) 51 | return new HoneycombStrictMode(); 52 | else if (PlacesConstants.SUPPORTS_GINGERBREAD) 53 | return new LegacyStrictMode(); 54 | else 55 | return null; 56 | } 57 | 58 | /** 59 | * Create a new LocationUpdateRequester 60 | * @param locationManager Location Manager 61 | * @return LocationUpdateRequester 62 | */ 63 | public static LocationUpdateRequester getLocationUpdateRequester(LocationManager locationManager) { 64 | return PlacesConstants.SUPPORTS_GINGERBREAD ? new GingerbreadLocationUpdateRequester(locationManager) : new FroyoLocationUpdateRequester(locationManager); 65 | } 66 | 67 | /** 68 | * Create a new SharedPreferenceSaver 69 | * @param context Context 70 | * @return SharedPreferenceSaver 71 | */ 72 | public static SharedPreferenceSaver getSharedPreferenceSaver(Context context) { 73 | return PlacesConstants.SUPPORTS_GINGERBREAD ? 74 | new GingerbreadSharedPreferenceSaver(context) : 75 | PlacesConstants.SUPPORTS_FROYO ? 76 | new FroyoSharedPreferenceSaver(context) : 77 | new LegacySharedPreferenceSaver(context); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/src/main/java/com/radioactiveyak/location_best_practices/utils/base/ILastLocationFinder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. 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 | 17 | package com.radioactiveyak.location_best_practices.utils.base; 18 | 19 | import android.location.Location; 20 | import android.location.LocationListener; 21 | 22 | /** 23 | * Interface definition for a Last Location Finder. 24 | * 25 | * Classes that implement this interface must provide methods to 26 | * find the "best" (most accurate and timely) previously detected 27 | * location using whatever providers are available. 28 | * 29 | * Where a timely / accurate previous location is not detected, classes 30 | * should return the last location and create a one-shot update to find 31 | * the current location. The one-shot update should be returned via the 32 | * Location Listener passed in through setChangedLocationListener. 33 | */ 34 | public interface ILastLocationFinder { 35 | /** 36 | * Find the most accurate and timely previously detected location 37 | * using all the location providers. Where the last result is beyond 38 | * the acceptable maximum distance or latency create a one-shot update 39 | * of the current location to be returned using the {@link LocationListener} 40 | * passed in through {@link setChangedLocationListener} 41 | * @param minDistance Minimum distance before we require a location update. 42 | * @param minTime Minimum time required between location updates. 43 | * @return The most accurate and / or timely previously detected location. 44 | */ 45 | public Location getLastBestLocation(int minDistance, long minTime); 46 | 47 | /** 48 | * Set the {@link LocationListener} that may receive a one-shot current location update. 49 | * @param l LocationListener 50 | */ 51 | public void setChangedLocationListener(LocationListener l); 52 | 53 | /** 54 | * Cancel the one-shot current location update. 55 | */ 56 | public void cancel(); 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/com/radioactiveyak/location_best_practices/utils/base/IStrictMode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. 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 | 17 | package com.radioactiveyak.location_best_practices.utils.base; 18 | 19 | /** 20 | * This Interface definition allows you to create OS version-specific 21 | * implementations that offer the full Strict Mode functionality 22 | * available in each platform release. 23 | */ 24 | public interface IStrictMode { 25 | /** 26 | * Enable {@link StrictMode} using whichever platform-specific flags you wish. 27 | */ 28 | public void enableStrictMode(); 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/radioactiveyak/location_best_practices/utils/base/LocationUpdateRequester.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. 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 | 17 | package com.radioactiveyak.location_best_practices.utils.base; 18 | 19 | import android.app.PendingIntent; 20 | import android.location.Criteria; 21 | import android.location.LocationManager; 22 | 23 | /** 24 | * Abstract base class that can be extended to provide active and passive location updates 25 | * optimized for each platform release. 26 | * 27 | * Uses broadcast Intents to notify the app of location changes. 28 | */ 29 | public abstract class LocationUpdateRequester { 30 | 31 | protected LocationManager locationManager; 32 | 33 | protected LocationUpdateRequester(LocationManager locationManager) { 34 | this.locationManager = locationManager; 35 | } 36 | 37 | /** 38 | * Request active location updates. 39 | * These updates will be triggered by a direct request from the Location Manager. 40 | * @param minTime Minimum time that should elapse between location update broadcasts. 41 | * @param minDistance Minimum distance that should have been moved between location update broadcasts. 42 | * @param criteria Criteria that define the Location Provider to use to detect the Location. 43 | * @param pendingIntent The Pending Intent to broadcast to notify the app of active location changes. 44 | */ 45 | public void requestLocationUpdates(long minTime, long minDistance, Criteria criteria, PendingIntent pendingIntent) {} 46 | 47 | /** 48 | * Request passive location updates. 49 | * These updates will be triggered by locations received by 3rd party apps that have requested location updates. 50 | * The miniumim time and distance for passive updates will typically be longer than for active updates. The trick 51 | * is to balance the difference to minimize battery drain by maximize freshness. 52 | * @param minTime Minimum time that should elapse between location update broadcasts. 53 | * @param minDistance Minimum distance that should have been moved between location update broadcasts. 54 | * @param pendingIntent The Pending Intent to broadcast to notify the app of passive location changes. 55 | */ 56 | public void requestPassiveLocationUpdates(long minTime, long minDistance, PendingIntent pendingIntent) {} 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/com/radioactiveyak/location_best_practices/utils/base/SharedPreferenceSaver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Google Inc. 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 | 17 | package com.radioactiveyak.location_best_practices.utils.base; 18 | 19 | import android.content.Context; 20 | import android.content.SharedPreferences; 21 | 22 | /** 23 | * Abstract base class that can be extended to provide classes that save 24 | * {@link SharedPreferences} in the most efficient way possible. 25 | * Decendent classes can optionally choose to backup some {@link SharedPreferences} 26 | * to the Google {@link BackupService} on platforms where this is available. 27 | */ 28 | public abstract class SharedPreferenceSaver { 29 | 30 | protected Context context; 31 | 32 | protected SharedPreferenceSaver(Context context) { 33 | this.context = context; 34 | } 35 | 36 | /** 37 | * Save the Shared Preferences modified through the Editor object. 38 | * @param editor Shared Preferences Editor to commit. 39 | * @param backup Backup to the cloud if possible. 40 | */ 41 | public void savePreferences(SharedPreferences.Editor editor, boolean backup) {} 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/res/anim-v11/slide_in_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/anim-v11/slide_out_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/retomeier/android-protips-location/6201f1e82dd0613a402bd2d655b1f9d05f717041/app/src/main/res/drawable-hdpi/icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/powered_by_google_on_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/retomeier/android-protips-location/6201f1e82dd0613a402bd2d655b1f9d05f717041/app/src/main/res/drawable-hdpi/powered_by_google_on_black.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/powered_by_google_on_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/retomeier/android-protips-location/6201f1e82dd0613a402bd2d655b1f9d05f717041/app/src/main/res/drawable-hdpi/powered_by_google_on_white.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-ldpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/retomeier/android-protips-location/6201f1e82dd0613a402bd2d655b1f9d05f717041/app/src/main/res/drawable-ldpi/icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-ldpi/powered_by_google_on_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/retomeier/android-protips-location/6201f1e82dd0613a402bd2d655b1f9d05f717041/app/src/main/res/drawable-ldpi/powered_by_google_on_black.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-ldpi/powered_by_google_on_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/retomeier/android-protips-location/6201f1e82dd0613a402bd2d655b1f9d05f717041/app/src/main/res/drawable-ldpi/powered_by_google_on_white.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/retomeier/android-protips-location/6201f1e82dd0613a402bd2d655b1f9d05f717041/app/src/main/res/drawable-mdpi/icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/powered_by_google_on_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/retomeier/android-protips-location/6201f1e82dd0613a402bd2d655b1f9d05f717041/app/src/main/res/drawable-mdpi/powered_by_google_on_black.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/powered_by_google_on_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/retomeier/android-protips-location/6201f1e82dd0613a402bd2d655b1f9d05f717041/app/src/main/res/drawable-mdpi/powered_by_google_on_white.png -------------------------------------------------------------------------------- /app/src/main/res/layout-port/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 12 | 17 | 22 | 23 | 29 | 30 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /app/src/main/res/layout-xlarge-port/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 11 | 16 | 17 | 23 | 24 | 31 | 32 | 38 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/main/res/layout/checkin_box.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 12 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 11 | 16 | 17 | 23 | 24 | 31 | 32 | 38 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/res/layout/place_detail.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 |