|
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 } |