diff options
-rw-r--r-- | remoting/android/java/AndroidManifest.xml.jinja2 | 3 | ||||
-rw-r--r-- | remoting/android/java/src/org/chromium/chromoting/AccountsAdapter.java | 43 | ||||
-rw-r--r-- | remoting/android/java/src/org/chromium/chromoting/Chromoting.java | 125 | ||||
-rw-r--r-- | remoting/remoting_android.gypi | 10 | ||||
-rw-r--r-- | remoting/resources/android/layout/account_dropdown.xml | 13 | ||||
-rw-r--r-- | remoting/resources/android/layout/account_selected.xml | 29 | ||||
-rw-r--r-- | remoting/resources/android/menu/chromoting_actionbar.xml | 4 | ||||
-rw-r--r-- | remoting/resources/android/values-land/dimens.xml | 13 | ||||
-rw-r--r-- | remoting/resources/android/values/dimens.xml | 13 | ||||
-rw-r--r-- | remoting/resources/android/values/strings.xml | 1 | ||||
-rw-r--r-- | remoting/resources/android/values/styles.xml | 15 |
11 files changed, 216 insertions, 53 deletions
diff --git a/remoting/android/java/AndroidManifest.xml.jinja2 b/remoting/android/java/AndroidManifest.xml.jinja2 index ba07b53..7afae2f 100644 --- a/remoting/android/java/AndroidManifest.xml.jinja2 +++ b/remoting/android/java/AndroidManifest.xml.jinja2 @@ -10,7 +10,8 @@ <application android:label="@string/product_name" android:icon="@drawable/chromoting128"> <activity android:name="org.chromium.chromoting.Chromoting" - android:configChanges="orientation|screenSize"> + android:configChanges="orientation|screenSize" + android:theme="@style/MainTheme"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> diff --git a/remoting/android/java/src/org/chromium/chromoting/AccountsAdapter.java b/remoting/android/java/src/org/chromium/chromoting/AccountsAdapter.java new file mode 100644 index 0000000..8812fc7 --- /dev/null +++ b/remoting/android/java/src/org/chromium/chromoting/AccountsAdapter.java @@ -0,0 +1,43 @@ +// 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.chromoting; + +import android.accounts.Account; +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; + +/** SpinnerAdapter class used for the ActionBar accounts spinner. */ +public class AccountsAdapter extends ArrayAdapter<Account> { + private LayoutInflater mInflater; + + public AccountsAdapter(Context context, Account[] accounts) { + // ArrayAdapter only uses the |resource| parameter to return a View from getView() and + // getDropDownView(). But these methods are overridden here to return custom Views, so it's + // OK to provide 0 as the resource for the base class. + super(context, 0, accounts); + mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View view = mInflater.inflate(R.layout.account_selected, parent, false); + Account account = getItem(position); + TextView target = (TextView)view.findViewById(R.id.account_name); + target.setText(account.name); + return view; + } + + @Override + public View getDropDownView(int position, View convertView, ViewGroup parent) { + TextView view = (TextView)mInflater.inflate(R.layout.account_dropdown, parent, false); + Account account = getItem(position); + view.setText(account.name); + return view; + } +} diff --git a/remoting/android/java/src/org/chromium/chromoting/Chromoting.java b/remoting/android/java/src/org/chromium/chromoting/Chromoting.java index 04741a4..9335f8a 100644 --- a/remoting/android/java/src/org/chromium/chromoting/Chromoting.java +++ b/remoting/android/java/src/org/chromium/chromoting/Chromoting.java @@ -10,11 +10,13 @@ import android.accounts.AccountManagerCallback; import android.accounts.AccountManagerFuture; import android.accounts.AuthenticatorException; import android.accounts.OperationCanceledException; +import android.app.ActionBar; import android.app.Activity; import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; +import android.content.res.Configuration; import android.os.Bundle; import android.util.Log; import android.view.Menu; @@ -34,7 +36,8 @@ import java.util.Arrays; * also requests and renews authentication tokens using the system account manager. */ public class Chromoting extends Activity implements JniInterface.ConnectionListener, - AccountManagerCallback<Bundle>, HostListLoader.Callback { + AccountManagerCallback<Bundle>, ActionBar.OnNavigationListener, + HostListLoader.Callback { /** Only accounts of this type will be selectable for authentication. */ private static final String ACCOUNT_TYPE = "com.google"; @@ -45,6 +48,9 @@ public class Chromoting extends Activity implements JniInterface.ConnectionListe /** User's account details. */ private Account mAccount; + /** List of accounts on the system. */ + private Account[] mAccounts; + /** Account auth token. */ private String mToken; @@ -57,9 +63,6 @@ public class Chromoting extends Activity implements JniInterface.ConnectionListe /** Refresh button. */ private MenuItem mRefreshButton; - /** Account switcher. */ - private MenuItem mAccountSwitcher; - /** Greeting at the top of the displayed list. */ private TextView mGreeting; @@ -74,7 +77,7 @@ public class Chromoting extends Activity implements JniInterface.ConnectionListe * this flag is set and a fresh authentication token is fetched from the AccountsService, and * used to request the host list a second time. */ - boolean mAlreadyTried; + boolean mTriedNewAuthToken; /** * Called when the activity is first created. Loads the native library and requests an @@ -85,7 +88,7 @@ public class Chromoting extends Activity implements JniInterface.ConnectionListe super.onCreate(savedInstanceState); setContentView(R.layout.main); - mAlreadyTried = false; + mTriedNewAuthToken = false; mHostListLoader = new HostListLoader(); // Get ahold of our view widgets. @@ -95,21 +98,40 @@ public class Chromoting extends Activity implements JniInterface.ConnectionListe // Bring native components online. JniInterface.loadLibrary(this); + mAccounts = AccountManager.get(this).getAccountsByType(ACCOUNT_TYPE); + if (mAccounts.length == 0) { + // TODO(lambroslambrou): Show a dialog with message: "To use <App Name>, you'll need + // to add a Google Account to your device." and two buttons: "Close", "Add account". + // The "Add account" button should navigate to the system "Add a Google Account" + // screen. + return; + } + SharedPreferences prefs = getPreferences(MODE_PRIVATE); + int index = -1; if (prefs.contains("account_name") && prefs.contains("account_type")) { - // Perform authentication using saved account selection. mAccount = new Account(prefs.getString("account_name", null), prefs.getString("account_type", null)); - AccountManager.get(this).getAuthToken(mAccount, TOKEN_SCOPE, null, this, this, null); - if (mAccountSwitcher != null) { - mAccountSwitcher.setTitle(mAccount.name); - } + index = Arrays.asList(mAccounts).indexOf(mAccount); + } + if (index == -1) { + // Preference not loaded, or does not correspond to a valid account, so just pick the + // first account arbitrarily. + index = 0; + mAccount = mAccounts[0]; + } + + if (mAccounts.length == 1) { + getActionBar().setDisplayShowTitleEnabled(true); + getActionBar().setSubtitle(mAccount.name); } else { - // Request auth callback once user has chosen an account. - Log.i("auth", "Requesting auth token from system"); - AccountManager.get(this).getAuthTokenByFeatures(ACCOUNT_TYPE, TOKEN_SCOPE, null, this, - null, null, this, null); + AccountsAdapter adapter = new AccountsAdapter(this, mAccounts); + getActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); + getActionBar().setListNavigationCallbacks(adapter, this); + getActionBar().setSelectedNavigationItem(index); } + + refreshHostList(); } /** Called when the activity is finally finished. */ @@ -119,26 +141,28 @@ public class Chromoting extends Activity implements JniInterface.ConnectionListe JniInterface.disconnectFromHost(); } + /** Called when the display is rotated (as registered in the manifest). */ + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + // Reload the spinner resources, since the font sizes are dependent on the screen + // orientation. + if (mAccounts.length != 1) { + AccountsAdapter adapter = new AccountsAdapter(this, mAccounts); + getActionBar().setListNavigationCallbacks(adapter, this); + } + } + /** Called to initialize the action bar. */ @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); - - Account[] usableAccounts = AccountManager.get(this).getAccountsByType(ACCOUNT_TYPE); - if (usableAccounts.length == 1 && usableAccounts[0].equals(mAccount)) { - // 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. + // If there is no account, 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); } return super.onCreateOptionsMenu(menu); @@ -147,16 +171,7 @@ public class Chromoting extends Activity implements JniInterface.ConnectionListe /** Called whenever an action bar button is pressed. */ @Override public boolean onOptionsItemSelected(MenuItem item) { - mAlreadyTried = false; - 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, this, null); - } else { - // The refresh button simply makes use of the currently-chosen account. - AccountManager.get(this).getAuthToken(mAccount, TOKEN_SCOPE, null, this, this, null); - } - + refreshHostList(); return true; } @@ -174,6 +189,13 @@ public class Chromoting extends Activity implements JniInterface.ConnectionListe this); } + private void refreshHostList() { + mTriedNewAuthToken = false; + + // The refresh button simply makes use of the currently-chosen account. + AccountManager.get(this).getAuthToken(mAccount, TOKEN_SCOPE, null, this, this, null); + } + @Override public void run(AccountManagerFuture<Bundle> future) { Log.i("auth", "User finished with auth dialogs"); @@ -182,6 +204,12 @@ public class Chromoting extends Activity implements JniInterface.ConnectionListe try { // Here comes our auth token from the Android system. result = future.getResult(); + String authToken = result.getString(AccountManager.KEY_AUTHTOKEN); + Log.i("auth", "Received an auth token from system"); + + mToken = authToken; + + mHostListLoader.retrieveHostList(authToken, this); } catch (OperationCanceledException ex) { explanation = getString(R.string.error_auth_canceled); } catch (AuthenticatorException ex) { @@ -195,20 +223,26 @@ public class Chromoting extends Activity implements JniInterface.ConnectionListe return; } - String accountName = result.getString(AccountManager.KEY_ACCOUNT_NAME); - String accountType = result.getString(AccountManager.KEY_ACCOUNT_TYPE); String authToken = result.getString(AccountManager.KEY_AUTHTOKEN); Log.i("auth", "Received an auth token from system"); - mAccount = new Account(accountName, accountType); mToken = authToken; - getPreferences(MODE_PRIVATE).edit().putString("account_name", accountName). - putString("account_type", accountType).apply(); mHostListLoader.retrieveHostList(authToken, this); } @Override + public boolean onNavigationItemSelected(int itemPosition, long itemId) { + mAccount = mAccounts[itemPosition]; + + getPreferences(MODE_PRIVATE).edit().putString("account_name", mAccount.name). + putString("account_type", mAccount.type).apply(); + + refreshHostList(); + return true; + } + + @Override public void onHostListReceived(HostInfo[] hosts) { // Store a copy of the array, so that it can't be mutated by the HostListLoader. HostInfo // is an immutable type, so a shallow copy of the array is sufficient here. @@ -244,11 +278,11 @@ public class Chromoting extends Activity implements JniInterface.ConnectionListe // This is the AUTH_FAILED case. - if (!mAlreadyTried) { + if (!mTriedNewAuthToken) { // This was our first connection attempt. AccountManager authenticator = AccountManager.get(this); - mAlreadyTried = true; + mTriedNewAuthToken = true; Log.w("auth", "Requesting renewal of rejected auth token"); authenticator.invalidateAuthToken(mAccount.type, mToken); @@ -270,9 +304,6 @@ public class Chromoting extends Activity implements JniInterface.ConnectionListe */ private void updateUi() { mRefreshButton.setEnabled(mAccount != null); - if (mAccount != null) { - mAccountSwitcher.setTitle(mAccount.name); - } if (mHosts == null) { mGreeting.setText(getString(R.string.inst_empty_list)); diff --git a/remoting/remoting_android.gypi b/remoting/remoting_android.gypi index d8e136a..371eb2a 100644 --- a/remoting/remoting_android.gypi +++ b/remoting/remoting_android.gypi @@ -54,6 +54,8 @@ { 'destination': '<(SHARED_INTERMEDIATE_DIR)/remoting/android/res/layout', 'files': [ + 'resources/android/layout/account_dropdown.xml', + 'resources/android/layout/account_selected.xml', 'resources/android/layout/desktop.xml', 'resources/android/layout/host.xml', 'resources/android/layout/main.xml', @@ -70,7 +72,15 @@ { 'destination': '<(SHARED_INTERMEDIATE_DIR)/remoting/android/res/values', 'files': [ + 'resources/android/values/dimens.xml', 'resources/android/values/strings.xml', + 'resources/android/values/styles.xml', + ], + }, + { + 'destination': '<(SHARED_INTERMEDIATE_DIR)/remoting/android/res/values-land', + 'files': [ + 'resources/android/values-land/dimens.xml', ], }, ], diff --git a/remoting/resources/android/layout/account_dropdown.xml b/remoting/resources/android/layout/account_dropdown.xml new file mode 100644 index 0000000..a9f0c96 --- /dev/null +++ b/remoting/resources/android/layout/account_dropdown.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- 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. +--> + +<TextView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_height="wrap_content" + android:layout_width="match_parent" + android:drawablePadding="15sp" + android:padding="15sp" + android:gravity="center_vertical"/> diff --git a/remoting/resources/android/layout/account_selected.xml b/remoting/resources/android/layout/account_selected.xml new file mode 100644 index 0000000..cbf523a --- /dev/null +++ b/remoting/resources/android/layout/account_selected.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="center_vertical|start" + android:orientation="vertical"> + <TextView android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/action_bar_title_top_margin" + android:singleLine="true" + android:ellipsize="end" + android:text="@string/product_name" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textSize="@dimen/action_bar_title_text_size"/> + <TextView android:id="@+id/account_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/action_bar_subtitle_top_margin" + android:singleLine="true" + android:ellipsize="end" + android:textAppearance="?android:attr/textAppearanceSmall" + android:textSize="@dimen/action_bar_subtitle_text_size"/> +</LinearLayout> diff --git a/remoting/resources/android/menu/chromoting_actionbar.xml b/remoting/resources/android/menu/chromoting_actionbar.xml index d22685b..6f057d0 100644 --- a/remoting/resources/android/menu/chromoting_actionbar.xml +++ b/remoting/resources/android/menu/chromoting_actionbar.xml @@ -11,8 +11,4 @@ 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" - android:showAsAction="ifRoom|withText"/> </menu> diff --git a/remoting/resources/android/values-land/dimens.xml b/remoting/resources/android/values-land/dimens.xml new file mode 100644 index 0000000..ffdc015 --- /dev/null +++ b/remoting/resources/android/values-land/dimens.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- 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. +--> + +<resources> + <dimen name="action_bar_title_text_size">16dp</dimen> + <dimen name="action_bar_subtitle_text_size">12dp</dimen> + <dimen name="action_bar_title_top_margin">-4dp</dimen> + <dimen name="action_bar_subtitle_top_margin">-2dp</dimen> +</resources> diff --git a/remoting/resources/android/values/dimens.xml b/remoting/resources/android/values/dimens.xml new file mode 100644 index 0000000..3e7b686 --- /dev/null +++ b/remoting/resources/android/values/dimens.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- 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. +--> + +<resources> + <dimen name="action_bar_title_text_size">18dp</dimen> + <dimen name="action_bar_subtitle_text_size">14dp</dimen> + <dimen name="action_bar_title_top_margin">0dp</dimen> + <dimen name="action_bar_subtitle_top_margin">-3dp</dimen> +</resources> diff --git a/remoting/resources/android/values/strings.xml b/remoting/resources/android/values/strings.xml index c51e3cf..6997aae 100644 --- a/remoting/resources/android/values/strings.xml +++ b/remoting/resources/android/values/strings.xml @@ -22,7 +22,6 @@ <!--Action bar buttons--> <string name="actionbar_directoryrefresh">Refresh</string> - <string name="actionbar_accountswitcher">Accounts</string> <string name="actionbar_disconnect">Disconnect</string> <string name="actionbar_hide">Hide</string> <string name="actionbar_keyboard">Keyboard</string> diff --git a/remoting/resources/android/values/styles.xml b/remoting/resources/android/values/styles.xml new file mode 100644 index 0000000..7157205 --- /dev/null +++ b/remoting/resources/android/values/styles.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- 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. +--> + +<resources> + <style name="MainTheme" parent="@android:style/Theme.DeviceDefault"> + <item name="android:actionBarStyle">@style/MainActionBar</item> + </style> + <style name="MainActionBar" parent="@android:style/Widget.DeviceDefault.ActionBar"> + <item name="android:displayOptions">showHome</item> + </style> +</resources> |