[go: nahoru, domu]

[Crash Reporting] Only upload Chrome crash reports over unmetered networks.

BUG=702454
TEST=none
R=gsennton@chromium.org, mariakhomenko@chromium.org

Review-Url: https://codereview.chromium.org/2751333004
Cr-Commit-Position: refs/heads/master@{#460227}
diff --git a/android_webview/java/src/org/chromium/android_webview/crash/AwMinidumpUploaderDelegate.java b/android_webview/java/src/org/chromium/android_webview/crash/AwMinidumpUploaderDelegate.java
index 1fe38fb..9be1b77 100644
--- a/android_webview/java/src/org/chromium/android_webview/crash/AwMinidumpUploaderDelegate.java
+++ b/android_webview/java/src/org/chromium/android_webview/crash/AwMinidumpUploaderDelegate.java
@@ -6,7 +6,6 @@
 
 import android.content.Context;
 import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
 import android.webkit.ValueCallback;
 
 import org.chromium.android_webview.PlatformServiceBridge;
@@ -16,6 +15,7 @@
 import org.chromium.base.VisibleForTesting;
 import org.chromium.components.minidump_uploader.MinidumpUploaderDelegate;
 import org.chromium.components.minidump_uploader.util.CrashReportingPermissionManager;
+import org.chromium.components.minidump_uploader.util.NetworkPermissionUtil;
 
 import java.io.File;
 
@@ -54,14 +54,10 @@
             }
             @Override
             public boolean isNetworkAvailableForCrashUploads() {
-                // JobScheduler will call onStopJob causing our upload to be interrupted when our
-                // network requirements no longer hold.
-                // TODO(isherman): This code should really be shared with Chrome. Chrome currently
-                // checks only whether the network is WiFi (or ethernet) vs. cellular. Most likely,
-                // Chrome should instead check whether the network is metered, as is done here.
-                NetworkInfo networkInfo = mConnectivityManager.getActiveNetworkInfo();
-                if (networkInfo == null || !networkInfo.isConnected()) return false;
-                return !mConnectivityManager.isActiveNetworkMetered();
+                // Note that this is the same critierion that the JobScheduler uses to schedule the
+                // job. JobScheduler will call onStopJob causing our upload to be interrupted when
+                // our network requirements no longer hold.
+                return NetworkPermissionUtil.isNetworkUnmetered(mConnectivityManager);
             }
             @Override
             public boolean isUsageAndCrashReportingPermittedByUser() {
diff --git a/android_webview/java/src/org/chromium/android_webview/crash/CrashReceiverService.java b/android_webview/java/src/org/chromium/android_webview/crash/CrashReceiverService.java
index 22baa75..a84bda3 100644
--- a/android_webview/java/src/org/chromium/android_webview/crash/CrashReceiverService.java
+++ b/android_webview/java/src/org/chromium/android_webview/crash/CrashReceiverService.java
@@ -99,11 +99,8 @@
     }
 
     private void scheduleNewJob() {
-        JobInfo.Builder builder =
-                new JobInfo
-                        .Builder(TaskIds.WEBVIEW_MINIDUMP_UPLOADING_JOB_ID,
-                                new ComponentName(this, AwMinidumpUploadJobService.class))
-                        .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
+        JobInfo.Builder builder = new JobInfo.Builder(TaskIds.WEBVIEW_MINIDUMP_UPLOADING_JOB_ID,
+                new ComponentName(this, AwMinidumpUploadJobService.class));
         MinidumpUploadJobService.scheduleUpload(this, builder);
     }
 
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 0b67e34..fdf17dab 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -345,6 +345,7 @@
     "//chrome/test/android:chrome_java_test_support",
     "//components/bookmarks/common/android:bookmarks_java",
     "//components/invalidation/impl:java",
+    "//components/minidump_uploader:minidump_uploader_java",
     "//components/payments/content:mojom_java",
     "//components/payments/content:mojom_parser_java",
     "//components/payments/content/android:java",
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/crash/ChromeMinidumpUploaderDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/crash/ChromeMinidumpUploaderDelegate.java
index 21b6e1d..8e0ec0a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/crash/ChromeMinidumpUploaderDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/crash/ChromeMinidumpUploaderDelegate.java
@@ -6,13 +6,13 @@
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
 import android.os.Build;
 import android.os.PersistableBundle;
 
 import org.chromium.chrome.browser.preferences.privacy.PrivacyPreferencesManager;
 import org.chromium.components.minidump_uploader.MinidumpUploaderDelegate;
 import org.chromium.components.minidump_uploader.util.CrashReportingPermissionManager;
+import org.chromium.components.minidump_uploader.util.NetworkPermissionUtil;
 
 import java.io.File;
 
@@ -68,15 +68,7 @@
 
             @Override
             public boolean isNetworkAvailableForCrashUploads() {
-                // TODO(isherman): This code should really be shared with the Android Webview
-                // implementation, which tests whether the connection is metered, rather than
-                // testing the type of the connection. Implement this change in M59 -- for M58, it's
-                // more important to maintain consistency with the previous implementation. When
-                // changing this, note that forced uploads do *not* require unmetered connections.
-                NetworkInfo networkInfo = mConnectivityManager.getActiveNetworkInfo();
-                if (networkInfo == null || !networkInfo.isConnected()) return false;
-                return networkInfo.getType() == ConnectivityManager.TYPE_WIFI
-                        || networkInfo.getType() == ConnectivityManager.TYPE_ETHERNET;
+                return NetworkPermissionUtil.isNetworkUnmetered(mConnectivityManager);
             }
 
             @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/crash/MinidumpUploadService.java b/chrome/android/java/src/org/chromium/chrome/browser/crash/MinidumpUploadService.java
index 7d9b699..a58990d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/crash/MinidumpUploadService.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/crash/MinidumpUploadService.java
@@ -105,7 +105,6 @@
                 new JobInfo
                         .Builder(TaskIds.CHROME_MINIDUMP_UPLOADING_JOB_ID,
                                 new ComponentName(context, ChromeMinidumpUploadJobService.class))
-                        .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
                         .setExtras(permissions);
         MinidumpUploadJobService.scheduleUpload(context, builder);
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/privacy/PrivacyPreferencesManager.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/privacy/PrivacyPreferencesManager.java
index 3b0e921..7572865 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/privacy/PrivacyPreferencesManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/privacy/PrivacyPreferencesManager.java
@@ -17,6 +17,7 @@
 import org.chromium.chrome.browser.physicalweb.PhysicalWeb;
 import org.chromium.chrome.browser.preferences.PrefServiceBridge;
 import org.chromium.components.minidump_uploader.util.CrashReportingPermissionManager;
