├── .gitignore
├── CODEOWNERS
├── README.md
├── app
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── ic_launcher-web.png
│ ├── java
│ └── com
│ │ └── example
│ │ └── android
│ │ └── shushme
│ │ ├── GeofenceBroadcastReceiver.java
│ │ ├── Geofencing.java
│ │ ├── MainActivity.java
│ │ ├── PlaceListAdapter.java
│ │ └── provider
│ │ ├── PlaceContentProvider.java
│ │ ├── PlaceContract.java
│ │ └── PlaceDbHelper.java
│ └── res
│ ├── drawable
│ ├── ic_globe_primary_24dp.xml
│ ├── ic_gps_fixed_white_24dp.xml
│ ├── ic_my_location_primary_24dp.xml
│ ├── ic_notifications_active_primary_24dp.xml
│ ├── ic_place_accent_24dp.xml
│ ├── ic_volume_off_white_24dp.xml
│ └── ic_volume_up_white_24dp.xml
│ ├── layout
│ ├── activity_main.xml
│ └── item_place_card.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxxhdpi
│ └── ic_launcher.png
│ ├── values-w820dp
│ └── dimens.xml
│ └── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── screenshots
├── screen_1.png
├── screen_2.png
├── screen_3.png
├── screen_4.png
├── screen_5.png
└── screen_6.png
├── settings.gradle
└── test_locations.kml
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | # built application files
4 | *.apk
5 | *.ap_
6 |
7 | # files for the dex VM
8 | *.dex
9 |
10 | # Java class files
11 | *.class
12 |
13 | # generated files
14 | bin/
15 | out/
16 | gen/
17 |
18 | # Libraries used by the app
19 | # Can explicitly add if we want, but shouldn't do so blindly. Licenses, bloat, etc.
20 | /libs
21 |
22 |
23 | # Build stuff (auto-generated by android update project ...)
24 | build.xml
25 | ant.properties
26 | local.properties
27 | project.properties
28 |
29 | # Eclipse project files
30 | .classpath
31 | .project
32 |
33 | # idea project files
34 | .idea/
35 | *.iml
36 | *.ipr
37 | *.iws
38 |
39 | # Gradle-based build
40 | .gradle
41 | build/
--------------------------------------------------------------------------------
/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @udacity/active-public-content
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ShushMe
2 |
3 | This is the toy app for the Places lesson of the [Advanced Android App Development course on Udacity](https://www.udacity.com/course/advanced-android-app-development--ud855).
4 |
5 |
6 | ## Screenshots
7 |
8 |   
9 |   
10 |
11 |
12 | ## How to use this repo while taking the course
13 |
14 | Each code repository in this class has a chain of commits that looks like this:
15 |
16 | 
17 |
18 | These commits show every step you'll take to create the app. They include **Exercise** commits and **Solution** commits.
19 |
20 | Exercise commits contain instructions for completing the exercise, while solution commits show the completed exercise. You can tell what a commit is by looking at its commit message.
21 |
22 | For example, **TFCM.01-Exercise-AddGradleDependencies** is the first code step in the Firebase Cloud Messaging (FCM) lesson. This is the exercise commit, and the exercise is called Add Gradle Dependencies.
23 |
24 | Each commit also has a **branch** associated with it of the same name as the commit message, seen below:
25 |
26 | 
28 | Access all branches from this tab
29 |
30 | 
32 |
33 |
34 | 
36 |
37 |
38 | The branches are also accessible from the drop-down in the "Code" tab
39 |
40 |
41 | ## Working with the Course Code
42 |
43 | Here are the basic steps for working with and completing exercises in the repo. This information is linked whenever you start a new exercise project, so don't feel you need to memorize all of this! In fact, skim it now, make sure that you know generally how to do the different tasks, and then come back when you start your first exercise.
44 |
45 | The basic steps are:
46 |
47 | 1. Clone the repo
48 | 2. Checkout the exercise branch
49 | 3. Find and complete the TODOs
50 | 4. Optionally commit your code changes
51 | 5. Compare with the solution
52 |
53 |
54 | **Step 1: Clone the repo**
55 |
56 | As you go through the course, you'll be instructed to clone the different exercise repositories, so you don't need to set these up now. You can clone a repository from github in a folder of your choice with the command:
57 |
58 | ```bash
59 | git clone https://github.com/udacity/REPOSITORY_NAME.git
60 | ```
61 |
62 | **Step 2: Checkout the exercise branch**
63 |
64 | As you do different exercises in the code, you'll be told which exercise you're on, as seen below:
65 | 
67 |
68 | To complete an exercise, you'll want to check out the branch associated with that exercise. For the exercise above, the command to check out that branch would be:
69 |
70 | ```bash
71 | git checkout TFCM.01-Exercise-AddGradleDependencies
72 | ```
73 |
74 | **Step 3: Find and complete the TODOs**
75 |
76 | This branch should always have **Exercise** in the title. Once you've checked out the branch, you'll have the code in the exact state you need. You'll even have TODOs, which are special comments that tell you all the steps you need to complete the exercise. You can easily navigate to all the TODOs using Android Studio's TODO tool. To open the TODO tool, click the button at the bottom of the screen that says TODO. This will display a list of all comments with TODO in the project.
77 |
78 | We've numbered the TODO steps so you can do them in order:
79 | 
81 |
82 | **Step 4: Optionally commit your code changes**
83 |
84 | After You've completed the TODOs, you can optionally commit your changes. This will allow you to see the code you wrote whenever you return to the branch. The following git code will add and save **all** your changes.
85 |
86 | ```bash
87 | git add .
88 | git commit -m "Your commit message"
89 | ```
90 |
91 | **Step 5: Compare with the solution**
92 |
93 | Most exercises will have a list of steps for you to check off in the classroom. Once you've checked these off, you'll see a pop up window with a link to the solution code. Note the **Diff** link:
94 |
95 | 
97 |
98 | The **Diff** link will take you to a Github diff as seen below:
99 | 
101 |
102 | All of the code that was added in the solution is in green, and the removed code (which will usually be the TODO comments) is in red.
103 | ## Report Issues
104 | Notice any issues with a repository? Please file a github issue in the repository.
105 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 25
5 | buildToolsVersion "25.0.2"
6 | defaultConfig {
7 | applicationId "com.example.android.shushme"
8 | minSdkVersion 16
9 | targetSdkVersion 25
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
25 | exclude group: 'com.android.support', module: 'support-annotations'
26 | })
27 | compile 'com.android.support:appcompat-v7:25.1.0'
28 | compile 'com.android.support:recyclerview-v7:25.0.1'
29 | compile 'com.google.android.gms:play-services-places:9.8.0'
30 | compile 'com.google.android.gms:play-services-location:9.8.0'
31 | testCompile 'junit:junit:4.12'
32 | }
33 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/asser/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/AdvancedAndroid_Shushme/164e332c47703f6be02632a49c8c8cfad04a8e9e/app/src/main/ic_launcher-web.png
--------------------------------------------------------------------------------
/app/src/main/java/com/example/android/shushme/GeofenceBroadcastReceiver.java:
--------------------------------------------------------------------------------
1 | package com.example.android.shushme;
2 |
3 | /*
4 | * Copyright (C) 2017 The Android Open Source Project
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 |
19 | import android.content.BroadcastReceiver;
20 | import android.content.Context;
21 | import android.content.Intent;
22 | import android.util.Log;
23 |
24 | public class GeofenceBroadcastReceiver extends BroadcastReceiver {
25 |
26 | public static final String TAG = GeofenceBroadcastReceiver.class.getSimpleName();
27 |
28 | /***
29 | * Handles the Broadcast message sent when the Geofence Transition is triggered
30 | * Careful here though, this is running on the main thread so make sure you start an AsyncTask for
31 | * anything that takes longer than say 10 second to run
32 | *
33 | * @param context
34 | * @param intent
35 | */
36 | @Override
37 | public void onReceive(Context context, Intent intent) {
38 | // Get the Geofence Event from the Intent sent through
39 | GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
40 | if (geofencingEvent.hasError()) {
41 | Log.e(TAG, String.format("Error code : %d", geofencingEvent.getErrorCode()));
42 | return;
43 | }
44 |
45 | // Get the transition type.
46 | int geofenceTransition = geofencingEvent.getGeofenceTransition();
47 | // Check which transition type has triggered this event
48 | if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER) {
49 | setRingerMode(context, AudioManager.RINGER_MODE_SILENT);
50 | } else if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) {
51 | setRingerMode(context, AudioManager.RINGER_MODE_NORMAL);
52 | } else {
53 | // Log the error.
54 | Log.e(TAG, String.format("Unknown transition : %d", geofenceTransition));
55 | // No need to do anything else
56 | return;
57 | }
58 | // Send the notification
59 | sendNotification(context, geofenceTransition);
60 | }
61 |
62 |
63 | /**
64 | * Posts a notification in the notification bar when a transition is detected
65 | * Uses different icon drawables for different transition types
66 | * If the user clicks the notification, control goes to the MainActivity
67 | *
68 | * @param context The calling context for building a task stack
69 | * @param transitionType The geofence transition type, can be Geofence.GEOFENCE_TRANSITION_ENTER
70 | * or Geofence.GEOFENCE_TRANSITION_EXIT
71 | */
72 | private void sendNotification(Context context, int transitionType) {
73 | // Create an explicit content Intent that starts the main Activity.
74 | Intent notificationIntent = new Intent(context, MainActivity.class);
75 |
76 | // Construct a task stack.
77 | TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
78 |
79 | // Add the main Activity to the task stack as the parent.
80 | stackBuilder.addParentStack(MainActivity.class);
81 |
82 | // Push the content Intent onto the stack.
83 | stackBuilder.addNextIntent(notificationIntent);
84 |
85 | // Get a PendingIntent containing the entire back stack.
86 | PendingIntent notificationPendingIntent =
87 | stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
88 |
89 | // Get a notification builder
90 | NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
91 |
92 | // Check the transition type to display the relevant icon image
93 | if (transitionType == Geofence.GEOFENCE_TRANSITION_ENTER) {
94 | builder.setSmallIcon(R.drawable.ic_volume_off_white_24dp)
95 | .setLargeIcon(BitmapFactory.decodeResource(context.getResources(),
96 | R.drawable.ic_volume_off_white_24dp))
97 | .setContentTitle(context.getString(R.string.silent_mode_activated));
98 | } else if (transitionType == Geofence.GEOFENCE_TRANSITION_EXIT) {
99 | builder.setSmallIcon(R.drawable.ic_volume_up_white_24dp)
100 | .setLargeIcon(BitmapFactory.decodeResource(context.getResources(),
101 | R.drawable.ic_volume_up_white_24dp))
102 | .setContentTitle(context.getString(R.string.back_to_normal));
103 | }
104 |
105 | // Continue building the notification
106 | builder.setContentText(context.getString(R.string.touch_to_relaunch));
107 | builder.setContentIntent(notificationPendingIntent);
108 |
109 | // Dismiss notification once the user touches it.
110 | builder.setAutoCancel(true);
111 |
112 | // Get an instance of the Notification manager
113 | NotificationManager mNotificationManager =
114 | (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
115 |
116 | // Issue the notification
117 | mNotificationManager.notify(0, builder.build());
118 | }
119 |
120 | /**
121 | * Changes the ringer mode on the device to either silent or back to normal
122 | *
123 | * @param context The context to access AUDIO_SERVICE
124 | * @param mode The desired mode to switch device to, can be AudioManager.RINGER_MODE_SILENT or
125 | * AudioManager.RINGER_MODE_NORMAL
126 | */
127 | private void setRingerMode(Context context, int mode) {
128 | NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
129 | // Check for DND permissions for API 24+
130 | if (android.os.Build.VERSION.SDK_INT < 24 ||
131 | (android.os.Build.VERSION.SDK_INT >= 24 && !nm.isNotificationPolicyAccessGranted())) {
132 | AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
133 | audioManager.setRingerMode(mode);
134 | }
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/android/shushme/Geofencing.java:
--------------------------------------------------------------------------------
1 | package com.example.android.shushme;
2 |
3 | /*
4 | * Copyright (C) 2017 The Android Open Source Project
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 |
19 | import android.app.PendingIntent;
20 | import android.content.Context;
21 | import android.content.Intent;
22 | import android.support.annotation.NonNull;
23 | import android.util.Log;
24 |
25 | import com.google.android.gms.common.api.GoogleApiClient;
26 | import com.google.android.gms.common.api.Result;
27 | import com.google.android.gms.common.api.ResultCallback;
28 | import com.google.android.gms.location.Geofence;
29 | import com.google.android.gms.location.GeofencingRequest;
30 | import com.google.android.gms.location.LocationServices;
31 | import com.google.android.gms.location.places.Place;
32 | import com.google.android.gms.location.places.PlaceBuffer;
33 |
34 | import java.util.ArrayList;
35 | import java.util.List;
36 |
37 | public class Geofencing implements ResultCallback {
38 |
39 | // Constants
40 | public static final String TAG = Geofencing.class.getSimpleName();
41 | private static final float GEOFENCE_RADIUS = 50; // 50 meters
42 | private static final long GEOFENCE_TIMEOUT = 24 * 60 * 60 * 1000; // 24 hours
43 |
44 | private List mGeofenceList;
45 | private PendingIntent mGeofencePendingIntent;
46 | private GoogleApiClient mGoogleApiClient;
47 | private Context mContext;
48 |
49 | public Geofencing(Context context, GoogleApiClient client) {
50 | mContext = context;
51 | mGoogleApiClient = client;
52 | mGeofencePendingIntent = null;
53 | mGeofenceList = new ArrayList<>();
54 | }
55 |
56 | /***
57 | * Registers the list of Geofences specified in mGeofenceList with Google Place Services
58 | * Uses {@code #mGoogleApiClient} to connect to Google Place Services
59 | * Uses {@link #getGeofencingRequest} to get the list of Geofences to be registered
60 | * Uses {@link #getGeofencePendingIntent} to get the pending intent to launch the IntentService
61 | * when the Geofence is triggered
62 | * Triggers {@link #onResult} when the geofences have been registered successfully
63 | */
64 | public void registerAllGeofences() {
65 | // Check that the API client is connected and that the list has Geofences in it
66 | if (mGoogleApiClient == null || !mGoogleApiClient.isConnected() ||
67 | mGeofenceList == null || mGeofenceList.size() == 0) {
68 | return;
69 | }
70 | try {
71 | LocationServices.GeofencingApi.addGeofences(
72 | mGoogleApiClient,
73 | getGeofencingRequest(),
74 | getGeofencePendingIntent()
75 | ).setResultCallback(this);
76 | } catch (SecurityException securityException) {
77 | // Catch exception generated if the app does not use ACCESS_FINE_LOCATION permission.
78 | Log.e(TAG, securityException.getMessage());
79 | }
80 | }
81 |
82 | /***
83 | * Unregisters all the Geofences created by this app from Google Place Services
84 | * Uses {@code #mGoogleApiClient} to connect to Google Place Services
85 | * Uses {@link #getGeofencePendingIntent} to get the pending intent passed when
86 | * registering the Geofences in the first place
87 | * Triggers {@link #onResult} when the geofences have been unregistered successfully
88 | */
89 | public void unRegisterAllGeofences() {
90 | if (mGoogleApiClient == null || !mGoogleApiClient.isConnected()) {
91 | return;
92 | }
93 | try {
94 | LocationServices.GeofencingApi.removeGeofences(
95 | mGoogleApiClient,
96 | // This is the same pending intent that was used in registerGeofences
97 | getGeofencePendingIntent()
98 | ).setResultCallback(this);
99 | } catch (SecurityException securityException) {
100 | // Catch exception generated if the app does not use ACCESS_FINE_LOCATION permission.
101 | Log.e(TAG, securityException.getMessage());
102 | }
103 | }
104 |
105 |
106 | /***
107 | * Updates the local ArrayList of Geofences using data from the passed in list
108 | * Uses the Place ID defined by the API as the Geofence object Id
109 | *
110 | * @param places the PlaceBuffer result of the getPlaceById call
111 | */
112 | public void updateGeofencesList(PlaceBuffer places) {
113 | mGeofenceList = new ArrayList<>();
114 | if (places == null || places.getCount() == 0) return;
115 | for (Place place : places) {
116 | // Read the place information from the DB cursor
117 | String placeUID = place.getId();
118 | double placeLat = place.getLatLng().latitude;
119 | double placeLng = place.getLatLng().longitude;
120 | // Build a Geofence object
121 | Geofence geofence = new Geofence.Builder()
122 | .setRequestId(placeUID)
123 | .setExpirationDuration(GEOFENCE_TIMEOUT)
124 | .setCircularRegion(placeLat, placeLng, GEOFENCE_RADIUS)
125 | .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER | Geofence.GEOFENCE_TRANSITION_EXIT)
126 | .build();
127 | // Add it to the list
128 | mGeofenceList.add(geofence);
129 | }
130 | }
131 |
132 | /***
133 | * Creates a GeofencingRequest object using the mGeofenceList ArrayList of Geofences
134 | * Used by {@code #registerGeofences}
135 | *
136 | * @return the GeofencingRequest object
137 | */
138 | private GeofencingRequest getGeofencingRequest() {
139 | GeofencingRequest.Builder builder = new GeofencingRequest.Builder();
140 | builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER);
141 | builder.addGeofences(mGeofenceList);
142 | return builder.build();
143 | }
144 |
145 | /***
146 | * Creates a PendingIntent object using the GeofenceTransitionsIntentService class
147 | * Used by {@code #registerGeofences}
148 | *
149 | * @return the PendingIntent object
150 | */
151 | private PendingIntent getGeofencePendingIntent() {
152 | // Reuse the PendingIntent if we already have it.
153 | if (mGeofencePendingIntent != null) {
154 | return mGeofencePendingIntent;
155 | }
156 | Intent intent = new Intent(mContext, GeofenceBroadcastReceiver.class);
157 | mGeofencePendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.
158 | FLAG_UPDATE_CURRENT);
159 | return mGeofencePendingIntent;
160 | }
161 |
162 | @Override
163 | public void onResult(@NonNull Result result) {
164 | Log.e(TAG, String.format("Error adding/removing geofence : %s",
165 | result.getStatus().toString()));
166 | }
167 |
168 | }
169 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/android/shushme/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.android.shushme;
2 |
3 | /*
4 | * Copyright (C) 2017 The Android Open Source Project
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 |
19 | import android.app.NotificationManager;
20 | import android.content.ContentValues;
21 | import android.content.Intent;
22 | import android.content.SharedPreferences;
23 | import android.content.pm.PackageManager;
24 | import android.database.Cursor;
25 | import android.net.Uri;
26 | import android.os.Bundle;
27 | import android.support.annotation.NonNull;
28 | import android.support.annotation.Nullable;
29 | import android.support.v4.app.ActivityCompat;
30 | import android.support.v7.app.AppCompatActivity;
31 | import android.support.v7.widget.LinearLayoutManager;
32 | import android.support.v7.widget.RecyclerView;
33 | import android.util.Log;
34 | import android.view.View;
35 | import android.widget.CheckBox;
36 | import android.widget.CompoundButton;
37 | import android.widget.Switch;
38 | import android.widget.Toast;
39 |
40 | import com.example.android.shushme.provider.PlaceContract;
41 | import com.google.android.gms.common.ConnectionResult;
42 | import com.google.android.gms.common.GooglePlayServicesNotAvailableException;
43 | import com.google.android.gms.common.GooglePlayServicesRepairableException;
44 | import com.google.android.gms.common.api.GoogleApiClient;
45 | import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
46 | import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
47 | import com.google.android.gms.common.api.PendingResult;
48 | import com.google.android.gms.common.api.ResultCallback;
49 | import com.google.android.gms.location.LocationServices;
50 | import com.google.android.gms.location.places.Place;
51 | import com.google.android.gms.location.places.PlaceBuffer;
52 | import com.google.android.gms.location.places.Places;
53 | import com.google.android.gms.location.places.ui.PlacePicker;
54 |
55 | import java.util.ArrayList;
56 | import java.util.List;
57 |
58 | public class MainActivity extends AppCompatActivity implements
59 | ConnectionCallbacks,
60 | OnConnectionFailedListener {
61 |
62 | // Constants
63 | public static final String TAG = MainActivity.class.getSimpleName();
64 | private static final int PERMISSIONS_REQUEST_FINE_LOCATION = 111;
65 | private static final int PLACE_PICKER_REQUEST = 1;
66 |
67 | // Member variables
68 | private PlaceListAdapter mAdapter;
69 | private RecyclerView mRecyclerView;
70 | private boolean mIsEnabled;
71 | private GoogleApiClient mClient;
72 | private Geofencing mGeofencing;
73 |
74 | /**
75 | * Called when the activity is starting
76 | *
77 | * @param savedInstanceState The Bundle that contains the data supplied in onSaveInstanceState
78 | */
79 | @Override
80 | protected void onCreate(Bundle savedInstanceState) {
81 | super.onCreate(savedInstanceState);
82 | setContentView(R.layout.activity_main);
83 |
84 | // Set up the recycler view
85 | mRecyclerView = (RecyclerView) findViewById(R.id.places_list_recycler_view);
86 | mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
87 | mAdapter = new PlaceListAdapter(this, null);
88 | mRecyclerView.setAdapter(mAdapter);
89 |
90 | // Initialize the switch state and Handle enable/disable switch change
91 | Switch onOffSwitch = (Switch) findViewById(R.id.enable_switch);
92 | mIsEnabled = getPreferences(MODE_PRIVATE).getBoolean(getString(R.string.setting_enabled), false);
93 | onOffSwitch.setChecked(mIsEnabled);
94 | onOffSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
95 | @Override
96 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
97 | SharedPreferences.Editor editor = getPreferences(MODE_PRIVATE).edit();
98 | editor.putBoolean(getString(R.string.setting_enabled), isChecked);
99 | mIsEnabled = isChecked;
100 | editor.commit();
101 | if (isChecked) mGeofencing.registerAllGeofences();
102 | else mGeofencing.unRegisterAllGeofences();
103 | }
104 |
105 | });
106 |
107 | // Build up the LocationServices API client
108 | // Uses the addApi method to request the LocationServices API
109 | // Also uses enableAutoManage to automatically when to connect/suspend the client
110 | mClient = new GoogleApiClient.Builder(this)
111 | .addConnectionCallbacks(this)
112 | .addOnConnectionFailedListener(this)
113 | .addApi(LocationServices.API)
114 | .addApi(Places.GEO_DATA_API)
115 | .enableAutoManage(this, this)
116 | .build();
117 |
118 | mGeofencing = new Geofencing(this, mClient);
119 |
120 | }
121 |
122 | /***
123 | * Called when the Google API Client is successfully connected
124 | *
125 | * @param connectionHint Bundle of data provided to clients by Google Play services
126 | */
127 | @Override
128 | public void onConnected(@Nullable Bundle connectionHint) {
129 | refreshPlacesData();
130 | Log.i(TAG, "API Client Connection Successful!");
131 | }
132 |
133 | /***
134 | * Called when the Google API Client is suspended
135 | *
136 | * @param cause cause The reason for the disconnection. Defined by constants CAUSE_*.
137 | */
138 | @Override
139 | public void onConnectionSuspended(int cause) {
140 | Log.i(TAG, "API Client Connection Suspended!");
141 | }
142 |
143 | /***
144 | * Called when the Google API Client failed to connect to Google Play Services
145 | *
146 | * @param result A ConnectionResult that can be used for resolving the error
147 | */
148 | @Override
149 | public void onConnectionFailed(@NonNull ConnectionResult result) {
150 | Log.e(TAG, "API Client Connection Failed!");
151 | }
152 |
153 | public void refreshPlacesData() {
154 | Uri uri = PlaceContract.PlaceEntry.CONTENT_URI;
155 | Cursor data = getContentResolver().query(
156 | uri,
157 | null,
158 | null,
159 | null,
160 | null);
161 |
162 | if (data == null || data.getCount() == 0) return;
163 | List guids = new ArrayList();
164 | while (data.moveToNext()) {
165 | guids.add(data.getString(data.getColumnIndex(PlaceContract.PlaceEntry.COLUMN_PLACE_ID)));
166 | }
167 | PendingResult placeResult = Places.GeoDataApi.getPlaceById(mClient,
168 | guids.toArray(new String[guids.size()]));
169 | placeResult.setResultCallback(new ResultCallback() {
170 | @Override
171 | public void onResult(@NonNull PlaceBuffer places) {
172 | mAdapter.swapPlaces(places);
173 | mGeofencing.updateGeofencesList(places);
174 | if (mIsEnabled) mGeofencing.registerAllGeofences();
175 | }
176 | });
177 | }
178 |
179 | /***
180 | * Button Click event handler to handle clicking the "Add new location" Button
181 | *
182 | * @param view
183 | */
184 | public void onAddPlaceButtonClicked(View view) {
185 | if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION)
186 | != PackageManager.PERMISSION_GRANTED) {
187 | Toast.makeText(this, getString(R.string.need_location_permission_message), Toast.LENGTH_LONG).show();
188 | return;
189 | }
190 | try {
191 | // Start a new Activity for the Place Picker API, this will trigger {@code #onActivityResult}
192 | // when a place is selected or with the user cancels.
193 | PlacePicker.IntentBuilder builder = new PlacePicker.IntentBuilder();
194 | Intent i = builder.build(this);
195 | startActivityForResult(i, PLACE_PICKER_REQUEST);
196 | } catch (GooglePlayServicesRepairableException e) {
197 | Log.e(TAG, String.format("GooglePlayServices Not Available [%s]", e.getMessage()));
198 | } catch (GooglePlayServicesNotAvailableException e) {
199 | Log.e(TAG, String.format("GooglePlayServices Not Available [%s]", e.getMessage()));
200 | } catch (Exception e) {
201 | Log.e(TAG, String.format("PlacePicker Exception: %s", e.getMessage()));
202 | }
203 | }
204 |
205 |
206 | /***
207 | * Called when the Place Picker Activity returns back with a selected place (or after canceling)
208 | *
209 | * @param requestCode The request code passed when calling startActivityForResult
210 | * @param resultCode The result code specified by the second activity
211 | * @param data The Intent that carries the result data.
212 | */
213 | protected void onActivityResult(int requestCode, int resultCode, Intent data) {
214 | if (requestCode == PLACE_PICKER_REQUEST && resultCode == RESULT_OK) {
215 | Place place = PlacePicker.getPlace(this, data);
216 | if (place == null) {
217 | Log.i(TAG, "No place selected");
218 | return;
219 | }
220 |
221 | // Extract the place information from the API
222 | String placeName = place.getName().toString();
223 | String placeAddress = place.getAddress().toString();
224 | String placeID = place.getId();
225 |
226 | // Insert a new place into DB
227 | ContentValues contentValues = new ContentValues();
228 | contentValues.put(PlaceContract.PlaceEntry.COLUMN_PLACE_ID, placeID);
229 | getContentResolver().insert(PlaceContract.PlaceEntry.CONTENT_URI, contentValues);
230 |
231 | // Get live data information
232 | refreshPlacesData();
233 | }
234 | }
235 |
236 | @Override
237 | public void onResume() {
238 | super.onResume();
239 |
240 | // Initialize location permissions checkbox
241 | CheckBox locationPermissions = (CheckBox) findViewById(R.id.location_permission_checkbox);
242 | if (ActivityCompat.checkSelfPermission(MainActivity.this,
243 | android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
244 | locationPermissions.setChecked(false);
245 | } else {
246 | locationPermissions.setChecked(true);
247 | locationPermissions.setEnabled(false);
248 | }
249 |
250 | // Initialize ringer permissions checkbox
251 | CheckBox ringerPermissions = (CheckBox) findViewById(R.id.ringer_permissions_checkbox);
252 | NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
253 | // Check if the API supports such permission change and check if permission is granted
254 | if (android.os.Build.VERSION.SDK_INT >= 24 && !nm.isNotificationPolicyAccessGranted()) {
255 | ringerPermissions.setChecked(false);
256 | } else {
257 | ringerPermissions.setChecked(true);
258 | ringerPermissions.setEnabled(false);
259 | }
260 | }
261 |
262 | public void onRingerPermissionsClicked(View view) {
263 | Intent intent = new Intent(android.provider.Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS);
264 | startActivity(intent);
265 | }
266 |
267 | public void onLocationPermissionClicked(View view) {
268 | ActivityCompat.requestPermissions(MainActivity.this,
269 | new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION},
270 | PERMISSIONS_REQUEST_FINE_LOCATION);
271 | }
272 | }
273 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/android/shushme/PlaceListAdapter.java:
--------------------------------------------------------------------------------
1 | package com.example.android.shushme;
2 |
3 | /*
4 | * Copyright (C) 2017 The Android Open Source Project
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 |
19 | import android.content.Context;
20 | import android.support.v7.widget.RecyclerView;
21 | import android.view.LayoutInflater;
22 | import android.view.View;
23 | import android.view.ViewGroup;
24 | import android.widget.TextView;
25 |
26 | import com.google.android.gms.location.places.PlaceBuffer;
27 |
28 | public class PlaceListAdapter extends RecyclerView.Adapter {
29 |
30 | private Context mContext;
31 | private PlaceBuffer mPlaces;
32 |
33 | /**
34 | * Constructor using the context and the db cursor
35 | *
36 | * @param context the calling context/activity
37 | */
38 | public PlaceListAdapter(Context context, PlaceBuffer places) {
39 | this.mContext = context;
40 | this.mPlaces = places;
41 | }
42 |
43 | /**
44 | * Called when RecyclerView needs a new ViewHolder of the given type to represent an item
45 | *
46 | * @param parent The ViewGroup into which the new View will be added
47 | * @param viewType The view type of the new View
48 | * @return A new PlaceViewHolder that holds a View with the item_place_card layout
49 | */
50 | @Override
51 | public PlaceViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
52 | // Get the RecyclerView item layout
53 | LayoutInflater inflater = LayoutInflater.from(mContext);
54 | View view = inflater.inflate(R.layout.item_place_card, parent, false);
55 | return new PlaceViewHolder(view);
56 | }
57 |
58 | /**
59 | * Binds the data from a particular position in the cursor to the corresponding view holder
60 | *
61 | * @param holder The PlaceViewHolder instance corresponding to the required position
62 | * @param position The current position that needs to be loaded with data
63 | */
64 | @Override
65 | public void onBindViewHolder(PlaceViewHolder holder, int position) {
66 | String placeName = mPlaces.get(position).getName().toString();
67 | String placeAddress = mPlaces.get(position).getAddress().toString();
68 | holder.nameTextView.setText(placeName);
69 | holder.addressTextView.setText(placeAddress);
70 | }
71 |
72 | public void swapPlaces(PlaceBuffer newPlaces){
73 | mPlaces = newPlaces;
74 | if (mPlaces != null) {
75 | // Force the RecyclerView to refresh
76 | this.notifyDataSetChanged();
77 | }
78 | }
79 |
80 | /**
81 | * Returns the number of items in the cursor
82 | *
83 | * @return Number of items in the cursor, or 0 if null
84 | */
85 | @Override
86 | public int getItemCount() {
87 | if(mPlaces==null) return 0;
88 | return mPlaces.getCount();
89 | }
90 |
91 | /**
92 | * PlaceViewHolder class for the recycler view item
93 | */
94 | class PlaceViewHolder extends RecyclerView.ViewHolder {
95 |
96 | TextView nameTextView;
97 | TextView addressTextView;
98 |
99 | public PlaceViewHolder(View itemView) {
100 | super(itemView);
101 | nameTextView = (TextView) itemView.findViewById(R.id.name_text_view);
102 | addressTextView = (TextView) itemView.findViewById(R.id.address_text_view);
103 | }
104 |
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/android/shushme/provider/PlaceContentProvider.java:
--------------------------------------------------------------------------------
1 | package com.example.android.shushme.provider;
2 |
3 | /*
4 | * Copyright (C) 2017 The Android Open Source Project
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
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.sqlite.SQLiteDatabase;
26 | import android.net.Uri;
27 | import android.support.annotation.NonNull;
28 |
29 | import static com.example.android.shushme.provider.PlaceContract.PlaceEntry;
30 |
31 |
32 | public class PlaceContentProvider extends ContentProvider {
33 |
34 | // Define final integer constants for the directory of places and a single item.
35 | // It's convention to use 100, 200, 300, etc for directories,
36 | // and related ints (101, 102, ..) for items in that directory.
37 | public static final int PLACES = 100;
38 | public static final int PLACE_WITH_ID = 101;
39 |
40 | // Declare a static variable for the Uri matcher that you construct
41 | private static final UriMatcher sUriMatcher = buildUriMatcher();
42 | private static final String TAG = PlaceContentProvider.class.getName();
43 |
44 | // Define a static buildUriMatcher method that associates URI's with their int match
45 | public static UriMatcher buildUriMatcher() {
46 | // Initialize a UriMatcher
47 | UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
48 | // Add URI matches
49 | uriMatcher.addURI(PlaceContract.AUTHORITY, PlaceContract.PATH_PLACES, PLACES);
50 | uriMatcher.addURI(PlaceContract.AUTHORITY, PlaceContract.PATH_PLACES + "/#", PLACE_WITH_ID);
51 | return uriMatcher;
52 | }
53 |
54 | // Member variable for a PlaceDbHelper that's initialized in the onCreate() method
55 | private PlaceDbHelper mPlaceDbHelper;
56 |
57 | @Override
58 | public boolean onCreate() {
59 | Context context = getContext();
60 | mPlaceDbHelper = new PlaceDbHelper(context);
61 | return true;
62 | }
63 |
64 | /***
65 | * Handles requests to insert a single new row of data
66 | *
67 | * @param uri
68 | * @param values
69 | * @return
70 | */
71 | @Override
72 | public Uri insert(@NonNull Uri uri, ContentValues values) {
73 | final SQLiteDatabase db = mPlaceDbHelper.getWritableDatabase();
74 |
75 | // Write URI matching code to identify the match for the places directory
76 | int match = sUriMatcher.match(uri);
77 | Uri returnUri; // URI to be returned
78 | switch (match) {
79 | case PLACES:
80 | // Insert new values into the database
81 | long id = db.insert(PlaceEntry.TABLE_NAME, null, values);
82 | if (id > 0) {
83 | returnUri = ContentUris.withAppendedId(PlaceContract.PlaceEntry.CONTENT_URI, id);
84 | } else {
85 | throw new android.database.SQLException("Failed to insert row into " + uri);
86 | }
87 | break;
88 | // Default case throws an UnsupportedOperationException
89 | default:
90 | throw new UnsupportedOperationException("Unknown uri: " + uri);
91 | }
92 |
93 | // Notify the resolver if the uri has been changed, and return the newly inserted URI
94 | getContext().getContentResolver().notifyChange(uri, null);
95 |
96 | // Return constructed uri (this points to the newly inserted row of data)
97 | return returnUri;
98 | }
99 |
100 | /***
101 | * Handles requests for data by URI
102 | *
103 | * @param uri
104 | * @param projection
105 | * @param selection
106 | * @param selectionArgs
107 | * @param sortOrder
108 | * @return
109 | */
110 | @Override
111 | public Cursor query(@NonNull Uri uri, String[] projection, String selection,
112 | String[] selectionArgs, String sortOrder) {
113 |
114 | // Get access to underlying database (read-only for query)
115 | final SQLiteDatabase db = mPlaceDbHelper.getReadableDatabase();
116 |
117 | // Write URI match code and set a variable to return a Cursor
118 | int match = sUriMatcher.match(uri);
119 | Cursor retCursor;
120 |
121 | switch (match) {
122 | // Query for the places directory
123 | case PLACES:
124 | retCursor = db.query(PlaceEntry.TABLE_NAME,
125 | projection,
126 | selection,
127 | selectionArgs,
128 | null,
129 | null,
130 | sortOrder);
131 | break;
132 | // Default exception
133 | default:
134 | throw new UnsupportedOperationException("Unknown uri: " + uri);
135 | }
136 |
137 | // Set a notification URI on the Cursor and return that Cursor
138 | retCursor.setNotificationUri(getContext().getContentResolver(), uri);
139 |
140 | // Return the desired Cursor
141 | return retCursor;
142 | }
143 |
144 | /***
145 | * Deletes a single row of data
146 | *
147 | * @param uri
148 | * @param selection
149 | * @param selectionArgs
150 | * @return number of rows affected
151 | */
152 | @Override
153 | public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
154 | // Get access to the database and write URI matching code to recognize a single item
155 | final SQLiteDatabase db = mPlaceDbHelper.getWritableDatabase();
156 | int match = sUriMatcher.match(uri);
157 | // Keep track of the number of deleted places
158 | int placesDeleted; // starts as 0
159 | switch (match) {
160 | // Handle the single item case, recognized by the ID included in the URI path
161 | case PLACE_WITH_ID:
162 | // Get the place ID from the URI path
163 | String id = uri.getPathSegments().get(1);
164 | // Use selections/selectionArgs to filter for this ID
165 | placesDeleted = db.delete(PlaceEntry.TABLE_NAME, "_id=?", new String[]{id});
166 | break;
167 | default:
168 | throw new UnsupportedOperationException("Unknown uri: " + uri);
169 | }
170 | // Notify the resolver of a change and return the number of items deleted
171 | if (placesDeleted != 0) {
172 | // A place (or more) was deleted, set notification
173 | getContext().getContentResolver().notifyChange(uri, null);
174 | }
175 | // Return the number of places deleted
176 | return placesDeleted;
177 | }
178 |
179 | /***
180 | * Updates a single row of data
181 | *
182 | * @param uri
183 | * @param selection
184 | * @param selectionArgs
185 | * @return number of rows affected
186 | */
187 | @Override
188 | public int update(@NonNull Uri uri, ContentValues values, String selection,
189 | String[] selectionArgs) {
190 | // Get access to underlying database
191 | final SQLiteDatabase db = mPlaceDbHelper.getWritableDatabase();
192 | int match = sUriMatcher.match(uri);
193 | // Keep track of the number of updated places
194 | int placesUpdated;
195 |
196 | switch (match) {
197 | case PLACE_WITH_ID:
198 | // Get the place ID from the URI path
199 | String id = uri.getPathSegments().get(1);
200 | // Use selections/selectionArgs to filter for this ID
201 | placesUpdated = db.update(PlaceEntry.TABLE_NAME, values, "_id=?", new String[]{id});
202 | break;
203 | // Default exception
204 | default:
205 | throw new UnsupportedOperationException("Unknown uri: " + uri);
206 | }
207 |
208 | // Notify the resolver of a change and return the number of items updated
209 | if (placesUpdated != 0) {
210 | // A place (or more) was updated, set notification
211 | getContext().getContentResolver().notifyChange(uri, null);
212 | }
213 | // Return the number of places deleted
214 | return placesUpdated;
215 | }
216 |
217 |
218 | @Override
219 | public String getType(@NonNull Uri uri) {
220 | throw new UnsupportedOperationException("Not yet implemented");
221 | }
222 | }
223 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/android/shushme/provider/PlaceContract.java:
--------------------------------------------------------------------------------
1 | package com.example.android.shushme.provider;
2 |
3 | /*
4 | * Copyright (C) 2017 The Android Open Source Project
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 |
19 | import android.net.Uri;
20 | import android.provider.BaseColumns;
21 |
22 | public class PlaceContract {
23 |
24 | // The authority, which is how your code knows which Content Provider to access
25 | public static final String AUTHORITY = "com.example.android.shushme";
26 |
27 | // The base content URI = "content://" +
28 | public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + AUTHORITY);
29 |
30 | // Define the possible paths for accessing data in this contract
31 | // This is the path for the "places" directory
32 | public static final String PATH_PLACES = "places";
33 |
34 | public static final class PlaceEntry implements BaseColumns {
35 |
36 | // TaskEntry content URI = base content URI + path
37 | public static final Uri CONTENT_URI =
38 | BASE_CONTENT_URI.buildUpon().appendPath(PATH_PLACES).build();
39 |
40 | public static final String TABLE_NAME = "places";
41 | public static final String COLUMN_PLACE_ID = "placeID";
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/android/shushme/provider/PlaceDbHelper.java:
--------------------------------------------------------------------------------
1 | package com.example.android.shushme.provider;
2 |
3 | /*
4 | * Copyright (C) 2017 The Android Open Source Project
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 |
19 | import android.content.Context;
20 | import android.database.sqlite.SQLiteDatabase;
21 | import android.database.sqlite.SQLiteOpenHelper;
22 |
23 | import com.example.android.shushme.provider.PlaceContract.PlaceEntry;
24 |
25 | public class PlaceDbHelper extends SQLiteOpenHelper {
26 |
27 | // The database name
28 | private static final String DATABASE_NAME = "shushme.db";
29 |
30 | // If you change the database schema, you must increment the database version
31 | private static final int DATABASE_VERSION = 1;
32 |
33 | // Constructor
34 | public PlaceDbHelper(Context context) {
35 | super(context, DATABASE_NAME, null, DATABASE_VERSION);
36 | }
37 |
38 | @Override
39 | public void onCreate(SQLiteDatabase sqLiteDatabase) {
40 |
41 | // Create a table to hold the places data
42 | final String SQL_CREATE_PLACES_TABLE = "CREATE TABLE " + PlaceEntry.TABLE_NAME + " (" +
43 | PlaceEntry._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
44 | PlaceEntry.COLUMN_PLACE_ID + " TEXT NOT NULL, " +
45 | "UNIQUE (" + PlaceEntry.COLUMN_PLACE_ID + ") ON CONFLICT REPLACE" +
46 | "); ";
47 |
48 | sqLiteDatabase.execSQL(SQL_CREATE_PLACES_TABLE);
49 | }
50 |
51 | @Override
52 | public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
53 | // For now simply drop the table and create a new one.
54 | sqLiteDatabase.execSQL("DROP TABLE IF EXISTS " + PlaceEntry.TABLE_NAME);
55 | onCreate(sqLiteDatabase);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_globe_primary_24dp.xml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
17 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_gps_fixed_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
17 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_my_location_primary_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_notifications_active_primary_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_place_accent_24dp.xml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
17 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_volume_off_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
17 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_volume_up_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
17 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
17 |
18 |
25 |
26 |
33 |
34 |
39 |
40 |
44 |
45 |
52 |
53 |
60 |
61 |
62 |
66 |
67 |
74 |
75 |
82 |
83 |
86 |
87 |
94 |
95 |
96 |
97 |
98 |
102 |
103 |
110 |
111 |
118 |
119 |
122 |
123 |
130 |
131 |
132 |
133 |
134 |
135 |
144 |
145 |
152 |
153 |
158 |
159 |
163 |
164 |
165 |
166 |
172 |
173 |
179 |
180 |
187 |
188 |
189 |
190 |
191 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_place_card.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
17 |
18 |
23 |
24 |
31 |
32 |
36 |
37 |
45 |
46 |
54 |
55 |
56 |
57 |
58 |
59 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/AdvancedAndroid_Shushme/164e332c47703f6be02632a49c8c8cfad04a8e9e/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/AdvancedAndroid_Shushme/164e332c47703f6be02632a49c8c8cfad04a8e9e/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/AdvancedAndroid_Shushme/164e332c47703f6be02632a49c8c8cfad04a8e9e/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/AdvancedAndroid_Shushme/164e332c47703f6be02632a49c8c8cfad04a8e9e/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/AdvancedAndroid_Shushme/164e332c47703f6be02632a49c8c8cfad04a8e9e/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | ShushMe
3 | Add new location
4 | Locations
5 | You need to enable location permissions first
6 | Location Permissions Granted
7 | EnabledSetting
8 | Settings
9 | Location Permissions
10 | Enable Geofences
11 | http://www.google.com/policies/privacy
12 | Ringer Permissions
13 | Silent mode activated
14 | Touch to launch the app.
15 | Back to normal
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.2.3'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | jcenter()
18 | }
19 | }
20 |
21 | task clean(type: Delete) {
22 | delete rootProject.buildDir
23 | }
24 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/AdvancedAndroid_Shushme/164e332c47703f6be02632a49c8c8cfad04a8e9e/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Dec 28 10:00:20 PST 2015
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/screenshots/screen_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/AdvancedAndroid_Shushme/164e332c47703f6be02632a49c8c8cfad04a8e9e/screenshots/screen_1.png
--------------------------------------------------------------------------------
/screenshots/screen_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/AdvancedAndroid_Shushme/164e332c47703f6be02632a49c8c8cfad04a8e9e/screenshots/screen_2.png
--------------------------------------------------------------------------------
/screenshots/screen_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/AdvancedAndroid_Shushme/164e332c47703f6be02632a49c8c8cfad04a8e9e/screenshots/screen_3.png
--------------------------------------------------------------------------------
/screenshots/screen_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/AdvancedAndroid_Shushme/164e332c47703f6be02632a49c8c8cfad04a8e9e/screenshots/screen_4.png
--------------------------------------------------------------------------------
/screenshots/screen_5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/AdvancedAndroid_Shushme/164e332c47703f6be02632a49c8c8cfad04a8e9e/screenshots/screen_5.png
--------------------------------------------------------------------------------
/screenshots/screen_6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/AdvancedAndroid_Shushme/164e332c47703f6be02632a49c8c8cfad04a8e9e/screenshots/screen_6.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/test_locations.kml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Queensland Conservatorium Library
6 | Library
7 |
8 | 153.0203,-27.4764,0
9 |
10 |
11 |
12 | Starbucks
13 | Starbucks
14 |
15 | 153.0257259,-27.4703356,0
16 |
17 |
18 |
19 | Subway
20 | Subway
21 |
22 | 153.0284443,-27.4703445,0
23 |
24 |
25 |
26 | Gardens
27 | Gardens
28 |
29 | 153.0298981,-27.4754086,0
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------