diff options
author | aurimas <aurimas@chromium.org> | 2014-09-30 16:48:42 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-09-30 23:49:11 +0000 |
commit | a683d267055e8b1cfd054986b8964b1a3456df55 (patch) | |
tree | f5b38e441a1061a4d051e28a66541ce312e4dd31 | |
parent | 9fd4726662eacd649b3071410d5d4c1d5f58c84d (diff) | |
download | chromium_src-a683d267055e8b1cfd054986b8964b1a3456df55.zip chromium_src-a683d267055e8b1cfd054986b8964b1a3456df55.tar.gz chromium_src-a683d267055e8b1cfd054986b8964b1a3456df55.tar.bz2 |
Upstream NFC beam URL sharing code and add it to ChromeShell.
BUG=417543
Review URL: https://codereview.chromium.org/615213003
Cr-Commit-Position: refs/heads/master@{#297549}
10 files changed, 245 insertions, 0 deletions
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/UmaBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/UmaBridge.java index 0e80f48..ed6fbe9 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/UmaBridge.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/UmaBridge.java @@ -29,6 +29,19 @@ public class UmaBridge { nativeRecordUsingMenu(isByHwButton, isDragging); } + // Android beam + public static void beamCallbackSuccess() { + nativeRecordBeamCallbackSuccess(); + } + + public static void beamInvalidAppState() { + nativeRecordBeamInvalidAppState(); + } + private static native void nativeRecordMenuShow(); private static native void nativeRecordUsingMenu(boolean isByHwButton, boolean isDragging); + + // Android Beam + private static native void nativeRecordBeamInvalidAppState(); + private static native void nativeRecordBeamCallbackSuccess(); } diff --git a/chrome/android/java/src/org/chromium/chrome/browser/nfc/BeamCallback.java b/chrome/android/java/src/org/chromium/chrome/browser/nfc/BeamCallback.java new file mode 100644 index 0000000..4811d20 --- /dev/null +++ b/chrome/android/java/src/org/chromium/chrome/browser/nfc/BeamCallback.java @@ -0,0 +1,151 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.chrome.browser.nfc; + +import android.app.Activity; +import android.nfc.NdefMessage; +import android.nfc.NdefRecord; +import android.nfc.NfcAdapter.CreateNdefMessageCallback; +import android.nfc.NfcAdapter.OnNdefPushCompleteCallback; +import android.nfc.NfcEvent; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.text.TextUtils; +import android.widget.Toast; + +import org.chromium.base.ThreadUtils; +import org.chromium.chrome.R; +import org.chromium.chrome.browser.UmaBridge; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * Beam callback that gets passed to Android to get triggered when devices are tapped to + * each other. + */ +class BeamCallback implements CreateNdefMessageCallback, OnNdefPushCompleteCallback { + + private static class Status { + public final Integer errorStrID; + public final String result; + + Status(Integer errorStrID) { + assert errorStrID != null; + this.errorStrID = errorStrID; + this.result = null; + } + + Status(String result) { + assert result != null; + this.result = result; + this.errorStrID = null; + } + } + + // In ICS returning null from createNdefMessage will cause beam to send our market + // link so we need to hook to the return from the beam overlay to display the error. + // But in SDK_INT >= 16, beam won't activate, so the hook wouldn't go off. (b/5943350) + private static final boolean NFC_BUGS_ACTIVE = Build.VERSION.SDK_INT < 16; + + // Arbitrarily chosen interval to delay toast to allow NFC animations to finish + // and our app to return to foreground. + private static final int TOAST_ERROR_DELAY_MS = 400; + + private final Activity mActivity; + private final BeamProvider mProvider; + + // We use this to delay the error message in ICS because it would be hidden behind + // the system beam overlay. It is only accessed by the NFC thread + private Runnable mErrorRunnableIfBeamSent; + + BeamCallback(Activity activity, BeamProvider provider) { + mActivity = activity; + mProvider = provider; + } + + @Override + public NdefMessage createNdefMessage(NfcEvent event) { + // Default status is an error + Status status = new Status(R.string.nfc_beam_error_bad_url); + try { + status = ThreadUtils.runOnUiThread(new Callable<Status>() { + @Override + public Status call() { + String url = mProvider.getTabUrlForBeam(); + if (url == null) return new Status(R.string.nfc_beam_error_overlay_active); + if (!isValidUrl(url)) return new Status(R.string.nfc_beam_error_bad_url); + return new Status(url); + } + }).get(2000, TimeUnit.MILLISECONDS); // Arbitrarily chosen timeout for query. + } catch (TimeoutException e) { + // Squelch this exception, we'll treat it as a bad tab + } catch (ExecutionException e) { + // And this + } catch (InterruptedException e) { + // And squelch this one too + } + + if (status.errorStrID != null) { + onInvalidBeam(status.errorStrID); + return null; + } + + UmaBridge.beamCallbackSuccess(); + mErrorRunnableIfBeamSent = null; + return new NdefMessage(new NdefRecord[] {NdefRecord.createUri(status.result)}); + } + + /** + * Trigger an error about NFC if we don't want to send anything. Also + * records a UMA stat. On ICS we only show the error if they attempt to + * beam, since the recipient will receive the market link. On JB we'll + * always show the error, since the beam animation won't trigger, which + * could be confusing to the user. + * + * @param errorStringId The resid of the string to display as error. + */ + private void onInvalidBeam(final int errorStringId) { + UmaBridge.beamInvalidAppState(); + Runnable errorRunnable = new Runnable() { + @Override + public void run() { + Toast.makeText(mActivity, errorStringId, Toast.LENGTH_SHORT).show(); + } + }; + if (NFC_BUGS_ACTIVE) { + mErrorRunnableIfBeamSent = errorRunnable; + } else { + ThreadUtils.runOnUiThread(errorRunnable); + } + } + + @Override + public void onNdefPushComplete(NfcEvent event) { + if (mErrorRunnableIfBeamSent != null) { + Handler h = new Handler(Looper.getMainLooper()); + h.postDelayed(mErrorRunnableIfBeamSent, TOAST_ERROR_DELAY_MS); + mErrorRunnableIfBeamSent = null; + } + } + + /** + * @return Whether given URL is valid and sharable via Beam. + */ + private static boolean isValidUrl(String url) { + if (TextUtils.isEmpty(url)) return false; + try { + String urlProtocol = (new URL(url)).getProtocol(); + return urlProtocol.matches("http|https"); + } catch (MalformedURLException e) { + return false; + } + } +} diff --git a/chrome/android/java/src/org/chromium/chrome/browser/nfc/BeamController.java b/chrome/android/java/src/org/chromium/chrome/browser/nfc/BeamController.java new file mode 100644 index 0000000..105daa6 --- /dev/null +++ b/chrome/android/java/src/org/chromium/chrome/browser/nfc/BeamController.java @@ -0,0 +1,34 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.chrome.browser.nfc; + +import android.app.Activity; +import android.nfc.NfcAdapter; +import android.util.Log; + +/** + * Initializes Android Beam (sharing URL via NFC) for devices that have NFC. If user taps their + * device with another Beam capable device, then Chrome gets the current URL, filters for security + * and returns the result to Android. + */ +public final class BeamController { + /** + * If the device has NFC, construct a BeamCallback and pass it to Android. + * + * @param activity Activity that is sending out beam messages. + * @param provider Provider that returns the URL that should be shared. + */ + public static void registerForBeam(final Activity activity, final BeamProvider provider) { + final NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(activity); + if (nfcAdapter == null) return; + try { + final BeamCallback beamCallback = new BeamCallback(activity, provider); + nfcAdapter.setNdefPushMessageCallback(beamCallback, activity); + nfcAdapter.setOnNdefPushCompleteCallback(beamCallback, activity); + } catch (IllegalStateException e) { + Log.w("BeamController", "NFC registration failure. Can't retry, giving up."); + } + } +} diff --git a/chrome/android/java/src/org/chromium/chrome/browser/nfc/BeamProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/nfc/BeamProvider.java new file mode 100644 index 0000000..4d95e20 --- /dev/null +++ b/chrome/android/java/src/org/chromium/chrome/browser/nfc/BeamProvider.java @@ -0,0 +1,15 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.chrome.browser.nfc; + +/** + * Interface for Activities that use Beam (sharing URL via NFC) controller. + */ +public interface BeamProvider { + /** + * @return URL of the current tab, null otherwise (e.g. user is in tab switcher). + */ + String getTabUrlForBeam(); +} diff --git a/chrome/android/java/src/org/chromium/chrome/browser/nfc/OWNERS b/chrome/android/java/src/org/chromium/chrome/browser/nfc/OWNERS new file mode 100644 index 0000000..577bf98 --- /dev/null +++ b/chrome/android/java/src/org/chromium/chrome/browser/nfc/OWNERS @@ -0,0 +1,2 @@ +aurimas@chromium.org +newt@chromium.org diff --git a/chrome/android/java/strings/android_chrome_strings.grd b/chrome/android/java/strings/android_chrome_strings.grd index ecff791..9eabb65 100644 --- a/chrome/android/java/strings/android_chrome_strings.grd +++ b/chrome/android/java/strings/android_chrome_strings.grd @@ -389,6 +389,14 @@ You are signing in with a managed account and giving its administrator control o Settings </message> + <!-- Android NFC Beam strings --> + <message name="IDS_NFC_BEAM_ERROR_OVERLAY_ACTIVE" desc="Android Beam error - a tab is not in the foreground [CHAR-LIMIT=40]"> + Select a tab to beam + </message> + <message name="IDS_NFC_BEAM_ERROR_BAD_URL" desc="Android Beam error - the current tab has an invalid url [CHAR-LIMIT=40]"> + Can’t beam current tab + </message> + </messages> </release> </grit> diff --git a/chrome/android/shell/java/AndroidManifest.xml b/chrome/android/shell/java/AndroidManifest.xml index e18acc7..aebe584 100644 --- a/chrome/android/shell/java/AndroidManifest.xml +++ b/chrome/android/shell/java/AndroidManifest.xml @@ -19,6 +19,7 @@ <uses-permission android:name="android.permission.GET_ACCOUNTS"/> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/> + <uses-permission android:name="android.permission.NFC"/> <uses-permission android:name="android.permission.RECORD_AUDIO"/> <uses-permission android:name="android.permission.VIBRATE"/> <uses-permission android:name="android.permission.WAKE_LOCK"/> diff --git a/chrome/android/shell/java/src/org/chromium/chrome/shell/ChromeShellActivity.java b/chrome/android/shell/java/src/org/chromium/chrome/shell/ChromeShellActivity.java index 48627d2..3de8cf7 100644 --- a/chrome/android/shell/java/src/org/chromium/chrome/shell/ChromeShellActivity.java +++ b/chrome/android/shell/java/src/org/chromium/chrome/shell/ChromeShellActivity.java @@ -28,6 +28,8 @@ import org.chromium.chrome.browser.FileProviderHelper; import org.chromium.chrome.browser.appmenu.AppMenuHandler; import org.chromium.chrome.browser.appmenu.AppMenuPropertiesDelegate; import org.chromium.chrome.browser.dom_distiller.DomDistillerTabUtils; +import org.chromium.chrome.browser.nfc.BeamController; +import org.chromium.chrome.browser.nfc.BeamProvider; import org.chromium.chrome.browser.printing.PrintingControllerFactory; import org.chromium.chrome.browser.printing.TabPrinter; import org.chromium.chrome.browser.share.ShareHelper; @@ -172,6 +174,15 @@ public class ChromeShellActivity extends Activity implements AppMenuPropertiesDe // SyncController that we have started. mSyncController.onStart(); ContentUriUtils.setFileProviderUtil(new FileProviderHelper()); + + BeamController.registerForBeam(this, new BeamProvider() { + @Override + public String getTabUrlForBeam() { + ChromeShellTab tab = getActiveTab(); + if (tab == null) return null; + return tab.getUrl(); + } + }); } @Override diff --git a/chrome/android/sync_shell/java/AndroidManifest.xml b/chrome/android/sync_shell/java/AndroidManifest.xml index 358b88b..e756c75 100644 --- a/chrome/android/sync_shell/java/AndroidManifest.xml +++ b/chrome/android/sync_shell/java/AndroidManifest.xml @@ -19,6 +19,7 @@ <uses-permission android:name="android.permission.GET_ACCOUNTS"/> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/> + <uses-permission android:name="android.permission.NFC"/> <uses-permission android:name="android.permission.RECORD_AUDIO"/> <uses-permission android:name="android.permission.VIBRATE"/> <uses-permission android:name="android.permission.WAKE_LOCK"/> diff --git a/chrome/browser/android/uma_bridge.cc b/chrome/browser/android/uma_bridge.cc index ab345bb..4b2406e 100644 --- a/chrome/browser/android/uma_bridge.cc +++ b/chrome/browser/android/uma_bridge.cc @@ -36,6 +36,15 @@ static void RecordUsingMenu(JNIEnv*, } } +// Android Beam +static void RecordBeamCallbackSuccess(JNIEnv*, jclass) { + RecordAction(UserMetricsAction("MobileBeamCallbackSuccess")); +} + +static void RecordBeamInvalidAppState(JNIEnv*, jclass) { + RecordAction(UserMetricsAction("MobileBeamInvalidAppState")); +} + namespace chrome { namespace android { |