Commit 20f21c7b authored by Yuwei Huang's avatar Yuwei Huang Committed by Commit Bot

[Remoting Android] Support for resizing host to fit client resolution

This CL adds support for Android client to resize host to fit client
resolution.

Bug: 874626
Change-Id: I9c3c8139eab20834e79deb2ce662a8ab1234b09c
Reviewed-on: https://chromium-review.googlesource.com/1182187Reviewed-by: default avatarJoe Downing <joedow@chromium.org>
Commit-Queue: Yuwei Huang <yuweih@chromium.org>
Cr-Commit-Position: refs/heads/master@{#585358}
parent f9e001f4
...@@ -27,6 +27,10 @@ ...@@ -27,6 +27,10 @@
<item android:id="@+id/actionbar_send_ctrl_alt_del" <item android:id="@+id/actionbar_send_ctrl_alt_del"
android:title="@string/send_ctrl_alt_del" android:title="@string/send_ctrl_alt_del"
app:showAsAction="withText"/> app:showAsAction="withText"/>
<item android:id="@+id/resize_to_client"
android:checkable="true"
android:title="@string/resize_to_client"
app:showAsAction="never|withText"/>
<item android:id="@+id/actionbar_disconnect" <item android:id="@+id/actionbar_disconnect"
android:title="@string/disconnect_myself_button" android:title="@string/disconnect_myself_button"
app:showAsAction="withText"/> app:showAsAction="withText"/>
......
...@@ -12,6 +12,7 @@ import android.graphics.PorterDuff; ...@@ -12,6 +12,7 @@ import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.util.DisplayMetrics;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
...@@ -93,4 +94,16 @@ public abstract class ChromotingUtil { ...@@ -93,4 +94,16 @@ public abstract class ChromotingUtil {
public static boolean openUrl(Activity parentActivity, Uri uri) { public static boolean openUrl(Activity parentActivity, Uri uri) {
return startActivitySafely(parentActivity, new Intent(Intent.ACTION_VIEW, uri)); return startActivitySafely(parentActivity, new Intent(Intent.ACTION_VIEW, uri));
} }
/**
* Converts a measurement in px to dp (density independent pixel).
*
* @param metrics The metrics used for conversion.
* @param value The value in px to be converted.
* @return The converted result in dp.
*/
public static int pxToDp(DisplayMetrics metrics, int value) {
// +0.5f to round up the result.
return (int) (value / metrics.density + 0.5f);
}
} }
...@@ -15,6 +15,7 @@ import android.os.Handler; ...@@ -15,6 +15,7 @@ import android.os.Handler;
import android.support.v7.app.ActionBar.OnMenuVisibilityListener; import android.support.v7.app.ActionBar.OnMenuVisibilityListener;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.util.DisplayMetrics;
import android.view.DisplayCutout; import android.view.DisplayCutout;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.Menu; import android.view.Menu;
...@@ -55,6 +56,7 @@ public class Desktop ...@@ -55,6 +56,7 @@ public class Desktop
/** Preference used to track the last input mode selected by the user. */ /** Preference used to track the last input mode selected by the user. */
private static final String PREFERENCE_INPUT_MODE = "input_mode"; private static final String PREFERENCE_INPUT_MODE = "input_mode";
private static final String PREFERENCE_RESIZE_TO_CLIENT = "resize_to_client";
/** The amount of time to wait to hide the ActionBar after user input is seen. */ /** The amount of time to wait to hide the ActionBar after user input is seen. */
private static final int ACTIONBAR_AUTO_HIDE_DELAY_MS = 3000; private static final int ACTIONBAR_AUTO_HIDE_DELAY_MS = 3000;
...@@ -76,6 +78,9 @@ public class Desktop ...@@ -76,6 +78,9 @@ public class Desktop
/** Indicates whether a Soft Input UI (such as a keyboard) is visible. */ /** Indicates whether a Soft Input UI (such as a keyboard) is visible. */
private boolean mSoftInputVisible = false; private boolean mSoftInputVisible = false;
/** Indicates whether resize-to-client is enabled. */
private boolean mResizeToClientEnabled = false;
/** Holds the scheduled task object which will be called to hide the ActionBar. */ /** Holds the scheduled task object which will be called to hide the ActionBar. */
private Runnable mActionBarAutoHideTask; private Runnable mActionBarAutoHideTask;
...@@ -154,6 +159,7 @@ public class Desktop ...@@ -154,6 +159,7 @@ public class Desktop
mActivityLifecycleListener.onActivityCreated(this, savedInstanceState); mActivityLifecycleListener.onActivityCreated(this, savedInstanceState);
mInputMode = getInitialInputModeValue(); mInputMode = getInitialInputModeValue();
mResizeToClientEnabled = getStoredResizeToClientEnabled();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
attachSystemUiResizeListener(); attachSystemUiResizeListener();
...@@ -283,6 +289,8 @@ public class Desktop ...@@ -283,6 +289,8 @@ public class Desktop
// Wait to set the input mode until after the default tinting has been applied. // Wait to set the input mode until after the default tinting has been applied.
setInputMode(mInputMode); setInputMode(mInputMode);
setResizeToClientEnabled(mResizeToClientEnabled);
// Keyboard state must be set after the keyboard icon has been added to the menu. // Keyboard state must be set after the keyboard icon has been added to the menu.
setKeyboardState(getResources().getConfiguration()); setKeyboardState(getResources().getConfiguration());
...@@ -352,6 +360,26 @@ public class Desktop ...@@ -352,6 +360,26 @@ public class Desktop
new InputModeChangedEventParameter(mInputMode, mHostTouchCapability)); new InputModeChangedEventParameter(mInputMode, mHostTouchCapability));
} }
private boolean getStoredResizeToClientEnabled() {
return getPreferences(MODE_PRIVATE).getBoolean(PREFERENCE_RESIZE_TO_CLIENT, false);
}
private void setResizeToClientEnabled(boolean resizeToClientEnabled) {
mResizeToClientEnabled = resizeToClientEnabled;
Menu menu = mToolbar.getMenu();
MenuItem resizeToClientMenuItem = menu.findItem(R.id.resize_to_client);
resizeToClientMenuItem.setChecked(mResizeToClientEnabled);
if (mResizeToClientEnabled != getStoredResizeToClientEnabled()) {
getPreferences(MODE_PRIVATE)
.edit()
.putBoolean(PREFERENCE_RESIZE_TO_CLIENT, mResizeToClientEnabled)
.apply();
}
if (mResizeToClientEnabled) {
sendPreferredHostResolution();
}
}
@Override @Override
public void onCapabilitiesChanged(List<String> newCapabilities) { public void onCapabilitiesChanged(List<String> newCapabilities) {
if (newCapabilities.contains(Capabilities.TOUCH_CAPABILITY)) { if (newCapabilities.contains(Capabilities.TOUCH_CAPABILITY)) {
...@@ -600,6 +628,9 @@ public class Desktop ...@@ -600,6 +628,9 @@ public class Desktop
mInjector.sendCtrlAltDel(); mInjector.sendCtrlAltDel();
return true; return true;
} }
if (id == R.id.resize_to_client) {
setResizeToClientEnabled(!item.isChecked());
}
if (id == R.id.actionbar_help) { if (id == R.id.actionbar_help) {
HelpSingleton.getInstance().launchHelp(this, HelpContext.DESKTOP); HelpSingleton.getInstance().launchHelp(this, HelpContext.DESKTOP);
return true; return true;
...@@ -669,6 +700,25 @@ public class Desktop ...@@ -669,6 +700,25 @@ public class Desktop
}); });
} }
/**
* Sends preferred resolution to the host that matches the aspect ratio of the screen. This is
* calculated by the size of the DesktopView minus the safe insets.
* This method does nothing if resize-to-client has not been enabled by the user.
*/
public void sendPreferredHostResolution() {
if (!mResizeToClientEnabled) {
return;
}
Rect safeInsets = getSafeInsets();
int safeAreaWidth = mRemoteHostDesktop.getWidth() - safeInsets.left - safeInsets.right;
int safeAreaHeight = mRemoteHostDesktop.getHeight() - safeInsets.top - safeInsets.bottom;
DisplayMetrics metrics = getResources().getDisplayMetrics();
safeAreaWidth = ChromotingUtil.pxToDp(metrics, safeAreaWidth);
safeAreaHeight = ChromotingUtil.pxToDp(metrics, safeAreaHeight);
mClient.sendClientResolution(safeAreaWidth, safeAreaHeight, metrics.density);
}
/** /**
* Called once when a keyboard key is pressed, then again when that same key is released. This * Called once when a keyboard key is pressed, then again when that same key is released. This
* is not guaranteed to be notified of all soft keyboard events: certain keyboards might not * is not guaranteed to be notified of all soft keyboard events: certain keyboards might not
......
...@@ -173,6 +173,9 @@ public class DesktopCanvas { ...@@ -173,6 +173,9 @@ public class DesktopCanvas {
return; return;
} }
// Reset to identity so that screen dimensions and image dimensions match up.
mRenderData.transform.reset();
float widthRatio = getSafeScreenWidth() / mRenderData.imageWidth; float widthRatio = getSafeScreenWidth() / mRenderData.imageWidth;
float heightRatio = getSafeScreenHeight() / mRenderData.imageHeight; float heightRatio = getSafeScreenHeight() / mRenderData.imageHeight;
float screenToImageScale = Math.max(widthRatio, heightRatio); float screenToImageScale = Math.max(widthRatio, heightRatio);
......
...@@ -389,6 +389,8 @@ public class TouchInputHandler { ...@@ -389,6 +389,8 @@ public class TouchInputHandler {
mPanGestureBounds = new Rect( mPanGestureBounds = new Rect(
mEdgeSlopInPx, mEdgeSlopInPx, width - mEdgeSlopInPx, height - mEdgeSlopInPx); mEdgeSlopInPx, mEdgeSlopInPx, width - mEdgeSlopInPx, height - mEdgeSlopInPx);
resizeImageToFitScreen(); resizeImageToFitScreen();
mDesktop.sendPreferredHostResolution();
} }
private void handleHostSizeChanged(int width, int height) { private void handleHostSizeChanged(int width, int height) {
......
...@@ -292,6 +292,22 @@ public class Client implements InputStub { ...@@ -292,6 +292,22 @@ public class Client implements InputStub {
nativeSendExtensionMessage(mNativeJniClient, type, data); nativeSendExtensionMessage(mNativeJniClient, type, data);
} }
/**
* Sends client resolution to the host so that the host can resize itself to fit the client
* without showing letterboxes.
*
* @param dipsWidth The width of the screen in density independent pixels.
* @param dipsHeight The height of the screen in density independent pixels.
* @param density The pixel density of the screen.
*/
public void sendClientResolution(int dipsWidth, int dipsHeight, float density) {
if (!mConnected) {
return;
}
nativeSendClientResolution(mNativeJniClient, dipsWidth, dipsHeight, density);
}
private native long nativeInit(); private native long nativeInit();
private native void nativeDestroy(long nativeJniClient); private native void nativeDestroy(long nativeJniClient);
...@@ -336,4 +352,8 @@ public class Client implements InputStub { ...@@ -336,4 +352,8 @@ public class Client implements InputStub {
/** Passes extension message to the native code. */ /** Passes extension message to the native code. */
private native void nativeSendExtensionMessage(long nativeJniClient, String type, String data); private native void nativeSendExtensionMessage(long nativeJniClient, String type, String data);
/** Sends client resolution to the host. */
private native void nativeSendClientResolution(
long nativeJniClient, int dipsWidth, int dipsHeight, float scale);
} }
...@@ -100,7 +100,7 @@ class ChromotingSession::Core : public ClientUserInterface, ...@@ -100,7 +100,7 @@ class ChromotingSession::Core : public ClientUserInterface,
void SendKeyEvent(int usb_key_code, bool key_down); void SendKeyEvent(int usb_key_code, bool key_down);
void SendTextEvent(const std::string& text); void SendTextEvent(const std::string& text);
void SendTouchEvent(const protocol::TouchEvent& touch_event); void SendTouchEvent(const protocol::TouchEvent& touch_event);
void SendClientResolution(int dips_width, int dips_height, int scale); void SendClientResolution(int dips_width, int dips_height, float scale);
void EnableVideoChannel(bool enable); void EnableVideoChannel(bool enable);
void SendClientMessage(const std::string& type, const std::string& data); void SendClientMessage(const std::string& type, const std::string& data);
...@@ -275,7 +275,7 @@ void ChromotingSession::Core::SendTouchEvent( ...@@ -275,7 +275,7 @@ void ChromotingSession::Core::SendTouchEvent(
void ChromotingSession::Core::SendClientResolution(int dips_width, void ChromotingSession::Core::SendClientResolution(int dips_width,
int dips_height, int dips_height,
int scale) { float scale) {
DCHECK(network_task_runner()->BelongsToCurrentThread()); DCHECK(network_task_runner()->BelongsToCurrentThread());
protocol::ClientResolution client_resolution; protocol::ClientResolution client_resolution;
client_resolution.set_dips_width(dips_width); client_resolution.set_dips_width(dips_width);
...@@ -708,7 +708,7 @@ void ChromotingSession::SendTouchEvent( ...@@ -708,7 +708,7 @@ void ChromotingSession::SendTouchEvent(
void ChromotingSession::SendClientResolution(int dips_width, void ChromotingSession::SendClientResolution(int dips_width,
int dips_height, int dips_height,
int scale) { float scale) {
RunCoreTaskOnNetworkThread(FROM_HERE, &Core::SendClientResolution, dips_width, RunCoreTaskOnNetworkThread(FROM_HERE, &Core::SendClientResolution, dips_width,
dips_height, scale); dips_height, scale);
} }
......
...@@ -118,7 +118,7 @@ class ChromotingSession : public ClientInputInjector { ...@@ -118,7 +118,7 @@ class ChromotingSession : public ClientInputInjector {
// Sends the provided touch event payload to the host. // Sends the provided touch event payload to the host.
void SendTouchEvent(const protocol::TouchEvent& touch_event); void SendTouchEvent(const protocol::TouchEvent& touch_event);
void SendClientResolution(int dips_width, int dips_height, int scale); void SendClientResolution(int dips_width, int dips_height, float scale);
// Enables or disables the video channel. // Enables or disables the video channel.
void EnableVideoChannel(bool enable); void EnableVideoChannel(bool enable);
......
...@@ -287,6 +287,15 @@ void JniClient::SendExtensionMessage( ...@@ -287,6 +287,15 @@ void JniClient::SendExtensionMessage(
ConvertJavaStringToUTF8(env, data)); ConvertJavaStringToUTF8(env, data));
} }
void JniClient::SendClientResolution(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& caller,
jint dips_width,
jint dips_height,
jfloat scale) {
session_->SendClientResolution(dips_width, dips_height, scale);
}
void JniClient::Destroy(JNIEnv* env, const JavaParamRef<jobject>& caller) { void JniClient::Destroy(JNIEnv* env, const JavaParamRef<jobject>& caller) {
delete this; delete this;
} }
......
...@@ -145,6 +145,12 @@ class JniClient : public ChromotingSession::Delegate { ...@@ -145,6 +145,12 @@ class JniClient : public ChromotingSession::Delegate {
const base::android::JavaParamRef<jstring>& type, const base::android::JavaParamRef<jstring>& type,
const base::android::JavaParamRef<jstring>& data); const base::android::JavaParamRef<jstring>& data);
void SendClientResolution(JNIEnv* env,
const base::android::JavaParamRef<jobject>& caller,
jint dips_width,
jint dips_height,
jfloat scale);
// Deletes this object. // Deletes this object.
void Destroy(JNIEnv* env, const base::android::JavaParamRef<jobject>& caller); void Destroy(JNIEnv* env, const base::android::JavaParamRef<jobject>& caller);
......
...@@ -1166,7 +1166,7 @@ General fixes and stability improvements. ...@@ -1166,7 +1166,7 @@ General fixes and stability improvements.
<message desc="Label for button to reconnect to the previous Me2Me host. This button appears on the 'session-finished' page." name="IDS_RECONNECT"> <message desc="Label for button to reconnect to the previous Me2Me host. This button appears on the 'session-finished' page." name="IDS_RECONNECT">
Reconnect Reconnect
</message> </message>
<message desc="Button for enabling or disabling the 'resize-to-client' functionality, whereby the host desktop is resized to match the client size as closely as possible." name="IDS_RESIZE_TO_CLIENT"> <message desc="Button for enabling or disabling the 'resize-to-client' functionality, whereby the host desktop is resized to match the client size as closely as possible." name="IDS_RESIZE_TO_CLIENT" formatter_data="android_java">
Resize desktop to fit Resize desktop to fit
</message> </message>
<message desc="Label for button to retry connecting to a Me2Me host, after failing to connect to that host. This button appears on the 'session-finished' page." name="IDS_RETRY"> <message desc="Label for button to retry connecting to a Me2Me host, after failing to connect to that host. This button appears on the 'session-finished' page." name="IDS_RETRY">
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment