From 867bd17a79c2ab94ce83b8b67b49801b0aa5da89 Mon Sep 17 00:00:00 2001 From: Sriram Ramasubramanian Date: Mon, 12 Mar 2012 12:48:55 -0700 Subject: [PATCH] Bug 734425: Support remote tabs on about:home [r=rnewman, r=mfinkle] --- mobile/android/base/AboutHomeContent.java | 90 ++++++++++- mobile/android/base/GeckoApp.java | 5 +- mobile/android/base/Makefile.in | 2 + mobile/android/base/RemoteTabs.java | 146 +++++++----------- mobile/android/base/TabsAccessor.java | 144 +++++++++++++++++ mobile/android/base/TabsTray.java | 52 ++----- mobile/android/base/db/TabsProvider.java.in | 3 +- .../base/locales/en-US/android_strings.dtd | 1 + .../resources/layout/abouthome_content.xml | 39 +++++ .../layout/abouthome_remote_tab_row.xml | 13 ++ mobile/android/base/strings.xml.in | 1 + 11 files changed, 367 insertions(+), 129 deletions(-) create mode 100644 mobile/android/base/TabsAccessor.java create mode 100644 mobile/android/base/resources/layout/abouthome_remote_tab_row.xml diff --git a/mobile/android/base/AboutHomeContent.java b/mobile/android/base/AboutHomeContent.java index aac47e135a2d..dda105f1303a 100644 --- a/mobile/android/base/AboutHomeContent.java +++ b/mobile/android/base/AboutHomeContent.java @@ -47,6 +47,7 @@ import java.net.URL; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.EnumSet; +import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -70,6 +71,7 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.os.SystemClock; import android.text.SpannableString; import android.text.style.StyleSpan; import android.text.style.UnderlineSpan; @@ -89,8 +91,10 @@ import android.widget.RelativeLayout; import android.widget.ScrollView; import android.widget.SimpleCursorAdapter; import android.widget.TextView; +import android.text.TextUtils; -public class AboutHomeContent extends ScrollView { +public class AboutHomeContent extends ScrollView + implements TabsAccessor.OnQueryTabsCompleteListener { private static final String LOGTAG = "GeckoAboutHome"; private static final int NUMBER_OF_TOP_SITES_PORTRAIT = 4; @@ -99,10 +103,13 @@ public class AboutHomeContent extends ScrollView { private static final int NUMBER_OF_COLS_PORTRAIT = 2; private static final int NUMBER_OF_COLS_LANDSCAPE = 3; + private static final int NUMBER_OF_REMOTE_TABS = 10; + static enum UpdateFlags { TOP_SITES, PREVIOUS_TABS, - RECOMMENDED_ADDONS; + RECOMMENDED_ADDONS, + REMOTE_TABS; public static final EnumSet ALL = EnumSet.allOf(UpdateFlags.class); } @@ -119,6 +126,9 @@ public class AboutHomeContent extends ScrollView { protected LinearLayout mAddonsLayout; protected LinearLayout mLastTabsLayout; + protected LinearLayout mRemoteTabsLayout; + + private View.OnClickListener mRemoteTabClickListener; public interface UriLoadCallback { public void callback(String uriSpec); @@ -172,6 +182,7 @@ public class AboutHomeContent extends ScrollView { mAddonsLayout = (LinearLayout) findViewById(R.id.recommended_addons); mLastTabsLayout = (LinearLayout) findViewById(R.id.last_tabs); + mRemoteTabsLayout = (LinearLayout) findViewById(R.id.remote_tabs); TextView allTopSitesText = (TextView) findViewById(R.id.all_top_sites_text); allTopSitesText.setOnClickListener(new View.OnClickListener() { @@ -188,6 +199,14 @@ public class AboutHomeContent extends ScrollView { } }); + TextView allRemoteTabsText = (TextView) findViewById(R.id.all_remote_tabs_text); + allRemoteTabsText.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + Context context = v.getContext(); + context.startActivity(new Intent(context, RemoteTabs.class)); + } + }); + TextView syncTextView = (TextView) findViewById(R.id.sync_text); String syncText = syncTextView.getText().toString() + " \u00BB"; String boldName = getContext().getResources().getString(R.string.abouthome_sync_bold_name); @@ -209,6 +228,24 @@ public class AboutHomeContent extends ScrollView { context.startActivity(intent); } }); + + mRemoteTabClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + String url = ((String) v.getTag()); + JSONObject args = new JSONObject(); + try { + args.put("url", url); + args.put("engine", null); + args.put("userEntered", false); + } catch (Exception e) { + Log.e(LOGTAG, "error building JSON arguments"); + } + + Log.d(LOGTAG, "Sending message to Gecko: " + SystemClock.uptimeMillis() + " - Tab:Add"); + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Tab:Add", args.toString())); + } + }; } public void onDestroy() { @@ -243,6 +280,14 @@ public class AboutHomeContent extends ScrollView { findViewById(R.id.no_top_sites_text).setVisibility(visibilityWithoutTopSites); } + private void setRemoteTabsVisibility(boolean visible) { + int visibility = visible ? View.VISIBLE : View.GONE; + findViewById(R.id.remote_tabs_title).setVisibility(visibility); + findViewById(R.id.remote_tabs_client).setVisibility(visibility); + findViewById(R.id.remote_tabs).setVisibility(visibility); + findViewById(R.id.all_remote_tabs_text).setVisibility(visibility); + } + private void setSyncVisibility(boolean visible) { int visibility = visible ? View.VISIBLE : View.GONE; findViewById(R.id.sync_box_container).setVisibility(visibility); @@ -353,6 +398,9 @@ public class AboutHomeContent extends ScrollView { if (flags.contains(UpdateFlags.RECOMMENDED_ADDONS)) readRecommendedAddons(activity); + + if (flags.contains(UpdateFlags.REMOTE_TABS)) + loadRemoteTabs(activity); } }); } @@ -605,6 +653,44 @@ public class AboutHomeContent extends ScrollView { } } + private void loadRemoteTabs(final Activity activity) { + if (!isSyncSetup()) { + setRemoteTabsVisibility(false); + return; + } + + TabsAccessor.getTabs(getContext(), NUMBER_OF_REMOTE_TABS, this); + } + + @Override + public void onQueryTabsComplete(List tabsList) { + ArrayList tabs = new ArrayList (tabsList); + if (tabs == null || tabs.size() == 0) { + setRemoteTabsVisibility(false); + return; + } + + mRemoteTabsLayout.removeAllViews(); + + String client = null; + + for (TabsAccessor.RemoteTab tab : tabs) { + if (client == null) + client = tab.name; + else if (!TextUtils.equals(client, tab.name)) + break; + + final TextView row = (TextView) mInflater.inflate(R.layout.abouthome_remote_tab_row, mRemoteTabsLayout, false); + row.setText(tab.title); + row.setTag(tab.url); + mRemoteTabsLayout.addView(row); + row.setOnClickListener(mRemoteTabClickListener); + } + + ((TextView) findViewById(R.id.remote_tabs_client)).setText(client); + setRemoteTabsVisibility(true); + } + public static class TopSitesGridView extends GridView { /** From layout xml: * 80dip image height diff --git a/mobile/android/base/GeckoApp.java b/mobile/android/base/GeckoApp.java index 4d239a6e288c..59fd71b84add 100644 --- a/mobile/android/base/GeckoApp.java +++ b/mobile/android/base/GeckoApp.java @@ -1107,7 +1107,8 @@ abstract public class GeckoApp }); } else { mAboutHomeContent.update(GeckoApp.mAppContext, - EnumSet.of(AboutHomeContent.UpdateFlags.TOP_SITES)); + EnumSet.of(AboutHomeContent.UpdateFlags.TOP_SITES, + AboutHomeContent.UpdateFlags.REMOTE_TABS)); } mAboutHomeContent.setVisibility(View.VISIBLE); @@ -2562,7 +2563,7 @@ abstract public class GeckoApp Log.e(LOGTAG, "error building JSON arguments"); } if (type == AwesomeBar.Type.ADD) { - Log.i(LOGTAG, "Sending message to Gecko: " + SystemClock.uptimeMillis() + " - Tab:Add"); + Log.d(LOGTAG, "Sending message to Gecko: " + SystemClock.uptimeMillis() + " - Tab:Add"); GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Tab:Add", args.toString())); } else { GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Tab:Load", args.toString())); diff --git a/mobile/android/base/Makefile.in b/mobile/android/base/Makefile.in index 8202a175f7cc..dc5748185847 100644 --- a/mobile/android/base/Makefile.in +++ b/mobile/android/base/Makefile.in @@ -109,6 +109,7 @@ FENNEC_JAVA_FILES = \ Tab.java \ Tabs.java \ TabsTray.java \ + TabsAccessor.java \ gfx/BitmapUtils.java \ gfx/BufferedCairoImage.java \ gfx/CairoGLInfo.java \ @@ -260,6 +261,7 @@ RES_LAYOUT = \ res/layout/abouthome_topsite_item.xml \ res/layout/abouthome_addon_row.xml \ res/layout/abouthome_last_tabs_row.xml \ + res/layout/abouthome_remote_tab_row.xml \ $(NULL) RES_LAYOUT_V11 = \ diff --git a/mobile/android/base/RemoteTabs.java b/mobile/android/base/RemoteTabs.java index 10fb918c4ee5..86d9840eb188 100644 --- a/mobile/android/base/RemoteTabs.java +++ b/mobile/android/base/RemoteTabs.java @@ -6,10 +6,7 @@ package org.mozilla.gecko; import java.util.ArrayList; import java.util.HashMap; - -import org.mozilla.gecko.db.BrowserContract.Clients; -import org.mozilla.gecko.db.BrowserContract.Tabs; -import org.mozilla.gecko.db.BrowserContract; +import java.util.List; import org.json.JSONObject; @@ -31,15 +28,16 @@ import android.text.TextUtils; import android.util.Log; public class RemoteTabs extends Activity - implements ExpandableListView.OnGroupClickListener, ExpandableListView.OnChildClickListener { + implements ExpandableListView.OnGroupClickListener, ExpandableListView.OnChildClickListener, + TabsAccessor.OnQueryTabsCompleteListener { private static final String LOGTAG = "GeckoRemoteTabs"; private static int sPreferredHeight; private static int sChildItemHeight; private static int sGroupItemHeight; private static ExpandableListView mList; - - private static ArrayList > mClientsList; + private static boolean mExitToTabsTray; + private static ArrayList >> mTabsList; // 50 for child + 2 for divider @@ -48,13 +46,6 @@ public class RemoteTabs extends Activity // 30 for group + 2 for divider private static final int GROUP_ITEM_HEIGHT = 32; - private static final String[] PROJECTION_COLUMNS = new String[] { - BrowserContract.Tabs.TITLE, // 0 - BrowserContract.Tabs.URL, // 1 - BrowserContract.Clients.GUID, // 2 - BrowserContract.Clients.NAME // 3 - }; - private static final String[] CLIENT_KEY = new String[] { "name" }; private static final String[] TAB_KEY = new String[] { "title" }; private static final int[] CLIENT_RESOURCE = new int[] { R.id.client }; @@ -84,14 +75,19 @@ public class RemoteTabs extends Activity sGroupItemHeight = (int) (GROUP_ITEM_HEIGHT * metrics.density); sPreferredHeight = (int) (0.67 * metrics.heightPixels); - // Query the database for remote tabs in AsyncTask - (new QueryRemoteTabsTask()).execute(); + TabsAccessor.getTabs(getApplicationContext(), this); + + // Exit to tabs-tray + mExitToTabsTray = getIntent().getBooleanExtra("exit-to-tabs-tray", false); } @Override public void onBackPressed() { - startActivity(new Intent(this, TabsTray.class)); - overridePendingTransition(R.anim.grow_fade_in, 0); + if (mExitToTabsTray) { + startActivity(new Intent(this, TabsTray.class)); + overridePendingTransition(R.anim.grow_fade_in, R.anim.shrink_fade_out); + } + finishActivity(); } @@ -124,7 +120,7 @@ public class RemoteTabs extends Activity Log.e(LOGTAG, "error building JSON arguments"); } - Log.i(LOGTAG, "Sending message to Gecko: " + SystemClock.uptimeMillis() + " - Tab:Add"); + Log.d(LOGTAG, "Sending message to Gecko: " + SystemClock.uptimeMillis() + " - Tab:Add"); GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Tab:Add", args.toString())); finishActivity(); return true; @@ -154,77 +150,53 @@ public class RemoteTabs extends Activity } } - // AsyncTask to query the database - private class QueryRemoteTabsTask extends GeckoAsyncTask { - @Override - protected Void doInBackground(Void... unused) { - mClientsList = new ArrayList >(); - mTabsList = new ArrayList >>(); - - Cursor tabs = getContentResolver().query(BrowserContract.Tabs.CONTENT_URI, - PROJECTION_COLUMNS, - BrowserContract.Tabs.CLIENT_GUID + " IS NOT NULL", - null, - null); - - if (tabs == null) - return null; - - String oldGuid = null; - ArrayList > tabsForClient = null; - HashMap client; - HashMap tab; - - try { - while (tabs.moveToNext()) { - String title = tabs.getString(0); - String url = tabs.getString(1); - String guid = tabs.getString(2); - String name = tabs.getString(3); - - if (oldGuid == null || !TextUtils.equals(oldGuid, guid)) { - client = new HashMap (); - client.put("name", name); - mClientsList.add(client); - - tabsForClient = new ArrayList >(); - mTabsList.add(tabsForClient); - - oldGuid = new String(guid); - } - - tab = new HashMap(); - tab.put("title", TextUtils.isEmpty(title) ? url : title); - tab.put("url", url); - tabsForClient.add(tab); - } - } finally { - tabs.close(); - } - - return null; + @Override + public void onQueryTabsComplete(List remoteTabsList) { + ArrayList remoteTabs = new ArrayList (remoteTabsList); + if (remoteTabs == null || remoteTabs.size() == 0) { + finishActivity(); + return; } + + ArrayList > clients = new ArrayList >(); - @Override - protected void onPostExecute(Void unused) { - if (mClientsList.size() == 0) { - finishActivity(); - return; - } - - mList.setAdapter(new SimpleExpandableListAdapter(getApplicationContext(), - mClientsList, - R.layout.remote_tabs_group, - CLIENT_KEY, - CLIENT_RESOURCE, - mTabsList, - R.layout.remote_tabs_child, - TAB_KEY, - TAB_RESOURCE)); - - for (int i = 0; i < mClientsList.size(); i++) { - mList.expandGroup(i); + mTabsList = new ArrayList >>(); + + String oldGuid = null; + ArrayList > tabsForClient = null; + HashMap client; + HashMap tab; + + for (TabsAccessor.RemoteTab remoteTab : remoteTabs) { + if (oldGuid == null || !TextUtils.equals(oldGuid, remoteTab.guid)) { + client = new HashMap (); + client.put("name", remoteTab.name); + clients.add(client); + + tabsForClient = new ArrayList >(); + mTabsList.add(tabsForClient); + + oldGuid = new String(remoteTab.guid); } + + tab = new HashMap(); + tab.put("title", TextUtils.isEmpty(remoteTab.title) ? remoteTab.url : remoteTab.title); + tab.put("url", remoteTab.url); + tabsForClient.add(tab); + } + + mList.setAdapter(new SimpleExpandableListAdapter(getApplicationContext(), + clients, + R.layout.remote_tabs_group, + CLIENT_KEY, + CLIENT_RESOURCE, + mTabsList, + R.layout.remote_tabs_child, + TAB_KEY, + TAB_RESOURCE)); + + for (int i = 0; i < clients.size(); i++) { + mList.expandGroup(i); } } } diff --git a/mobile/android/base/TabsAccessor.java b/mobile/android/base/TabsAccessor.java new file mode 100644 index 000000000000..05505c570012 --- /dev/null +++ b/mobile/android/base/TabsAccessor.java @@ -0,0 +1,144 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.mozilla.gecko.db.BrowserContract.Clients; +import org.mozilla.gecko.db.BrowserContract.Tabs; +import org.mozilla.gecko.db.BrowserContract; + +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.util.Log; + +public final class TabsAccessor { + private static final String LOGTAG = "GeckoTabsAccessor"; + + private static final String[] TABS_PROJECTION_COLUMNS = new String[] { + BrowserContract.Tabs.TITLE, + BrowserContract.Tabs.URL, + BrowserContract.Clients.GUID, + BrowserContract.Clients.NAME + }; + + // Projection column numbers + public static enum TABS_COLUMN { + TITLE, + URL, + GUID, + NAME + }; + + private static final String TABS_SELECTION = BrowserContract.Tabs.CLIENT_GUID + " IS NOT NULL"; + + public static class RemoteTab { + public String title; + public String url; + public String guid; + public String name; + } + + public interface OnQueryTabsCompleteListener { + public void onQueryTabsComplete(List tabs); + } + + public interface OnClientsAvailableListener { + public void areAvailable(boolean available); + } + + // Helper method to check if there are any clients available + public static void areClientsAvailable(final Context context, final OnClientsAvailableListener listener) { + if (listener == null) + return; + + (new GeckoAsyncTask () { + @Override + protected Boolean doInBackground(Void... unused) { + Cursor cursor = context.getContentResolver().query(BrowserContract.Clients.CONTENT_URI, + null, + null, + null, + null); + + if (cursor == null) + return false; + + try { + return cursor.moveToNext(); + } finally { + cursor.close(); + } + } + + @Override + protected void onPostExecute(Boolean availability) { + listener.areAvailable(availability); + } + }).execute(); + } + + // This method returns all tabs from all remote clients, + // ordered by most recent client first, most recent tab first + public static void getTabs(final Context context, final OnQueryTabsCompleteListener listener) { + getTabs(context, 0, listener); + } + + // This method returns limited number of tabs from all remote clients, + // ordered by most recent client first, most recent tab first + public static void getTabs(final Context context, final int limit, final OnQueryTabsCompleteListener listener) { + // If there is no listener, no point in doing work. + if (listener == null) + return; + + (new GeckoAsyncTask> () { + @Override + protected List doInBackground(Void... unused) { + Uri uri = BrowserContract.Tabs.CONTENT_URI; + + if (limit > 0) { + uri = uri.buildUpon() + .appendQueryParameter(BrowserContract.PARAM_LIMIT, String.valueOf(limit)) + .build(); + } + + Cursor cursor = context.getContentResolver().query(uri, + TABS_PROJECTION_COLUMNS, + TABS_SELECTION, + null, + null); + + if (cursor == null) + return null; + + RemoteTab tab; + final ArrayList tabs = new ArrayList (); + try { + while (cursor.moveToNext()) { + tab = new RemoteTab(); + tab.title = cursor.getString(TABS_COLUMN.TITLE.ordinal()); + tab.url = cursor.getString(TABS_COLUMN.URL.ordinal()); + tab.guid = cursor.getString(TABS_COLUMN.GUID.ordinal()); + tab.name = cursor.getString(TABS_COLUMN.NAME.ordinal()); + + tabs.add(tab); + } + } finally { + cursor.close(); + } + + return Collections.unmodifiableList(tabs); + } + + @Override + protected void onPostExecute(List tabs) { + listener.onQueryTabsComplete(tabs); + } + }).execute(); + } +} diff --git a/mobile/android/base/TabsTray.java b/mobile/android/base/TabsTray.java index fa141a411046..a2ff0ef91c8b 100644 --- a/mobile/android/base/TabsTray.java +++ b/mobile/android/base/TabsTray.java @@ -39,13 +39,9 @@ package org.mozilla.gecko; import java.util.ArrayList; -import org.mozilla.gecko.db.BrowserContract.Clients; -import org.mozilla.gecko.db.BrowserContract; - import android.accounts.AccountManager; import android.app.Activity; import android.content.Context; -import android.database.Cursor; import android.content.Intent; import android.graphics.drawable.Drawable; import android.os.Bundle; @@ -128,8 +124,17 @@ public class TabsTray extends Activity implements Tabs.OnTabsChangedListener { // If sync is set up, query the database for remote clients // Cleanup after Bug: 734211 is fixed - if (AccountManager.get(getApplicationContext()).getAccountsByType("org.mozilla.firefox_sync").length > 0) - (new QueryForRemoteClientsTask()).execute(); + if (AccountManager.get(getApplicationContext()).getAccountsByType("org.mozilla.firefox_sync").length > 0) { + TabsAccessor.areClientsAvailable(getApplicationContext(), new TabsAccessor.OnClientsAvailableListener() { + @Override + public void areAvailable(boolean available) { + if (available) + mRemoteTabs.setVisibility(View.VISIBLE); + else + mRemoteTabs.setVisibility(View.GONE); + } + }); + } } @Override @@ -177,8 +182,10 @@ public class TabsTray extends Activity implements Tabs.OnTabsChangedListener { } void showRemoteTabs() { - startActivity(new Intent(this, RemoteTabs.class)); - overridePendingTransition(R.anim.grow_fade_in, 0); + Intent intent = new Intent(this, RemoteTabs.class); + intent.putExtra("exit-to-tabs-tray", true); + startActivity(intent); + overridePendingTransition(R.anim.grow_fade_in, R.anim.shrink_fade_out); finishActivity(); } @@ -211,35 +218,6 @@ public class TabsTray extends Activity implements Tabs.OnTabsChangedListener { } } - // AsyncTask to see if there is any remote tabs in the database - private class QueryForRemoteClientsTask extends GeckoAsyncTask { - @Override - protected Boolean doInBackground(Void... unused) { - Cursor clients = getContentResolver().query(BrowserContract.Clients.CONTENT_URI, - null, - null, - null, - null); - - if (clients == null) - return false; - - try { - return clients.moveToNext(); - } finally { - clients.close(); - } - } - - @Override - protected void onPostExecute(Boolean clientsExist) { - if (clientsExist.booleanValue()) - mRemoteTabs.setVisibility(View.VISIBLE); - else - mRemoteTabs.setVisibility(View.GONE); - } - } - // Adapter to bind tabs into a list private class TabsAdapter extends BaseAdapter { public TabsAdapter(Context context, ArrayList tabs) { diff --git a/mobile/android/base/db/TabsProvider.java.in b/mobile/android/base/db/TabsProvider.java.in index 80787afe29c4..979f69988fb8 100644 --- a/mobile/android/base/db/TabsProvider.java.in +++ b/mobile/android/base/db/TabsProvider.java.in @@ -511,6 +511,7 @@ public class TabsProvider extends ContentProvider { final int match = URI_MATCHER.match(uri); SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + String limit = uri.getQueryParameter(BrowserContract.PARAM_LIMIT); switch (match) { case TABS_ID: @@ -555,7 +556,7 @@ public class TabsProvider extends ContentProvider { trace("Running built query."); Cursor cursor = qb.query(db, projection, selection, selectionArgs, null, - null, sortOrder, null); + null, sortOrder, limit); cursor.setNotificationUri(getContext().getContentResolver(), BrowserContract.TABS_AUTHORITY_URI); diff --git a/mobile/android/base/locales/en-US/android_strings.dtd b/mobile/android/base/locales/en-US/android_strings.dtd index 2389cff071fe..4556c66bcd5c 100644 --- a/mobile/android/base/locales/en-US/android_strings.dtd +++ b/mobile/android/base/locales/en-US/android_strings.dtd @@ -28,6 +28,7 @@ + diff --git a/mobile/android/base/resources/layout/abouthome_content.xml b/mobile/android/base/resources/layout/abouthome_content.xml index cb3cdd59aa9d..37f73e405f18 100644 --- a/mobile/android/base/resources/layout/abouthome_content.xml +++ b/mobile/android/base/resources/layout/abouthome_content.xml @@ -177,6 +177,45 @@ android:visibility="gone" android:text="@string/abouthome_addons_browse"/> + + + + + + + + diff --git a/mobile/android/base/resources/layout/abouthome_remote_tab_row.xml b/mobile/android/base/resources/layout/abouthome_remote_tab_row.xml new file mode 100644 index 000000000000..7082f6ccdf17 --- /dev/null +++ b/mobile/android/base/resources/layout/abouthome_remote_tab_row.xml @@ -0,0 +1,13 @@ + + diff --git a/mobile/android/base/strings.xml.in b/mobile/android/base/strings.xml.in index 9babfa95d694..8d0d36e31aeb 100644 --- a/mobile/android/base/strings.xml.in +++ b/mobile/android/base/strings.xml.in @@ -35,6 +35,7 @@ &awesomebar_default_text; &remote_tabs; + &remote_tabs_show_all; &quit; &bookmark;