OLD | NEW |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 package org.chromium.chrome.browser.superviseduser; | 5 package org.chromium.chrome.browser.superviseduser; |
6 | 6 |
7 import android.annotation.TargetApi; | 7 import android.annotation.TargetApi; |
8 import android.content.BroadcastReceiver; | 8 import android.content.BroadcastReceiver; |
9 import android.content.Context; | 9 import android.content.Context; |
10 import android.content.Intent; | 10 import android.content.Intent; |
11 import android.content.IntentFilter; | 11 import android.content.IntentFilter; |
12 import android.os.Build; | 12 import android.os.Build; |
13 import android.os.Bundle; | 13 import android.os.Bundle; |
14 import android.os.SystemClock; | 14 import android.os.SystemClock; |
15 import android.os.UserManager; | 15 import android.os.UserManager; |
16 | 16 |
| 17 import org.chromium.base.Callback; |
17 import org.chromium.base.ThreadUtils; | 18 import org.chromium.base.ThreadUtils; |
18 import org.chromium.base.VisibleForTesting; | 19 import org.chromium.base.VisibleForTesting; |
19 import org.chromium.base.annotations.CalledByNative; | 20 import org.chromium.base.annotations.CalledByNative; |
20 import org.chromium.base.library_loader.LibraryLoader; | 21 import org.chromium.base.library_loader.LibraryLoader; |
21 import org.chromium.base.library_loader.ProcessInitException; | 22 import org.chromium.base.library_loader.ProcessInitException; |
22 import org.chromium.base.metrics.RecordHistogram; | 23 import org.chromium.base.metrics.RecordHistogram; |
| 24 import org.chromium.chrome.browser.childaccounts.ChildAccountService; |
| 25 import org.chromium.chrome.browser.firstrun.ForcedSigninProcessor; |
23 import org.chromium.chrome.browser.init.ChromeBrowserInitializer; | 26 import org.chromium.chrome.browser.init.ChromeBrowserInitializer; |
| 27 import org.chromium.components.signin.ChromeSigninController; |
| 28 import org.chromium.components.supervisedusererrorpage.FilteringBehaviorReason; |
24 import org.chromium.components.webrestrictions.browser.WebRestrictionsContentPro
vider; | 29 import org.chromium.components.webrestrictions.browser.WebRestrictionsContentPro
vider; |
25 | 30 |
26 import java.util.concurrent.ArrayBlockingQueue; | 31 import java.util.concurrent.ArrayBlockingQueue; |
27 import java.util.concurrent.BlockingQueue; | 32 import java.util.concurrent.BlockingQueue; |
28 import java.util.concurrent.TimeUnit; | 33 import java.util.concurrent.TimeUnit; |
29 | 34 |
30 /** | 35 /** |
31 * Content provider for telling other apps (e.g. WebView apps) about the supervi
sed user URL filter. | 36 * Content provider for telling other apps (e.g. WebView apps) about the supervi
sed user URL filter. |
32 */ | 37 */ |
33 public class SupervisedUserContentProvider extends WebRestrictionsContentProvide
r { | 38 public class SupervisedUserContentProvider extends WebRestrictionsContentProvide
r { |
34 private static final String SUPERVISED_USER_CONTENT_PROVIDER_ENABLED = | 39 private static final String SUPERVISED_USER_CONTENT_PROVIDER_ENABLED = |
35 "SupervisedUserContentProviderEnabled"; | 40 "SupervisedUserContentProviderEnabled"; |
36 private long mNativeSupervisedUserContentProvider; | 41 private long mNativeSupervisedUserContentProvider; |
37 private boolean mChromeAlreadyStarted; | 42 private boolean mChromeAlreadyStarted; |
38 private static Object sEnabledLock = new Object(); | 43 private static Object sEnabledLock = new Object(); |
| 44 private static Object sContentProviderLock = new Object(); |
| 45 |
| 46 private static final String TAG = "SupervisedUserContent"; |
39 | 47 |
40 // Three value "boolean" caching enabled state, null if not yet known. | 48 // Three value "boolean" caching enabled state, null if not yet known. |
41 private static Boolean sEnabled; | 49 private static Boolean sEnabled; |
42 | 50 |
43 private long getSupervisedUserContentProvider() throws ProcessInitException
{ | 51 @VisibleForTesting |
44 mChromeAlreadyStarted = LibraryLoader.isInitialized(); | 52 void startForcedSigninProcessor(Context appContext, Runnable onComplete) { |
45 if (mNativeSupervisedUserContentProvider != 0) { | 53 ForcedSigninProcessor.start(appContext, onComplete); |
46 return mNativeSupervisedUserContentProvider; | |
47 } | |
48 | |
49 ChromeBrowserInitializer.getInstance(getContext()).handleSynchronousStar
tup(); | |
50 | |
51 mNativeSupervisedUserContentProvider = nativeCreateSupervisedUserContent
Provider(); | |
52 return mNativeSupervisedUserContentProvider; | |
53 } | 54 } |
54 | 55 |
55 void setNativeSupervisedUserContentProviderForTesting(long nativeProvider) { | 56 @VisibleForTesting |
56 mNativeSupervisedUserContentProvider = nativeProvider; | 57 void listenForChildAccountStatusChange(Callback<Boolean> callback) { |
| 58 ChildAccountService.listenForStatusChange(callback); |
| 59 } |
| 60 |
| 61 private long getSupervisedUserContentProvider() { |
| 62 // This may lock for some time, but is always called on a background thr
ead, and will only |
| 63 // take significant time if the Chrome process isn't already running. |
| 64 synchronized (sContentProviderLock) { |
| 65 mChromeAlreadyStarted = LibraryLoader.isInitialized(); |
| 66 if (mNativeSupervisedUserContentProvider != 0) { |
| 67 return mNativeSupervisedUserContentProvider; |
| 68 } |
| 69 final Context appContext = getContext().getApplicationContext(); |
| 70 final SupervisedUserReply<Long> reply = new SupervisedUserReply<>(); |
| 71 ThreadUtils.runOnUiThread(new Runnable() { |
| 72 @Override |
| 73 public void run() { |
| 74 try { |
| 75 ChromeBrowserInitializer.getInstance(appContext).handleS
ynchronousStartup(); |
| 76 } catch (ProcessInitException e) { |
| 77 reply.onQueryFinished(0L); |
| 78 return; |
| 79 } |
| 80 final ChromeSigninController chromeSigninController = |
| 81 ChromeSigninController.get(appContext); |
| 82 if (chromeSigninController.isSignedIn()) { |
| 83 reply.onQueryFinished(nativeCreateSupervisedUserContentP
rovider()); |
| 84 return; |
| 85 } |
| 86 // Try to sign in, Chrome needs to be signed in to get the U
RL filter. |
| 87 startForcedSigninProcessor(appContext, new Runnable() { |
| 88 @Override |
| 89 public void run() { |
| 90 if (!chromeSigninController.isSignedIn()) { |
| 91 reply.onQueryFinished(0L); |
| 92 return; |
| 93 } |
| 94 // Wait for the status change; Chrome can't check an
y URLs until this |
| 95 // has happened. |
| 96 listenForChildAccountStatusChange(new Callback<Boole
an>() { |
| 97 @Override |
| 98 public void onResult(Boolean result) { |
| 99 reply.onQueryFinished( |
| 100 nativeCreateSupervisedUserContentPro
vider()); |
| 101 } |
| 102 }); |
| 103 } |
| 104 }); |
| 105 } |
| 106 }); |
| 107 try { |
| 108 Long result = reply.getResult(); |
| 109 if (result == null) return 0; |
| 110 mNativeSupervisedUserContentProvider = result; |
| 111 return mNativeSupervisedUserContentProvider; |
| 112 } catch (InterruptedException e) { |
| 113 return 0; |
| 114 } |
| 115 } |
57 } | 116 } |
58 | 117 |
59 static class SupervisedUserReply<T> { | 118 static class SupervisedUserReply<T> { |
60 private static final long RESULT_TIMEOUT_SECONDS = 10; | 119 private static final long RESULT_TIMEOUT_SECONDS = 10; |
61 final BlockingQueue<T> mQueue = new ArrayBlockingQueue<>(1); | 120 final BlockingQueue<T> mQueue = new ArrayBlockingQueue<>(1); |
62 | 121 |
63 void onQueryFinished(T reply) { | 122 void onQueryFinished(T reply) { |
64 // This must be called precisely once per query. | 123 // This must be called precisely once per query. |
65 mQueue.add(reply); | 124 mQueue.add(reply); |
66 } | 125 } |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
102 @Override | 161 @Override |
103 protected WebRestrictionsResult shouldProceed(final String url) { | 162 protected WebRestrictionsResult shouldProceed(final String url) { |
104 // This will be called on multiple threads (but never the UI thread), | 163 // This will be called on multiple threads (but never the UI thread), |
105 // see http://developer.android.com/guide/components/processes-and-threa
ds.html#ThreadSafe. | 164 // see http://developer.android.com/guide/components/processes-and-threa
ds.html#ThreadSafe. |
106 // The reply comes back on a different thread (possibly the UI thread) s
ome time later. | 165 // The reply comes back on a different thread (possibly the UI thread) s
ome time later. |
107 // As such it needs to correctly match the replies to the calls. It does
this by creating a | 166 // As such it needs to correctly match the replies to the calls. It does
this by creating a |
108 // reply object for each query, and passing this through the callback st
ructure. The reply | 167 // reply object for each query, and passing this through the callback st
ructure. The reply |
109 // object also handles waiting for the reply. | 168 // object also handles waiting for the reply. |
110 long startTimeMs = SystemClock.elapsedRealtime(); | 169 long startTimeMs = SystemClock.elapsedRealtime(); |
111 final SupervisedUserQueryReply queryReply = new SupervisedUserQueryReply
(); | 170 final SupervisedUserQueryReply queryReply = new SupervisedUserQueryReply
(); |
| 171 final long contentProvider = getSupervisedUserContentProvider(); |
| 172 if (contentProvider == 0) { |
| 173 return new WebRestrictionsResult( |
| 174 false, new int[] {FilteringBehaviorReason.NOT_SIGNED_IN}, nu
ll); |
| 175 } |
112 ThreadUtils.runOnUiThread(new Runnable() { | 176 ThreadUtils.runOnUiThread(new Runnable() { |
113 @Override | 177 @Override |
114 public void run() { | 178 public void run() { |
115 try { | 179 nativeShouldProceed(contentProvider, queryReply, url); |
116 nativeShouldProceed(getSupervisedUserContentProvider(), quer
yReply, url); | |
117 } catch (ProcessInitException e) { | |
118 queryReply.onQueryFailedNoErrorData(); | |
119 } | |
120 } | 180 } |
121 }); | 181 }); |
122 try { | 182 try { |
123 // This will block until an onQueryComplete call on a different thre
ad adds | 183 // This will block until an onQueryComplete call on a different thre
ad adds |
124 // something to the queue. | 184 // something to the queue. |
125 WebRestrictionsResult result = queryReply.getResult(); | 185 WebRestrictionsResult result = queryReply.getResult(); |
126 String histogramName = mChromeAlreadyStarted | 186 String histogramName = mChromeAlreadyStarted |
127 ? "SupervisedUserContentProvider.ChromeStartedRequestTime" | 187 ? "SupervisedUserContentProvider.ChromeStartedRequestTime" |
128 : "SupervisedUserContentProvider.ChromeNotStartedRequestTime
"; | 188 : "SupervisedUserContentProvider.ChromeNotStartedRequestTime
"; |
129 RecordHistogram.recordTimesHistogram(histogramName, | 189 RecordHistogram.recordTimesHistogram(histogramName, |
(...skipping 22 matching lines...) Expand all Loading... |
152 | 212 |
153 @Override | 213 @Override |
154 protected boolean requestInsert(final String url) { | 214 protected boolean requestInsert(final String url) { |
155 // This will be called on multiple threads (but never the UI thread), | 215 // This will be called on multiple threads (but never the UI thread), |
156 // see http://developer.android.com/guide/components/processes-and-threa
ds.html#ThreadSafe. | 216 // see http://developer.android.com/guide/components/processes-and-threa
ds.html#ThreadSafe. |
157 // The reply comes back on a different thread (possibly the UI thread) s
ome time later. | 217 // The reply comes back on a different thread (possibly the UI thread) s
ome time later. |
158 // As such it needs to correctly match the replies to the calls. It does
this by creating a | 218 // As such it needs to correctly match the replies to the calls. It does
this by creating a |
159 // reply object for each query, and passing this through the callback st
ructure. The reply | 219 // reply object for each query, and passing this through the callback st
ructure. The reply |
160 // object also handles waiting for the reply. | 220 // object also handles waiting for the reply. |
161 final SupervisedUserInsertReply insertReply = new SupervisedUserInsertRe
ply(); | 221 final SupervisedUserInsertReply insertReply = new SupervisedUserInsertRe
ply(); |
| 222 final long contentProvider = getSupervisedUserContentProvider(); |
| 223 if (contentProvider == 0) return false; |
162 ThreadUtils.runOnUiThread(new Runnable() { | 224 ThreadUtils.runOnUiThread(new Runnable() { |
163 @Override | 225 @Override |
164 public void run() { | 226 public void run() { |
165 try { | 227 nativeRequestInsert(contentProvider, insertReply, url); |
166 nativeRequestInsert(getSupervisedUserContentProvider(), inse
rtReply, url); | |
167 } catch (ProcessInitException e) { | |
168 insertReply.onInsertRequestSendComplete(false); | |
169 } | |
170 } | 228 } |
171 }); | 229 }); |
172 try { | 230 try { |
173 Boolean result = insertReply.getResult(); | 231 Boolean result = insertReply.getResult(); |
174 if (result == null) return false; | 232 if (result == null) return false; |
175 return result; | 233 return result; |
176 } catch (InterruptedException e) { | 234 } catch (InterruptedException e) { |
177 return false; | 235 return false; |
178 } | 236 } |
179 } | 237 } |
180 | 238 |
181 @Override | |
182 public Bundle call(String method, String arg, Bundle bundle) { | |
183 if (method.equals("setFilterForTesting")) setFilterForTesting(); | |
184 return null; | |
185 } | |
186 | |
187 void setFilterForTesting() { | |
188 ThreadUtils.runOnUiThreadBlocking(new Runnable() { | |
189 @Override | |
190 public void run() { | |
191 try { | |
192 nativeSetFilterForTesting(getSupervisedUserContentProvider()
); | |
193 } catch (ProcessInitException e) { | |
194 // There is no way of returning anything sensible here, so i
gnore the error and | |
195 // do nothing. | |
196 } | |
197 } | |
198 }); | |
199 } | |
200 | |
201 @CalledByNative | 239 @CalledByNative |
202 void onSupervisedUserFilterUpdated() { | 240 void onSupervisedUserFilterUpdated() { |
203 onFilterChanged(); | 241 onFilterChanged(); |
204 } | 242 } |
205 | 243 |
206 private static Boolean getEnabled() { | 244 private static Boolean getEnabled() { |
207 synchronized (sEnabledLock) { | 245 synchronized (sEnabledLock) { |
208 return sEnabled; | 246 return sEnabled; |
209 } | 247 } |
210 } | 248 } |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
242 @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) | 280 @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) |
243 private void updateEnabledState() { | 281 private void updateEnabledState() { |
244 // This method uses AppRestrictions directly, rather than using the Poli
cy interface, | 282 // This method uses AppRestrictions directly, rather than using the Poli
cy interface, |
245 // because it must be callable in contexts in which the native library h
asn't been | 283 // because it must be callable in contexts in which the native library h
asn't been |
246 // loaded. It will always be called from a background thread (except pos
sibly in tests) | 284 // loaded. It will always be called from a background thread (except pos
sibly in tests) |
247 // so can get the App Restrictions synchronously. | 285 // so can get the App Restrictions synchronously. |
248 UserManager userManager = (UserManager) getContext().getSystemService(Co
ntext.USER_SERVICE); | 286 UserManager userManager = (UserManager) getContext().getSystemService(Co
ntext.USER_SERVICE); |
249 Bundle appRestrictions = userManager | 287 Bundle appRestrictions = userManager |
250 .getApplicationRestrictions(getContext().getPackageName()); | 288 .getApplicationRestrictions(getContext().getPackageName()); |
251 setEnabled(appRestrictions.getBoolean(SUPERVISED_USER_CONTENT_PROVIDER_E
NABLED)); | 289 setEnabled(appRestrictions.getBoolean(SUPERVISED_USER_CONTENT_PROVIDER_E
NABLED)); |
252 }; | 290 } |
| 291 |
| 292 @Override |
| 293 protected String[] getErrorColumnNames() { |
| 294 String result[] = {"Reason", "Allow access requests", "Is child account"
, |
| 295 "Profile image URL", "Second profile image URL", "Custodian", "C
ustodian email", |
| 296 "Second custodian", "Second custodian email"}; |
| 297 return result; |
| 298 } |
| 299 |
| 300 // Helpers for testing. |
| 301 |
| 302 @Override |
| 303 public Bundle call(String method, String arg, Bundle bundle) { |
| 304 if (method.equals("setFilterForTesting")) setFilterForTesting(); |
| 305 return null; |
| 306 } |
| 307 |
| 308 void setFilterForTesting() { |
| 309 final long contentProvider = getSupervisedUserContentProvider(); |
| 310 if (contentProvider == 0) return; |
| 311 ThreadUtils.runOnUiThread(new Runnable() { |
| 312 @Override |
| 313 public void run() { |
| 314 nativeSetFilterForTesting(contentProvider); |
| 315 } |
| 316 }); |
| 317 } |
| 318 |
| 319 void setNativeSupervisedUserContentProviderForTesting(long nativeProvider) { |
| 320 mNativeSupervisedUserContentProvider = nativeProvider; |
| 321 } |
253 | 322 |
254 @VisibleForTesting | 323 @VisibleForTesting |
255 public static void enableContentProviderForTesting() { | 324 public static void enableContentProviderForTesting() { |
256 setEnabled(true); | 325 setEnabled(true); |
257 } | 326 } |
258 | 327 |
259 native long nativeCreateSupervisedUserContentProvider(); | 328 native long nativeCreateSupervisedUserContentProvider(); |
260 | 329 |
261 native void nativeShouldProceed(long nativeSupervisedUserContentProvider, | 330 native void nativeShouldProceed(long nativeSupervisedUserContentProvider, |
262 SupervisedUserQueryReply queryReply, String url); | 331 SupervisedUserQueryReply queryReply, String url); |
263 | 332 |
264 native void nativeRequestInsert(long nativeSupervisedUserContentProvider, | 333 native void nativeRequestInsert(long nativeSupervisedUserContentProvider, |
265 SupervisedUserInsertReply insertReply, String url); | 334 SupervisedUserInsertReply insertReply, String url); |
266 | 335 |
267 private native void nativeSetFilterForTesting(long nativeSupervisedUserConte
ntProvider); | 336 private native void nativeSetFilterForTesting(long nativeSupervisedUserConte
ntProvider); |
268 | 337 |
269 @Override | |
270 protected String[] getErrorColumnNames() { | |
271 String result[] = {"Reason", "Allow access requests", "Is child account"
, | |
272 "Profile image URL", "Second profile image URL", "Custodian", "C
ustodian email", | |
273 "Second custodian", "Second custodian email"}; | |
274 return result; | |
275 } | |
276 } | 338 } |
OLD | NEW |