| OLD | NEW | 
|---|
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be | 
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. | 
| 4 | 4 | 
| 5 #include "chrome/browser/signin/oauth2_token_service_delegate_android.h" | 5 #include "chrome/browser/signin/oauth2_token_service_delegate_android.h" | 
| 6 | 6 | 
| 7 #include "base/android/context_utils.h" |  | 
| 8 #include "base/android/jni_android.h" | 7 #include "base/android/jni_android.h" | 
| 9 #include "base/android/jni_array.h" | 8 #include "base/android/jni_array.h" | 
| 10 #include "base/android/jni_string.h" | 9 #include "base/android/jni_string.h" | 
| 11 #include "base/bind.h" | 10 #include "base/bind.h" | 
| 12 #include "base/logging.h" | 11 #include "base/logging.h" | 
| 13 #include "base/macros.h" | 12 #include "base/macros.h" | 
| 14 #include "base/metrics/histogram_macros.h" | 13 #include "base/metrics/histogram_macros.h" | 
| 15 #include "base/stl_util.h" | 14 #include "base/stl_util.h" | 
| 16 #include "chrome/browser/profiles/profile_android.h" | 15 #include "chrome/browser/profiles/profile_android.h" | 
| 17 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h" | 16 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h" | 
| (...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 88   ScopedJavaLocalRef<jstring> j_username = | 87   ScopedJavaLocalRef<jstring> j_username = | 
| 89       ConvertUTF8ToJavaString(env, account_id_); | 88       ConvertUTF8ToJavaString(env, account_id_); | 
| 90   ScopedJavaLocalRef<jstring> j_scope = ConvertUTF8ToJavaString(env, scope); | 89   ScopedJavaLocalRef<jstring> j_scope = ConvertUTF8ToJavaString(env, scope); | 
| 91   std::unique_ptr<FetchOAuth2TokenCallback> heap_callback( | 90   std::unique_ptr<FetchOAuth2TokenCallback> heap_callback( | 
| 92       new FetchOAuth2TokenCallback( | 91       new FetchOAuth2TokenCallback( | 
| 93           base::Bind(&AndroidAccessTokenFetcher::OnAccessTokenResponse, | 92           base::Bind(&AndroidAccessTokenFetcher::OnAccessTokenResponse, | 
| 94                      weak_factory_.GetWeakPtr()))); | 93                      weak_factory_.GetWeakPtr()))); | 
| 95 | 94 | 
| 96   // Call into Java to get a new token. | 95   // Call into Java to get a new token. | 
| 97   Java_OAuth2TokenService_getOAuth2AuthToken( | 96   Java_OAuth2TokenService_getOAuth2AuthToken( | 
| 98       env, base::android::GetApplicationContext(), j_username, j_scope, | 97       env, j_username, j_scope, | 
| 99       reinterpret_cast<intptr_t>(heap_callback.release())); | 98       reinterpret_cast<intptr_t>(heap_callback.release())); | 
| 100 } | 99 } | 
| 101 | 100 | 
| 102 void AndroidAccessTokenFetcher::CancelRequest() { | 101 void AndroidAccessTokenFetcher::CancelRequest() { | 
| 103   request_was_cancelled_ = true; | 102   request_was_cancelled_ = true; | 
| 104 } | 103 } | 
| 105 | 104 | 
| 106 void AndroidAccessTokenFetcher::OnAccessTokenResponse( | 105 void AndroidAccessTokenFetcher::OnAccessTokenResponse( | 
| 107     const GoogleServiceAuthError& error, | 106     const GoogleServiceAuthError& error, | 
| 108     const std::string& access_token, | 107     const std::string& access_token, | 
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 145     : error(error) {} | 144     : error(error) {} | 
| 146 | 145 | 
| 147 OAuth2TokenServiceDelegateAndroid::OAuth2TokenServiceDelegateAndroid( | 146 OAuth2TokenServiceDelegateAndroid::OAuth2TokenServiceDelegateAndroid( | 
| 148     AccountTrackerService* account_tracker_service) | 147     AccountTrackerService* account_tracker_service) | 
| 149     : account_tracker_service_(account_tracker_service), | 148     : account_tracker_service_(account_tracker_service), | 
| 150       fire_refresh_token_loaded_(RT_LOAD_NOT_START) { | 149       fire_refresh_token_loaded_(RT_LOAD_NOT_START) { | 
| 151   DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::ctor"; | 150   DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::ctor"; | 
| 152   DCHECK(account_tracker_service_); | 151   DCHECK(account_tracker_service_); | 
| 153   JNIEnv* env = AttachCurrentThread(); | 152   JNIEnv* env = AttachCurrentThread(); | 
| 154   base::android::ScopedJavaLocalRef<jobject> local_java_ref = | 153   base::android::ScopedJavaLocalRef<jobject> local_java_ref = | 
| 155       Java_OAuth2TokenService_create(env, | 154       Java_OAuth2TokenService_create(env, reinterpret_cast<intptr_t>(this)); | 
| 156                                      base::android::GetApplicationContext(), |  | 
| 157                                      reinterpret_cast<intptr_t>(this)); |  | 
| 158   java_ref_.Reset(env, local_java_ref.obj()); | 155   java_ref_.Reset(env, local_java_ref.obj()); | 
| 159 | 156 | 
| 160   if (account_tracker_service_->GetMigrationState() == | 157   if (account_tracker_service_->GetMigrationState() == | 
| 161       AccountTrackerService::MIGRATION_IN_PROGRESS) { | 158       AccountTrackerService::MIGRATION_IN_PROGRESS) { | 
| 162     std::vector<std::string> accounts = GetAccounts(); | 159     std::vector<std::string> accounts = GetAccounts(); | 
| 163     std::vector<std::string> accounts_id; | 160     std::vector<std::string> accounts_id; | 
| 164     for (auto account_name : accounts) { | 161     for (auto account_name : accounts) { | 
| 165       AccountInfo account_info = | 162       AccountInfo account_info = | 
| 166           account_tracker_service_->FindAccountInfoByEmail(account_name); | 163           account_tracker_service_->FindAccountInfoByEmail(account_name); | 
| 167       DCHECK(!account_info.gaia.empty()); | 164       DCHECK(!account_info.gaia.empty()); | 
| 168       accounts_id.push_back(account_info.gaia); | 165       accounts_id.push_back(account_info.gaia); | 
| 169     } | 166     } | 
| 170     ScopedJavaLocalRef<jobjectArray> java_accounts( | 167     ScopedJavaLocalRef<jobjectArray> java_accounts( | 
| 171         base::android::ToJavaArrayOfStrings(env, accounts_id)); | 168         base::android::ToJavaArrayOfStrings(env, accounts_id)); | 
| 172     Java_OAuth2TokenService_saveStoredAccounts( | 169     Java_OAuth2TokenService_saveStoredAccounts(env, java_accounts); | 
| 173         env, base::android::GetApplicationContext(), java_accounts); |  | 
| 174   } | 170   } | 
| 175 | 171 | 
| 176   if (!is_testing_profile_) { | 172   if (!is_testing_profile_) { | 
| 177     Java_OAuth2TokenService_validateAccounts( | 173     Java_OAuth2TokenService_validateAccounts(AttachCurrentThread(), java_ref_, | 
| 178         AttachCurrentThread(), java_ref_, | 174                                              JNI_TRUE); | 
| 179         base::android::GetApplicationContext(), JNI_TRUE); |  | 
| 180   } | 175   } | 
| 181 } | 176 } | 
| 182 | 177 | 
| 183 OAuth2TokenServiceDelegateAndroid::~OAuth2TokenServiceDelegateAndroid() { | 178 OAuth2TokenServiceDelegateAndroid::~OAuth2TokenServiceDelegateAndroid() { | 
| 184 } | 179 } | 
| 185 | 180 | 
| 186 // static | 181 // static | 
| 187 ScopedJavaLocalRef<jobject> OAuth2TokenServiceDelegateAndroid::GetForProfile( | 182 ScopedJavaLocalRef<jobject> OAuth2TokenServiceDelegateAndroid::GetForProfile( | 
| 188     JNIEnv* env, | 183     JNIEnv* env, | 
| 189     jclass clazz, | 184     jclass clazz, | 
| (...skipping 16 matching lines...) Expand all  Loading... | 
| 206 | 201 | 
| 207 bool OAuth2TokenServiceDelegateAndroid::RefreshTokenIsAvailable( | 202 bool OAuth2TokenServiceDelegateAndroid::RefreshTokenIsAvailable( | 
| 208     const std::string& account_id) const { | 203     const std::string& account_id) const { | 
| 209   DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::RefreshTokenIsAvailable" | 204   DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::RefreshTokenIsAvailable" | 
| 210            << " account= " << account_id; | 205            << " account= " << account_id; | 
| 211   std::string account_name = MapAccountIdToAccountName(account_id); | 206   std::string account_name = MapAccountIdToAccountName(account_id); | 
| 212   JNIEnv* env = AttachCurrentThread(); | 207   JNIEnv* env = AttachCurrentThread(); | 
| 213   ScopedJavaLocalRef<jstring> j_account_id = | 208   ScopedJavaLocalRef<jstring> j_account_id = | 
| 214       ConvertUTF8ToJavaString(env, account_name); | 209       ConvertUTF8ToJavaString(env, account_name); | 
| 215   jboolean refresh_token_is_available = | 210   jboolean refresh_token_is_available = | 
| 216       Java_OAuth2TokenService_hasOAuth2RefreshToken( | 211       Java_OAuth2TokenService_hasOAuth2RefreshToken(env, j_account_id); | 
| 217           env, base::android::GetApplicationContext(), j_account_id); |  | 
| 218   return refresh_token_is_available == JNI_TRUE; | 212   return refresh_token_is_available == JNI_TRUE; | 
| 219 } | 213 } | 
| 220 | 214 | 
| 221 bool OAuth2TokenServiceDelegateAndroid::RefreshTokenHasError( | 215 bool OAuth2TokenServiceDelegateAndroid::RefreshTokenHasError( | 
| 222     const std::string& account_id) const { | 216     const std::string& account_id) const { | 
| 223   auto it = errors_.find(account_id); | 217   auto it = errors_.find(account_id); | 
| 224   // TODO(rogerta): should we distinguish between transient and persistent? | 218   // TODO(rogerta): should we distinguish between transient and persistent? | 
| 225   return it == errors_.end() ? false : IsError(it->second.error); | 219   return it == errors_.end() ? false : IsError(it->second.error); | 
| 226 } | 220 } | 
| 227 | 221 | 
| 228 void OAuth2TokenServiceDelegateAndroid::UpdateAuthError( | 222 void OAuth2TokenServiceDelegateAndroid::UpdateAuthError( | 
| 229     const std::string& account_id, | 223     const std::string& account_id, | 
| 230     const GoogleServiceAuthError& error) { | 224     const GoogleServiceAuthError& error) { | 
| 231   DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::UpdateAuthError" | 225   DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::UpdateAuthError" | 
| 232            << " account=" << account_id | 226            << " account=" << account_id | 
| 233            << " error=" << error.ToString(); | 227            << " error=" << error.ToString(); | 
| 234   if (error.state() == GoogleServiceAuthError::NONE) { | 228   if (error.state() == GoogleServiceAuthError::NONE) { | 
| 235     errors_.erase(account_id); | 229     errors_.erase(account_id); | 
| 236   } else { | 230   } else { | 
| 237     // TODO(rogerta): should we distinguish between transient and persistent? | 231     // TODO(rogerta): should we distinguish between transient and persistent? | 
| 238     errors_[account_id] = ErrorInfo(error); | 232     errors_[account_id] = ErrorInfo(error); | 
| 239   } | 233   } | 
| 240 } | 234 } | 
| 241 | 235 | 
| 242 std::vector<std::string> OAuth2TokenServiceDelegateAndroid::GetAccounts() { | 236 std::vector<std::string> OAuth2TokenServiceDelegateAndroid::GetAccounts() { | 
| 243   std::vector<std::string> accounts; | 237   std::vector<std::string> accounts; | 
| 244   JNIEnv* env = AttachCurrentThread(); | 238   JNIEnv* env = AttachCurrentThread(); | 
| 245   ScopedJavaLocalRef<jobjectArray> j_accounts = | 239   ScopedJavaLocalRef<jobjectArray> j_accounts = | 
| 246       Java_OAuth2TokenService_getAccounts( | 240       Java_OAuth2TokenService_getAccounts(env); | 
| 247           env, base::android::GetApplicationContext()); |  | 
| 248   // TODO(fgorski): We may decide to filter out some of the accounts. | 241   // TODO(fgorski): We may decide to filter out some of the accounts. | 
| 249   base::android::AppendJavaStringArrayToStringVector(env, j_accounts.obj(), | 242   base::android::AppendJavaStringArrayToStringVector(env, j_accounts.obj(), | 
| 250                                                      &accounts); | 243                                                      &accounts); | 
| 251   return accounts; | 244   return accounts; | 
| 252 } | 245 } | 
| 253 | 246 | 
| 254 std::vector<std::string> | 247 std::vector<std::string> | 
| 255 OAuth2TokenServiceDelegateAndroid::GetSystemAccountNames() { | 248 OAuth2TokenServiceDelegateAndroid::GetSystemAccountNames() { | 
| 256   std::vector<std::string> account_names; | 249   std::vector<std::string> account_names; | 
| 257   JNIEnv* env = AttachCurrentThread(); | 250   JNIEnv* env = AttachCurrentThread(); | 
| 258   ScopedJavaLocalRef<jobjectArray> j_accounts = | 251   ScopedJavaLocalRef<jobjectArray> j_accounts = | 
| 259       Java_OAuth2TokenService_getSystemAccountNames( | 252       Java_OAuth2TokenService_getSystemAccountNames(env); | 
| 260           env, base::android::GetApplicationContext()); |  | 
| 261   base::android::AppendJavaStringArrayToStringVector(env, j_accounts.obj(), | 253   base::android::AppendJavaStringArrayToStringVector(env, j_accounts.obj(), | 
| 262                                                      &account_names); | 254                                                      &account_names); | 
| 263   return account_names; | 255   return account_names; | 
| 264 } | 256 } | 
| 265 | 257 | 
| 266 OAuth2AccessTokenFetcher* | 258 OAuth2AccessTokenFetcher* | 
| 267 OAuth2TokenServiceDelegateAndroid::CreateAccessTokenFetcher( | 259 OAuth2TokenServiceDelegateAndroid::CreateAccessTokenFetcher( | 
| 268     const std::string& account_id, | 260     const std::string& account_id, | 
| 269     net::URLRequestContextGetter* getter, | 261     net::URLRequestContextGetter* getter, | 
| 270     OAuth2AccessTokenConsumer* consumer) { | 262     OAuth2AccessTokenConsumer* consumer) { | 
| 271   DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::CreateAccessTokenFetcher" | 263   DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::CreateAccessTokenFetcher" | 
| 272            << " account= " << account_id; | 264            << " account= " << account_id; | 
| 273   ValidateAccountId(account_id); | 265   ValidateAccountId(account_id); | 
| 274   return new AndroidAccessTokenFetcher(consumer, | 266   return new AndroidAccessTokenFetcher(consumer, | 
| 275                                        MapAccountIdToAccountName(account_id)); | 267                                        MapAccountIdToAccountName(account_id)); | 
| 276 } | 268 } | 
| 277 | 269 | 
| 278 void OAuth2TokenServiceDelegateAndroid::InvalidateAccessToken( | 270 void OAuth2TokenServiceDelegateAndroid::InvalidateAccessToken( | 
| 279     const std::string& account_id, | 271     const std::string& account_id, | 
| 280     const std::string& client_id, | 272     const std::string& client_id, | 
| 281     const OAuth2TokenService::ScopeSet& scopes, | 273     const OAuth2TokenService::ScopeSet& scopes, | 
| 282     const std::string& access_token) { | 274     const std::string& access_token) { | 
| 283   ValidateAccountId(account_id); | 275   ValidateAccountId(account_id); | 
| 284   JNIEnv* env = AttachCurrentThread(); | 276   JNIEnv* env = AttachCurrentThread(); | 
| 285   ScopedJavaLocalRef<jstring> j_access_token = | 277   ScopedJavaLocalRef<jstring> j_access_token = | 
| 286       ConvertUTF8ToJavaString(env, access_token); | 278       ConvertUTF8ToJavaString(env, access_token); | 
| 287   Java_OAuth2TokenService_invalidateOAuth2AuthToken( | 279   Java_OAuth2TokenService_invalidateOAuth2AuthToken(env, j_access_token); | 
| 288       env, base::android::GetApplicationContext(), j_access_token); |  | 
| 289 } | 280 } | 
| 290 | 281 | 
| 291 void OAuth2TokenServiceDelegateAndroid::ValidateAccounts( | 282 void OAuth2TokenServiceDelegateAndroid::ValidateAccounts( | 
| 292     JNIEnv* env, | 283     JNIEnv* env, | 
| 293     const JavaParamRef<jobject>& obj, | 284     const JavaParamRef<jobject>& obj, | 
| 294     const JavaParamRef<jstring>& j_current_acc, | 285     const JavaParamRef<jstring>& j_current_acc, | 
| 295     jboolean j_force_notifications) { | 286     jboolean j_force_notifications) { | 
| 296   std::string signed_in_account_name; | 287   std::string signed_in_account_name; | 
| 297   DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::ValidateAccounts from java"; | 288   DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::ValidateAccounts from java"; | 
| 298   if (j_current_acc) | 289   if (j_current_acc) | 
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 339   ScopedJavaLocalRef<jobjectArray> java_accounts; | 330   ScopedJavaLocalRef<jobjectArray> java_accounts; | 
| 340   if (currently_signed_in) { | 331   if (currently_signed_in) { | 
| 341     java_accounts = base::android::ToJavaArrayOfStrings(env, curr_ids); | 332     java_accounts = base::android::ToJavaArrayOfStrings(env, curr_ids); | 
| 342   } else { | 333   } else { | 
| 343     java_accounts = | 334     java_accounts = | 
| 344         base::android::ToJavaArrayOfStrings(env, std::vector<std::string>()); | 335         base::android::ToJavaArrayOfStrings(env, std::vector<std::string>()); | 
| 345   } | 336   } | 
| 346 | 337 | 
| 347   // Save the current accounts in the token service before calling | 338   // Save the current accounts in the token service before calling | 
| 348   // FireRefreshToken* methods. | 339   // FireRefreshToken* methods. | 
| 349   Java_OAuth2TokenService_saveStoredAccounts( | 340   Java_OAuth2TokenService_saveStoredAccounts(env, java_accounts); | 
| 350       env, base::android::GetApplicationContext(), java_accounts); |  | 
| 351 | 341 | 
| 352   for (const std::string& refreshed_id : refreshed_ids) | 342   for (const std::string& refreshed_id : refreshed_ids) | 
| 353     FireRefreshTokenAvailable(refreshed_id); | 343     FireRefreshTokenAvailable(refreshed_id); | 
| 354   for (const std::string& revoked_id : revoked_ids) | 344   for (const std::string& revoked_id : revoked_ids) | 
| 355     FireRefreshTokenRevoked(revoked_id); | 345     FireRefreshTokenRevoked(revoked_id); | 
| 356   if (fire_refresh_token_loaded_ == RT_WAIT_FOR_VALIDATION) { | 346   if (fire_refresh_token_loaded_ == RT_WAIT_FOR_VALIDATION) { | 
| 357     fire_refresh_token_loaded_ = RT_LOADED; | 347     fire_refresh_token_loaded_ = RT_LOADED; | 
| 358     FireRefreshTokensLoaded(); | 348     FireRefreshTokensLoaded(); | 
| 359   } else if (fire_refresh_token_loaded_ == RT_LOAD_NOT_START) { | 349   } else if (fire_refresh_token_loaded_ == RT_LOAD_NOT_START) { | 
| 360     fire_refresh_token_loaded_ = RT_HAS_BEEN_VALIDATED; | 350     fire_refresh_token_loaded_ = RT_HAS_BEEN_VALIDATED; | 
| (...skipping 144 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 505 void OAuth2TokenServiceDelegateAndroid::RevokeAllCredentials() { | 495 void OAuth2TokenServiceDelegateAndroid::RevokeAllCredentials() { | 
| 506   DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::RevokeAllCredentials"; | 496   DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::RevokeAllCredentials"; | 
| 507   ScopedBatchChange batch(this); | 497   ScopedBatchChange batch(this); | 
| 508   std::vector<std::string> accounts_to_revoke = GetAccounts(); | 498   std::vector<std::string> accounts_to_revoke = GetAccounts(); | 
| 509 | 499 | 
| 510   // Clear accounts in the token service before calling | 500   // Clear accounts in the token service before calling | 
| 511   // |FireRefreshTokenRevoked|. | 501   // |FireRefreshTokenRevoked|. | 
| 512   JNIEnv* env = AttachCurrentThread(); | 502   JNIEnv* env = AttachCurrentThread(); | 
| 513   ScopedJavaLocalRef<jobjectArray> java_accounts( | 503   ScopedJavaLocalRef<jobjectArray> java_accounts( | 
| 514       base::android::ToJavaArrayOfStrings(env, std::vector<std::string>())); | 504       base::android::ToJavaArrayOfStrings(env, std::vector<std::string>())); | 
| 515   Java_OAuth2TokenService_saveStoredAccounts( | 505   Java_OAuth2TokenService_saveStoredAccounts(env, java_accounts); | 
| 516       env, base::android::GetApplicationContext(), java_accounts); |  | 
| 517 | 506 | 
| 518   for (const std::string& account : accounts_to_revoke) | 507   for (const std::string& account : accounts_to_revoke) | 
| 519     FireRefreshTokenRevoked(account); | 508     FireRefreshTokenRevoked(account); | 
| 520 } | 509 } | 
| 521 | 510 | 
| 522 void OAuth2TokenServiceDelegateAndroid::LoadCredentials( | 511 void OAuth2TokenServiceDelegateAndroid::LoadCredentials( | 
| 523     const std::string& primary_account_id) { | 512     const std::string& primary_account_id) { | 
| 524   if (primary_account_id.empty()) { | 513   if (primary_account_id.empty()) { | 
| 525     FireRefreshTokensLoaded(); | 514     FireRefreshTokensLoaded(); | 
| 526     return; | 515     return; | 
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 567               : isTransientError | 556               : isTransientError | 
| 568                     ? GoogleServiceAuthError::CONNECTION_FAILED | 557                     ? GoogleServiceAuthError::CONNECTION_FAILED | 
| 569                     : GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS); | 558                     : GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS); | 
| 570   heap_callback->Run(err, token, base::Time()); | 559   heap_callback->Run(err, token, base::Time()); | 
| 571 } | 560 } | 
| 572 | 561 | 
| 573 // static | 562 // static | 
| 574 bool OAuth2TokenServiceDelegateAndroid::Register(JNIEnv* env) { | 563 bool OAuth2TokenServiceDelegateAndroid::Register(JNIEnv* env) { | 
| 575   return RegisterNativesImpl(env); | 564   return RegisterNativesImpl(env); | 
| 576 } | 565 } | 
| OLD | NEW | 
|---|