Java tutorial
/* * Copyright 2016 The Android Open Source Project * * 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.afwsamples.testdpc.safetynet; import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.Bundle; import android.support.annotation.ColorInt; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.content.ContextCompat; import android.text.method.ScrollingMovementMethod; import android.util.Base64; import android.view.LayoutInflater; import android.view.View; import android.widget.TextView; import com.afwsamples.testdpc.R; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.ResultCallbacks; import com.google.android.gms.common.api.Status; import com.google.android.gms.safetynet.SafetyNet; import org.json.JSONException; import org.json.JSONObject; import java.security.SecureRandom; import static com.google.android.gms.safetynet.SafetyNetApi.AttestationResult; /** * Demonstrate how to use SafetyNet API to check device compatibility. * Please notice that you should verifying the payload in your server. * For more details, please check http://developer.android.com/training/safetynet/index.html. */ public class SafetyNetFragment extends DialogFragment implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener { private GoogleApiClient mGoogleApiClient; private TextView mMessageView; private @ColorInt int BLACK, DARK_RED; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Kick start the checking mGoogleApiClient = buildGoogleApiClient(); } @Override public void onStart() { super.onStart(); updateMessageView(R.string.safetynet_running, false); mGoogleApiClient.connect(); } @Override public void onStop() { super.onStop(); mGoogleApiClient.disconnect(); } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { BLACK = ContextCompat.getColor(getActivity(), R.color.text_black); DARK_RED = ContextCompat.getColor(getActivity(), R.color.dark_red); LayoutInflater inflater = LayoutInflater.from(getActivity()); View rootView = inflater.inflate(R.layout.safety_net_attest_dialog, null); mMessageView = (TextView) rootView.findViewById(R.id.message_view); // Show scrollbar in textview. mMessageView.setMovementMethod(new ScrollingMovementMethod()); return new AlertDialog.Builder(getActivity()).setView(rootView).setTitle(R.string.safetynet_dialog_title) .setNeutralButton(android.R.string.ok, null).create(); } @Override public void onConnected(@Nullable Bundle bundle) { if (hasInternetConnection()) { runSaftyNetTest(); } else { updateMessageView(R.string.safetynet_fail_reason_no_internet, true); } } @Override public void onConnectionSuspended(int i) { updateMessageView(R.string.cancel_safetynet_msg, true); } @Override public void onConnectionFailed(@NonNull ConnectionResult connectionResult) { if (connectionResult.getErrorCode() == ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED) { updateMessageView(R.string.safetynet_fail_reason_gmscore_upgrade, true); } else { updateMessageView(getString(R.string.safetynet_fail_reason_error_code, connectionResult.getErrorCode()), true); } } private GoogleApiClient buildGoogleApiClient() { return new GoogleApiClient.Builder(getActivity()).addApi(SafetyNet.API).addConnectionCallbacks(this) .addOnConnectionFailedListener(this).build(); } /** * For simplicity, we generate the nonce in the client. However, it should be generated on the * server for anti-replay protection. */ private byte[] generateNonce() { byte[] nonce = new byte[32]; SecureRandom secureRandom = new SecureRandom(); secureRandom.nextBytes(nonce); return nonce; } private void runSaftyNetTest() { final byte[] nonce = generateNonce(); SafetyNet.SafetyNetApi.attest(mGoogleApiClient, nonce) .setResultCallback(new ResultCallbacks<AttestationResult>() { @Override public void onSuccess(@NonNull AttestationResult attestationResult) { if (isDetached()) { return; } final String jws = attestationResult.getJwsResult(); try { final JSONObject jsonObject = retrievePayloadFromJws(jws); final String jsonString = jsonObject.toString(4); final String verifyOnServerString = getString(R.string.safetynet_verify_on_server); updateMessageView(verifyOnServerString + "\n" + jsonString, false); } catch (JSONException ex) { updateMessageView(R.string.safetynet_fail_reason_invalid_jws, true); } } @Override public void onFailure(@NonNull Status status) { if (isDetached()) { return; } updateMessageView(R.string.safetynet_fail_to_run_api, true); } }); } private void updateMessageView(int message, boolean isError) { updateMessageView(getString(message), isError); } private void updateMessageView(String message, boolean isError) { mMessageView.setText(message); mMessageView.setTextColor((isError) ? DARK_RED : BLACK); } private boolean hasInternetConnection() { ConnectivityManager cm = (ConnectivityManager) getActivity().getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); return activeNetwork != null && activeNetwork.isConnected(); } private static JSONObject retrievePayloadFromJws(String jws) throws JSONException { String[] parts = jws.split("\\."); if (parts.length != 3) { throw new JSONException("Invalid JWS"); } return new JSONObject(new String(Base64.decode(parts[1], Base64.URL_SAFE))); } }