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 }