Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 package org.mozilla.gecko;
7 import org.mozilla.gecko.db.BrowserContract;
8 import org.mozilla.gecko.util.ThreadUtils;
9 import org.mozilla.gecko.util.UiAsyncTask;
11 import org.json.JSONArray;
12 import org.json.JSONException;
14 import android.content.ContentResolver;
15 import android.content.ContentValues;
16 import android.content.Context;
17 import android.database.Cursor;
18 import android.net.Uri;
19 import android.util.Log;
21 import java.util.ArrayList;
22 import java.util.Collections;
23 import java.util.List;
25 public final class TabsAccessor {
26 private static final String LOGTAG = "GeckoTabsAccessor";
28 private static final String[] CLIENTS_AVAILABILITY_PROJECTION = new String[] {
29 BrowserContract.Clients.GUID
30 };
32 private static final String[] TABS_PROJECTION_COLUMNS = new String[] {
33 BrowserContract.Tabs.TITLE,
34 BrowserContract.Tabs.URL,
35 BrowserContract.Clients.GUID,
36 BrowserContract.Clients.NAME
37 };
39 // Projection column numbers
40 public static enum TABS_COLUMN {
41 TITLE,
42 URL,
43 GUID,
44 NAME
45 };
47 private static final String CLIENTS_SELECTION = BrowserContract.Clients.GUID + " IS NOT NULL";
48 private static final String TABS_SELECTION = BrowserContract.Tabs.CLIENT_GUID + " IS NOT NULL";
50 private static final String LOCAL_CLIENT_SELECTION = BrowserContract.Clients.GUID + " IS NULL";
51 private static final String LOCAL_TABS_SELECTION = BrowserContract.Tabs.CLIENT_GUID + " IS NULL";
53 public static class RemoteTab {
54 public String title;
55 public String url;
56 public String guid;
57 public String name;
58 }
60 public interface OnQueryTabsCompleteListener {
61 public void onQueryTabsComplete(List<RemoteTab> tabs);
62 }
64 // This method returns all tabs from all remote clients,
65 // ordered by most recent client first, most recent tab first
66 public static void getTabs(final Context context, final OnQueryTabsCompleteListener listener) {
67 getTabs(context, 0, listener);
68 }
70 // This method returns limited number of tabs from all remote clients,
71 // ordered by most recent client first, most recent tab first
72 public static void getTabs(final Context context, final int limit, final OnQueryTabsCompleteListener listener) {
73 // If there is no listener, no point in doing work.
74 if (listener == null)
75 return;
77 (new UiAsyncTask<Void, Void, List<RemoteTab>>(ThreadUtils.getBackgroundHandler()) {
78 @Override
79 protected List<RemoteTab> doInBackground(Void... unused) {
80 Uri uri = BrowserContract.Tabs.CONTENT_URI;
82 if (limit > 0) {
83 uri = uri.buildUpon()
84 .appendQueryParameter(BrowserContract.PARAM_LIMIT, String.valueOf(limit))
85 .build();
86 }
88 Cursor cursor = context.getContentResolver().query(uri,
89 TABS_PROJECTION_COLUMNS,
90 TABS_SELECTION,
91 null,
92 null);
94 if (cursor == null)
95 return null;
97 RemoteTab tab;
98 final ArrayList<RemoteTab> tabs = new ArrayList<RemoteTab> ();
99 try {
100 while (cursor.moveToNext()) {
101 tab = new RemoteTab();
102 tab.title = cursor.getString(TABS_COLUMN.TITLE.ordinal());
103 tab.url = cursor.getString(TABS_COLUMN.URL.ordinal());
104 tab.guid = cursor.getString(TABS_COLUMN.GUID.ordinal());
105 tab.name = cursor.getString(TABS_COLUMN.NAME.ordinal());
107 tabs.add(tab);
108 }
109 } finally {
110 cursor.close();
111 }
113 return Collections.unmodifiableList(tabs);
114 }
116 @Override
117 protected void onPostExecute(List<RemoteTab> tabs) {
118 listener.onQueryTabsComplete(tabs);
119 }
120 }).execute();
121 }
123 // Updates the modified time of the local client with the current time.
124 private static void updateLocalClient(final ContentResolver cr) {
125 ContentValues values = new ContentValues();
126 values.put(BrowserContract.Clients.LAST_MODIFIED, System.currentTimeMillis());
127 cr.update(BrowserContract.Clients.CONTENT_URI, values, LOCAL_CLIENT_SELECTION, null);
128 }
130 // Deletes all local tabs.
131 private static void deleteLocalTabs(final ContentResolver cr) {
132 cr.delete(BrowserContract.Tabs.CONTENT_URI, LOCAL_TABS_SELECTION, null);
133 }
135 /**
136 * Tabs are positioned in the DB in the same order that they appear in the tabs param.
137 * - URL should never empty or null. Skip this tab if there's no URL.
138 * - TITLE should always a string, either a page title or empty.
139 * - LAST_USED should always be numeric.
140 * - FAVICON should be a URL or null.
141 * - HISTORY should be serialized JSON array of URLs.
142 * - POSITION should always be numeric.
143 * - CLIENT_GUID should always be null to represent the local client.
144 */
145 private static void insertLocalTabs(final ContentResolver cr, final Iterable<Tab> tabs) {
146 // Reuse this for serializing individual history URLs as JSON.
147 JSONArray history = new JSONArray();
148 ArrayList<ContentValues> valuesToInsert = new ArrayList<ContentValues>();
150 int position = 0;
151 for (Tab tab : tabs) {
152 // Skip this tab if it has a null URL or is in private browsing mode
153 String url = tab.getURL();
154 if (url == null || tab.isPrivate())
155 continue;
157 ContentValues values = new ContentValues();
158 values.put(BrowserContract.Tabs.URL, url);
159 values.put(BrowserContract.Tabs.TITLE, tab.getTitle());
160 values.put(BrowserContract.Tabs.LAST_USED, tab.getLastUsed());
162 String favicon = tab.getFaviconURL();
163 if (favicon != null)
164 values.put(BrowserContract.Tabs.FAVICON, favicon);
165 else
166 values.putNull(BrowserContract.Tabs.FAVICON);
168 // We don't have access to session history in Java, so for now, we'll
169 // just use a JSONArray that holds most recent history item.
170 try {
171 history.put(0, tab.getURL());
172 values.put(BrowserContract.Tabs.HISTORY, history.toString());
173 } catch (JSONException e) {
174 Log.w(LOGTAG, "JSONException adding URL to tab history array.", e);
175 }
177 values.put(BrowserContract.Tabs.POSITION, position++);
179 // A null client guid corresponds to the local client.
180 values.putNull(BrowserContract.Tabs.CLIENT_GUID);
182 valuesToInsert.add(values);
183 }
185 ContentValues[] valuesToInsertArray = valuesToInsert.toArray(new ContentValues[valuesToInsert.size()]);
186 cr.bulkInsert(BrowserContract.Tabs.CONTENT_URI, valuesToInsertArray);
187 }
189 // Deletes all local tabs and replaces them with a new list of tabs.
190 public static synchronized void persistLocalTabs(final ContentResolver cr, final Iterable<Tab> tabs) {
191 deleteLocalTabs(cr);
192 insertLocalTabs(cr, tabs);
193 updateLocalClient(cr);
194 }
195 }