+import org.chromium.components.minidump_uploader.util.NetworkPermissionUtil;
 
 /**
  * Reads, writes, and migrates preferences related to network usage and privacy.
@@ -184,24 +185,13 @@
         sharedPreferencesEditor.apply();
     }
 
-    private NetworkInfo getActiveNetworkInfo() {
+    protected boolean isNetworkAvailable() {
         ConnectivityManager connectivityManager =
                 (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
-        return connectivityManager.getActiveNetworkInfo();
-    }
-
-    protected boolean isNetworkAvailable() {
-        NetworkInfo networkInfo = getActiveNetworkInfo();
+        NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
         return (networkInfo != null && networkInfo.isConnected());
     }
 
-    protected boolean isWiFiOrEthernetNetwork() {
-        NetworkInfo networkInfo = getActiveNetworkInfo();
-        return networkInfo != null
-                && (networkInfo.getType() == ConnectivityManager.TYPE_WIFI
-                        || networkInfo.getType() == ConnectivityManager.TYPE_ETHERNET);
-    }
-
     protected boolean isMobileNetworkCapable() {
         ConnectivityManager connectivityManager =
                 (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
@@ -274,7 +264,9 @@
      */
     @Override
     public boolean isNetworkAvailableForCrashUploads() {
-        return isNetworkAvailable() && isWiFiOrEthernetNetwork();
+        ConnectivityManager connectivityManager =
+                (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+        return NetworkPermissionUtil.isNetworkUnmetered(connectivityManager);
     }
 
     /**
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index 622b432..af83c832 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -1477,7 +1477,6 @@
   "javatests/src/org/chromium/chrome/browser/preferences/datareduction/DataReductionPromoUtilsTest.java",
   "javatests/src/org/chromium/chrome/browser/preferences/password/SavePasswordsPreferencesTest.java",
   "javatests/src/org/chromium/chrome/browser/preferences/privacy/ClearBrowsingDataPreferencesTest.java",
-  "javatests/src/org/chromium/chrome/browser/preferences/privacy/PrivacyPreferencesManagerTest.java",
   "javatests/src/org/chromium/chrome/browser/preferences/privacy/PrivacyPreferencesManagerNativeTest.java",
   "javatests/src/org/chromium/chrome/browser/preferences/website/ManageSpaceActivityTest.java",
   "javatests/src/org/chromium/chrome/browser/preferences/website/SiteSettingsPreferencesTest.java",
@@ -1634,6 +1633,7 @@
   "junit/src/org/chromium/chrome/browser/physicalweb/PwsClientImplTest.java",
   "junit/src/org/chromium/chrome/browser/physicalweb/PwsResultTest.java",
   "junit/src/org/chromium/chrome/browser/physicalweb/UrlInfoTest.java",
+  "junit/src/org/chromium/chrome/browser/preferences/privacy/PrivacyPreferencesManagerTest.java",
   "junit/src/org/chromium/chrome/browser/snackbar/SnackbarCollectionUnitTest.java",
   "junit/src/org/chromium/chrome/browser/suggestions/TileGroupTest.java",
   "junit/src/org/chromium/chrome/browser/suggestions/TileTest.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/preferences/privacy/PrivacyPreferencesManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/preferences/privacy/PrivacyPreferencesManagerTest.java
deleted file mode 100644
index 6e19851..0000000
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/preferences/privacy/PrivacyPreferencesManagerTest.java
+++ /dev/null
@@ -1,121 +0,0 @@
-// 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.preferences.privacy;
-
-import android.content.Context;
-import android.support.test.filters.SmallTest;
-import android.test.InstrumentationTestCase;
-import android.test.UiThreadTest;
-
-import org.chromium.base.CommandLine;
-import org.chromium.base.ContextUtils;
-import org.chromium.base.test.util.AdvancedMockContext;
-import org.chromium.base.test.util.Feature;
-
-/**
- *  Tests "Usage and Crash reporting" preferences.
- */
-public class PrivacyPreferencesManagerTest extends InstrumentationTestCase {
-
-    private static final boolean CONNECTED = true;
-    private static final boolean DISCONNECTED = false;
-
-    private static final boolean WIFI_ON = true;
-    private static final boolean WIFI_OFF = false;
-
-    private static final boolean METRICS_UPLOAD_OK = true;
-    private static final boolean METRICS_UPLOAD_NOT_PERMITTED = false;
-
-    private static final boolean CRASH_NETWORK_OK = true;
-    private static final boolean CRASH_NETWORK_NOT_PERMITTED = false;
-
-    private static final boolean METRIC_REPORTING_ENABLED = true;
-    private static final boolean METRIC_REPORTING_DISABLED = false;
-
-    // Perform the same test a few times to make sure any sort of
-    // caching still works.
-    private static final int REPS = 3;
-
-    @SmallTest
-    @Feature({"Android-AppBase"})
-    @UiThreadTest
-    public void testAllowCrashDumpUploadNowCellDev() {
-        CommandLine.init(null);
-        runTest(CONNECTED, WIFI_ON, METRIC_REPORTING_ENABLED, METRICS_UPLOAD_OK, CRASH_NETWORK_OK);
-        runTest(CONNECTED, WIFI_OFF, METRIC_REPORTING_ENABLED, METRICS_UPLOAD_OK,
-                CRASH_NETWORK_NOT_PERMITTED);
-        runTest(DISCONNECTED, WIFI_OFF, METRIC_REPORTING_ENABLED, METRICS_UPLOAD_NOT_PERMITTED,
-                CRASH_NETWORK_NOT_PERMITTED);
-
-        runTest(CONNECTED, WIFI_ON, METRIC_REPORTING_DISABLED, METRICS_UPLOAD_NOT_PERMITTED,
-                CRASH_NETWORK_OK);
-        runTest(CONNECTED, WIFI_OFF, METRIC_REPORTING_DISABLED, METRICS_UPLOAD_NOT_PERMITTED,
-                CRASH_NETWORK_NOT_PERMITTED);
-        runTest(DISCONNECTED, WIFI_OFF, METRIC_REPORTING_DISABLED, METRICS_UPLOAD_NOT_PERMITTED,
-                CRASH_NETWORK_NOT_PERMITTED);
-    }
-
-    private void runTest(boolean isConnected, boolean wifiOn, boolean isMetricsReportingEnabled,
-            boolean expectedMetricsUploadPermitted,
-            boolean expectedNetworkAvailableForCrashUploads) {
-        PermissionContext context = new PermissionContext(getInstrumentation().getTargetContext());
-        ContextUtils.initApplicationContextForTests(context.getApplicationContext());
-        PrivacyPreferencesManager preferenceManager = new MockPrivacyPreferencesManager(
-                context, isConnected, wifiOn, isMetricsReportingEnabled);
-
-        for (int i = 0; i < REPS; i++) {
-            String state = String.format("[connected = %b, wifi = %b, reporting = %b]", isConnected,
-                    wifiOn, isMetricsReportingEnabled);
-            String msg = String.format("Metrics reporting should be %1$b for %2$s",
-                    expectedMetricsUploadPermitted, state);
-            assertEquals(msg, expectedMetricsUploadPermitted,
-                    preferenceManager.isMetricsUploadPermitted());
-
-            msg = String.format("Crash reporting should be %1$b for wifi %2$s",
-                    expectedNetworkAvailableForCrashUploads, wifiOn);
-            assertEquals(msg, expectedNetworkAvailableForCrashUploads,
-                    preferenceManager.isNetworkAvailableForCrashUploads());
-        }
-    }
-
-    private static class MockPrivacyPreferencesManager extends PrivacyPreferencesManager {
-        private final boolean mIsConnected;
-        private final boolean mIsWifi;
-
-        MockPrivacyPreferencesManager(Context context, boolean isConnected, boolean isWifi,
-                boolean isMetricsReportingEnabled) {
-            super(context);
-            mIsConnected = isConnected;
-            mIsWifi = isWifi;
-
-            setUsageAndCrashReporting(isMetricsReportingEnabled);
-        }
-
-        @Override
-        public boolean isNetworkAvailable() {
-            return mIsConnected;
-        }
-
-        @Override
-        public boolean isWiFiOrEthernetNetwork() {
-            return mIsWifi;
-        }
-    }
-
-    private static class PermissionContext extends AdvancedMockContext {
-        public PermissionContext(Context targetContext) {
-            super(targetContext);
-        }
-
-        @Override
-        public Object getSystemService(String name) {
-            if (Context.CONNECTIVITY_SERVICE.equals(name)) {
-                return null;
-            }
-            fail("Should not ask for any other service than the ConnectionManager.");
-            return super.getSystemService(name);
-        }
-    }
-}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/preferences/privacy/PrivacyPreferencesManagerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/preferences/privacy/PrivacyPreferencesManagerTest.java
new file mode 100644
index 0000000..f64216f
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/preferences/privacy/PrivacyPreferencesManagerTest.java
@@ -0,0 +1,111 @@
+// 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.preferences.privacy;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.CommandLine;
+import org.chromium.base.ContextUtils;
+import org.chromium.testing.local.LocalRobolectricTestRunner;
+
+/**
+ * junit tests for {@link PrivacyPreferencesManager}'s handling of "Usage and Crash reporting"
+ * preferences.
+ */
+@RunWith(LocalRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class PrivacyPreferencesManagerTest {
+    // Parameters to simulate user- and network-permission state.
+    private static final boolean CONNECTED = true;
+    private static final boolean DISCONNECTED = false;
+
+    private static final boolean METERED = true;
+    private static final boolean UNMETERED = false;
+
+    private static final boolean METRICS_REPORTING_ENABLED = true;
+    private static final boolean METRICS_REPORTING_DISABLED = false;
+
+    // Used to set test expectations.
+    private static final boolean METRICS_UPLOAD_PERMITTED = true;
+    private static final boolean METRICS_UPLOAD_NOT_PERMITTED = false;
+
+    private static final boolean CRASH_NETWORK_AVAILABLE = true;
+    private static final boolean CRASH_NETWORK_UNAVAILABLE = false;
+
+    @Test
+    public void testUsageAndCrashReportingAccessors() {
+        CommandLine.init(null);
+
+        // TODO(yolandyan): Use Junit4 parameters to clean up this test structure.
+        runTest(CONNECTED, UNMETERED, METRICS_REPORTING_ENABLED, METRICS_UPLOAD_PERMITTED,
+                CRASH_NETWORK_AVAILABLE);
+        runTest(CONNECTED, METERED, METRICS_REPORTING_ENABLED, METRICS_UPLOAD_PERMITTED,
+                CRASH_NETWORK_UNAVAILABLE);
+        runTest(DISCONNECTED, UNMETERED, METRICS_REPORTING_ENABLED, METRICS_UPLOAD_NOT_PERMITTED,
+                CRASH_NETWORK_UNAVAILABLE);
+
+        runTest(CONNECTED, UNMETERED, METRICS_REPORTING_DISABLED, METRICS_UPLOAD_NOT_PERMITTED,
+                CRASH_NETWORK_AVAILABLE);
+        runTest(CONNECTED, METERED, METRICS_REPORTING_DISABLED, METRICS_UPLOAD_NOT_PERMITTED,
+                CRASH_NETWORK_UNAVAILABLE);
+        runTest(DISCONNECTED, UNMETERED, METRICS_REPORTING_DISABLED, METRICS_UPLOAD_NOT_PERMITTED,
+                CRASH_NETWORK_UNAVAILABLE);
+    }
+
+    private void runTest(boolean isConnected, boolean isMetered, boolean isMetricsReportingEnabled,
+            boolean expectedMetricsUploadPermitted,
+            boolean expectedNetworkAvailableForCrashUploads) {
+        // Mock out the network info accessors.
+        NetworkInfo networkInfo = mock(NetworkInfo.class);
+        when(networkInfo.isConnected()).thenReturn(isConnected);
+
+        ConnectivityManager connectivityManager = mock(ConnectivityManager.class);
+        when(connectivityManager.getActiveNetworkInfo()).thenReturn(networkInfo);
+        when(connectivityManager.isActiveNetworkMetered()).thenReturn(isMetered);
+
+        Context context = mock(Context.class);
+        when(context.getSystemService(Context.CONNECTIVITY_SERVICE))
+                .thenReturn(connectivityManager);
+
+        // Perform other setup.
+        ContextUtils.initApplicationContextForTests(RuntimeEnvironment.application);
+        PrivacyPreferencesManager preferenceManager = new TestPrivacyPreferencesManager(context);
+        preferenceManager.setUsageAndCrashReporting(isMetricsReportingEnabled);
+
+        // Test the usage and crash reporting permission accessors!
+        String state = String.format("[connected = %b, metered = %b, reporting = %b]", isConnected,
+                isMetered, isMetricsReportingEnabled);
+        String msg = String.format(
+                "Metrics reporting should be %1$b for %2$s", expectedMetricsUploadPermitted, state);
+        assertEquals(
+                msg, expectedMetricsUploadPermitted, preferenceManager.isMetricsUploadPermitted());
+
+        msg = String.format("Crash reporting should be %1$b for metered state %2$s",
+                expectedNetworkAvailableForCrashUploads, isMetered);
+        assertEquals(msg, expectedNetworkAvailableForCrashUploads,
+                preferenceManager.isNetworkAvailableForCrashUploads());
+    }
+
+    private static class TestPrivacyPreferencesManager extends PrivacyPreferencesManager {
+        TestPrivacyPreferencesManager(Context context) {
+            super(context);
+        }
+
+        // Stub out this call, as it could otherwise call into native code.
+        public void syncUsageAndCrashReportingPrefs() {}
+    }
+}
diff --git a/components/minidump_uploader/BUILD.gn b/components/minidump_uploader/BUILD.gn
index fd469b7b9..c017ccd3 100644
--- a/components/minidump_uploader/BUILD.gn
+++ b/components/minidump_uploader/BUILD.gn
@@ -20,6 +20,7 @@
     "android/java/src/org/chromium/components/minidump_uploader/util/CrashReportingPermissionManager.java",
     "android/java/src/org/chromium/components/minidump_uploader/util/HttpURLConnectionFactory.java",
     "android/java/src/org/chromium/components/minidump_uploader/util/HttpURLConnectionFactoryImpl.java",
+    "android/java/src/org/chromium/components/minidump_uploader/util/NetworkPermissionUtil.java",
   ]
 }
 
