Java tutorial
/* * Copyright 2013 SciFY NPO <info@scify.org>. * * This product is part of the NewSum Free Software. * For more information about NewSum visit * * http://www.scify.gr/site/en/our-projects/completed-projects/newsum-menu-en * * 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. * * If this code or its output is used, extended, re-engineered, integrated, * or embedded to any extent in another software or hardware, there MUST be * an explicit attribution to this work in the resulting source code, * the packaging (where such packaging exists), or user interface * (where such an interface exists). * The attribution must be of the form "Powered by NewSum, SciFY" */ package gr.scify.newsum.ui; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GooglePlayServicesClient.ConnectionCallbacks; import com.google.android.gms.common.GooglePlayServicesClient.OnConnectionFailedListener; import com.google.android.gms.common.GooglePlayServicesUtil; import com.google.android.gms.plus.PlusClient; import com.google.android.gms.plus.PlusClient.OnAccessRevokedListener; import android.app.Activity; import android.app.Dialog; import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentSender.SendIntentException; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.support.v4.app.DialogFragment; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import java.util.Arrays; /** * A fragment that you can add to your layout to automate connecting a {@link PlusClient}. * The key integration points are to attach the fragment with {@link #getPlusClientFragment} * in your Activity's {@code onCreate} method, then call {@link #signIn} when the user clicks your * sign-in button and forward calls to {@code onActivityResult} to {@link #handleOnActivityResult}. */ public final class PlusClientFragment extends Fragment implements ConnectionCallbacks, OnConnectionFailedListener, OnAccessRevokedListener { /** * Tag to refer to this fragment. */ private static final String TAG_PLUS_CLIENT = "plusClientFragment"; /** * Tag to refer to an error (resolution) dialog. */ private static final String TAG_ERROR_DIALOG = "plusClientFragmentErrorDialog"; /** * Tag to refer to a progress dialog when sign in is requested. */ private static final String TAG_PROGRESS_DIALOG = "plusClientFragmentProgressDialog"; /** * Array of strings representing visible activities to request for {@link #getArguments()}. */ private static final String ARG_VISIBLE_ACTIVITIES = "visible_activities"; /** * Integer request code to apply to requests, as set by {@link #signIn(int)}. */ private static final String STATE_REQUEST_CODE = "request_code"; /** * Signed in successfully connection state. */ private static final ConnectionResult CONNECTION_RESULT_SUCCESS = new ConnectionResult(ConnectionResult.SUCCESS, null); /** * An invalid request code to use to indicate that {@link #signIn(int)} hasn't been called. */ private static final int INVALID_REQUEST_CODE = -1; // The PlusClient to connect. private PlusClient mPlusClient; // The last result from onConnectionFailed. private ConnectionResult mLastConnectionResult; // The request specified in signIn or INVALID_REQUEST_CODE if not signing in. private int mRequestCode; // A handler to post callbacks (rather than call them in a potentially reentrant way.) private Handler mHandler; /** Tracks if {@link PlusClient#connect()} has been called. */ private boolean mIsConnecting; /** * Local handler to send callbacks on sign in. */ private final class PlusClientFragmentHandler extends Handler { public static final int WHAT_SIGNED_IN = 1; public PlusClientFragmentHandler() { super(Looper.getMainLooper()); } @Override public void handleMessage(Message msg) { if (msg.what != WHAT_SIGNED_IN) { return; } Activity activity = getActivity(); if (mPlusClient.isConnected() && activity instanceof OnSignedInListener) { ((OnSignedInListener) activity).onSignedIn(mPlusClient); } } } /** * Listener interface for sign in events. Activities hosting a {@link PlusClientFragment} * must implement this. */ public static interface OnSignedInListener { /** * Called when the {@link PlusClient} has been connected successfully. * * @param plusClient The connected {@link PlusClient} to make requests on. */ void onSignedIn(PlusClient plusClient); } /** * Attach a {@link PlusClient} managing fragment to you activity. * * @param activity The activity to attach the fragment to. * @param visibleActivities An array of visible activities to request. * @return The fragment managing a {@link PlusClient}. */ public static PlusClientFragment getPlusClientFragment(FragmentActivity activity, String[] visibleActivities) { if (!(activity instanceof OnSignedInListener)) { throw new IllegalArgumentException( "The activity must implement OnSignedInListener to receive callbacks."); } // Check if the fragment is already attached. FragmentManager fragmentManager = activity.getSupportFragmentManager(); Fragment fragment = fragmentManager.findFragmentByTag(TAG_PLUS_CLIENT); if (fragment instanceof PlusClientFragment) { // The fragment is attached. If it has the right visible activities, return it. if (Arrays.equals(visibleActivities, fragment.getArguments().getStringArray(ARG_VISIBLE_ACTIVITIES))) { return (PlusClientFragment) fragment; } } FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); // If a fragment was already attached, remove it to clean up. if (fragment != null) { fragmentTransaction.remove(fragment); } // Create a new fragment and attach it to the fragment manager. Bundle arguments = new Bundle(); arguments.putStringArray(ARG_VISIBLE_ACTIVITIES, visibleActivities); PlusClientFragment signInFragment = new PlusClientFragment(); signInFragment.setArguments(arguments); fragmentTransaction.add(signInFragment, TAG_PLUS_CLIENT); fragmentTransaction.commit(); return signInFragment; } /** * Creates a {@link PlusClient} and kicks-off the connection flow. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Retain instance to avoid reconnecting on rotate. This means that onDestroy and onCreate // will not be called on configuration changes. setRetainInstance(true); mHandler = new PlusClientFragmentHandler(); // Create the PlusClient. PlusClient.Builder plusClientBuilder = new PlusClient.Builder(getActivity().getApplicationContext(), this, this); String[] visibleActivities = getArguments().getStringArray(ARG_VISIBLE_ACTIVITIES); if (visibleActivities != null && visibleActivities.length > 0) { plusClientBuilder.setVisibleActivities(visibleActivities); } mPlusClient = plusClientBuilder.build(); if (savedInstanceState == null) { mRequestCode = INVALID_REQUEST_CODE; } else { mRequestCode = savedInstanceState.getInt(STATE_REQUEST_CODE, INVALID_REQUEST_CODE); } // Attempt to connect. mPlusClient.connect(); mIsConnecting = true; } /** * Disconnects the {@link PlusClient} to avoid leaks. */ @Override public void onDestroy() { super.onDestroy(); if (mIsConnecting || mPlusClient.isConnected()) { mPlusClient.disconnect(); } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt(STATE_REQUEST_CODE, mRequestCode); } @Override public void onResume() { super.onResume(); if (mRequestCode == INVALID_REQUEST_CODE) { // No user interaction, hide the progress dialog. hideProgressDialog(); hideErrorDialog(); } else if (mLastConnectionResult != null && !mLastConnectionResult.isSuccess()) { showProgressDialog(); } } @Override public void onStart() { super.onStart(); if (mRequestCode == INVALID_REQUEST_CODE && !mIsConnecting) { mLastConnectionResult = null; mPlusClient.connect(); mIsConnecting = true; } } @Override public void onConnectionFailed(ConnectionResult connectionResult) { mLastConnectionResult = connectionResult; mIsConnecting = false; // On a failed connection try again. if (isResumed() && mRequestCode != INVALID_REQUEST_CODE) { resolveLastResult(); } } @Override public void onAccessRevoked(ConnectionResult status) { // Reconnect to get a new mPlusClient. mLastConnectionResult = null; // Cancel sign in. mRequestCode = INVALID_REQUEST_CODE; // Reconnect to fetch the sign-in (account chooser) intent from the plus client. mPlusClient.connect(); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // Let new activities know the signed-in state. if (mPlusClient.isConnected()) { mHandler.sendEmptyMessage(PlusClientFragmentHandler.WHAT_SIGNED_IN); } } @Override public void onDisconnected() { mIsConnecting = false; } /** * Shows any UI required to resolve the error connecting. */ public void signIn(int requestCode) { if (requestCode < 0) { throw new IllegalArgumentException("A non-negative request code is required."); } if (mPlusClient.isConnected()) { // Already connected! Schedule callback. mHandler.sendEmptyMessage(PlusClientFragmentHandler.WHAT_SIGNED_IN); return; } if (mRequestCode != INVALID_REQUEST_CODE) { // We're already signing in. return; } mRequestCode = requestCode; if (mLastConnectionResult == null) { // We're starting up, show progress. showProgressDialog(); return; } resolveLastResult(); } /** * Perform resolution given a non-null result. */ private void resolveLastResult() { if (GooglePlayServicesUtil.isUserRecoverableError(mLastConnectionResult.getErrorCode())) { // Show a dialog to install or enable Google Play services. showErrorDialog(ErrorDialogFragment.create(mLastConnectionResult.getErrorCode(), mRequestCode)); return; } if (mLastConnectionResult.hasResolution()) { startResolution(); } } public static final class ErrorDialogFragment extends GooglePlayServicesErrorDialogFragment { public static ErrorDialogFragment create(int errorCode, int requestCode) { ErrorDialogFragment fragment = new ErrorDialogFragment(); fragment.setArguments(createArguments(errorCode, requestCode)); return fragment; } @Override public void onCancel(DialogInterface dialog) { super.onCancel(dialog); FragmentActivity activity = getActivity(); if (activity == null) { return; } Fragment fragment = activity.getSupportFragmentManager().findFragmentByTag(TAG_PLUS_CLIENT); if (fragment instanceof PlusClientFragment) { ((PlusClientFragment) fragment).onDialogCanceled(getTag()); } } @Override public void onDismiss(DialogInterface dialog) { super.onDismiss(dialog); FragmentActivity activity = getActivity(); if (activity == null) { return; } Fragment fragment = activity.getSupportFragmentManager().findFragmentByTag(TAG_PLUS_CLIENT); if (fragment instanceof PlusClientFragment) { ((PlusClientFragment) fragment).onDialogDismissed(getTag()); } } } private void onDialogCanceled(String tag) { mRequestCode = INVALID_REQUEST_CODE; hideProgressDialog(); } private void onDialogDismissed(String tag) { if (TAG_PROGRESS_DIALOG.equals(tag)) { mRequestCode = INVALID_REQUEST_CODE; hideProgressDialog(); } } private void showProgressDialog() { DialogFragment progressDialog = (DialogFragment) getFragmentManager() .findFragmentByTag(TAG_PROGRESS_DIALOG); if (progressDialog == null) { progressDialog = ProgressDialogFragment.create(); progressDialog.show(getFragmentManager(), TAG_PROGRESS_DIALOG); } } public static final class ProgressDialogFragment extends DialogFragment { private static final String ARG_MESSAGE = "message"; public static ProgressDialogFragment create(int message) { ProgressDialogFragment progressDialogFragment = new ProgressDialogFragment(); Bundle args = new Bundle(); args.putInt(ARG_MESSAGE, message); progressDialogFragment.setArguments(args); return progressDialogFragment; } public static ProgressDialogFragment create() { return create(R.string.progress_message); } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { ProgressDialog progressDialog = new ProgressDialog(getActivity()); progressDialog.setIndeterminate(true); progressDialog.setMessage(getString(getArguments().getInt(ARG_MESSAGE))); return progressDialog; } @Override public void onCancel(DialogInterface dialog) { super.onCancel(dialog); FragmentActivity activity = getActivity(); if (activity == null) { return; } Fragment fragment = activity.getSupportFragmentManager().findFragmentByTag(TAG_PLUS_CLIENT); if (fragment instanceof PlusClientFragment) { ((PlusClientFragment) fragment).onDialogCanceled(getTag()); } } @Override public void onDismiss(DialogInterface dialog) { super.onDismiss(dialog); FragmentActivity activity = getActivity(); if (activity == null) { return; } Fragment fragment = activity.getSupportFragmentManager().findFragmentByTag(TAG_PLUS_CLIENT); if (fragment instanceof PlusClientFragment) { ((PlusClientFragment) fragment).onDialogDismissed(getTag()); } } } protected void hideProgressDialog() { mRequestCode = INVALID_REQUEST_CODE; DialogFragment progressDialog = (DialogFragment) getFragmentManager() .findFragmentByTag(TAG_PROGRESS_DIALOG); if (progressDialog != null) { progressDialog.dismiss(); } } private void showErrorDialog(DialogFragment errorDialog) { DialogFragment oldErrorDialog = (DialogFragment) getFragmentManager().findFragmentByTag(TAG_ERROR_DIALOG); if (oldErrorDialog != null) { oldErrorDialog.dismiss(); } hideProgressDialog(); errorDialog.show(getFragmentManager(), TAG_ERROR_DIALOG); } private void hideErrorDialog() { mRequestCode = INVALID_REQUEST_CODE; DialogFragment errorDialog = (DialogFragment) getFragmentManager().findFragmentByTag(TAG_ERROR_DIALOG); if (errorDialog != null) { errorDialog.dismiss(); } } private void startResolution() { try { mLastConnectionResult.startResolutionForResult(getActivity(), mRequestCode); } catch (SendIntentException e) { // The intent we had is not valid right now, perhaps the remote process died. // Try to reconnect to get a new resolution intent. mLastConnectionResult = null; showProgressDialog(); mPlusClient.connect(); mIsConnecting = true; } } public boolean handleOnActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode != mRequestCode) { return false; } switch (resultCode) { case Activity.RESULT_OK: mLastConnectionResult = null; mPlusClient.connect(); mIsConnecting = true; break; case Activity.RESULT_CANCELED: // User canceled sign in, clear the request code. mRequestCode = INVALID_REQUEST_CODE; break; } return true; } /** * Sign out of the app. */ public void signOut() { if (mPlusClient.isConnected()) { mPlusClient.clearDefaultAccount(); } if (mIsConnecting || mPlusClient.isConnected()) { mPlusClient.disconnect(); // Reconnect to get a new mPlusClient. mLastConnectionResult = null; // Cancel sign in. mRequestCode = INVALID_REQUEST_CODE; // Reconnect to fetch the sign-in (account chooser) intent from the plus client. mPlusClient.connect(); } } /** * Revoke access to the current app. */ public void revokeAccessAndDisconnect() { if (mPlusClient.isConnected()) { mPlusClient.revokeAccessAndDisconnect(this); } } @Override public void onConnected(Bundle connectionHint) { // Successful connection! mLastConnectionResult = CONNECTION_RESULT_SUCCESS; mRequestCode = INVALID_REQUEST_CODE; mIsConnecting = false; hideProgressDialog(); Activity activity = getActivity(); if (activity instanceof OnSignedInListener) { ((OnSignedInListener) activity).onSignedIn(mPlusClient); } } }