| |
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/. */ |
| |
4 |
| |
5 package org.mozilla.gecko; |
| |
6 |
| |
7 import org.mozilla.gecko.db.BrowserContract; |
| |
8 import org.mozilla.gecko.util.ThreadUtils; |
| |
9 import org.mozilla.gecko.util.UiAsyncTask; |
| |
10 |
| |
11 import org.json.JSONArray; |
| |
12 import org.json.JSONException; |
| |
13 |
| |
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; |
| |
20 |
| |
21 import java.util.ArrayList; |
| |
22 import java.util.Collections; |
| |
23 import java.util.List; |
| |
24 |
| |
25 public final class TabsAccessor { |
| |
26 private static final String LOGTAG = "GeckoTabsAccessor"; |
| |
27 |
| |
28 private static final String[] CLIENTS_AVAILABILITY_PROJECTION = new String[] { |
| |
29 BrowserContract.Clients.GUID |
| |
30 }; |
| |
31 |
| |
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 }; |
| |
38 |
| |
39 // Projection column numbers |
| |
40 public static enum TABS_COLUMN { |
| |
41 TITLE, |
| |
42 URL, |
| |
43 GUID, |
| |
44 NAME |
| |
45 }; |
| |
46 |
| |
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"; |
| |
49 |
| |
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"; |
| |
52 |
| |
53 public static class RemoteTab { |
| |
54 public String title; |
| |
55 public String url; |
| |
56 public String guid; |
| |
57 public String name; |
| |
58 } |
| |
59 |
| |
60 public interface OnQueryTabsCompleteListener { |
| |
61 public void onQueryTabsComplete(List<RemoteTab> tabs); |
| |
62 } |
| |
63 |
| |
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 } |
| |
69 |
| |
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; |
| |
76 |
| |
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; |
| |
81 |
| |
82 if (limit > 0) { |
| |
83 uri = uri.buildUpon() |
| |
84 .appendQueryParameter(BrowserContract.PARAM_LIMIT, String.valueOf(limit)) |
| |
85 .build(); |
| |
86 } |
| |
87 |
| |
88 Cursor cursor = context.getContentResolver().query(uri, |
| |
89 TABS_PROJECTION_COLUMNS, |
| |
90 TABS_SELECTION, |
| |
91 null, |
| |
92 null); |
| |
93 |
| |
94 if (cursor == null) |
| |
95 return null; |
| |
96 |
| |
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()); |
| |
106 |
| |
107 tabs.add(tab); |
| |
108 } |
| |
109 } finally { |
| |
110 cursor.close(); |
| |
111 } |
| |
112 |
| |
113 return Collections.unmodifiableList(tabs); |
| |
114 } |
| |
115 |
| |
116 @Override |
| |
117 protected void onPostExecute(List<RemoteTab> tabs) { |
| |
118 listener.onQueryTabsComplete(tabs); |
| |
119 } |
| |
120 }).execute(); |
| |
121 } |
| |
122 |
| |
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 } |
| |
129 |
| |
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 } |
| |
134 |
| |
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>(); |
| |
149 |
| |
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; |
| |
156 |
| |
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()); |
| |
161 |
| |
162 String favicon = tab.getFaviconURL(); |
| |
163 if (favicon != null) |
| |
164 values.put(BrowserContract.Tabs.FAVICON, favicon); |
| |
165 else |
| |
166 values.putNull(BrowserContract.Tabs.FAVICON); |
| |
167 |
| |
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 } |
| |
176 |
| |
177 values.put(BrowserContract.Tabs.POSITION, position++); |
| |
178 |
| |
179 // A null client guid corresponds to the local client. |
| |
180 values.putNull(BrowserContract.Tabs.CLIENT_GUID); |
| |
181 |
| |
182 valuesToInsert.add(values); |
| |
183 } |
| |
184 |
| |
185 ContentValues[] valuesToInsertArray = valuesToInsert.toArray(new ContentValues[valuesToInsert.size()]); |
| |
186 cr.bulkInsert(BrowserContract.Tabs.CONTENT_URI, valuesToInsertArray); |
| |
187 } |
| |
188 |
| |
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 } |