diff --git a/components/minidump_uploader/android/java/src/org/chromium/components/minidump_uploader/MinidumpUploadJobService.java b/components/minidump_uploader/android/java/src/org/chromium/components/minidump_uploader/MinidumpUploadJobService.java
index 9e29f12..4ad29bf 100644
--- a/components/minidump_uploader/android/java/src/org/chromium/components/minidump_uploader/MinidumpUploadJobService.java
+++ b/components/minidump_uploader/android/java/src/org/chromium/components/minidump_uploader/MinidumpUploadJobService.java
@@ -50,7 +50,7 @@
         JobScheduler scheduler =
                 (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
         JobInfo uploadJob =
-                jobInfoBuilder
+                jobInfoBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
                         .setBackoffCriteria(JOB_INITIAL_BACKOFF_TIME_IN_MS, JOB_BACKOFF_POLICY)
                         .build();
         int result = scheduler.schedule(uploadJob);
diff --git a/components/minidump_uploader/android/java/src/org/chromium/components/minidump_uploader/util/NetworkPermissionUtil.java b/components/minidump_uploader/android/java/src/org/chromium/components/minidump_uploader/util/NetworkPermissionUtil.java
new file mode 100644
index 0000000..829e5f4
--- /dev/null
+++ b/components/minidump_uploader/android/java/src/org/chromium/components/minidump_uploader/util/NetworkPermissionUtil.java
@@ -0,0 +1,20 @@
+// Copyright 2017 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.components.minidump_uploader.util;
+
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+
+/**
+ * A container for determining whether it's ok to upload crash reports over the currently active
+ * network.
+ */
+public class NetworkPermissionUtil {
+    public static boolean isNetworkUnmetered(ConnectivityManager connectivityManager) {
+        NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
+        if (networkInfo == null || !networkInfo.isConnected()) return false;
+        return !connectivityManager.isActiveNetworkMetered();
+    }
+}