1 /*
2  * Copyright (C) 2014 The Android Open Source Project
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.example.android.wearable.agendadata;
18 
19 import static com.example.android.wearable.agendadata.Constants.TAG;
20 
21 import android.Manifest;
22 import android.content.Intent;
23 import android.content.IntentSender;
24 import android.content.pm.PackageManager;
25 import android.net.Uri;
26 import android.os.Bundle;
27 import android.support.annotation.NonNull;
28 import android.support.design.widget.Snackbar;
29 import android.support.v4.app.ActivityCompat;
30 import android.support.v7.app.AppCompatActivity;
31 import android.util.Log;
32 import android.view.View;
33 import android.widget.ScrollView;
34 import android.widget.TextView;
35 
36 import com.google.android.gms.common.ConnectionResult;
37 import com.google.android.gms.common.api.GoogleApiClient;
38 import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
39 import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
40 import com.google.android.gms.common.api.ResultCallback;
41 import com.google.android.gms.wearable.DataApi;
42 import com.google.android.gms.wearable.DataItem;
43 import com.google.android.gms.wearable.DataItemBuffer;
44 import com.google.android.gms.wearable.Wearable;
45 
46 /**
47  * Syncs or deletes calendar events (event time, description, and background image) to your
48  * Wearable via the Wearable DataApi at the click of a button. Includes code to handle dynamic M+
49  * permissions as well.
50  */
51 public class MainActivity extends AppCompatActivity implements
52         ConnectionCallbacks,
53         OnConnectionFailedListener,
54         ActivityCompat.OnRequestPermissionsResultCallback {
55 
56     /* Request code for launching the Intent to resolve Google Play services errors. */
57     private static final int REQUEST_RESOLVE_ERROR = 1000;
58 
59     /* Id to identify calendar and contact permissions request. */
60     private static final int REQUEST_CALENDAR_AND_CONTACTS = 0;
61 
62 
63     private GoogleApiClient mGoogleApiClient;
64     private boolean mResolvingError = false;
65 
66     private View mLayout;
67 
68     private TextView mLogTextView;
69     private ScrollView mScroller;
70 
71     @Override
onCreate(Bundle savedInstanceState)72     protected void onCreate(Bundle savedInstanceState) {
73         super.onCreate(savedInstanceState);
74 
75         setContentView(R.layout.main);
76         mLayout = findViewById(R.id.main_layout);
77 
78         mLogTextView = (TextView) findViewById(R.id.log);
79         mScroller = (ScrollView) findViewById(R.id.scroller);
80 
81         mGoogleApiClient = new GoogleApiClient.Builder(this)
82                 .addApi(Wearable.API)
83                 .addConnectionCallbacks(this)
84                 .addOnConnectionFailedListener(this)
85                 .build();
86     }
87 
88     @Override
onStart()89     protected void onStart() {
90         super.onStart();
91         if (!mResolvingError) {
92             mGoogleApiClient.connect();
93         }
94     }
95 
96     @Override
onStop()97     protected void onStop() {
98         if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
99             mGoogleApiClient.disconnect();
100         }
101 
102         super.onStop();
103     }
104 
onGetEventsClicked(View view)105     public void onGetEventsClicked(View view) {
106 
107         Log.i(TAG, "onGetEventsClicked(): Checking permission.");
108 
109         // BEGIN_INCLUDE(calendar_and_contact_permissions)
110         // Check if the Calendar permission is already available.
111         boolean calendarApproved =
112                 ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_CALENDAR)
113                         == PackageManager.PERMISSION_GRANTED;
114 
115         boolean contactsApproved =
116                 ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS)
117                         == PackageManager.PERMISSION_GRANTED;
118 
119         if (!calendarApproved || !contactsApproved) {
120             // Calendar and/or Contact permissions have not been granted.
121            requestCalendarAndContactPermissions();
122 
123         } else {
124             // Calendar permissions is already available, start service
125             Log.i(TAG, "Permissions already granted. Starting service.");
126             pushCalendarToWear();
127         }
128         // END_INCLUDE(calendar_and_contact_permissions)
129 
130     }
131 
132     /*
133      * Requests Calendar and Contact permissions.
134      * If the permission has been denied previously, a SnackBar will prompt the user to grant the
135      * permission, otherwise it is requested directly.
136      */
requestCalendarAndContactPermissions()137     private void requestCalendarAndContactPermissions() {
138         Log.i(TAG, "CALENDAR permission has NOT been granted. Requesting permission.");
139 
140         // BEGIN_INCLUDE(calendar_and_contact_permissions_request)
141 
142         boolean showCalendarPermissionRationale =
143                 ActivityCompat.shouldShowRequestPermissionRationale(this,
144                         Manifest.permission.READ_CALENDAR);
145         boolean showContactsPermissionRationale =
146                 ActivityCompat.shouldShowRequestPermissionRationale(this,
147                         Manifest.permission.READ_CONTACTS);
148 
149         if (showCalendarPermissionRationale || showContactsPermissionRationale) {
150             /*
151              * Provide an additional rationale to the user if the permission was not granted and
152              * the user would benefit from additional context for the use of the permission. For
153              * example, if the user has previously denied the permission.
154              */
155             Log.i(TAG, "Display calendar & contact permissions rationale for additional context.");
156 
157             Snackbar.make(mLayout, R.string.permissions_rationale,
158                     Snackbar.LENGTH_INDEFINITE)
159                     .setAction(R.string.ok, new View.OnClickListener() {
160                         @Override
161                         public void onClick(View view) {
162                             ActivityCompat.requestPermissions(MainActivity.this,
163                                     new String[] {
164                                             Manifest.permission.READ_CALENDAR,
165                                             Manifest.permission.READ_CONTACTS},
166                                     REQUEST_CALENDAR_AND_CONTACTS);
167                         }
168                     })
169                     .show();
170 
171 
172         } else {
173 
174             // Calendar/Contact permissions have not been granted yet. Request it directly.
175             ActivityCompat.requestPermissions(
176                     this,
177                     new String[]{
178                             Manifest.permission.READ_CALENDAR,
179                             Manifest.permission.READ_CONTACTS
180                     },
181                     REQUEST_CALENDAR_AND_CONTACTS);
182         }
183         // END_INCLUDE(calendar_and_contact_permissions_request)
184     }
185 
pushCalendarToWear()186     private void pushCalendarToWear() {
187         startService(new Intent(this, CalendarQueryService.class));
188     }
189 
onDeleteEventsClicked(View view)190     public void onDeleteEventsClicked(View view) {
191         if (mGoogleApiClient.isConnected()) {
192             Wearable.DataApi.getDataItems(mGoogleApiClient)
193                     .setResultCallback(new ResultCallback<DataItemBuffer>() {
194                         @Override
195                         public void onResult(DataItemBuffer result) {
196                             try {
197                                 if (result.getStatus().isSuccess()) {
198                                     deleteDataItems(result);
199                                 } else {
200                                     if (Log.isLoggable(TAG, Log.DEBUG)) {
201                                         Log.d(TAG, "onDeleteEventsClicked(): failed to get Data "
202                                                 + "Items");
203                                     }
204                                 }
205                             } finally {
206                                 result.release();
207                             }
208                         }
209                     });
210         } else {
211             Log.e(TAG, "Failed to delete data items"
212                     + " - Client disconnected from Google Play Services");
213         }
214     }
215 
deleteDataItems(final DataItemBuffer dataItemList)216     private void deleteDataItems(final DataItemBuffer dataItemList) {
217         if (mGoogleApiClient.isConnected()) {
218             for (final DataItem dataItem : dataItemList) {
219                 final Uri dataItemUri = dataItem.getUri();
220                 /*
221                  * In a real calendar application, this might delete the corresponding calendar
222                  * events from the calendar data provider. However, we simply delete the DataItem,
223                  * but leave the phone's calendar data intact for this simple sample.
224                  */
225                 Wearable.DataApi.deleteDataItems(mGoogleApiClient, dataItemUri)
226                         .setResultCallback(new ResultCallback<DataApi.DeleteDataItemsResult>() {
227                             @Override
228                             public void onResult(DataApi.DeleteDataItemsResult deleteResult) {
229                                 if (deleteResult.getStatus().isSuccess()) {
230                                     appendLog("Successfully deleted data item: " + dataItemUri);
231                                 } else {
232                                     appendLog("Failed to delete data item:" + dataItemUri);
233                                 }
234                             }
235                         });
236             }
237         } else {
238             Log.e(TAG, "Failed to delete data items"
239                      + " - Client disconnected from Google Play Services");
240         }
241     }
242 
243     @Override
onConnected(Bundle connectionHint)244     public void onConnected(Bundle connectionHint) {
245         if (Log.isLoggable(TAG, Log.DEBUG)) {
246             Log.d(TAG, "Connected to Google Api Service.");
247         }
248         mResolvingError = false;
249     }
250 
251     @Override
onConnectionSuspended(int cause)252     public void onConnectionSuspended(int cause) {
253         if (Log.isLoggable(TAG, Log.DEBUG)) {
254             Log.d(TAG, "onConnectionSuspended(): Cause id: " + cause);
255         }
256     }
257 
258     @Override
onConnectionFailed(ConnectionResult result)259     public void onConnectionFailed(ConnectionResult result) {
260         if (Log.isLoggable(TAG, Log.DEBUG)) {
261             Log.d(TAG, "Disconnected from Google Api Service");
262         }
263 
264         if (mResolvingError) {
265             // Already attempting to resolve an error.
266             return;
267         } else if (result.hasResolution()) {
268             try {
269                 mResolvingError = true;
270                 result.startResolutionForResult(this, REQUEST_RESOLVE_ERROR);
271             } catch (IntentSender.SendIntentException e) {
272                 // There was an error with the resolution intent. Try again.
273                 mResolvingError = false;
274                 mGoogleApiClient.connect();
275             }
276         } else {
277             mResolvingError = false;
278         }
279     }
280 
281     @Override
onActivityResult(int requestCode, int resultCode, Intent data)282     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
283         super.onActivityResult(requestCode, resultCode, data);
284         if (Log.isLoggable(TAG, Log.DEBUG)) {
285             Log.d(TAG, "onActivityResult request/result codes: " + requestCode + "/" + resultCode);
286         }
287 
288         if (requestCode == REQUEST_RESOLVE_ERROR) {
289             mResolvingError = false;
290             if (resultCode == RESULT_OK) {
291                 // Make sure the app is not already connected or attempting to connect
292                 if (!mGoogleApiClient.isConnecting() && !mGoogleApiClient.isConnected()) {
293                     mGoogleApiClient.connect();
294                 }
295             }
296         }
297     }
298 
299     /**
300      * Callback received when a permissions request has been completed.
301      */
302     @Override
onRequestPermissionsResult( int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)303     public void onRequestPermissionsResult(
304             int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
305 
306         if (Log.isLoggable(TAG, Log.DEBUG)) {
307             Log.d(TAG, "onRequestPermissionsResult(): " + permissions);
308         }
309 
310         if (requestCode == REQUEST_CALENDAR_AND_CONTACTS) {
311             // BEGIN_INCLUDE(permissions_result)
312             // Received permission result for calendar permission.
313             Log.i(TAG, "Received response for Calendar permission request.");
314 
315             // Check if all required permissions have been granted.
316             if ((grantResults.length == 2)
317                     && (grantResults[0] == PackageManager.PERMISSION_GRANTED)
318                     && (grantResults[1] == PackageManager.PERMISSION_GRANTED)) {
319                 // Calendar/Contact permissions have been granted, pull all calendar events
320                 Log.i(TAG, "All permission has now been granted. Showing preview.");
321                 Snackbar.make(mLayout, R.string.permisions_granted, Snackbar.LENGTH_SHORT).show();
322 
323                 pushCalendarToWear();
324 
325             } else {
326                 Log.i(TAG, "CALENDAR and/or CONTACT permissions were NOT granted.");
327                 Snackbar.make(mLayout, R.string.permissions_denied, Snackbar.LENGTH_SHORT).show();
328             }
329             // END_INCLUDE(permissions_result)
330 
331         } else {
332             super.onRequestPermissionsResult(requestCode, permissions, grantResults);
333         }
334     }
335 
appendLog(final String s)336     private void appendLog(final String s) {
337         mLogTextView.post(new Runnable() {
338             @Override
339             public void run() {
340                 mLogTextView.append(s);
341                 mLogTextView.append("\n");
342                 mScroller.fullScroll(View.FOCUS_DOWN);
343             }
344         });
345     }
346 }