Merge "DPM changes to support remote bugreports"
diff --git a/api/current.txt b/api/current.txt
index 90228a9..0f3b099 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5737,6 +5737,9 @@
ctor public DeviceAdminReceiver();
method public android.app.admin.DevicePolicyManager getManager(android.content.Context);
method public android.content.ComponentName getWho(android.content.Context);
+ method public void onBugreportFailed(android.content.Context, android.content.Intent, int);
+ method public void onBugreportShared(android.content.Context, android.content.Intent, java.lang.String);
+ method public void onBugreportSharingDeclined(android.content.Context, android.content.Intent);
method public java.lang.String onChoosePrivateKeyAlias(android.content.Context, android.content.Intent, int, android.net.Uri, java.lang.String);
method public java.lang.CharSequence onDisableRequested(android.content.Context, android.content.Intent);
method public void onDisabled(android.content.Context, android.content.Intent);
@@ -5761,11 +5764,16 @@
field public static final java.lang.String ACTION_PASSWORD_FAILED = "android.app.action.ACTION_PASSWORD_FAILED";
field public static final java.lang.String ACTION_PASSWORD_SUCCEEDED = "android.app.action.ACTION_PASSWORD_SUCCEEDED";
field public static final java.lang.String ACTION_PROFILE_PROVISIONING_COMPLETE = "android.app.action.PROFILE_PROVISIONING_COMPLETE";
+ field public static final int BUGREPORT_FAILURE_FAILED_COMPLETING = 0; // 0x0
+ field public static final int BUGREPORT_FAILURE_FILE_NO_LONGER_AVAILABLE = 1; // 0x1
field public static final java.lang.String DEVICE_ADMIN_META_DATA = "android.app.device_admin";
field public static final java.lang.String EXTRA_DISABLE_WARNING = "android.app.extra.DISABLE_WARNING";
field public static final java.lang.String EXTRA_LOCK_TASK_PACKAGE = "android.app.extra.LOCK_TASK_PACKAGE";
}
+ public static abstract class DeviceAdminReceiver.BugreportFailureCode implements java.lang.annotation.Annotation {
+ }
+
public class DevicePolicyManager {
method public void addCrossProfileIntentFilter(android.content.ComponentName, android.content.IntentFilter, int);
method public boolean addCrossProfileWidgetProvider(android.content.ComponentName, java.lang.String);
@@ -5844,6 +5852,7 @@
method public boolean removeCrossProfileWidgetProvider(android.content.ComponentName, java.lang.String);
method public boolean removeKeyPair(android.content.ComponentName, java.lang.String);
method public boolean removeUser(android.content.ComponentName, android.os.UserHandle);
+ method public boolean requestBugreport(android.content.ComponentName);
method public boolean resetPassword(java.lang.String, int);
method public void setAccountManagementDisabled(android.content.ComponentName, java.lang.String, boolean);
method public boolean setAlwaysOnVpnPackage(android.content.ComponentName, java.lang.String);
diff --git a/api/system-current.txt b/api/system-current.txt
index a194e6e..360a40b 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5870,6 +5870,9 @@
ctor public DeviceAdminReceiver();
method public android.app.admin.DevicePolicyManager getManager(android.content.Context);
method public android.content.ComponentName getWho(android.content.Context);
+ method public void onBugreportFailed(android.content.Context, android.content.Intent, int);
+ method public void onBugreportShared(android.content.Context, android.content.Intent, java.lang.String);
+ method public void onBugreportSharingDeclined(android.content.Context, android.content.Intent);
method public java.lang.String onChoosePrivateKeyAlias(android.content.Context, android.content.Intent, int, android.net.Uri, java.lang.String);
method public java.lang.CharSequence onDisableRequested(android.content.Context, android.content.Intent);
method public void onDisabled(android.content.Context, android.content.Intent);
@@ -5894,11 +5897,16 @@
field public static final java.lang.String ACTION_PASSWORD_FAILED = "android.app.action.ACTION_PASSWORD_FAILED";
field public static final java.lang.String ACTION_PASSWORD_SUCCEEDED = "android.app.action.ACTION_PASSWORD_SUCCEEDED";
field public static final java.lang.String ACTION_PROFILE_PROVISIONING_COMPLETE = "android.app.action.PROFILE_PROVISIONING_COMPLETE";
+ field public static final int BUGREPORT_FAILURE_FAILED_COMPLETING = 0; // 0x0
+ field public static final int BUGREPORT_FAILURE_FILE_NO_LONGER_AVAILABLE = 1; // 0x1
field public static final java.lang.String DEVICE_ADMIN_META_DATA = "android.app.device_admin";
field public static final java.lang.String EXTRA_DISABLE_WARNING = "android.app.extra.DISABLE_WARNING";
field public static final java.lang.String EXTRA_LOCK_TASK_PACKAGE = "android.app.extra.LOCK_TASK_PACKAGE";
}
+ public static abstract class DeviceAdminReceiver.BugreportFailureCode implements java.lang.annotation.Annotation {
+ }
+
public class DevicePolicyManager {
method public void addCrossProfileIntentFilter(android.content.ComponentName, android.content.IntentFilter, int);
method public boolean addCrossProfileWidgetProvider(android.content.ComponentName, java.lang.String);
@@ -5986,6 +5994,7 @@
method public boolean removeCrossProfileWidgetProvider(android.content.ComponentName, java.lang.String);
method public boolean removeKeyPair(android.content.ComponentName, java.lang.String);
method public boolean removeUser(android.content.ComponentName, android.os.UserHandle);
+ method public boolean requestBugreport(android.content.ComponentName);
method public boolean resetPassword(java.lang.String, int);
method public void setAccountManagementDisabled(android.content.ComponentName, java.lang.String, boolean);
method public deprecated boolean setActiveProfileOwner(android.content.ComponentName, java.lang.String) throws java.lang.IllegalArgumentException;
diff --git a/api/test-current.txt b/api/test-current.txt
index 3c271f3..fb11fcc 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -5739,6 +5739,9 @@
ctor public DeviceAdminReceiver();
method public android.app.admin.DevicePolicyManager getManager(android.content.Context);
method public android.content.ComponentName getWho(android.content.Context);
+ method public void onBugreportFailed(android.content.Context, android.content.Intent, int);
+ method public void onBugreportShared(android.content.Context, android.content.Intent, java.lang.String);
+ method public void onBugreportSharingDeclined(android.content.Context, android.content.Intent);
method public java.lang.String onChoosePrivateKeyAlias(android.content.Context, android.content.Intent, int, android.net.Uri, java.lang.String);
method public java.lang.CharSequence onDisableRequested(android.content.Context, android.content.Intent);
method public void onDisabled(android.content.Context, android.content.Intent);
@@ -5763,11 +5766,16 @@
field public static final java.lang.String ACTION_PASSWORD_FAILED = "android.app.action.ACTION_PASSWORD_FAILED";
field public static final java.lang.String ACTION_PASSWORD_SUCCEEDED = "android.app.action.ACTION_PASSWORD_SUCCEEDED";
field public static final java.lang.String ACTION_PROFILE_PROVISIONING_COMPLETE = "android.app.action.PROFILE_PROVISIONING_COMPLETE";
+ field public static final int BUGREPORT_FAILURE_FAILED_COMPLETING = 0; // 0x0
+ field public static final int BUGREPORT_FAILURE_FILE_NO_LONGER_AVAILABLE = 1; // 0x1
field public static final java.lang.String DEVICE_ADMIN_META_DATA = "android.app.device_admin";
field public static final java.lang.String EXTRA_DISABLE_WARNING = "android.app.extra.DISABLE_WARNING";
field public static final java.lang.String EXTRA_LOCK_TASK_PACKAGE = "android.app.extra.LOCK_TASK_PACKAGE";
}
+ public static abstract class DeviceAdminReceiver.BugreportFailureCode implements java.lang.annotation.Annotation {
+ }
+
public class DevicePolicyManager {
method public void addCrossProfileIntentFilter(android.content.ComponentName, android.content.IntentFilter, int);
method public boolean addCrossProfileWidgetProvider(android.content.ComponentName, java.lang.String);
@@ -5846,6 +5854,7 @@
method public boolean removeCrossProfileWidgetProvider(android.content.ComponentName, java.lang.String);
method public boolean removeKeyPair(android.content.ComponentName, java.lang.String);
method public boolean removeUser(android.content.ComponentName, android.os.UserHandle);
+ method public boolean requestBugreport(android.content.ComponentName);
method public boolean resetPassword(java.lang.String, int);
method public void setAccountManagementDisabled(android.content.ComponentName, java.lang.String, boolean);
method public boolean setAlwaysOnVpnPackage(android.content.ComponentName, java.lang.String);
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index 84b6d39..1c4dcf7 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -17,6 +17,7 @@
package android.app.admin;
import android.accounts.AccountManager;
+import android.annotation.IntDef;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
@@ -29,6 +30,9 @@
import android.os.Bundle;
import android.security.KeyChain;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Base class for implementing a device administration component. This
* class provides a convenience for interpreting the raw intent actions
@@ -227,6 +231,75 @@
public static final String ACTION_PROFILE_PROVISIONING_COMPLETE =
"android.app.action.PROFILE_PROVISIONING_COMPLETE";
+ /**
+ * Action sent to a device administrator to notify that the device user
+ * has declined sharing a bugreport.
+ *
+ * <p>The calling device admin must be the device owner to receive this broadcast.
+ * @see DevicePolicyManager#requestBugreport
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_BUGREPORT_SHARING_DECLINED =
+ "android.app.action.BUGREPORT_SHARING_DECLINED";
+
+ /**
+ * Action sent to a device administrator to notify that the collection of a bugreport
+ * has failed.
+ *
+ * <p>The calling device admin must be the device owner to receive this broadcast.
+ * @see DevicePolicyManager#requestBugreport
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_BUGREPORT_FAILED = "android.app.action.BUGREPORT_FAILED";
+
+ /**
+ * Action sent to a device administrator to share the bugreport.
+ *
+ * <p>The calling device admin must be the device owner to receive this broadcast.
+ * @see DevicePolicyManager#requestBugreport
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_BUGREPORT_SHARE =
+ "android.app.action.BUGREPORT_SHARE";
+
+ /**
+ * A string containing the SHA-256 hash of the bugreport file.
+ *
+ * @see #ACTION_BUGREPORT_SHARE
+ * @hide
+ */
+ public static final String EXTRA_BUGREPORT_HASH = "android.app.extra.BUGREPORT_HASH";
+
+ /**
+ * An {@code int} failure code representing the reason of the bugreport failure.
+ *
+ * @see #ACTION_BUGREPORT_FAILED
+ * @see #BUGREPORT_FAILURE_FAILED_COMPLETING, #BUGREPORT_FAILURE_FILE_NO_LONGER_AVAILABLE
+ * @hide
+ */
+ public static final String EXTRA_BUGREPORT_FAILURE_REASON =
+ "android.app.extra.BUGREPORT_FAILURE_REASON";
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ BUGREPORT_FAILURE_FAILED_COMPLETING,
+ BUGREPORT_FAILURE_FILE_NO_LONGER_AVAILABLE
+ })
+ /**
+ * An interface representing reason of bugreport failure.
+ *
+ * @see #EXTRA_BUGREPORT_FAILURE_REASON
+ * @hide
+ */
+ public @interface BugreportFailureCode {}
+ /** Bugreport completion process failed. */
+ public static final int BUGREPORT_FAILURE_FAILED_COMPLETING = 0;
+ /** Bugreport is no longer available for collection. */
+ public static final int BUGREPORT_FAILURE_FILE_NO_LONGER_AVAILABLE = 1;
+
/** @hide */
public static final String ACTION_CHOOSE_PRIVATE_KEY_ALIAS = "android.app.action.CHOOSE_PRIVATE_KEY_ALIAS";
@@ -482,6 +555,48 @@
}
/**
+ * Called when sharing a bugreport has been cancelled by the user of the device.
+ *
+ * <p>This callback is only applicable to device owners.
+ *
+ * @param context The running context as per {@link #onReceive}.
+ * @param intent The received intent as per {@link #onReceive}.
+ * @see DevicePolicyManager#requestBugreport
+ */
+ public void onBugreportSharingDeclined(Context context, Intent intent) {
+ }
+
+ /**
+ * Called when the bugreport has been shared with the device administrator app.
+ *
+ * <p>This callback is only applicable to device owners.
+ *
+ * @param context The running context as per {@link #onReceive}.
+ * @param intent The received intent as per {@link #onReceive}. Contains the URI of
+ * the bugreport file (with MIME type "application/vnd.android.bugreport"), that can be accessed
+ * by calling {@link Intent#getData()}
+ * @param bugreportHash SHA-256 hash of the bugreport file.
+ * @see DevicePolicyManager#requestBugreport
+ */
+ public void onBugreportShared(Context context, Intent intent, String bugreportHash) {
+ }
+
+ /**
+ * Called when the bugreport collection flow has failed.
+ *
+ * <p>This callback is only applicable to device owners.
+ *
+ * @param context The running context as per {@link #onReceive}.
+ * @param intent The received intent as per {@link #onReceive}.
+ * @param failureCode int containing failure code. One of
+ * #BUGREPORT_FAILURE_FAILED_COMPLETING or #BUGREPORT_FAILURE_FILE_NO_LONGER_AVAILABLE
+ * @see DevicePolicyManager#requestBugreport
+ */
+ public void onBugreportFailed(Context context, Intent intent,
+ @BugreportFailureCode int failureCode) {
+ }
+
+ /**
* Intercept standard device administrator broadcasts. Implementations
* should not override this method; it is better to implement the
* convenience callbacks for each action.
@@ -524,6 +639,15 @@
} else if (ACTION_NOTIFY_PENDING_SYSTEM_UPDATE.equals(action)) {
long receivedTime = intent.getLongExtra(EXTRA_SYSTEM_UPDATE_RECEIVED_TIME, -1);
onSystemUpdatePending(context, intent, receivedTime);
+ } else if (ACTION_BUGREPORT_SHARING_DECLINED.equals(action)) {
+ onBugreportSharingDeclined(context, intent);
+ } else if (ACTION_BUGREPORT_SHARE.equals(action)) {
+ String bugreportFileHash = intent.getStringExtra(EXTRA_BUGREPORT_HASH);
+ onBugreportShared(context, intent, bugreportFileHash);
+ } else if (ACTION_BUGREPORT_FAILED.equals(action)) {
+ int failureCode = intent.getIntExtra(EXTRA_BUGREPORT_FAILURE_REASON,
+ BUGREPORT_FAILURE_FAILED_COMPLETING);
+ onBugreportFailed(context, intent, failureCode);
}
}
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 45b23d0..9e39a5f 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2560,6 +2560,28 @@
}
/**
+ * Called by a device owner to request a bugreport.
+ *
+ * <p>There must be only one user on the device, managed by the device owner.
+ * Otherwise a security exception will be thrown.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @return {@code true} if the bugreport collection started successfully, or {@code false}
+ * if it wasn't triggered because a previous bugreport operation is still active
+ * (either the bugreport is still running or waiting for the user to share or decline)
+ */
+ public boolean requestBugreport(@NonNull ComponentName admin) {
+ if (mService != null) {
+ try {
+ return mService.requestBugreport(admin);
+ } catch (RemoteException e) {
+ Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, e);
+ }
+ }
+ return false;
+ }
+
+ /**
* Determine whether or not creating a guest user has been disabled for the device
*
* @hide
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 7771440..08cab88 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -91,6 +91,8 @@
boolean getStorageEncryption(in ComponentName who, int userHandle);
int getStorageEncryptionStatus(int userHandle);
+ boolean requestBugreport(in ComponentName who);
+
void setCameraDisabled(in ComponentName who, boolean disabled);
boolean getCameraDisabled(in ComponentName who, int userHandle);
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index b3cc234..a277568 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -94,6 +94,10 @@
<protected-broadcast android:name="android.app.action.EXIT_DESK_MODE" />
<protected-broadcast android:name="android.app.action.NEXT_ALARM_CLOCK_CHANGED" />
+ <protected-broadcast android:name="android.app.action.BUGREPORT_SHARING_DECLINED" />
+ <protected-broadcast android:name="android.app.action.BUGREPORT_FAILED" />
+ <protected-broadcast android:name="android.app.action.BUGREPORT_SHARE" />
+
<protected-broadcast android:name="android.appwidget.action.APPWIDGET_UPDATE_OPTIONS" />
<protected-broadcast android:name="android.appwidget.action.APPWIDGET_DELETED" />
<protected-broadcast android:name="android.appwidget.action.APPWIDGET_DISABLED" />
@@ -251,6 +255,8 @@
<protected-broadcast android:name="com.android.server.WifiManager.action.START_SCAN" />
<protected-broadcast android:name="com.android.server.WifiManager.action.START_PNO" />
<protected-broadcast android:name="com.android.server.WifiManager.action.DELAYED_DRIVER_STOP" />
+ <protected-broadcast android:name="com.android.server.action.REMOTE_BUGREPORT_SHARING_ACCEPTED" />
+ <protected-broadcast android:name="com.android.server.action.REMOTE_BUGREPORT_SHARING_DECLINED" />
<protected-broadcast android:name="android.net.wifi.WIFI_STATE_CHANGED" />
<protected-broadcast android:name="android.net.wifi.WIFI_AP_STATE_CHANGED" />
<protected-broadcast android:name="android.net.wifi.WIFI_CREDENTIAL_CHANGED" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 4ef3f59..4ad36f5 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2899,6 +2899,25 @@
<!-- Message of notification shown when ADB is actively connected to the phone. -->
<string name="adb_active_notification_message">Touch to disable USB debugging.</string>
+ <!-- Title of notification shown to ask for user consent for sharing a bugreport that was requested remotely by the IT administrator. STOPSHIP: this is not final -->
+ <string name="share_remote_bugreport_notification_title">Remote bugreport request</string>
+ <!-- Ticker of notification shown to ask for user consent for sharing a bugreport that was requested remotely by the IT administrator. STOPSHIP: this is not final -->
+ <string name="share_remote_bugreport_notification_ticker">Remote bugreport request</string>
+ <!-- Message of notification shown to ask for user consent for sharing a bugreport that was requested remotely by the IT administrator. STOPSHIP: this is not final -->
+ <string name="share_remote_bugreport_notification_message">Your IT admin requested remote bugreport to carry out incident investigation</string>
+ <!-- Title of notification shown for remote bugreport progress. STOPSHIP: this is not final -->
+ <string name="remote_bugreport_progress_notification_title">Remote bugreport</string>
+ <!-- Ticker of notification shown for remote bugreport progress. STOPSHIP: this is not final -->
+ <string name="remote_bugreport_progress_notification_ticker">Remote bugreport collection in progress</string>
+ <!-- Message of notification shown for remote bugreport progress. User can cancel the bugreport STOPSHIP: this is not final -->
+ <string name="remote_bugreport_progress_notification_message_can_cancel">Remote bugreport in progress. Touch to cancel.</string>
+ <!-- Message of notification shown for remote bugreport progress. User cannot cancel the bugreport STOPSHIP: this is not final -->
+ <string name="remote_bugreport_progress_notification_message_cannot_cancel">Remote bugreport in progress.</string>
+ <!-- Acceptance label of notification shown to ask for user consent for sharing the remote bugreport. STOPSHIP: this is not final -->
+ <string name="share_remote_bugreport_notification_accept">ACCEPT</string>
+ <!-- Decline label of notification shown to ask for user consent for sharing the remote bugreport. STOPSHIP: this is not final -->
+ <string name="share_remote_bugreport_notification_decline">DECLINE</string>
+
<!-- Used to replace %s in urls retreived from the signin server with locales. For Some -->
<!-- devices we don't support all the locales we ship to and need to replace the '%s' with a -->
<!-- locale string based on mcc values. By default (0-length string) we don't replace the %s -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index dc288d5..b4cb664 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1770,6 +1770,15 @@
<java-symbol type="string" name="accessibility_binding_label" />
<java-symbol type="string" name="adb_active_notification_message" />
<java-symbol type="string" name="adb_active_notification_title" />
+ <java-symbol type="string" name="share_remote_bugreport_notification_title" />
+ <java-symbol type="string" name="share_remote_bugreport_notification_ticker" />
+ <java-symbol type="string" name="share_remote_bugreport_notification_message" />
+ <java-symbol type="string" name="remote_bugreport_progress_notification_title" />
+ <java-symbol type="string" name="remote_bugreport_progress_notification_ticker" />
+ <java-symbol type="string" name="remote_bugreport_progress_notification_message_can_cancel" />
+ <java-symbol type="string" name="remote_bugreport_progress_notification_message_cannot_cancel" />
+ <java-symbol type="string" name="share_remote_bugreport_notification_accept" />
+ <java-symbol type="string" name="share_remote_bugreport_notification_decline" />
<java-symbol type="string" name="aerr_application" />
<java-symbol type="string" name="aerr_process" />
<java-symbol type="string" name="aerr_process_silence" />
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f14b032..1ada0ac 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -33,6 +33,7 @@
import android.accounts.AccountManager;
import android.annotation.NonNull;
import android.app.Activity;
+import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.app.AlarmManager;
import android.app.AppGlobals;
@@ -82,6 +83,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.PowerManager;
import android.os.PowerManagerInternal;
@@ -156,6 +158,7 @@
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* Implementation of the device policy APIs.
@@ -270,6 +273,46 @@
*/
private boolean mHasFeature;
+ private final AtomicBoolean mRemoteBugreportServiceIsActive = new AtomicBoolean();
+ private final AtomicBoolean mRemoteBugreportSharingAccepted = new AtomicBoolean();
+
+ private final Runnable mRemoteBugreportTimeoutRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if(mRemoteBugreportServiceIsActive.get()) {
+ onBugreportFailed();
+ }
+ }
+ };
+
+ private final BroadcastReceiver mRemoteBugreportFinishedReceiver = new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (RemoteBugreportUtils.ACTION_REMOTE_BUGREPORT_DISPATCH.equals(intent.getAction())
+ && mRemoteBugreportServiceIsActive.get()) {
+ onBugreportFinished(intent);
+ }
+ }
+ };
+
+ private final BroadcastReceiver mRemoteBugreportConsentReceiver = new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ mInjector.getNotificationManager().cancel(LOG_TAG,
+ RemoteBugreportUtils.REMOTE_BUGREPORT_CONSENT_NOTIFICATION_ID);
+ if (RemoteBugreportUtils.ACTION_REMOTE_BUGREPORT_SHARING_ACCEPTED.equals(action)) {
+ onBugreportSharingAccepted();
+ } else if (RemoteBugreportUtils.ACTION_REMOTE_BUGREPORT_SHARING_DECLINED
+ .equals(action)) {
+ onBugreportSharingDeclined();
+ }
+ mContext.unregisterReceiver(mRemoteBugreportConsentReceiver);
+ }
+ };
+
public static final class Lifecycle extends SystemService {
private DevicePolicyManagerService mService;
@@ -343,6 +386,20 @@
final String action = intent.getAction();
final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
getSendingUserId());
+
+ if (Intent.ACTION_BOOT_COMPLETED.equals(action)
+ && userHandle == mOwners.getDeviceOwnerUserId()
+ && getDeviceOwnerRemoteBugreportUri() != null) {
+ IntentFilter filterConsent = new IntentFilter();
+ filterConsent.addAction(
+ RemoteBugreportUtils.ACTION_REMOTE_BUGREPORT_SHARING_DECLINED);
+ filterConsent.addAction(
+ RemoteBugreportUtils.ACTION_REMOTE_BUGREPORT_SHARING_ACCEPTED);
+ mContext.registerReceiver(mRemoteBugreportConsentReceiver, filterConsent);
+ mInjector.getNotificationManager().notify(
+ LOG_TAG, RemoteBugreportUtils.REMOTE_BUGREPORT_CONSENT_NOTIFICATION_ID,
+ RemoteBugreportUtils.buildRemoteBugreportConsentNotification(mContext));
+ }
if (Intent.ACTION_BOOT_COMPLETED.equals(action)
|| ACTION_EXPIRED_PASSWORD_NOTIFICATION.equals(action)) {
if (VERBOSE_LOG) {
@@ -4594,6 +4651,212 @@
}
}
+ private void ensureDeviceOwnerManagingSingleUser(ComponentName who) throws SecurityException {
+ synchronized (this) {
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ }
+ final long callingIdentity = mInjector.binderClearCallingIdentity();
+ try {
+ if (mInjector.userManagerIsSplitSystemUser()) {
+ // In split system user mode, only allow the case where the device owner is managing
+ // the only non-system user of the device
+ if (mUserManager.getUserCount() > 2
+ || mOwners.getDeviceOwnerUserId() == UserHandle.USER_SYSTEM) {
+ throw new SecurityException(
+ "There should only be one user, managed by Device Owner");
+ }
+ } else if (mUserManager.getUserCount() > 1) {
+ throw new SecurityException(
+ "There should only be one user, managed by Device Owner");
+ }
+ } finally {
+ mInjector.binderRestoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ @Override
+ public boolean requestBugreport(ComponentName who) {
+ if (!mHasFeature) {
+ return false;
+ }
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ ensureDeviceOwnerManagingSingleUser(who);
+
+ if (mRemoteBugreportServiceIsActive.get()
+ || (getDeviceOwnerRemoteBugreportUri() != null)) {
+ Slog.d(LOG_TAG, "Remote bugreport wasn't started because there's already one running.");
+ return false;
+ }
+
+ final long callingIdentity = mInjector.binderClearCallingIdentity();
+ try {
+ ActivityManagerNative.getDefault().requestBugReport(
+ ActivityManager.BUGREPORT_OPTION_REMOTE);
+
+ mRemoteBugreportServiceIsActive.set(true);
+ mRemoteBugreportSharingAccepted.set(false);
+ registerRemoteBugreportReceivers();
+ mInjector.getNotificationManager().notify(
+ LOG_TAG, RemoteBugreportUtils.REMOTE_BUGREPORT_CONSENT_NOTIFICATION_ID,
+ RemoteBugreportUtils.buildRemoteBugreportConsentNotification(mContext));
+ mInjector.getNotificationManager().notify(
+ LOG_TAG, RemoteBugreportUtils.REMOTE_BUGREPORT_IN_PROGRESS_NOTIFICATION_ID,
+ RemoteBugreportUtils.buildRemoteBugreportInProgressNotification(mContext,
+ /* canCancelBugReport */ true));
+ mHandler.postDelayed(mRemoteBugreportTimeoutRunnable,
+ RemoteBugreportUtils.REMOTE_BUGREPORT_TIMEOUT_MILLIS);
+ return true;
+ } catch (RemoteException re) {
+ // should never happen
+ Slog.e(LOG_TAG, "Failed to make remote calls to start bugreportremote service", re);
+ return false;
+ } finally {
+ mInjector.binderRestoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ private synchronized void sendDeviceOwnerCommand(String action, Bundle extras) {
+ Intent intent = new Intent(action);
+ intent.setComponent(mOwners.getDeviceOwnerComponent());
+ if (extras != null) {
+ intent.putExtras(extras);
+ }
+ mContext.sendBroadcastAsUser(intent, UserHandle.of(mOwners.getDeviceOwnerUserId()));
+ }
+
+ private synchronized String getDeviceOwnerRemoteBugreportUri() {
+ return mOwners.getDeviceOwnerRemoteBugreportUri();
+ }
+
+ private synchronized void setDeviceOwnerRemoteBugreportUriAndHash(String bugreportUri,
+ String bugreportHash) {
+ mOwners.setDeviceOwnerRemoteBugreportUriAndHash(bugreportUri, bugreportHash);
+ }
+
+ private void registerRemoteBugreportReceivers() {
+ try {
+ IntentFilter filterFinished = new IntentFilter(
+ RemoteBugreportUtils.ACTION_REMOTE_BUGREPORT_DISPATCH,
+ RemoteBugreportUtils.BUGREPORT_MIMETYPE);
+ mContext.registerReceiver(mRemoteBugreportFinishedReceiver, filterFinished);
+ } catch (IntentFilter.MalformedMimeTypeException e) {
+ // should never happen, as setting a constant
+ Slog.w(LOG_TAG, "Failed to set type " + RemoteBugreportUtils.BUGREPORT_MIMETYPE, e);
+ }
+ IntentFilter filterConsent = new IntentFilter();
+ filterConsent.addAction(RemoteBugreportUtils.ACTION_REMOTE_BUGREPORT_SHARING_DECLINED);
+ filterConsent.addAction(RemoteBugreportUtils.ACTION_REMOTE_BUGREPORT_SHARING_ACCEPTED);
+ mContext.registerReceiver(mRemoteBugreportConsentReceiver, filterConsent);
+ }
+
+ private void onBugreportFinished(Intent intent) {
+ mHandler.removeCallbacks(mRemoteBugreportTimeoutRunnable);
+ mRemoteBugreportServiceIsActive.set(false);
+ mInjector.getNotificationManager().cancel(LOG_TAG,
+ RemoteBugreportUtils.REMOTE_BUGREPORT_IN_PROGRESS_NOTIFICATION_ID);
+ Uri bugreportUri = intent.getData();
+ String bugreportUriString = null;
+ if (bugreportUri != null) {
+ bugreportUriString = bugreportUri.toString();
+ }
+ String bugreportHash = intent.getStringExtra(
+ RemoteBugreportUtils.EXTRA_REMOTE_BUGREPORT_HASH);
+ if (mRemoteBugreportSharingAccepted.get()) {
+ shareBugreportWithDeviceOwnerIfExists(bugreportUriString, bugreportHash);
+ } else {
+ setDeviceOwnerRemoteBugreportUriAndHash(bugreportUriString, bugreportHash);
+ }
+ mContext.unregisterReceiver(mRemoteBugreportFinishedReceiver);
+ }
+
+ private void onBugreportFailed() {
+ mRemoteBugreportServiceIsActive.set(false);
+ mInjector.systemPropertiesSet(RemoteBugreportUtils.CTL_STOP,
+ RemoteBugreportUtils.REMOTE_BUGREPORT_SERVICE);
+ mRemoteBugreportSharingAccepted.set(false);
+ setDeviceOwnerRemoteBugreportUriAndHash(null, null);
+ mInjector.getNotificationManager().cancel(LOG_TAG,
+ RemoteBugreportUtils.REMOTE_BUGREPORT_CONSENT_NOTIFICATION_ID);
+ mInjector.getNotificationManager().cancel(LOG_TAG,
+ RemoteBugreportUtils.REMOTE_BUGREPORT_IN_PROGRESS_NOTIFICATION_ID);
+ Bundle extras = new Bundle();
+ extras.putInt(DeviceAdminReceiver.EXTRA_BUGREPORT_FAILURE_REASON,
+ DeviceAdminReceiver.BUGREPORT_FAILURE_FAILED_COMPLETING);
+ sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_BUGREPORT_FAILED, extras);
+ mContext.unregisterReceiver(mRemoteBugreportConsentReceiver);
+ mContext.unregisterReceiver(mRemoteBugreportFinishedReceiver);
+ }
+
+ private void onBugreportSharingAccepted() {
+ mRemoteBugreportSharingAccepted.set(true);
+ String bugreportUriString = null;
+ String bugreportHash = null;
+ synchronized (this) {
+ bugreportUriString = getDeviceOwnerRemoteBugreportUri();
+ bugreportHash = mOwners.getDeviceOwnerRemoteBugreportHash();
+ }
+ if (bugreportUriString != null) {
+ shareBugreportWithDeviceOwnerIfExists(bugreportUriString, bugreportHash);
+ } else if (mRemoteBugreportServiceIsActive.get()) {
+ mInjector.getNotificationManager().notify(LOG_TAG,
+ RemoteBugreportUtils.REMOTE_BUGREPORT_IN_PROGRESS_NOTIFICATION_ID,
+ RemoteBugreportUtils.buildRemoteBugreportInProgressNotification(mContext,
+ /* canCancelBugReport */ false));
+ }
+ }
+
+ private void onBugreportSharingDeclined() {
+ if (mRemoteBugreportServiceIsActive.get()) {
+ mInjector.systemPropertiesSet(RemoteBugreportUtils.CTL_STOP,
+ RemoteBugreportUtils.REMOTE_BUGREPORT_SERVICE);
+ mRemoteBugreportServiceIsActive.set(false);
+ mHandler.removeCallbacks(mRemoteBugreportTimeoutRunnable);
+ mContext.unregisterReceiver(mRemoteBugreportFinishedReceiver);
+ }
+ mRemoteBugreportSharingAccepted.set(false);
+ setDeviceOwnerRemoteBugreportUriAndHash(null, null);
+ mInjector.getNotificationManager().cancel(LOG_TAG,
+ RemoteBugreportUtils.REMOTE_BUGREPORT_IN_PROGRESS_NOTIFICATION_ID);
+ sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_BUGREPORT_SHARING_DECLINED, null);
+ }
+
+ private void shareBugreportWithDeviceOwnerIfExists(String bugreportUriString,
+ String bugreportHash) {
+ ParcelFileDescriptor pfd = null;
+ try {
+ if (bugreportUriString == null) {
+ throw new FileNotFoundException();
+ }
+ Uri bugreportUri = Uri.parse(bugreportUriString);
+ pfd = mContext.getContentResolver().openFileDescriptor(bugreportUri, "r");
+
+ synchronized (this) {
+ Intent intent = new Intent(DeviceAdminReceiver.ACTION_BUGREPORT_SHARE);
+ intent.setComponent(mOwners.getDeviceOwnerComponent());
+ intent.setDataAndType(bugreportUri, RemoteBugreportUtils.BUGREPORT_MIMETYPE);
+ intent.putExtra(DeviceAdminReceiver.EXTRA_BUGREPORT_HASH, bugreportHash);
+ mContext.grantUriPermission(mOwners.getDeviceOwnerComponent().getPackageName(),
+ bugreportUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ mContext.sendBroadcastAsUser(intent, UserHandle.of(mOwners.getDeviceOwnerUserId()));
+ }
+ } catch (FileNotFoundException e) {
+ Bundle extras = new Bundle();
+ extras.putInt(DeviceAdminReceiver.EXTRA_BUGREPORT_FAILURE_REASON,
+ DeviceAdminReceiver.BUGREPORT_FAILURE_FILE_NO_LONGER_AVAILABLE);
+ sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_BUGREPORT_FAILED, extras);
+ } finally {
+ try {
+ if (pfd != null) {
+ pfd.close();
+ }
+ } catch (IOException ex) {
+ // Ignore
+ }
+ mRemoteBugreportSharingAccepted.set(false);
+ setDeviceOwnerRemoteBugreportUriAndHash(null, null);
+ }
+ }
+
/**
* Disables all device cameras according to the specified admin.
*/
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index f7de0b3..880f810 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -77,6 +77,8 @@
private static final String ATTR_NAME = "name";
private static final String ATTR_PACKAGE = "package";
private static final String ATTR_COMPONENT_NAME = "component";
+ private static final String ATTR_REMOTE_BUGREPORT_URI = "remoteBugreportUri";
+ private static final String ATTR_REMOTE_BUGREPORT_HASH = "remoteBugreportHash";
private static final String ATTR_USERID = "userId";
private static final String ATTR_USER_RESTRICTIONS_MIGRATED = "userRestrictionsMigrated";
@@ -161,6 +163,14 @@
return mDeviceOwner != null ? mDeviceOwner.admin : null;
}
+ String getDeviceOwnerRemoteBugreportUri() {
+ return mDeviceOwner != null ? mDeviceOwner.remoteBugreportUri : null;
+ }
+
+ String getDeviceOwnerRemoteBugreportHash() {
+ return mDeviceOwner != null ? mDeviceOwner.remoteBugreportHash : null;
+ }
+
void setDeviceOwner(ComponentName admin, String ownerName, int userId) {
if (userId < 0) {
Slog.e(TAG, "Invalid user id for device owner user: " + userId);
@@ -175,7 +185,8 @@
// userRestrictionsMigrated should always be true.
void setDeviceOwnerWithRestrictionsMigrated(ComponentName admin, String ownerName, int userId,
boolean userRestrictionsMigrated) {
- mDeviceOwner = new OwnerInfo(ownerName, admin, userRestrictionsMigrated);
+ mDeviceOwner = new OwnerInfo(ownerName, admin, userRestrictionsMigrated,
+ /* remoteBugreportUri =*/ null, /* remoteBugreportHash =*/ null);
mDeviceOwnerUserId = userId;
mUserManagerInternal.setDeviceManaged(true);
@@ -191,7 +202,8 @@
void setProfileOwner(ComponentName admin, String ownerName, int userId) {
// For a newly set PO, there's no need for migration.
mProfileOwners.put(userId, new OwnerInfo(ownerName, admin,
- /* userRestrictionsMigrated =*/ true));
+ /* userRestrictionsMigrated =*/ true, /* remoteBugreportUri =*/ null,
+ /* remoteBugreportHash =*/ null));
mUserManagerInternal.setUserManaged(userId, true);
}
@@ -266,6 +278,16 @@
writeDeviceOwner();
}
+ /** Sets the remote bugreport uri and hash, and also writes to the file. */
+ void setDeviceOwnerRemoteBugreportUriAndHash(String remoteBugreportUri,
+ String remoteBugreportHash) {
+ if (mDeviceOwner != null) {
+ mDeviceOwner.remoteBugreportUri = remoteBugreportUri;
+ mDeviceOwner.remoteBugreportHash = remoteBugreportHash;
+ }
+ writeDeviceOwner();
+ }
+
/** Sets the user restrictions migrated flag, and also writes to the file. */
void setProfileOwnerUserRestrictionsMigrated(int userId) {
OwnerInfo profileOwner = mProfileOwners.get(userId);
@@ -295,7 +317,8 @@
String name = parser.getAttributeValue(null, ATTR_NAME);
String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
mDeviceOwner = new OwnerInfo(name, packageName,
- /* userRestrictionsMigrated =*/ false);
+ /* userRestrictionsMigrated =*/ false, /* remoteBugreportUri =*/ null,
+ /* remoteBugreportHash =*/ null);
mDeviceOwnerUserId = UserHandle.USER_SYSTEM;
} else if (tag.equals(TAG_DEVICE_INITIALIZER)) {
// Deprecated tag
@@ -311,7 +334,7 @@
profileOwnerComponentStr);
if (admin != null) {
profileOwnerInfo = new OwnerInfo(profileOwnerName, admin,
- /* userRestrictionsMigrated =*/ false);
+ /* userRestrictionsMigrated =*/ false, null, null);
} else {
// This shouldn't happen but switch from package name -> component name
// might have written bad device owner files. b/17652534
@@ -321,7 +344,8 @@
}
if (profileOwnerInfo == null) {
profileOwnerInfo = new OwnerInfo(profileOwnerName, profileOwnerPackageName,
- /* userRestrictionsMigrated =*/ false);
+ /* userRestrictionsMigrated =*/ false,
+ /* remoteBugreportUri =*/ null, /* remoteBugreportHash =*/ null);
}
mProfileOwners.put(userId, profileOwnerInfo);
} else if (TAG_SYSTEM_UPDATE_POLICY.equals(tag)) {
@@ -579,19 +603,27 @@
public final String packageName;
public final ComponentName admin;
public boolean userRestrictionsMigrated;
+ public String remoteBugreportUri;
+ public String remoteBugreportHash;
- public OwnerInfo(String name, String packageName, boolean userRestrictionsMigrated) {
+ public OwnerInfo(String name, String packageName, boolean userRestrictionsMigrated,
+ String remoteBugreportUri, String remoteBugreportHash) {
this.name = name;
this.packageName = packageName;
this.admin = new ComponentName(packageName, "");
this.userRestrictionsMigrated = userRestrictionsMigrated;
+ this.remoteBugreportUri = remoteBugreportUri;
+ this.remoteBugreportHash = remoteBugreportHash;
}
- public OwnerInfo(String name, ComponentName admin, boolean userRestrictionsMigrated) {
+ public OwnerInfo(String name, ComponentName admin, boolean userRestrictionsMigrated,
+ String remoteBugreportUri, String remoteBugreportHash) {
this.name = name;
this.admin = admin;
this.packageName = admin.getPackageName();
this.userRestrictionsMigrated = userRestrictionsMigrated;
+ this.remoteBugreportUri = remoteBugreportUri;
+ this.remoteBugreportHash = remoteBugreportHash;
}
public void writeToXml(XmlSerializer out, String tag) throws IOException {
@@ -605,6 +637,12 @@
}
out.attribute(null, ATTR_USER_RESTRICTIONS_MIGRATED,
String.valueOf(userRestrictionsMigrated));
+ if (remoteBugreportUri != null) {
+ out.attribute(null, ATTR_REMOTE_BUGREPORT_URI, remoteBugreportUri);
+ }
+ if (remoteBugreportHash != null) {
+ out.attribute(null, ATTR_REMOTE_BUGREPORT_HASH, remoteBugreportHash);
+ }
out.endTag(null, tag);
}
@@ -617,12 +655,17 @@
parser.getAttributeValue(null, ATTR_USER_RESTRICTIONS_MIGRATED);
final boolean userRestrictionsMigrated =
("true".equals(userRestrictionsMigratedStr));
+ final String remoteBugreportUri = parser.getAttributeValue(null,
+ ATTR_REMOTE_BUGREPORT_URI);
+ final String remoteBugreportHash = parser.getAttributeValue(null,
+ ATTR_REMOTE_BUGREPORT_HASH);
// Has component name? If so, return [name, component]
if (componentName != null) {
final ComponentName admin = ComponentName.unflattenFromString(componentName);
if (admin != null) {
- return new OwnerInfo(name, admin, userRestrictionsMigrated);
+ return new OwnerInfo(name, admin, userRestrictionsMigrated,
+ remoteBugreportUri, remoteBugreportHash);
} else {
// This shouldn't happen but switch from package name -> component name
// might have written bad device owner files. b/17652534
@@ -632,7 +675,8 @@
}
// Else, build with [name, package]
- return new OwnerInfo(name, packageName, userRestrictionsMigrated);
+ return new OwnerInfo(name, packageName, userRestrictionsMigrated, remoteBugreportUri,
+ remoteBugreportHash);
}
public void dump(String prefix, PrintWriter pw) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportUtils.java b/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportUtils.java
new file mode 100644
index 0000000..806a7d9
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportUtils.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicepolicy;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.text.format.DateUtils;
+
+import com.android.internal.R;
+
+/**
+ * Utilities class for the remote bugreport operation.
+ */
+class RemoteBugreportUtils {
+
+ static final int REMOTE_BUGREPORT_CONSENT_NOTIFICATION_ID = 678435657;
+ static final int REMOTE_BUGREPORT_IN_PROGRESS_NOTIFICATION_ID = 590907895;
+
+ static final long REMOTE_BUGREPORT_TIMEOUT_MILLIS = 10 * DateUtils.MINUTE_IN_MILLIS;
+
+ static final String CTL_STOP = "ctl.stop";
+ static final String REMOTE_BUGREPORT_SERVICE = "bugreportremote";
+
+ static final String ACTION_REMOTE_BUGREPORT_DISPATCH =
+ "android.intent.action.REMOTE_BUGREPORT_DISPATCH";
+ static final String ACTION_REMOTE_BUGREPORT_SHARING_ACCEPTED =
+ "com.android.server.action.REMOTE_BUGREPORT_SHARING_ACCEPTED";
+ static final String ACTION_REMOTE_BUGREPORT_SHARING_DECLINED =
+ "com.android.server.action.REMOTE_BUGREPORT_SHARING_DECLINED";
+ static final String EXTRA_REMOTE_BUGREPORT_HASH = "android.intent.extra.REMOTE_BUGREPORT_HASH";
+
+ static final String BUGREPORT_MIMETYPE = "application/vnd.android.bugreport";
+
+ static Notification buildRemoteBugreportConsentNotification(Context context) {
+ PendingIntent pendingIntentAccept = PendingIntent.getBroadcast(
+ context, REMOTE_BUGREPORT_CONSENT_NOTIFICATION_ID,
+ new Intent(ACTION_REMOTE_BUGREPORT_SHARING_ACCEPTED),
+ PendingIntent.FLAG_CANCEL_CURRENT);
+ PendingIntent pendingIntentDecline = PendingIntent.getBroadcast(
+ context, REMOTE_BUGREPORT_CONSENT_NOTIFICATION_ID,
+ new Intent(ACTION_REMOTE_BUGREPORT_SHARING_DECLINED),
+ PendingIntent.FLAG_CANCEL_CURRENT);
+
+ return new Notification.Builder(context)
+ .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
+ .setContentTitle(context.getString(
+ R.string.share_remote_bugreport_notification_title))
+ .setTicker(context.getString(R.string.share_remote_bugreport_notification_ticker))
+ .setContentText(context.getString(
+ R.string.share_remote_bugreport_notification_message))
+ .setStyle(new Notification.BigTextStyle().bigText(context.getString(
+ R.string.share_remote_bugreport_notification_message)))
+ .addAction(new Notification.Action.Builder(null /* icon */,
+ context.getString(R.string.share_remote_bugreport_notification_decline),
+ pendingIntentDecline).build())
+ .addAction(new Notification.Action.Builder(null /* icon */,
+ context.getString(R.string.share_remote_bugreport_notification_accept),
+ pendingIntentAccept).build())
+ .setOngoing(true)
+ .setLocalOnly(true)
+ .setColor(context.getColor(
+ com.android.internal.R.color.system_notification_accent_color))
+ .setPriority(Notification.PRIORITY_MAX)
+ .setVibrate(new long[0])
+ .build();
+ }
+
+ static Notification buildRemoteBugreportInProgressNotification(Context context,
+ boolean canCancelBugreport) {
+ Notification.Builder builder = new Notification.Builder(context)
+ .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
+ .setContentTitle(context.getString(
+ R.string.remote_bugreport_progress_notification_title))
+ .setTicker(context.getString(
+ R.string.remote_bugreport_progress_notification_ticker))
+ .setOngoing(true)
+ .setLocalOnly(true)
+ .setColor(context.getColor(
+ com.android.internal.R.color.system_notification_accent_color))
+ .setPriority(Notification.PRIORITY_HIGH);
+
+ String message = null;
+ if (canCancelBugreport) {
+ PendingIntent pendingIntentCancel = PendingIntent.getBroadcast(context,
+ REMOTE_BUGREPORT_IN_PROGRESS_NOTIFICATION_ID,
+ new Intent(ACTION_REMOTE_BUGREPORT_SHARING_DECLINED),
+ PendingIntent.FLAG_CANCEL_CURRENT);
+ message = context.getString(
+ R.string.remote_bugreport_progress_notification_message_can_cancel);
+ builder.setContentIntent(pendingIntentCancel);
+ } else {
+ message = context.getString(
+ R.string.remote_bugreport_progress_notification_message_cannot_cancel);
+ }
+ return builder.setContentText(message)
+ .setStyle(new Notification.BigTextStyle().bigText(message))
+ .build();
+ }
+}
+