summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--remoting/android/java/AndroidManifest.xml5
-rw-r--r--remoting/android/java/src/org/chromium/chromoting/Chromoting.java87
-rw-r--r--remoting/android/java/src/org/chromium/chromoting/Desktop.java6
-rw-r--r--remoting/android/java/src/org/chromium/chromoting/DesktopView.java25
-rw-r--r--remoting/client/jni/android_keymap.cc7
-rw-r--r--remoting/client/jni/chromoting_jni_instance.cc3
-rw-r--r--remoting/client/jni/chromoting_jni_runtime.cc21
-rw-r--r--remoting/client/jni/chromoting_jni_runtime.h4
-rw-r--r--remoting/resources/menu/chromoting_actionbar.xml4
-rw-r--r--remoting/resources/strings.xml2
10 files changed, 124 insertions, 40 deletions
diff --git a/remoting/android/java/AndroidManifest.xml b/remoting/android/java/AndroidManifest.xml
index ff27b57..c43fa1d 100644
--- a/remoting/android/java/AndroidManifest.xml
+++ b/remoting/android/java/AndroidManifest.xml
@@ -1,12 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.chromium.chromoting"
- android:versionCode="9"
- android:versionName="0.09">
+ android:versionCode="10"
+ android:versionName="0.10">
<uses-sdk android:minSdkVersion="14"
android:targetSdkVersion="14"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
<uses-permission android:name="android.permission.INTERNET"/>
+ <uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/>
<uses-permission android:name="android.permission.USE_CREDENTIALS"/>
<application android:label="@string/app_name"
android:icon="@drawable/chromoting128">
diff --git a/remoting/android/java/src/org/chromium/chromoting/Chromoting.java b/remoting/android/java/src/org/chromium/chromoting/Chromoting.java
index cfcbf49..e322550 100644
--- a/remoting/android/java/src/org/chromium/chromoting/Chromoting.java
+++ b/remoting/android/java/src/org/chromium/chromoting/Chromoting.java
@@ -69,6 +69,9 @@ public class Chromoting extends Activity {
/** List of hosts. */
private JSONArray mHosts;
+ /** Refresh button. */
+ private MenuItem mRefreshButton;
+
/** Account switcher. */
private MenuItem mAccountSwitcher;
@@ -139,8 +142,21 @@ public class Chromoting extends Activity {
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.chromoting_actionbar, menu);
+ mRefreshButton = menu.findItem(R.id.actionbar_directoryrefresh);
mAccountSwitcher = menu.findItem(R.id.actionbar_accountswitcher);
- if (mAccount != null) {
+
+ Account[] usableAccounts = AccountManager.get(this).getAccountsByType(ACCOUNT_TYPE);
+ if (usableAccounts.length == 1 && mAccount.equals(usableAccounts[0])) {
+ // If we're using the only available account, don't offer account switching.
+ // (If there are *no* accounts available, clicking this allows you to add a new one.)
+ mAccountSwitcher.setEnabled(false);
+ }
+
+ if (mAccount == null) {
+ // If no account has been chosen, don't allow the user to refresh the listing.
+ mRefreshButton.setEnabled(false);
+ } else {
+ // If the user has picked an account, show its name directly on the account switcher.
mAccountSwitcher.setTitle(mAccount.name);
}
@@ -150,17 +166,25 @@ public class Chromoting extends Activity {
/** Called whenever an action bar button is pressed. */
@Override
public boolean onOptionsItemSelected(MenuItem item) {
- // The only button is the account switcher, so defer to the android accounts system now.
- AccountManager.get(this).getAuthTokenByFeatures(
- ACCOUNT_TYPE,
- TOKEN_SCOPE,
- null,
- this,
- null,
- null,
- new HostListDirectoryGrabber(this),
- mNetwork
- );
+ if (item == mAccountSwitcher) {
+ // The account switcher triggers a listing of all available accounts.
+ AccountManager.get(this).getAuthTokenByFeatures(
+ ACCOUNT_TYPE,
+ TOKEN_SCOPE,
+ null,
+ this,
+ null,
+ null,
+ new HostListDirectoryGrabber(this),
+ mNetwork
+ );
+ }
+ else {
+ // The refresh button simply makes use of the currently-chosen account.
+ AccountManager.get(this).getAuthToken(mAccount, TOKEN_SCOPE, null, this,
+ new HostListDirectoryGrabber(this), mNetwork);
+ }
+
return true;
}
@@ -235,23 +259,31 @@ public class Chromoting extends Activity {
} else if (ex instanceof AuthenticatorException) {
explanation = getString(R.string.error_no_accounts);
} else if (ex instanceof IOException) {
- if (!mAlreadyTried) { // This was our first connection attempt.
- Log.w("auth", "Unable to authenticate with (expired?) token");
+ if (!mAlreadyTried) {
+ // This was our first connection attempt.
- // Ask system to renew the auth token in case it expired.
- AccountManager authenticator = AccountManager.get(mUi);
synchronized (mUi) {
- authenticator.invalidateAuthToken(mAccount.type, mToken);
- mToken = null;
- Log.i("auth", "Requesting auth token renewal");
- authenticator.getAuthToken(
- mAccount, TOKEN_SCOPE, null, mUi, this, mNetwork);
+ if (mAccount != null) {
+ // We got an account, but couldn't log into it. We'll retry in case
+ // the system's cached authentication token had already expired.
+ AccountManager authenticator = AccountManager.get(mUi);
+ mAlreadyTried = true;
+
+ Log.w("auth", "Requesting renewal of rejected auth token");
+ authenticator.invalidateAuthToken(mAccount.type, mToken);
+ mToken = null;
+ authenticator.getAuthToken(
+ mAccount, TOKEN_SCOPE, null, mUi, this, mNetwork);
+
+ // We're not in an error state *yet*.
+ return;
+ }
}
- // We're not in an error state *yet*.
- mAlreadyTried = true;
- return;
- } else { // Authentication truly failed.
+ // We didn't even get an account, so the auth server is likely unreachable.
+ explanation = getString(R.string.error_bad_connection);
+ } else {
+ // Authentication truly failed.
Log.e("auth", "Fresh auth token was also rejected");
explanation = getString(R.string.error_auth_failed);
}
@@ -287,7 +319,10 @@ public class Chromoting extends Activity {
@Override
public void run() {
synchronized (mUi) {
- mAccountSwitcher.setTitle(mAccount.name);
+ mRefreshButton.setEnabled(mAccount != null);
+ if (mAccount != null) {
+ mAccountSwitcher.setTitle(mAccount.name);
+ }
}
if (mHosts == null) {
diff --git a/remoting/android/java/src/org/chromium/chromoting/Desktop.java b/remoting/android/java/src/org/chromium/chromoting/Desktop.java
index 3d59961..8a9917d 100644
--- a/remoting/android/java/src/org/chromium/chromoting/Desktop.java
+++ b/remoting/android/java/src/org/chromium/chromoting/Desktop.java
@@ -96,12 +96,6 @@ public class Desktop extends Activity {
default:
// We try to send all other key codes to the host directly.
JniInterface.keyboardAction(event.getKeyCode(), depressed);
-
- if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER ||
- event.getKeyCode() == KeyEvent.KEYCODE_NUMPAD_ENTER) {
- // We stop this key from propagating to prevent the keyboard from closing.
- return true;
- }
}
return super.dispatchKeyEvent(event);
diff --git a/remoting/android/java/src/org/chromium/chromoting/DesktopView.java b/remoting/android/java/src/org/chromium/chromoting/DesktopView.java
index 1e18f91..b1eac42 100644
--- a/remoting/android/java/src/org/chromium/chromoting/DesktopView.java
+++ b/remoting/android/java/src/org/chromium/chromoting/DesktopView.java
@@ -13,12 +13,15 @@ import android.graphics.Matrix;
import android.graphics.Paint;
import android.os.Bundle;
import android.os.Looper;
+import android.text.InputType;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
import org.chromium.chromoting.jni.JniInterface;
@@ -81,6 +84,10 @@ public class DesktopView extends SurfaceView implements Runnable, SurfaceHolder.
public DesktopView(Activity context) {
super(context);
+
+ // Give this view keyboard focus, allowing us to customize the soft keyboard's settings.
+ setFocusableInTouchMode(true);
+
mActionBar = context.getActionBar();
getHolder().addCallback(this);
@@ -265,6 +272,24 @@ public class DesktopView extends SurfaceView implements Runnable, SurfaceHolder.
JniInterface.provideRedrawCallback(null);
}
+ /** Called when a software keyboard is requested, and specifies its options. */
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ // Disables rich input support and instead requests simple key events.
+ outAttrs.inputType = InputType.TYPE_NULL;
+
+ // Prevents most third-party IMEs from ignoring our Activity's adjustResize preference.
+ outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_FULLSCREEN;
+
+ // Ensures that keyboards will not decide to hide the remote desktop on small displays.
+ outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_EXTRACT_UI;
+
+ // Stops software keyboards from closing as soon as the enter key is pressed.
+ outAttrs.imeOptions |= EditorInfo.IME_MASK_ACTION | EditorInfo.IME_FLAG_NO_ENTER_ACTION;
+
+ return null;
+ }
+
/** Called when a mouse action is made. */
private void handleMouseMovement(float x, float y, int button, boolean pressed) {
float[] coordinates = {x, y};
diff --git a/remoting/client/jni/android_keymap.cc b/remoting/client/jni/android_keymap.cc
index 743e15e..45d67c4 100644
--- a/remoting/client/jni/android_keymap.cc
+++ b/remoting/client/jni/android_keymap.cc
@@ -199,12 +199,15 @@ const uint32 usb_keycodes[] = {
0x0700b7, // NUMPAD_RIGHT_PAREN
};
-}
+} // namespace
namespace remoting {
uint32 AndroidKeycodeToUsbKeycode(size_t android) {
- DCHECK_LT(android, sizeof (usb_keycodes) / sizeof (uint32));
+ if (android >= sizeof (usb_keycodes) / sizeof (uint32)) {
+ LOG(WARNING) << "Attempted to decode out-of-range Android keycode";
+ return 0;
+ }
return usb_keycodes[android];
}
diff --git a/remoting/client/jni/chromoting_jni_instance.cc b/remoting/client/jni/chromoting_jni_instance.cc
index 3e11b2fa..b68aab9 100644
--- a/remoting/client/jni/chromoting_jni_instance.cc
+++ b/remoting/client/jni/chromoting_jni_instance.cc
@@ -132,8 +132,7 @@ void ChromotingJniInstance::PerformKeyboardAction(int key_code, bool key_down) {
action.set_usb_keycode(usb_code);
action.set_pressed(key_down);
connection_->input_stub()->InjectKeyEvent(action);
- }
- else {
+ } else {
LOG(WARNING) << "Ignoring unknown keycode: " << key_code;
}
}
diff --git a/remoting/client/jni/chromoting_jni_runtime.cc b/remoting/client/jni/chromoting_jni_runtime.cc
index 8fa9018..8d51a83 100644
--- a/remoting/client/jni/chromoting_jni_runtime.cc
+++ b/remoting/client/jni/chromoting_jni_runtime.cc
@@ -7,6 +7,7 @@
#include "base/android/base_jni_registrar.h"
#include "base/android/jni_android.h"
#include "base/memory/singleton.h"
+#include "base/synchronization/waitable_event.h"
#include "media/base/yuv_convert.h"
#include "net/android/net_jni_registrar.h"
#include "remoting/base/url_request_context.h"
@@ -68,8 +69,19 @@ ChromotingJniRuntime::~ChromotingJniRuntime() {
JNIEnv* env = base::android::AttachCurrentThread();
env->DeleteGlobalRef(class_);
- // TODO(solb): Detach all threads from JVM here.
- // crbug.com/259594
+
+ base::WaitableEvent done_event(false, false);
+ network_task_runner_->PostTask(FROM_HERE, base::Bind(
+ &ChromotingJniRuntime::DetachFromVmAndSignal,
+ base::Unretained(this),
+ &done_event));
+ done_event.Wait();
+ display_task_runner_->PostTask(FROM_HERE, base::Bind(
+ &ChromotingJniRuntime::DetachFromVmAndSignal,
+ base::Unretained(this),
+ &done_event));
+ done_event.Wait();
+ base::android::DetachFromVM();
}
void ChromotingJniRuntime::ConnectToHost(const char* username,
@@ -178,4 +190,9 @@ void ChromotingJniRuntime::RedrawCanvas() {
env->GetStaticMethodID(class_, "redrawGraphicsInternal", "()V"));
}
+void ChromotingJniRuntime::DetachFromVmAndSignal(base::WaitableEvent* waiter) {
+ base::android::DetachFromVM();
+ waiter->Signal();
+}
+
} // namespace remoting
diff --git a/remoting/client/jni/chromoting_jni_runtime.h b/remoting/client/jni/chromoting_jni_runtime.h
index 1a6952b..93f933f 100644
--- a/remoting/client/jni/chromoting_jni_runtime.h
+++ b/remoting/client/jni/chromoting_jni_runtime.h
@@ -6,6 +6,7 @@
#define REMOTING_CLIENT_JNI_CHROMOTING_JNI_RUNTIME_H_
#include <jni.h>
+#include <string>
#include "base/at_exit.h"
#include "net/url_request/url_request_context_getter.h"
@@ -94,6 +95,9 @@ class ChromotingJniRuntime {
// cross-thread calls on the class.
virtual ~ChromotingJniRuntime();
+ // Detaches JVM from the current thread, then signals. Doesn't own |waiter|.
+ void DetachFromVmAndSignal(base::WaitableEvent* waiter);
+
// Reference to the Java class into which we make JNI calls.
jclass class_;
diff --git a/remoting/resources/menu/chromoting_actionbar.xml b/remoting/resources/menu/chromoting_actionbar.xml
index 1507dd4..e602a36 100644
--- a/remoting/resources/menu/chromoting_actionbar.xml
+++ b/remoting/resources/menu/chromoting_actionbar.xml
@@ -1,6 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!--Action bar buttons for the Android app's host listing-->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@+id/actionbar_directoryrefresh"
+ android:title="@string/actionbar_directoryrefresh"
+ android:icon="@android:drawable/ic_popup_sync"
+ android:showAsAction="ifRoom"/>
<item android:id="@+id/actionbar_accountswitcher"
android:title="@string/actionbar_accountswitcher"
android:icon="@android:drawable/ic_menu_more"
diff --git a/remoting/resources/strings.xml b/remoting/resources/strings.xml
index 949a2aa..a2c2095 100644
--- a/remoting/resources/strings.xml
+++ b/remoting/resources/strings.xml
@@ -18,6 +18,7 @@
<string name="pin_entry_cancel">Cancel</string>
<!--Action bar buttons-->
+ <string name="actionbar_directoryrefresh">Refresh</string>
<string name="actionbar_accountswitcher">Accounts</string>
<string name="actionbar_hide">Hide</string>
<string name="actionbar_keyboard">Keyboard</string>
@@ -31,6 +32,7 @@
<string name="error_auth_canceled">Authentication prompt canceled by user</string>
<string name="error_no_accounts">Device not linked to any Google accounts</string>
<string name="error_auth_failed">Authentication with specified account failed</string>
+ <string name="error_bad_connection">No network connection</string>
<string name="error_cataloging_hosts">Unable to display host list</string>
<string name="error_displaying_host">Unable to display host entry</string>
<string name="error_unexpected_response">Account has no remote desktop hosts registered</string>