Index: chrome/android/java/src/org/chromium/chrome/browser/partnercustomizations/PartnerBrowserCustomizations.java |
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/partnercustomizations/PartnerBrowserCustomizations.java b/chrome/android/java/src/org/chromium/chrome/browser/partnercustomizations/PartnerBrowserCustomizations.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..01ccb13012ae7e4d85156e28fb8dfc20a758ab51 |
--- /dev/null |
+++ b/chrome/android/java/src/org/chromium/chrome/browser/partnercustomizations/PartnerBrowserCustomizations.java |
@@ -0,0 +1,289 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+package org.chromium.chrome.browser.partnercustomizations; |
+ |
+import android.content.ContentResolver; |
+import android.content.Context; |
+import android.content.pm.ApplicationInfo; |
+import android.content.pm.ProviderInfo; |
+import android.database.Cursor; |
+import android.net.Uri; |
+import android.os.AsyncTask; |
+import android.text.TextUtils; |
+import android.util.Log; |
+ |
+import org.chromium.base.ThreadUtils; |
+import org.chromium.base.VisibleForTesting; |
+import org.chromium.chrome.browser.partnerbookmarks.PartnerBookmarksReader; |
+ |
+import java.util.ArrayList; |
+import java.util.List; |
+ |
+/** |
+ * Reads and caches partner browser customizations information if it exists. |
+ */ |
+public class PartnerBrowserCustomizations { |
+ private static final String TAG = "PartnerBrowserProvider"; |
+ private static final String PROVIDER_AUTHORITY = "com.android.partnerbrowsercustomizations"; |
+ |
+ // Private homepage structure. |
+ static final String PARTNER_HOMEPAGE_PATH = "homepage"; |
+ static final String PARTNER_DISABLE_BOOKMARKS_EDITING_PATH = "disablebookmarksediting"; |
+ @VisibleForTesting |
+ public static final String PARTNER_DISABLE_INCOGNITO_MODE_PATH = "disableincognitomode"; |
+ |
+ private static String sProviderAuthority = PROVIDER_AUTHORITY; |
+ private static boolean sIgnoreBrowserProviderSystemPackageCheck = false; |
+ private static volatile String sHomepage; |
+ private static volatile boolean sIncognitoModeDisabled; |
+ private static volatile boolean sBookmarksEditingDisabled; |
+ private static boolean sIsInitialized; |
+ private static List<Runnable> sInitializeAsyncCallbacks = new ArrayList<Runnable>(); |
+ |
+ /** |
+ * @return True if the partner homepage content provider exists and enabled. Note that The data |
+ * this method reads is not initialized until the asynchronous initialization of this |
+ * class has been completed. |
+ */ |
+ public static boolean isHomepageProviderAvailableAndEnabled() { |
+ return !TextUtils.isEmpty(getHomePageUrl()); |
+ } |
+ |
+ /** |
+ * @return Whether incognito mode is disabled by the partner. |
+ */ |
+ public static boolean isIncognitoDisabled() { |
+ return sIncognitoModeDisabled; |
+ } |
+ |
+ /** |
+ * @return Whether partner bookmarks editing is disabled by the partner. |
+ */ |
+ @VisibleForTesting |
+ static boolean isBookmarksEditingDisabled() { |
+ return sBookmarksEditingDisabled; |
+ } |
+ |
+ /** |
+ * @return True, if initialization is finished. Checking that there is no provider, or failing |
+ * to read provider is also considered initialization. |
+ */ |
+ @VisibleForTesting |
+ static boolean isInitialized() { |
+ return sIsInitialized; |
+ } |
+ |
+ @VisibleForTesting |
+ public static void setProviderAuthorityForTests(String providerAuthority) { |
+ sProviderAuthority = providerAuthority; |
+ } |
+ |
+ /** |
+ * For security, we only allow system package to be a browser customizations provider. However, |
+ * requiring root and installing system apk makes testing harder, so we decided to have this |
+ * hack for testing. This must not be called other than tests. |
+ * |
+ * @param ignore whether we should ignore browser provider system package checking. |
+ */ |
+ @VisibleForTesting |
+ public static void ignoreBrowserProviderSystemPackageCheckForTests(boolean ignore) { |
+ sIgnoreBrowserProviderSystemPackageCheck = ignore; |
+ } |
+ |
+ @VisibleForTesting |
+ public static Uri buildQueryUri(String path) { |
+ return new Uri.Builder() |
+ .scheme("content") |
+ .authority(sProviderAuthority) |
+ .appendPath(path) |
+ .build(); |
+ } |
+ |
+ /** |
+ * Constructs an async task that reads PartnerBrowserCustomization provider. |
+ * |
+ * @param context The current application context. |
+ * @param timeoutMs If initializing takes more than this time, cancels it. The unit is ms. |
+ */ |
+ public static void initializeAsync(final Context context, long timeoutMs) { |
+ sIsInitialized = false; |
+ // Setup an initializing async task. |
+ final AsyncTask<Void, Void, Void> initializeAsyncTask = |
+ new AsyncTask<Void, Void, Void>() { |
+ private boolean mDisablePartnerBookmarksShim; |
+ private boolean mHomepageUriChanged; |
+ |
+ private void refreshHomepage() { |
+ try { |
+ ContentResolver contentResolver = context.getContentResolver(); |
+ Cursor cursor = contentResolver.query( |
+ buildQueryUri(PARTNER_HOMEPAGE_PATH), null, null, null, null); |
+ if (cursor != null && cursor.moveToFirst() && cursor.getColumnCount() == 1 |
+ && !isCancelled()) { |
+ if (TextUtils.isEmpty(sHomepage) |
+ || !sHomepage.equals(cursor.getString(0))) { |
+ mHomepageUriChanged = true; |
+ } |
+ sHomepage = cursor.getString(0); |
+ } |
+ if (cursor != null) cursor.close(); |
+ } catch (Exception e) { |
+ Log.w(TAG, "Partner homepage provider URL read failed : ", e); |
+ } |
+ } |
+ |
+ private void refreshIncognitoModeDisabled() { |
+ try { |
+ ContentResolver contentResolver = context.getContentResolver(); |
+ Cursor cursor = contentResolver.query( |
+ buildQueryUri(PARTNER_DISABLE_INCOGNITO_MODE_PATH), |
+ null, null, null, null); |
+ if (cursor != null && cursor.moveToFirst() && cursor.getColumnCount() == 1 |
+ && !isCancelled()) { |
+ sIncognitoModeDisabled = cursor.getInt(0) == 1; |
+ } |
+ if (cursor != null) cursor.close(); |
+ } catch (Exception e) { |
+ Log.w(TAG, "Partner disable incognito mode read failed : ", e); |
+ } |
+ } |
+ |
+ private void refreshBookmarksEditingDisabled() { |
+ try { |
+ ContentResolver contentResolver = context.getContentResolver(); |
+ Cursor cursor = contentResolver.query( |
+ buildQueryUri(PARTNER_DISABLE_BOOKMARKS_EDITING_PATH), |
+ null, null, null, null); |
+ if (cursor != null && cursor.moveToFirst() && cursor.getColumnCount() == 1 |
+ && !isCancelled()) { |
+ boolean bookmarksEditingDisabled = cursor.getInt(0) == 1; |
+ if (bookmarksEditingDisabled != sBookmarksEditingDisabled) { |
+ mDisablePartnerBookmarksShim = true; |
+ } |
+ sBookmarksEditingDisabled = bookmarksEditingDisabled; |
+ } |
+ if (cursor != null) cursor.close(); |
+ } catch (Exception e) { |
+ Log.w(TAG, "Partner disable bookmarks editing read failed : ", e); |
+ } |
+ } |
+ |
+ @Override |
+ protected Void doInBackground(Void... params) { |
+ try { |
+ ProviderInfo providerInfo = context.getPackageManager() |
+ .resolveContentProvider(sProviderAuthority, 0); |
+ if (providerInfo == null) return null; |
+ |
+ if ((providerInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0 |
+ && !sIgnoreBrowserProviderSystemPackageCheck) { |
+ Log.w("TAG", "Browser Cutomizations content provider package, " |
+ + providerInfo.packageName + ", is not a system package. " |
+ + "This could be a malicious attepment from a third party app, " |
+ + "so skip reading the browser content provider."); |
+ return null; |
+ } |
+ |
+ if (isCancelled()) return null; |
+ refreshIncognitoModeDisabled(); |
+ |
+ if (isCancelled()) return null; |
+ refreshBookmarksEditingDisabled(); |
+ |
+ if (isCancelled()) return null; |
+ refreshHomepage(); |
+ } catch (Exception e) { |
+ Log.w(TAG, "Fetching partner customizations failed", e); |
+ } |
+ return null; |
+ } |
+ |
+ @Override |
+ protected void onPostExecute(Void result) { |
+ onFinalized(); |
+ } |
+ |
+ @Override |
+ protected void onCancelled(Void result) { |
+ onFinalized(); |
+ } |
+ |
+ private void onFinalized() { |
+ sIsInitialized = true; |
+ |
+ for (Runnable callback : sInitializeAsyncCallbacks) { |
+ callback.run(); |
+ } |
+ sInitializeAsyncCallbacks.clear(); |
+ |
+ if (mHomepageUriChanged) { |
+ HomepageManager.getInstance(context).notifyHomepageUpdated(); |
+ } |
+ |
+ // Disable partner bookmarks editing if necessary. |
+ if (mDisablePartnerBookmarksShim) { |
+ PartnerBookmarksReader.disablePartnerBookmarksEditing(); |
+ } |
+ } |
+ }; |
+ |
+ initializeAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
+ |
+ // Cancel the initialization if it reaches timeout. |
+ ThreadUtils.postOnUiThreadDelayed(new Runnable() { |
+ @Override |
+ public void run() { |
+ initializeAsyncTask.cancel(true); |
+ } |
+ }, timeoutMs); |
+ } |
+ |
+ /** |
+ * Sets a callback that will be executed when the initialization is done. |
+ * |
+ * @param callback This is called when the initialization is done. |
+ */ |
+ public static void setOnInitializeAsyncFinished(final Runnable callback) { |
+ if (sIsInitialized) { |
+ ThreadUtils.postOnUiThread(callback); |
+ } else { |
+ sInitializeAsyncCallbacks.add(callback); |
+ } |
+ } |
+ |
+ /** |
+ * Sets a callback that will be executed when the initialization is done. |
+ * |
+ * @param callback This is called when the initialization is done. |
+ * @param timeoutMs If initializing takes more than this time since this function is called, |
+ * force run |callback| early. The unit is ms. |
+ */ |
+ public static void setOnInitializeAsyncFinished(final Runnable callback, long timeoutMs) { |
+ sInitializeAsyncCallbacks.add(callback); |
+ |
+ ThreadUtils.postOnUiThreadDelayed( |
+ new Runnable() { |
+ @Override |
+ public void run() { |
+ if (sInitializeAsyncCallbacks.remove(callback)) callback.run(); |
+ } |
+ }, |
+ sIsInitialized ? 0 : timeoutMs); |
+ } |
+ |
+ public static void destroy() { |
+ sIsInitialized = false; |
+ sInitializeAsyncCallbacks.clear(); |
+ sHomepage = null; |
+ } |
+ |
+ /** |
+ * @return Home page URL from Android provider. If null, that means either there is no homepage |
+ * provider or provider set it to null to disable homepage. |
+ */ |
+ public static String getHomePageUrl() { |
+ return sHomepage; |
+ } |
+} |