在本教程中,我们将讨论智能锁功能,并在我们的Android应用程序中实现它。
谷歌智能锁
Smart Lock用于通过一次性保存凭据来自动登录您的应用程序。这意味着,如果你在一段时间后重新安装你的应用程序,你可以自动使用之前保存的凭据登录,前提是你没有从Chrome密码中删除它们。
谷歌智能锁只需轻点即可登录。
为了在您的应用程序中集成Smart Lock,您需要使用凭据API。凭据API允许用户:
- 打开应用程序时请求凭据。
- 保存登录表单中的凭据。
- 在应用程序和网站之间同步凭据。
- 显示电子邮件提示,以防我们希望在登录/注册过程中帮助用户。
要在您的应用程序中使用Google Smart Lock,您需要添加以下依赖项:
1dependencies {
2 implementation 'com.google.android.gms:play-services-auth:16.0.0'
3}
SmartLock需要在Android应用程序中设置GoogleApiClient。SmartLock允许在只有一个凭据时自动登录。当有多个凭据时,它们会显示在对话框中。
早些时候,我们常常依赖SharedPreferences在本地自动签名和保存凭据。现在,有了谷歌智能锁,一切都由谷歌服务器来处理。
以下是凭据API中提供的主要方法:
保存(GoogleApiClient客户端,凭据凭据)
请求(GoogleApiClient客户端,证书请求)
-请求为应用保存的所有凭据。getHintPickerIntent(GoogleApiClient客户端,HintRequestRequest
)` -显示您拥有的登录帐户列表,以便快速填写登录表单。disableAutoSignIn(GoogleApiClient客户端)
删除(GoogleApiClient客户端,凭据凭据)
你可以通过访问passwords.google.com.查看为谷歌账户保存的所有凭据智能锁应用的流程是什么? 您需要按照以下方式组织您的登录屏幕代码:
- 检查凭据。如果存在单一凭据,请自动签署或自动填写登录表。
- 如果有多个凭据,请在对话框中显示并让用户选择。
- 如果没有保存的凭据,您可以让用户填写表单,或通过自动填充或显示一个提示对话框来使其更容易登录。
入门
让我们开始在Android应用程序中实现智能锁功能。设置GoogleApiClient
1mGoogleApiClient = new GoogleApiClient.Builder(this)
2 .addConnectionCallbacks(this)
3 .addApi(Auth.CREDENTIALS_API)
4 .enableAutoManage(this, this)
5 .build();
实现GoogleApiClient接口并实现方法。初始化凭证客户端
1CredentialsOptions options = new CredentialsOptions.Builder()
2 .forceEnableSaveDialog()
3 .build();
4
5CredentialsClient mCredentialsApiClient = Credentials.getClient(this, options);
Android Oreo及以上版本需要forceEnableSaveDialog() 。** 创建证书请求**
1CredentialRequest mCredentialRequest = new CredentialRequest.Builder()
2 .setPasswordLoginSupported(true)
3 .setAccountTypes(IdentityProviders.GOOGLE)
4 .build();
取回凭据
1Auth.CredentialsApi.request(mGoogleApiClient, mCredentialRequest).setResultCallback(this);
setResultCallBack
需要我们从接口ResultCallback<CredentialRequestResult>
重写方法onResult
1@Override
2 public void onResult(@NonNull CredentialRequestResult credentialRequestResult) {
3
4 Status status = credentialRequestResult.getStatus();
5 if (status.isSuccess()) {
6 onCredentialRetrieved(credentialRequestResult.getCredential());
7 } else {
8 if (status.getStatusCode() == CommonStatusCodes.RESOLUTION_REQUIRED) {
9 try {
10 isResolving = true;
11 status.startResolutionForResult(this, RC_READ);
12 } catch (IntentSender.SendIntentException e) {
13 Log.d(TAG, e.toString());
14 }
15 } else {
16
17 showHintDialog();
18 }
19 }
20 }
有三种情况-
单个凭据-成功-多个凭据-解析它们并在对话框中显示所有可用凭据
- 无凭据-显示包含所有可用登录帐户的提示对话框
状态码RESOLUTION_REQUIRED
表示有多个凭证需要解析。为此,我们调用startResolutionForResult
,它在onActivityResult方法中返回结果。我们使用布尔标志来防止出现多个解析。这将导致构建多个对话框。现在我们已经了解了SmartLock功能的要点,让我们通过保存和删除凭据功能来完全实现它。
项目结构
代码
Active_main.xml布局的代码如下所示:
1<?xml version="1.0" encoding="utf-8"?>
2<android.support.constraint.ConstraintLayout xmlns:android="https://schemas.android.com/apk/res/android"
3 xmlns:app="https://schemas.android.com/apk/res-auto"
4 xmlns:tools="https://schemas.android.com/tools"
5 android:layout_width="match_parent"
6 android:layout_height="match_parent"
7 android:importantForAutofill="noExcludeDescendants">
8
9 <Button
10 android:id="@+id/btnLogin"
11 android:layout_width="wrap_content"
12 android:layout_height="wrap_content"
13 android:layout_marginLeft="8dp"
14 android:layout_marginRight="8dp"
15 android:layout_marginTop="24dp"
16 android:text="Login"
17 app:layout_constraintLeft_toLeftOf="parent"
18 app:layout_constraintRight_toRightOf="parent"
19 app:layout_constraintTop_toBottomOf="@+id/inPassword" />
20
21 <EditText
22 android:id="@+id/inEmail"
23 android:layout_width="0dp"
24 android:layout_height="wrap_content"
25 android:layout_marginLeft="16dp"
26 android:layout_marginRight="16dp"
27 android:layout_marginTop="32dp"
28 android:ems="10"
29 android:hint="email"
30 android:inputType="textEmailAddress"
31 app:layout_constraintHorizontal_bias="0.503"
32 app:layout_constraintLeft_toLeftOf="parent"
33 app:layout_constraintRight_toRightOf="parent" />
34
35 <EditText
36 android:id="@+id/inPassword"
37 android:layout_width="0dp"
38 android:layout_height="wrap_content"
39 android:layout_marginLeft="16dp"
40 android:layout_marginRight="16dp"
41 android:layout_marginTop="8dp"
42 android:ems="10"
43 android:hint="password"
44 android:inputType="textPassword"
45 app:layout_constraintLeft_toLeftOf="parent"
46 app:layout_constraintRight_toRightOf="parent"
47 app:layout_constraintTop_toBottomOf="@+id/inEmail" />
48
49</android.support.constraint.ConstraintLayout>
android:importantForAutofill=
noExcludeDescendants>
用于在编辑文本字段上禁用自动填充。我们将在单独的教程中讨论自动填充API。下面给出了MainActivity.java类的代码:
1package com.journaldev.androidgooglesmartlock;
2
3import android.app.PendingIntent;
4import android.content.Intent;
5import android.content.IntentSender;
6import android.support.annotation.NonNull;
7import android.support.annotation.Nullable;
8import android.support.v7.app.AppCompatActivity;
9import android.os.Bundle;
10import android.text.TextUtils;
11import android.util.Log;
12import android.util.Patterns;
13import android.view.View;
14import android.widget.Button;
15import android.widget.EditText;
16import android.widget.Toast;
17
18import com.google.android.gms.auth.api.Auth;
19import com.google.android.gms.auth.api.credentials.Credential;
20import com.google.android.gms.auth.api.credentials.CredentialPickerConfig;
21import com.google.android.gms.auth.api.credentials.CredentialRequest;
22import com.google.android.gms.auth.api.credentials.CredentialRequestResponse;
23import com.google.android.gms.auth.api.credentials.CredentialRequestResult;
24import com.google.android.gms.auth.api.credentials.Credentials;
25import com.google.android.gms.auth.api.credentials.CredentialsClient;
26import com.google.android.gms.auth.api.credentials.CredentialsOptions;
27import com.google.android.gms.auth.api.credentials.HintRequest;
28import com.google.android.gms.auth.api.credentials.IdentityProviders;
29import com.google.android.gms.auth.api.signin.GoogleSignIn;
30import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
31import com.google.android.gms.auth.api.signin.GoogleSignInClient;
32import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
33import com.google.android.gms.common.ConnectionResult;
34import com.google.android.gms.common.api.ApiException;
35import com.google.android.gms.common.api.CommonStatusCodes;
36import com.google.android.gms.common.api.GoogleApiClient;
37import com.google.android.gms.common.api.ResolvableApiException;
38import com.google.android.gms.common.api.ResultCallback;
39import com.google.android.gms.common.api.Status;
40import com.google.android.gms.tasks.OnCompleteListener;
41import com.google.android.gms.tasks.Task;
42
43import java.util.regex.Pattern;
44
45public class MainActivity extends AppCompatActivity implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, ResultCallback<CredentialRequestResult> {
46
47 private GoogleApiClient mGoogleApiClient;
48 CredentialsClient mCredentialsApiClient;
49 CredentialRequest mCredentialRequest;
50 public static final String TAG = "API123";
51 private static final int RC_READ = 3;
52 private static final int RC_SAVE = 1;
53 private static final int RC_HINT = 2;
54 boolean isResolving;
55
56 Button btnLogin;
57 EditText inEmail, inPassword;
58
59 @Override
60 protected void onCreate(Bundle savedInstanceState) {
61 super.onCreate(savedInstanceState);
62 setContentView(R.layout.activity_main);
63
64 setUpGoogleApiClient();
65
66 //needed for Android Oreo.
67 CredentialsOptions options = new CredentialsOptions.Builder()
68 .forceEnableSaveDialog()
69 .build();
70
71 mCredentialsApiClient = Credentials.getClient(this, options);
72 createCredentialRequest();
73
74 btnLogin = findViewById(R.id.btnLogin);
75 inEmail = findViewById(R.id.inEmail);
76 inPassword = findViewById(R.id.inPassword);
77
78 btnLogin.setOnClickListener(new View.OnClickListener() {
79 @Override
80 public void onClick(View view) {
81
82 String email = inEmail.getText().toString();
83 String password = inPassword.getText().toString();
84 if (TextUtils.isEmpty(email) || TextUtils.isEmpty(password) || !Patterns.EMAIL_ADDRESS.matcher(email).matches())
85 showToast("Please enter valid email and password");
86
87 else {
88
89 Credential credential = new Credential.Builder(email)
90 .setPassword(password)
91 .build();
92
93 saveCredentials(credential);
94 }
95
96 }
97 });
98 }
99
100 public void setUpGoogleApiClient() {
101
102 mGoogleApiClient = new GoogleApiClient.Builder(this)
103 .addConnectionCallbacks(this)
104 .addApi(Auth.CREDENTIALS_API)
105 .enableAutoManage(this, this)
106 .build();
107 }
108
109 public void createCredentialRequest() {
110 mCredentialRequest = new CredentialRequest.Builder()
111 .setPasswordLoginSupported(true)
112 .setAccountTypes(IdentityProviders.GOOGLE)
113 .build();
114 }
115
116 public void requestCredentials() {
117 Auth.CredentialsApi.request(mGoogleApiClient, mCredentialRequest).setResultCallback(this);
118 }
119
120 private void onCredentialRetrieved(Credential credential) {
121 String accountType = credential.getAccountType();
122 if (accountType == null) {
123 // Sign the user in with information from the Credential.
124 gotoNext();
125 } else if (accountType.equals(IdentityProviders.GOOGLE)) {
126
127 GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
128 .requestEmail()
129 .build();
130
131 GoogleSignInClient signInClient = GoogleSignIn.getClient(this, gso);
132 Task<GoogleSignInAccount> task = signInClient.silentSignIn();
133
134 task.addOnCompleteListener(new OnCompleteListener<GoogleSignInAccount>() {
135 @Override
136 public void onComplete(@NonNull Task<GoogleSignInAccount> task) {
137 if (task.isSuccessful()) {
138 // See "Handle successful credential requests"
139 populateLoginFields(task.getResult().getEmail(), null);
140 } else {
141 showToast("Unable to do a google sign in");
142 }
143 }
144 });
145 }
146 }
147
148 public void gotoNext() {
149 startActivity(new Intent(this, SecondActivity.class));
150 finish();
151 }
152
153 public void showToast(String s) {
154 Toast.makeText(getApplicationContext(), s, Toast.LENGTH_SHORT).show();
155 }
156
157 @Override
158 public void onConnected(@Nullable Bundle bundle) {
159 Log.d("API123", "onConnected");
160 requestCredentials();
161
162 }
163
164 @Override
165 public void onConnectionSuspended(int i) {
166
167 }
168
169 @Override
170 public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
171
172 }
173
174 @Override
175 protected void onDestroy() {
176 mGoogleApiClient.disconnect();
177 super.onDestroy();
178 }
179
180 @Override
181 public void onResult(@NonNull CredentialRequestResult credentialRequestResult) {
182
183 Status status = credentialRequestResult.getStatus();
184 if (status.isSuccess()) {
185 onCredentialRetrieved(credentialRequestResult.getCredential());
186 } else {
187 if (status.getStatusCode() == CommonStatusCodes.RESOLUTION_REQUIRED) {
188 try {
189 isResolving = true;
190 status.startResolutionForResult(this, RC_READ);
191 } catch (IntentSender.SendIntentException e) {
192 Log.d(TAG, e.toString());
193 }
194 } else {
195
196 showHintDialog();
197 }
198 }
199 }
200
201 @Override
202 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
203 super.onActivityResult(requestCode, resultCode, data);
204
205 Log.d(TAG, "onActivityResult");
206 if (requestCode == RC_READ) {
207 if (resultCode == RESULT_OK) {
208 Credential credential = data.getParcelableExtra(Credential.EXTRA_KEY);
209 onCredentialRetrieved(credential);
210 } else {
211 Log.d(TAG, "Request failed");
212 }
213 isResolving = false;
214 }
215
216 if (requestCode == RC_HINT) {
217 if (resultCode == RESULT_OK) {
218 Credential credential = data.getParcelableExtra(Credential.EXTRA_KEY);
219 populateLoginFields(credential.getId(), "");
220 } else {
221 showToast("Hint dialog closed");
222 }
223 }
224
225 if (requestCode == RC_SAVE) {
226 if (resultCode == RESULT_OK) {
227 Log.d(TAG, "SAVE: OK");
228 gotoNext();
229 showToast("Credentials saved");
230 }
231 }
232
233 }
234
235 public void populateLoginFields(String email, String password) {
236 if (!TextUtils.isEmpty(email))
237 inEmail.setText(email);
238
239 if (!TextUtils.isEmpty(password))
240 inPassword.setText(password);
241 }
242
243 public void showHintDialog() {
244 HintRequest hintRequest = new HintRequest.Builder()
245 .setHintPickerConfig(new CredentialPickerConfig.Builder()
246 .setShowCancelButton(true)
247 .build())
248 .setEmailAddressIdentifierSupported(true)
249 .setAccountTypes(IdentityProviders.GOOGLE)
250 .build();
251
252 PendingIntent intent = mCredentialsApiClient.getHintPickerIntent(hintRequest);
253 try {
254 startIntentSenderForResult(intent.getIntentSender(), RC_HINT, null, 0, 0, 0);
255 } catch (IntentSender.SendIntentException e) {
256 Log.e(TAG, "Could not start hint picker Intent", e);
257 }
258 }
259
260 public void saveCredentials(Credential credential) {
261
262 mCredentialsApiClient.save(credential).addOnCompleteListener(new OnCompleteListener<Void>() {
263 @Override
264 public void onComplete(@NonNull Task<Void> task) {
265 if (task.isSuccessful()) {
266 Log.d(TAG, "SAVE: OK");
267 showToast("Credentials saved");
268 return;
269 }
270
271 Exception e = task.getException();
272 if (e instanceof ResolvableApiException) {
273 // Try to resolve the save request. This will prompt the user if
274 // the credential is new.
275 ResolvableApiException rae = (ResolvableApiException) e;
276 try {
277 rae.startResolutionForResult(MainActivity.this, RC_SAVE);
278 } catch (IntentSender.SendIntentException f) {
279 // Could not resolve the request
280 Log.e(TAG, "Failed to send resolution.", f);
281 showToast("Saved failed");
282 }
283 } else {
284 // Request has no resolution
285 showToast("Saved failed");
286 }
287 }
288 });
289
290 }
291}
在onConnected方法中,我们请求可用的凭据。这意味着一旦活动启动,就会检索凭据(如果有的话)。如果存在单个凭据,则它将自动签名并转到下一个活动。Activity_Second.xml布局的代码如下所示:
1<?xml version="1.0" encoding="utf-8"?>
2<android.support.constraint.ConstraintLayout xmlns:android="https://schemas.android.com/apk/res/android"
3 xmlns:app="https://schemas.android.com/apk/res-auto"
4 xmlns:tools="https://schemas.android.com/tools"
5 android:layout_width="match_parent"
6 android:layout_height="match_parent"
7 tools:context=".SecondActivity">
8
9 <Button
10 android:id="@+id/btnDeleteAccount"
11 android:layout_width="wrap_content"
12 android:layout_height="wrap_content"
13 android:layout_marginTop="8dp"
14 android:text="Delete account"
15 app:layout_constraintEnd_toEndOf="parent"
16 app:layout_constraintStart_toStartOf="parent"
17 app:layout_constraintTop_toBottomOf="@+id/btnSignOut" />
18
19 <TextView
20 android:id="@+id/textView"
21 android:layout_width="wrap_content"
22 android:layout_height="wrap_content"
23 android:text="You are logged in."
24 app:layout_constraintBottom_toBottomOf="parent"
25 app:layout_constraintEnd_toEndOf="parent"
26 app:layout_constraintStart_toStartOf="parent"
27 app:layout_constraintTop_toTopOf="parent" />
28
29 <Button
30 android:id="@+id/btnSignOut"
31 android:layout_width="wrap_content"
32 android:layout_height="wrap_content"
33 android:layout_marginTop="8dp"
34 android:text="SIGN OUT"
35 app:layout_constraintEnd_toEndOf="parent"
36 app:layout_constraintStart_toStartOf="parent"
37 app:layout_constraintTop_toBottomOf="@+id/textView" />
38
39 <Button
40 android:id="@+id/btnSignOutDisableAutoSign"
41 android:layout_width="wrap_content"
42 android:layout_height="wrap_content"
43 android:text="SIGN OUT AND DISABLE AUTO SIGN IN"
44 app:layout_constraintEnd_toEndOf="parent"
45 app:layout_constraintStart_toStartOf="parent"
46 app:layout_constraintTop_toBottomOf="@+id/btnDeleteAccount" />
47</android.support.constraint.ConstraintLayout>
在Second Activity中,我们将执行三个不同的操作-注销、注销,并在下次禁用自动登录、删除凭据。Second Activity.Java类的代码如下所示:
1package com.journaldev.androidgooglesmartlock;
2
3import android.content.Intent;
4import android.content.IntentSender;
5import android.support.annotation.NonNull;
6import android.support.annotation.Nullable;
7import android.support.v7.app.AppCompatActivity;
8import android.os.Bundle;
9import android.util.Log;
10import android.view.View;
11import android.widget.Button;
12import android.widget.Toast;
13
14import com.google.android.gms.auth.api.Auth;
15import com.google.android.gms.auth.api.credentials.Credential;
16import com.google.android.gms.auth.api.credentials.CredentialRequest;
17import com.google.android.gms.auth.api.credentials.CredentialRequestResult;
18import com.google.android.gms.auth.api.credentials.Credentials;
19import com.google.android.gms.auth.api.credentials.CredentialsClient;
20import com.google.android.gms.common.ConnectionResult;
21import com.google.android.gms.common.api.GoogleApiClient;
22import com.google.android.gms.common.api.ResultCallback;
23import com.google.android.gms.common.api.Status;
24
25public class SecondActivity extends AppCompatActivity implements View.OnClickListener, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, ResultCallback<CredentialRequestResult> {
26
27 Button btnSignOut, btnSignOutDisableAuto, btnDelete;
28 private GoogleApiClient mGoogleApiClient;
29 CredentialsClient mCredentialsApiClient;
30 CredentialRequest mCredentialRequest;
31 public static final String TAG = "API123";
32 private static final int RC_REQUEST = 4;
33
34 @Override
35 protected void onCreate(Bundle savedInstanceState) {
36 super.onCreate(savedInstanceState);
37 setContentView(R.layout.activity_second);
38
39 setUpGoogleApiClient();
40 mCredentialsApiClient = Credentials.getClient(this);
41
42 btnSignOut = findViewById(R.id.btnSignOut);
43 btnSignOutDisableAuto = findViewById(R.id.btnSignOutDisableAutoSign);
44 btnDelete = findViewById(R.id.btnDeleteAccount);
45
46 btnSignOut.setOnClickListener(this);
47 btnSignOutDisableAuto.setOnClickListener(this);
48 btnDelete.setOnClickListener(this);
49 }
50
51 @Override
52 public void onClick(View view) {
53 switch (view.getId()) {
54 case R.id.btnSignOut:
55 signOut(false);
56 break;
57 case R.id.btnSignOutDisableAutoSign:
58 signOut(true);
59 break;
60 case R.id.btnDeleteAccount:
61 requestCredentials();
62
63 break;
64 }
65 }
66
67 @Override
68 public void onConnected(@Nullable Bundle bundle) {
69
70 }
71
72 @Override
73 public void onConnectionSuspended(int i) {
74
75 }
76
77 @Override
78 public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
79
80 }
81
82 @Override
83 public void onResult(@NonNull CredentialRequestResult credentialRequestResult) {
84
85 Status status = credentialRequestResult.getStatus();
86 if (status.isSuccess()) {
87 onCredentialSuccess(credentialRequestResult.getCredential());
88 } else {
89 if (status.hasResolution()) {
90 try {
91 status.startResolutionForResult(this, RC_REQUEST);
92 } catch (IntentSender.SendIntentException e) {
93 Log.d(TAG, e.toString());
94 }
95 } else {
96 showToast("Request Failed");
97 }
98 }
99 }
100
101 public void setUpGoogleApiClient() {
102
103 mGoogleApiClient = new GoogleApiClient.Builder(this)
104 .addConnectionCallbacks(this)
105 .addApi(Auth.CREDENTIALS_API)
106 .enableAutoManage(this, this)
107 .build();
108 }
109
110 private void requestCredentials() {
111 mCredentialRequest = new CredentialRequest.Builder()
112 .setPasswordLoginSupported(true)
113 .build();
114
115 Auth.CredentialsApi.request(mGoogleApiClient, mCredentialRequest).setResultCallback(this);
116 }
117
118 @Override
119 protected void onDestroy() {
120 mGoogleApiClient.disconnect();
121 super.onDestroy();
122 }
123
124 private void onCredentialSuccess(Credential credential) {
125
126 Auth.CredentialsApi.delete(mGoogleApiClient, credential).setResultCallback(new ResultCallback<Status>() {
127 @Override
128 public void onResult(@NonNull Status status) {
129 if (status.isSuccess()) {
130 signOut(false);
131 } else {
132 showToast("Account Deletion Failed");
133 }
134 }
135 });
136
137 }
138
139 @Override
140 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
141 super.onActivityResult(requestCode, resultCode, data);
142 if (requestCode == RC_REQUEST) {
143 if (resultCode == RESULT_OK) {
144 showToast("Deleted");
145 Credential credential = data.getParcelableExtra(Credential.EXTRA_KEY);
146 onCredentialSuccess(credential);
147 } else {
148 Log.d(TAG, "Request failed");
149 }
150 }
151 }
152
153 public void showToast(String s) {
154 Toast.makeText(getApplicationContext(), s, Toast.LENGTH_SHORT).show();
155 }
156
157 private void signOut(boolean disableAutoSignIn) {
158
159 if (disableAutoSignIn)
160 Auth.CredentialsApi.disableAutoSignIn(mGoogleApiClient);
161
162 startActivity(new Intent(this, MainActivity.class));
163 finish();
164 }
165}
上述应用程序的实际运行输出如下:我们创建了第一个帐户,并看到每次打开应用程序时,它都会自动登录。除非我们禁用自动签名功能。然后,它会在登录之前请求许可。我们创建了另一个帐户。这一次是我们删除它的时候。并且应用程序在删除后自动登录到第一个帐户。这就是本教程的结束。您可以从下面的链接下载该项目: