Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 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 package org.chromium.chrome.browser.payments; | 5 package org.chromium.chrome.browser.payments; |
| 6 | 6 |
| 7 import android.app.Activity; | 7 import android.app.Activity; |
| 8 import android.graphics.Bitmap; | 8 import android.graphics.Bitmap; |
| 9 import android.os.Handler; | 9 import android.os.Handler; |
| 10 import android.text.TextUtils; | 10 import android.text.TextUtils; |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 22 import org.chromium.chrome.browser.preferences.PreferencesLauncher; | 22 import org.chromium.chrome.browser.preferences.PreferencesLauncher; |
| 23 import org.chromium.chrome.browser.preferences.autofill.AutofillCreditCardEditor ; | 23 import org.chromium.chrome.browser.preferences.autofill.AutofillCreditCardEditor ; |
| 24 import org.chromium.chrome.browser.preferences.autofill.AutofillProfileEditor; | 24 import org.chromium.chrome.browser.preferences.autofill.AutofillProfileEditor; |
| 25 import org.chromium.chrome.browser.profiles.Profile; | 25 import org.chromium.chrome.browser.profiles.Profile; |
| 26 import org.chromium.chrome.browser.util.UrlUtilities; | 26 import org.chromium.chrome.browser.util.UrlUtilities; |
| 27 import org.chromium.content.browser.ContentViewCore; | 27 import org.chromium.content.browser.ContentViewCore; |
| 28 import org.chromium.content_public.browser.WebContents; | 28 import org.chromium.content_public.browser.WebContents; |
| 29 import org.chromium.mojo.system.MojoException; | 29 import org.chromium.mojo.system.MojoException; |
| 30 import org.chromium.mojom.payments.PaymentDetails; | 30 import org.chromium.mojom.payments.PaymentDetails; |
| 31 import org.chromium.mojom.payments.PaymentItem; | 31 import org.chromium.mojom.payments.PaymentItem; |
| 32 import org.chromium.mojom.payments.PaymentMethodData; | |
| 32 import org.chromium.mojom.payments.PaymentOptions; | 33 import org.chromium.mojom.payments.PaymentOptions; |
| 33 import org.chromium.mojom.payments.PaymentRequest; | 34 import org.chromium.mojom.payments.PaymentRequest; |
| 34 import org.chromium.mojom.payments.PaymentRequestClient; | 35 import org.chromium.mojom.payments.PaymentRequestClient; |
| 35 import org.chromium.mojom.payments.PaymentResponse; | 36 import org.chromium.mojom.payments.PaymentResponse; |
| 36 import org.chromium.mojom.payments.ShippingOption; | 37 import org.chromium.mojom.payments.ShippingOption; |
| 37 import org.chromium.ui.base.WindowAndroid; | 38 import org.chromium.ui.base.WindowAndroid; |
| 38 import org.json.JSONException; | 39 import org.json.JSONException; |
| 39 import org.json.JSONObject; | 40 import org.json.JSONObject; |
| 40 | 41 |
| 41 import java.util.ArrayList; | 42 import java.util.ArrayList; |
| 42 import java.util.Arrays; | 43 import java.util.Arrays; |
| 43 import java.util.HashSet; | 44 import java.util.HashMap; |
| 44 import java.util.Iterator; | |
| 45 import java.util.List; | 45 import java.util.List; |
| 46 import java.util.Locale; | 46 import java.util.Locale; |
| 47 import java.util.Set; | 47 import java.util.Set; |
| 48 import java.util.regex.Pattern; | 48 import java.util.regex.Pattern; |
| 49 | 49 |
| 50 /** | 50 /** |
| 51 * Android implementation of the PaymentRequest service defined in | 51 * Android implementation of the PaymentRequest service defined in |
| 52 * third_party/WebKit/public/platform/modules/payments/payment_request.mojom. | 52 * third_party/WebKit/public/platform/modules/payments/payment_request.mojom. |
| 53 */ | 53 */ |
| 54 public class PaymentRequestImpl implements PaymentRequest, PaymentRequestUI.Clie nt, | 54 public class PaymentRequestImpl implements PaymentRequest, PaymentRequestUI.Clie nt, |
| 55 PaymentApp.InstrumentsCallback, PaymentInstrument.DetailsCallback { | 55 PaymentApp.InstrumentsCallback, PaymentInstrument.DetailsCallback { |
| 56 /** | 56 /** |
| 57 * The size for the favicon in density-independent pixels. | 57 * The size for the favicon in density-independent pixels. |
| 58 */ | 58 */ |
| 59 private static final int FAVICON_SIZE_DP = 24; | 59 private static final int FAVICON_SIZE_DP = 24; |
| 60 | 60 |
| 61 private static final String TAG = "cr_PaymentRequest"; | 61 private static final String TAG = "cr_PaymentRequest"; |
| 62 | 62 |
| 63 private final Handler mHandler = new Handler(); | 63 private final Handler mHandler = new Handler(); |
| 64 | 64 |
| 65 private Activity mContext; | 65 private Activity mContext; |
| 66 private String mMerchantName; | 66 private String mMerchantName; |
| 67 private String mOrigin; | 67 private String mOrigin; |
| 68 private Bitmap mFavicon; | 68 private Bitmap mFavicon; |
| 69 private List<PaymentApp> mApps; | 69 private List<PaymentApp> mApps; |
| 70 private PaymentRequestClient mClient; | 70 private PaymentRequestClient mClient; |
| 71 private Set<String> mSupportedMethods; | 71 private HashMap<String, JSONObject> mMethodData; |
| 72 private List<LineItem> mLineItems; | 72 private List<LineItem> mLineItems; |
| 73 private List<PaymentItem> mDisplayItems; | 73 private List<PaymentItem> mDisplayItems; |
| 74 private List<ShippingOption> mShippingOptions; | 74 private List<ShippingOption> mShippingOptions; |
| 75 private SectionInformation mShippingOptionsSection; | 75 private SectionInformation mShippingOptionsSection; |
| 76 private JSONObject mData; | |
| 77 private SectionInformation mShippingAddressesSection; | 76 private SectionInformation mShippingAddressesSection; |
| 78 private List<PaymentApp> mPendingApps; | 77 private List<PaymentApp> mPendingApps; |
| 79 private List<PaymentInstrument> mPendingInstruments; | 78 private List<PaymentInstrument> mPendingInstruments; |
| 80 private SectionInformation mPaymentMethodsSection; | 79 private SectionInformation mPaymentMethodsSection; |
| 81 private PaymentRequestUI mUI; | 80 private PaymentRequestUI mUI; |
| 82 private Callback<PaymentInformation> mPaymentInformationCallback; | 81 private Callback<PaymentInformation> mPaymentInformationCallback; |
| 83 private Pattern mRegionCodePattern; | 82 private Pattern mRegionCodePattern; |
| 84 private boolean mMerchantNeedsShippingAddress; | 83 private boolean mMerchantNeedsShippingAddress; |
| 85 | 84 |
| 86 /** | 85 /** |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 138 | 137 |
| 139 if (mContext == null) { | 138 if (mContext == null) { |
| 140 disconnectFromClientWithDebugMessage("Web contents don't have associ ated activity"); | 139 disconnectFromClientWithDebugMessage("Web contents don't have associ ated activity"); |
| 141 } | 140 } |
| 142 } | 141 } |
| 143 | 142 |
| 144 /** | 143 /** |
| 145 * Called by the merchant website to show the payment request to the user. | 144 * Called by the merchant website to show the payment request to the user. |
| 146 */ | 145 */ |
| 147 @Override | 146 @Override |
| 148 public void show(String[] supportedMethods, PaymentDetails details, PaymentO ptions options, | 147 public void show(PaymentMethodData[] methodData, PaymentDetails details, |
| 149 String stringifiedData) { | 148 PaymentOptions options) { |
| 150 if (mClient == null) return; | 149 if (mClient == null) return; |
| 151 | 150 |
| 152 if (mSupportedMethods != null) { | 151 if (mMethodData != null) { |
| 153 disconnectFromClientWithDebugMessage("PaymentRequest.show() called m ore than once."); | 152 disconnectFromClientWithDebugMessage("PaymentRequest.show() called m ore than once."); |
| 154 return; | 153 return; |
| 155 } | 154 } |
| 156 | 155 |
| 157 mSupportedMethods = getValidatedSupportedMethods(supportedMethods); | 156 mMethodData = getValidatedMethodData(methodData); |
| 158 if (mSupportedMethods == null) { | 157 if (mMethodData == null) { |
| 159 disconnectFromClientWithDebugMessage("Invalid payment methods"); | 158 disconnectFromClientWithDebugMessage("Invalid payment methods or dat a"); |
| 160 return; | 159 return; |
| 161 } | 160 } |
| 162 | 161 |
| 163 if (!setLineItemsAndShippingOptionsOrDisconnectFromClient(details)) retu rn; | 162 if (!setLineItemsAndShippingOptionsOrDisconnectFromClient(details)) retu rn; |
| 164 | 163 |
| 165 // If the merchant requests shipping and does not provide shipping optio ns here, then the | 164 // If the merchant requests shipping and does not provide shipping optio ns here, then the |
| 166 // merchant needs the shipping address to calculate shipping price and a vailability. | 165 // merchant needs the shipping address to calculate shipping price and a vailability. |
| 167 boolean requestShipping = options != null && options.requestShipping; | 166 boolean requestShipping = options != null && options.requestShipping; |
| 168 mMerchantNeedsShippingAddress = requestShipping && mShippingOptionsSecti on.isEmpty(); | 167 mMerchantNeedsShippingAddress = requestShipping && mShippingOptionsSecti on.isEmpty(); |
| 169 | 168 |
| 170 mData = getValidatedData(mSupportedMethods, stringifiedData); | |
| 171 if (mData == null) { | |
| 172 disconnectFromClientWithDebugMessage("Invalid payment method specifi c data"); | |
| 173 return; | |
| 174 } | |
| 175 | |
| 176 List<AutofillAddress> addresses = new ArrayList<>(); | 169 List<AutofillAddress> addresses = new ArrayList<>(); |
| 177 List<AutofillProfile> profiles = PersonalDataManager.getInstance().getAd dressOnlyProfiles(); | 170 List<AutofillProfile> profiles = PersonalDataManager.getInstance().getAd dressOnlyProfiles(); |
| 178 for (int i = 0; i < profiles.size(); i++) { | 171 for (int i = 0; i < profiles.size(); i++) { |
| 179 AutofillProfile profile = profiles.get(i); | 172 AutofillProfile profile = profiles.get(i); |
| 180 if (profile.getCountryCode() != null | 173 if (profile.getCountryCode() != null |
| 181 && mRegionCodePattern.matcher(profile.getCountryCode()).matc hes() | 174 && mRegionCodePattern.matcher(profile.getCountryCode()).matc hes() |
| 182 && profile.getStreetAddress() != null && profile.getRegion() != null | 175 && profile.getStreetAddress() != null && profile.getRegion() != null |
| 183 && profile.getLocality() != null && profile.getDependentLoca lity() != null | 176 && profile.getLocality() != null && profile.getDependentLoca lity() != null |
| 184 && profile.getPostalCode() != null && profile.getSortingCode () != null | 177 && profile.getPostalCode() != null && profile.getSortingCode () != null |
| 185 && profile.getCompanyName() != null && profile.getFullName() != null) { | 178 && profile.getCompanyName() != null && profile.getFullName() != null) { |
| 186 addresses.add(new AutofillAddress(profile)); | 179 addresses.add(new AutofillAddress(profile)); |
| 187 } | 180 } |
| 188 } | 181 } |
| 189 | 182 |
| 190 int selectedIndex = SectionInformation.NO_SELECTION; | 183 int selectedIndex = SectionInformation.NO_SELECTION; |
| 191 if (!addresses.isEmpty() && mShippingOptionsSection.getSelectedItem() != null) { | 184 if (!addresses.isEmpty() && mShippingOptionsSection.getSelectedItem() != null) { |
| 192 selectedIndex = 0; | 185 selectedIndex = 0; |
| 193 } | 186 } |
| 194 mShippingAddressesSection = new SectionInformation( | 187 mShippingAddressesSection = new SectionInformation( |
| 195 PaymentRequestUI.TYPE_SHIPPING_ADDRESSES, selectedIndex, address es); | 188 PaymentRequestUI.TYPE_SHIPPING_ADDRESSES, selectedIndex, address es); |
| 196 | 189 |
| 197 mPendingApps = new ArrayList<>(mApps); | 190 mPendingApps = new ArrayList<>(mApps); |
| 198 mPendingInstruments = new ArrayList<>(); | 191 mPendingInstruments = new ArrayList<>(); |
| 199 boolean isGettingInstruments = false; | 192 boolean isGettingInstruments = false; |
| 200 | 193 |
| 201 for (int i = 0; i < mApps.size(); i++) { | 194 for (int i = 0; i < mApps.size(); i++) { |
| 202 PaymentApp app = mApps.get(i); | 195 PaymentApp app = mApps.get(i); |
| 203 Set<String> appMethods = app.getSupportedMethodNames(); | 196 Set<String> appMethods = app.getSupportedMethodNames(); |
| 204 appMethods.retainAll(mSupportedMethods); | 197 appMethods.retainAll(mMethodData.keySet()); |
| 205 if (appMethods.isEmpty()) { | 198 if (appMethods.isEmpty()) { |
| 206 mPendingApps.remove(app); | 199 mPendingApps.remove(app); |
| 207 } else { | 200 } else { |
| 208 isGettingInstruments = true; | 201 isGettingInstruments = true; |
| 209 app.getInstruments(mDisplayItems, this); | 202 app.getInstruments(mDisplayItems, this); |
| 210 } | 203 } |
| 211 } | 204 } |
| 212 | 205 |
| 213 if (!isGettingInstruments) { | 206 if (!isGettingInstruments) { |
| 214 mPaymentMethodsSection = new SectionInformation(PaymentRequestUI.TYP E_PAYMENT_METHODS); | 207 mPaymentMethodsSection = new SectionInformation(PaymentRequestUI.TYP E_PAYMENT_METHODS); |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 258 getValidatedShippingOptions(details.displayItems[0].amount.curre ncyCode, details); | 251 getValidatedShippingOptions(details.displayItems[0].amount.curre ncyCode, details); |
| 259 if (mShippingOptionsSection == null) { | 252 if (mShippingOptionsSection == null) { |
| 260 disconnectFromClientWithDebugMessage("Invalid shipping options"); | 253 disconnectFromClientWithDebugMessage("Invalid shipping options"); |
| 261 return false; | 254 return false; |
| 262 } | 255 } |
| 263 mShippingOptions = Arrays.asList(details.shippingOptions); | 256 mShippingOptions = Arrays.asList(details.shippingOptions); |
| 264 | 257 |
| 265 return true; | 258 return true; |
| 266 } | 259 } |
| 267 | 260 |
| 268 private HashSet<String> getValidatedSupportedMethods(String[] methods) { | 261 private HashMap<String, JSONObject> getValidatedMethodData(PaymentMethodData [] methodData) { |
| 269 // Payment methods are required. | 262 // Payment methodData are required. |
| 270 if (methods == null || methods.length == 0) return null; | 263 if (methodData == null || methodData.length == 0) return null; |
|
gone
2016/06/06 18:07:33
nit: newline here.
| |
| 264 HashMap<String, JSONObject> result = new HashMap<>(); | |
| 265 for (int i = 0; i < methodData.length; i++) { | |
| 266 JSONObject data = null; | |
| 267 if (!TextUtils.isEmpty(methodData[i].data)) { | |
| 268 try { | |
| 269 data = new JSONObject(methodData[i].data); | |
| 270 } catch (JSONException e) { | |
| 271 // Payment method specific data should be a JSON object. | |
| 272 return null; | |
|
gone
2016/06/06 18:07:33
I confirmed with rouslan that you're intentionally
| |
| 273 } | |
| 274 } | |
| 271 | 275 |
| 272 HashSet<String> result = new HashSet<>(); | 276 String[] methods = methodData[i].supportedMethods; |
| 273 for (int i = 0; i < methods.length; i++) { | 277 |
| 274 // Payment methods should be non-empty. | 278 // Payment methods are required. |
| 275 if (TextUtils.isEmpty(methods[i])) return null; | 279 if (methods == null || methods.length == 0) return null; |
| 276 result.add(methods[i]); | 280 |
| 281 for (int j = 0; j < methods.length; j++) { | |
| 282 // Payment methods should be non-empty. | |
| 283 if (TextUtils.isEmpty(methods[j])) return null; | |
| 284 result.put(methods[j], data); | |
| 285 } | |
| 277 } | 286 } |
| 278 | 287 |
| 279 return result; | 288 return result; |
| 280 } | 289 } |
| 281 | 290 |
| 282 private List<LineItem> getValidatedLineItems(PaymentDetails details) { | 291 private List<LineItem> getValidatedLineItems(PaymentDetails details) { |
| 283 // Line items are required. | 292 // Line items are required. |
| 284 if (details == null || details.displayItems == null || details.displayIt ems.length == 0) { | 293 if (details == null || details.displayItems == null || details.displayIt ems.length == 0) { |
| 285 return null; | 294 return null; |
| 286 } | 295 } |
| (...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 370 for (int i = 0; i < details.shippingOptions.length; i++) { | 379 for (int i = 0; i < details.shippingOptions.length; i++) { |
| 371 ShippingOption option = details.shippingOptions[i]; | 380 ShippingOption option = details.shippingOptions[i]; |
| 372 result.add(new PaymentOption(option.id, option.label, | 381 result.add(new PaymentOption(option.id, option.label, |
| 373 formatter.format(option.amount.value), PaymentOption.NO_ICON )); | 382 formatter.format(option.amount.value), PaymentOption.NO_ICON )); |
| 374 } | 383 } |
| 375 | 384 |
| 376 return new SectionInformation(PaymentRequestUI.TYPE_SHIPPING_OPTIONS, | 385 return new SectionInformation(PaymentRequestUI.TYPE_SHIPPING_OPTIONS, |
| 377 result.size() == 1 ? 0 : SectionInformation.NO_SELECTION, result ); | 386 result.size() == 1 ? 0 : SectionInformation.NO_SELECTION, result ); |
| 378 } | 387 } |
| 379 | 388 |
| 380 private JSONObject getValidatedData(Set<String> supportedMethods, String str ingifiedData) { | |
| 381 if (TextUtils.isEmpty(stringifiedData)) return new JSONObject(); | |
| 382 | |
| 383 JSONObject result; | |
| 384 try { | |
| 385 result = new JSONObject(stringifiedData); | |
| 386 } catch (JSONException e) { | |
| 387 // Payment method specific data should be a JSON object. | |
| 388 return null; | |
| 389 } | |
| 390 | |
| 391 Iterator<String> it = result.keys(); | |
| 392 while (it.hasNext()) { | |
| 393 String name = it.next(); | |
| 394 // Each key should be one of the supported payment methods. | |
| 395 if (!supportedMethods.contains(name)) return null; | |
| 396 // Each value should be a JSON object. | |
| 397 if (result.optJSONObject(name) == null) return null; | |
| 398 } | |
| 399 | |
| 400 return result; | |
| 401 } | |
| 402 | |
| 403 /** | 389 /** |
| 404 * Called to retrieve the data to show in the initial PaymentRequest UI. | 390 * Called to retrieve the data to show in the initial PaymentRequest UI. |
| 405 */ | 391 */ |
| 406 @Override | 392 @Override |
| 407 public void getDefaultPaymentInformation(Callback<PaymentInformation> callba ck) { | 393 public void getDefaultPaymentInformation(Callback<PaymentInformation> callba ck) { |
| 408 mPaymentInformationCallback = callback; | 394 mPaymentInformationCallback = callback; |
| 409 | 395 |
| 410 if (mPaymentMethodsSection == null) return; | 396 if (mPaymentMethodsSection == null) return; |
| 411 | 397 |
| 412 mHandler.post(new Runnable() { | 398 mHandler.post(new Runnable() { |
| (...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 484 mContext, AutofillCreditCardEditor.class.getName()); | 470 mContext, AutofillCreditCardEditor.class.getName()); |
| 485 } | 471 } |
| 486 } | 472 } |
| 487 | 473 |
| 488 @Override | 474 @Override |
| 489 public void onPayClicked(PaymentOption selectedShippingAddress, | 475 public void onPayClicked(PaymentOption selectedShippingAddress, |
| 490 PaymentOption selectedShippingOption, PaymentOption selectedPaymentM ethod) { | 476 PaymentOption selectedShippingOption, PaymentOption selectedPaymentM ethod) { |
| 491 assert selectedPaymentMethod instanceof PaymentInstrument; | 477 assert selectedPaymentMethod instanceof PaymentInstrument; |
| 492 PaymentInstrument instrument = (PaymentInstrument) selectedPaymentMethod ; | 478 PaymentInstrument instrument = (PaymentInstrument) selectedPaymentMethod ; |
| 493 instrument.getDetails(mMerchantName, mOrigin, mDisplayItems, | 479 instrument.getDetails(mMerchantName, mOrigin, mDisplayItems, |
| 494 mData.optJSONObject(instrument.getMethodName()), this); | 480 mMethodData.get(instrument.getMethodName()), this); |
| 495 } | 481 } |
| 496 | 482 |
| 497 @Override | 483 @Override |
| 498 public void onDismiss() { | 484 public void onDismiss() { |
| 499 disconnectFromClientWithDebugMessage("Dialog dismissed"); | 485 disconnectFromClientWithDebugMessage("Dialog dismissed"); |
| 500 closeUI(false); | 486 closeUI(false); |
| 501 } | 487 } |
| 502 | 488 |
| 503 /** | 489 /** |
| 504 * Called by the merchant website to abort the payment. | 490 * Called by the merchant website to abort the payment. |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 538 /** | 524 /** |
| 539 * Called after retrieving the list of payment instruments in an app. | 525 * Called after retrieving the list of payment instruments in an app. |
| 540 */ | 526 */ |
| 541 @Override | 527 @Override |
| 542 public void onInstrumentsReady(PaymentApp app, List<PaymentInstrument> instr uments) { | 528 public void onInstrumentsReady(PaymentApp app, List<PaymentInstrument> instr uments) { |
| 543 mPendingApps.remove(app); | 529 mPendingApps.remove(app); |
| 544 | 530 |
| 545 if (instruments != null) { | 531 if (instruments != null) { |
| 546 for (int i = 0; i < instruments.size(); i++) { | 532 for (int i = 0; i < instruments.size(); i++) { |
| 547 PaymentInstrument instrument = instruments.get(i); | 533 PaymentInstrument instrument = instruments.get(i); |
| 548 if (mSupportedMethods.contains(instrument.getMethodName())) { | 534 if (mMethodData.containsKey(instrument.getMethodName())) { |
| 549 mPendingInstruments.add(instrument); | 535 mPendingInstruments.add(instrument); |
| 550 } else { | 536 } else { |
| 551 instrument.dismiss(); | 537 instrument.dismiss(); |
| 552 } | 538 } |
| 553 } | 539 } |
| 554 } | 540 } |
| 555 | 541 |
| 556 if (mPendingApps.isEmpty()) { | 542 if (mPendingApps.isEmpty()) { |
| 557 mPaymentMethodsSection = new SectionInformation( | 543 mPaymentMethodsSection = new SectionInformation( |
| 558 PaymentRequestUI.TYPE_PAYMENT_METHODS, 0, mPendingInstrument s); | 544 PaymentRequestUI.TYPE_PAYMENT_METHODS, 0, mPendingInstrument s); |
| (...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 626 } | 612 } |
| 627 mPaymentMethodsSection = null; | 613 mPaymentMethodsSection = null; |
| 628 } | 614 } |
| 629 } | 615 } |
| 630 | 616 |
| 631 private void closeClient() { | 617 private void closeClient() { |
| 632 if (mClient != null) mClient.close(); | 618 if (mClient != null) mClient.close(); |
| 633 mClient = null; | 619 mClient = null; |
| 634 } | 620 } |
| 635 } | 621 } |
| OLD | NEW |