1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/TabsAccessor.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,195 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +package org.mozilla.gecko; 1.9 + 1.10 +import org.mozilla.gecko.db.BrowserContract; 1.11 +import org.mozilla.gecko.util.ThreadUtils; 1.12 +import org.mozilla.gecko.util.UiAsyncTask; 1.13 + 1.14 +import org.json.JSONArray; 1.15 +import org.json.JSONException; 1.16 + 1.17 +import android.content.ContentResolver; 1.18 +import android.content.ContentValues; 1.19 +import android.content.Context; 1.20 +import android.database.Cursor; 1.21 +import android.net.Uri; 1.22 +import android.util.Log; 1.23 + 1.24 +import java.util.ArrayList; 1.25 +import java.util.Collections; 1.26 +import java.util.List; 1.27 + 1.28 +public final class TabsAccessor { 1.29 + private static final String LOGTAG = "GeckoTabsAccessor"; 1.30 + 1.31 + private static final String[] CLIENTS_AVAILABILITY_PROJECTION = new String[] { 1.32 + BrowserContract.Clients.GUID 1.33 + }; 1.34 + 1.35 + private static final String[] TABS_PROJECTION_COLUMNS = new String[] { 1.36 + BrowserContract.Tabs.TITLE, 1.37 + BrowserContract.Tabs.URL, 1.38 + BrowserContract.Clients.GUID, 1.39 + BrowserContract.Clients.NAME 1.40 + }; 1.41 + 1.42 + // Projection column numbers 1.43 + public static enum TABS_COLUMN { 1.44 + TITLE, 1.45 + URL, 1.46 + GUID, 1.47 + NAME 1.48 + }; 1.49 + 1.50 + private static final String CLIENTS_SELECTION = BrowserContract.Clients.GUID + " IS NOT NULL"; 1.51 + private static final String TABS_SELECTION = BrowserContract.Tabs.CLIENT_GUID + " IS NOT NULL"; 1.52 + 1.53 + private static final String LOCAL_CLIENT_SELECTION = BrowserContract.Clients.GUID + " IS NULL"; 1.54 + private static final String LOCAL_TABS_SELECTION = BrowserContract.Tabs.CLIENT_GUID + " IS NULL"; 1.55 + 1.56 + public static class RemoteTab { 1.57 + public String title; 1.58 + public String url; 1.59 + public String guid; 1.60 + public String name; 1.61 + } 1.62 + 1.63 + public interface OnQueryTabsCompleteListener { 1.64 + public void onQueryTabsComplete(List<RemoteTab> tabs); 1.65 + } 1.66 + 1.67 + // This method returns all tabs from all remote clients, 1.68 + // ordered by most recent client first, most recent tab first 1.69 + public static void getTabs(final Context context, final OnQueryTabsCompleteListener listener) { 1.70 + getTabs(context, 0, listener); 1.71 + } 1.72 + 1.73 + // This method returns limited number of tabs from all remote clients, 1.74 + // ordered by most recent client first, most recent tab first 1.75 + public static void getTabs(final Context context, final int limit, final OnQueryTabsCompleteListener listener) { 1.76 + // If there is no listener, no point in doing work. 1.77 + if (listener == null) 1.78 + return; 1.79 + 1.80 + (new UiAsyncTask<Void, Void, List<RemoteTab>>(ThreadUtils.getBackgroundHandler()) { 1.81 + @Override 1.82 + protected List<RemoteTab> doInBackground(Void... unused) { 1.83 + Uri uri = BrowserContract.Tabs.CONTENT_URI; 1.84 + 1.85 + if (limit > 0) { 1.86 + uri = uri.buildUpon() 1.87 + .appendQueryParameter(BrowserContract.PARAM_LIMIT, String.valueOf(limit)) 1.88 + .build(); 1.89 + } 1.90 + 1.91 + Cursor cursor = context.getContentResolver().query(uri, 1.92 + TABS_PROJECTION_COLUMNS, 1.93 + TABS_SELECTION, 1.94 + null, 1.95 + null); 1.96 + 1.97 + if (cursor == null) 1.98 + return null; 1.99 + 1.100 + RemoteTab tab; 1.101 + final ArrayList<RemoteTab> tabs = new ArrayList<RemoteTab> (); 1.102 + try { 1.103 + while (cursor.moveToNext()) { 1.104 + tab = new RemoteTab(); 1.105 + tab.title = cursor.getString(TABS_COLUMN.TITLE.ordinal()); 1.106 + tab.url = cursor.getString(TABS_COLUMN.URL.ordinal()); 1.107 + tab.guid = cursor.getString(TABS_COLUMN.GUID.ordinal()); 1.108 + tab.name = cursor.getString(TABS_COLUMN.NAME.ordinal()); 1.109 + 1.110 + tabs.add(tab); 1.111 + } 1.112 + } finally { 1.113 + cursor.close(); 1.114 + } 1.115 + 1.116 + return Collections.unmodifiableList(tabs); 1.117 + } 1.118 + 1.119 + @Override 1.120 + protected void onPostExecute(List<RemoteTab> tabs) { 1.121 + listener.onQueryTabsComplete(tabs); 1.122 + } 1.123 + }).execute(); 1.124 + } 1.125 + 1.126 + // Updates the modified time of the local client with the current time. 1.127 + private static void updateLocalClient(final ContentResolver cr) { 1.128 + ContentValues values = new ContentValues(); 1.129 + values.put(BrowserContract.Clients.LAST_MODIFIED, System.currentTimeMillis()); 1.130 + cr.update(BrowserContract.Clients.CONTENT_URI, values, LOCAL_CLIENT_SELECTION, null); 1.131 + } 1.132 + 1.133 + // Deletes all local tabs. 1.134 + private static void deleteLocalTabs(final ContentResolver cr) { 1.135 + cr.delete(BrowserContract.Tabs.CONTENT_URI, LOCAL_TABS_SELECTION, null); 1.136 + } 1.137 + 1.138 + /** 1.139 + * Tabs are positioned in the DB in the same order that they appear in the tabs param. 1.140 + * - URL should never empty or null. Skip this tab if there's no URL. 1.141 + * - TITLE should always a string, either a page title or empty. 1.142 + * - LAST_USED should always be numeric. 1.143 + * - FAVICON should be a URL or null. 1.144 + * - HISTORY should be serialized JSON array of URLs. 1.145 + * - POSITION should always be numeric. 1.146 + * - CLIENT_GUID should always be null to represent the local client. 1.147 + */ 1.148 + private static void insertLocalTabs(final ContentResolver cr, final Iterable<Tab> tabs) { 1.149 + // Reuse this for serializing individual history URLs as JSON. 1.150 + JSONArray history = new JSONArray(); 1.151 + ArrayList<ContentValues> valuesToInsert = new ArrayList<ContentValues>(); 1.152 + 1.153 + int position = 0; 1.154 + for (Tab tab : tabs) { 1.155 + // Skip this tab if it has a null URL or is in private browsing mode 1.156 + String url = tab.getURL(); 1.157 + if (url == null || tab.isPrivate()) 1.158 + continue; 1.159 + 1.160 + ContentValues values = new ContentValues(); 1.161 + values.put(BrowserContract.Tabs.URL, url); 1.162 + values.put(BrowserContract.Tabs.TITLE, tab.getTitle()); 1.163 + values.put(BrowserContract.Tabs.LAST_USED, tab.getLastUsed()); 1.164 + 1.165 + String favicon = tab.getFaviconURL(); 1.166 + if (favicon != null) 1.167 + values.put(BrowserContract.Tabs.FAVICON, favicon); 1.168 + else 1.169 + values.putNull(BrowserContract.Tabs.FAVICON); 1.170 + 1.171 + // We don't have access to session history in Java, so for now, we'll 1.172 + // just use a JSONArray that holds most recent history item. 1.173 + try { 1.174 + history.put(0, tab.getURL()); 1.175 + values.put(BrowserContract.Tabs.HISTORY, history.toString()); 1.176 + } catch (JSONException e) { 1.177 + Log.w(LOGTAG, "JSONException adding URL to tab history array.", e); 1.178 + } 1.179 + 1.180 + values.put(BrowserContract.Tabs.POSITION, position++); 1.181 + 1.182 + // A null client guid corresponds to the local client. 1.183 + values.putNull(BrowserContract.Tabs.CLIENT_GUID); 1.184 + 1.185 + valuesToInsert.add(values); 1.186 + } 1.187 + 1.188 + ContentValues[] valuesToInsertArray = valuesToInsert.toArray(new ContentValues[valuesToInsert.size()]); 1.189 + cr.bulkInsert(BrowserContract.Tabs.CONTENT_URI, valuesToInsertArray); 1.190 + } 1.191 + 1.192 + // Deletes all local tabs and replaces them with a new list of tabs. 1.193 + public static synchronized void persistLocalTabs(final ContentResolver cr, final Iterable<Tab> tabs) { 1.194 + deleteLocalTabs(cr); 1.195 + insertLocalTabs(cr, tabs); 1.196 + updateLocalClient(cr); 1.197 + } 1.198 +}