Java tutorial
/* * Copyright (C) 2016 The Nerdery, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.nerderylabs.android.nerdalert.ui.activity; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.PendingResult; import com.google.android.gms.common.api.ResultCallback; import com.google.android.gms.common.api.Status; import com.google.android.gms.nearby.Nearby; import com.google.android.gms.nearby.messages.Message; import com.google.android.gms.nearby.messages.MessageListener; import com.google.android.gms.nearby.messages.NearbyMessagesStatusCodes; import com.google.android.gms.nearby.messages.PublishCallback; import com.google.android.gms.nearby.messages.PublishOptions; import com.google.android.gms.nearby.messages.SubscribeCallback; import com.google.android.gms.nearby.messages.SubscribeOptions; import com.nerderylabs.android.nerdalert.Constants; import com.nerderylabs.android.nerdalert.R; import com.nerderylabs.android.nerdalert.model.Neighbor; import com.nerderylabs.android.nerdalert.model.Tabs; import com.nerderylabs.android.nerdalert.settings.Settings; import com.nerderylabs.android.nerdalert.ui.fragment.MainFragment; import com.nerderylabs.android.nerdalert.ui.fragment.TabFragment; import com.nerderylabs.android.nerdalert.util.NearbyApiUtil; import android.content.Intent; import android.content.IntentSender; import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.Window; import android.view.WindowManager; import android.widget.Toast; public class MainActivity extends AppCompatActivity implements NearbyInterface, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener { private static final String TAG = MainActivity.class.getSimpleName(); private static final String MAIN_FRAGMENT_TAG = TAG + "_main_fragment_tag"; private GoogleApiClient googleApiClient; // Tracks if we are currently resolving an error related to Nearby permissions. Used to avoid // duplicate Nearby permission dialogs if the user initiates both subscription and publication // actions without having opted into Nearby. private boolean resolvingNearbyPermissionError = false; // The listener that receives new Nearby messages when subscribing private MessageListener messageListener; private Neighbor publishedInfo = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // set the navigation bar color on Lollipop+ devices if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Window window = getWindow(); window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); window.setStatusBarColor(ContextCompat.getColor(this, R.color.color_primary_dark)); } setContentView(R.layout.activity_main); // setup the Google API Client, requesting access to the Nearby Messages API // DO NOT use the application context here, otherwise the Nearby API will fail with the // following error when publishing/subscribing: // Attempting to perform a high-power operation from a non-Activity Context googleApiClient = new GoogleApiClient.Builder(this).addApi(Nearby.MESSAGES_API).addConnectionCallbacks(this) .addOnConnectionFailedListener(this).build(); initializeMessageListener(); FragmentManager fm = getSupportFragmentManager(); MainFragment mainFragment = (MainFragment) fm.findFragmentByTag(MAIN_FRAGMENT_TAG); if (mainFragment == null) { mainFragment = new MainFragment(); fm.beginTransaction().add(R.id.container, mainFragment, MAIN_FRAGMENT_TAG).commit(); } } @Override protected void onStart() { super.onStart(); if (!googleApiClient.isConnected()) { googleApiClient.connect(); } } @Override protected void onStop() { unpublish(); unsubscribe(); googleApiClient.disconnect(); // sometimes the app dies before the callbacks complete, so let's force the // unpublish/unsubscribe state so the FAB isn't spinning when the app starts back up. Settings.setPublishing(this, false); Settings.setSubscribing(this, false); super.onStop(); } // If the user has requested a subscription or publication task that requires // GoogleApiClient to be connected, we keep track of that task and execute it here, since // we now have a connected GoogleApiClient. @Override public void onConnected(Bundle bundle) { Log.i(TAG, "Google API Client connected"); if (Settings.isPublishing(this)) { publish(); } else { unpublish(); } if (Settings.isSubscribing(this)) { subscribe(); } else { unsubscribe(); } } @Override public void onConnectionSuspended(int i) { String text1 = getString(R.string.google_api_connection_suspended); String text2; switch (i) { case CAUSE_NETWORK_LOST: text2 = getString(R.string.google_api_network_lost); break; case CAUSE_SERVICE_DISCONNECTED: text2 = getString(R.string.google_api_service_disconnected); break; default: text2 = getString(R.string.google_api_unknown); break; } String error = text1 + ": " + text2; Log.w(TAG, error); Toast.makeText(this, error, Toast.LENGTH_SHORT).show(); } @Override public void onConnectionFailed(@NonNull ConnectionResult connectionResult) { String error = getString(R.string.google_api_connection_failed); Log.e(TAG, error); Toast.makeText(this, error, Toast.LENGTH_SHORT).show(); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == Constants.REQUEST_GOOGLE_PLAY_ERROR) { resolvingNearbyPermissionError = false; if (resultCode == RESULT_OK) { publish(); subscribe(); } else if (resultCode == RESULT_CANCELED) { Log.w(TAG, "User denied requested permissions."); Toast.makeText(this, getString(R.string.permission_denied_nearby), Toast.LENGTH_LONG).show(); } else { Log.e(TAG, "Failed to resolve error with code=" + resultCode); } } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == Constants.REQUEST_ASK_PERMISSIONS) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { Log.i(TAG, "Permission Granted!"); MainFragment fragment = (MainFragment) getSupportFragmentManager().findFragmentById(R.id.container); fragment.restoreUserInformation(); } else { Log.w(TAG, "Permission Denied!"); Toast.makeText(this, getString(R.string.permission_denied_contacts), Toast.LENGTH_LONG).show(); } } else { super.onRequestPermissionsResult(requestCode, permissions, grantResults); } } private void publish() { if (publishedInfo != null) { publish(publishedInfo); } } @Override public void publish(Neighbor myInfo) { Log.d(TAG, "publish( " + myInfo + " )"); publishedInfo = myInfo; // Cannot proceed without a connected GoogleApiClient. Reconnect and execute the pending // task in onConnected(). if (!googleApiClient.isConnected()) { if (!googleApiClient.isConnecting()) { googleApiClient.connect(); } } else { // finally, the part that actually uses the API we're demoing... Message message = NearbyApiUtil.newNearbyMessage(this, myInfo); PublishOptions.Builder builder = new PublishOptions.Builder(); builder.setStrategy(NearbyApiUtil.MESSAGE_STRATEGY); builder.setCallback(new PublishCallback() { @Override public void onExpired() { Log.i(TAG, "PublishCallback.onExpired(): No longer publishing"); Settings.setPublishing(MainActivity.this, false); } }); PublishOptions options = builder.build(); PendingResult<Status> result = Nearby.Messages.publish(googleApiClient, message, options); result.setResultCallback(new ResultCallback<Status>() { @Override public void onResult(@NonNull Status status) { if (status.isSuccess()) { // we're done publishing! Log.i(TAG, "Nearby publish successful"); Settings.setPublishing(MainActivity.this, true); } else { Log.w(TAG, "Nearby publish unsuccessful"); Settings.setPublishing(MainActivity.this, false); handleUnsuccessfulNearbyResult(status); } } }); } } private void unpublish() { if (publishedInfo != null) { unpublish(publishedInfo); } } @Override public void unpublish(Neighbor myInfo) { Log.d(TAG, "unpublish( " + myInfo + " )"); // Cannot proceed without a connected GoogleApiClient. Reconnect and execute the pending // task in onConnected(). if (!googleApiClient.isConnected()) { if (!googleApiClient.isConnecting()) { googleApiClient.connect(); } } else { Message message = NearbyApiUtil.newNearbyMessage(this, myInfo); PendingResult<Status> result = Nearby.Messages.unpublish(googleApiClient, message); result.setResultCallback(new ResultCallback<Status>() { @Override public void onResult(@NonNull Status status) { if (status.isSuccess()) { Log.i(TAG, "Nearby unpublish successful"); Settings.setPublishing(MainActivity.this, false); } else { Log.w(TAG, "Nearby unpublish unsuccessful"); Settings.setPublishing(MainActivity.this, true); handleUnsuccessfulNearbyResult(status); } } }); } } @Override public void subscribe() { Log.d(TAG, "subscribe()"); Settings.setSubscribing(this, true); // Cannot proceed without a connected GoogleApiClient. Reconnect and execute the pending // task in onConnected(). if (!googleApiClient.isConnected()) { if (!googleApiClient.isConnecting()) { googleApiClient.connect(); } } else { SubscribeOptions.Builder builder = new SubscribeOptions.Builder(); builder.setStrategy(NearbyApiUtil.MESSAGE_STRATEGY); builder.setCallback(new SubscribeCallback() { @Override public void onExpired() { Log.i(TAG, "SubscribeCallback.onExpired(): No longer subscribing"); Settings.setSubscribing(MainActivity.this, false); } }); SubscribeOptions options = builder.build(); Nearby.Messages.subscribe(googleApiClient, messageListener, options) .setResultCallback(new ResultCallback<Status>() { @Override public void onResult(@NonNull Status status) { if (status.isSuccess()) { Log.i(TAG, "Nearby subscribe successful"); Settings.setSubscribing(MainActivity.this, true); } else { Log.w(TAG, "Nearby subscribe unsuccessful"); Settings.setSubscribing(MainActivity.this, false); handleUnsuccessfulNearbyResult(status); } } }); } } @Override public void unsubscribe() { Log.d(TAG, "unsubscribe()"); // Cannot proceed without a connected GoogleApiClient. Reconnect and execute the pending // task in onConnected(). if (!googleApiClient.isConnected()) { if (!googleApiClient.isConnecting()) { googleApiClient.connect(); } } else { Nearby.Messages.unsubscribe(googleApiClient, messageListener) .setResultCallback(new ResultCallback<Status>() { @Override public void onResult(@NonNull Status status) { if (status.isSuccess()) { Log.i(TAG, "Nearby unsubscribe successful"); Settings.setSubscribing(MainActivity.this, false); // clear the list of Neighbors since we're not subscribing anymore TabFragment nerdFragment = (TabFragment) findViewPagerFragment(R.id.viewpager, Tabs.NERDS.getTabIndex()); nerdFragment.clearNeighborList(); TabFragment beaconFragment = (TabFragment) findViewPagerFragment(R.id.viewpager, Tabs.BEACONS.getTabIndex()); beaconFragment.clearNeighborList(); } else { Log.w(TAG, "Nearby unsubscribe unsuccessful"); Settings.setSubscribing(MainActivity.this, true); handleUnsuccessfulNearbyResult(status); } } }); } } // Handles errors generated when performing a subscription or publication action. Uses // Status#startResolutionForResult to display an opt-in dialog to handle the case // where a device is not opted into using Nearby. private void handleUnsuccessfulNearbyResult(Status status) { Log.e(TAG, "processing error, status = " + status); if (status.getStatusCode() == NearbyMessagesStatusCodes.APP_NOT_OPTED_IN) { if (!resolvingNearbyPermissionError) { try { resolvingNearbyPermissionError = true; status.startResolutionForResult(this, Constants.REQUEST_GOOGLE_PLAY_ERROR); } catch (IntentSender.SendIntentException e) { e.printStackTrace(); } } } else { if (status.getStatusCode() == ConnectionResult.NETWORK_ERROR) { Toast.makeText(this, getString(R.string.network_unavailable), Toast.LENGTH_LONG).show(); } else { // To keep things simple, pop a toast for all other error messages. Toast.makeText(this.getApplicationContext(), "Unsuccessful: " + status.getStatusMessage(), Toast.LENGTH_LONG).show(); } } } private void initializeMessageListener() { messageListener = new MessageListener() { @Override public void onFound(Message message) { // found message Log.d(TAG, "Message Found: " + message.getContent().length + " bytes"); Neighbor neighbor = NearbyApiUtil.parseNearbyMessage(message); if (neighbor != null) { TabFragment tabFragment; if (message.getType().equals(NearbyApiUtil.TYPE_BEACON)) { tabFragment = (TabFragment) findViewPagerFragment(R.id.viewpager, Tabs.BEACONS.getTabIndex()); } else { tabFragment = (TabFragment) findViewPagerFragment(R.id.viewpager, Tabs.NERDS.getTabIndex()); } if (tabFragment != null) { Log.d(TAG, "Adding neighbor: " + neighbor); tabFragment.addNeighbor(neighbor); } } } @Override public void onLost(Message message) { // lost message Log.d(TAG, "Message Lost: " + message.getContent().length + " bytes"); Neighbor neighbor = NearbyApiUtil.parseNearbyMessage(message); if (neighbor != null) { TabFragment tabFragment; if (message.getType().equals(NearbyApiUtil.TYPE_BEACON)) { tabFragment = (TabFragment) findViewPagerFragment(R.id.viewpager, Tabs.BEACONS.getTabIndex()); } else { tabFragment = (TabFragment) findViewPagerFragment(R.id.viewpager, Tabs.NERDS.getTabIndex()); } if (tabFragment != null) { Log.d(TAG, "Adding neighbor: " + neighbor); tabFragment.removeNeighbor(neighbor); } } } }; } // Fragments added to a ViewPager via the FragmentPagerManager are auto-tagged when // instantiated using the private static method FragmentPagerAdapter.makeFragmentName(). // This method reverses the tag format generated in order to retrieve the fragment. private Fragment findViewPagerFragment(int viewPagerId, int index) { return getSupportFragmentManager().findFragmentByTag("android:switcher:" + viewPagerId + ":" + index); } }