Merge "Add metrics logging for file operations."
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Metrics.java b/packages/DocumentsUI/src/com/android/documentsui/Metrics.java
index eb90b75..c35be10 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/Metrics.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/Metrics.java
@@ -17,7 +17,10 @@
package com.android.documentsui;
import static com.android.documentsui.Shared.DEBUG;
+import static com.android.internal.util.Preconditions.checkArgument;
+import android.annotation.IntDef;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
@@ -25,9 +28,16 @@
import android.provider.DocumentsContract;
import android.util.Log;
+import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.RootInfo;
+import com.android.documentsui.services.FileOperationService;
+import com.android.documentsui.services.FileOperationService.OpType;
import com.android.internal.logging.MetricsLogger;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
/** @hide */
public final class Metrics {
private static final String TAG = "Metrics";
@@ -47,6 +57,9 @@
private static final String COUNT_BROWSE_ROOT = "docsui_browse_root";
private static final String COUNT_MANAGE_ROOT = "docsui_manage_root";
private static final String COUNT_MULTI_WINDOW = "docsui_multi_window";
+ private static final String COUNT_FILEOP_SYSTEM = "docsui_fileop_system";
+ private static final String COUNT_FILEOP_EXTERNAL = "docsui_fileop_external";
+ private static final String COUNT_FILEOP_CANCELED = "docsui_fileop_canceled";
// Indices for bucketing roots in the roots histogram. "Other" is the catch-all index for any
// root that is not explicitly recognized by the Metrics code (see {@link
@@ -65,6 +78,21 @@
// are logged analogously to roots. Use negative numbers to identify apps.
private static final int ROOT_THIRD_PARTY_APP = -1;
+ @IntDef(flag = true, value = {
+ ROOT_NONE,
+ ROOT_OTHER,
+ ROOT_AUDIO,
+ ROOT_DEVICE_STORAGE,
+ ROOT_DOWNLOADS,
+ ROOT_HOME,
+ ROOT_IMAGES,
+ ROOT_RECENTS,
+ ROOT_VIDEOS,
+ ROOT_THIRD_PARTY_APP
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Root {}
+
// Indices for bucketing mime types.
private static final int MIME_OTHER = -2; // anything not enumerated below
private static final int MIME_NONE = -1; // null mime
@@ -77,6 +105,66 @@
private static final int MIME_TEXT = 6; // text/*
private static final int MIME_VIDEO = 7; // video/*
+ @IntDef(flag = true, value = {
+ MIME_OTHER,
+ MIME_NONE,
+ MIME_ANY,
+ MIME_APPLICATION,
+ MIME_AUDIO,
+ MIME_IMAGE,
+ MIME_MESSAGE,
+ MIME_MULTIPART,
+ MIME_TEXT,
+ MIME_VIDEO
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Mime {}
+
+ // Codes representing different kinds of file operations. These are used for bucketing
+ // operations in the COUNT_FILEOP_{SYSTEM|EXTERNAL} histograms.
+ private static final int FILEOP_OTHER = 0; // any file operation not listed below
+ private static final int FILEOP_COPY_INTRA_PROVIDER = 1; // Copy within a provider
+ private static final int FILEOP_COPY_SYSTEM_PROVIDER = 2; // Copy to a system provider.
+ private static final int FILEOP_COPY_EXTERNAL_PROVIDER = 3; // Copy to a 3rd-party provider.
+ private static final int FILEOP_MOVE_INTRA_PROVIDER = 4; // Move within a provider.
+ private static final int FILEOP_MOVE_SYSTEM_PROVIDER = 5; // Move to a system provider.
+ private static final int FILEOP_MOVE_EXTERNAL_PROVIDER = 6; // Move to a 3rd-party provider.
+ private static final int FILEOP_DELETE = 7;
+ private static final int FILEOP_OTHER_ERROR = -1;
+ private static final int FILEOP_COPY_ERROR = -2;
+ private static final int FILEOP_MOVE_ERROR = -3;
+ private static final int FILEOP_DELETE_ERROR = -4;
+
+ @IntDef(flag = true, value = {
+ FILEOP_OTHER,
+ FILEOP_COPY_INTRA_PROVIDER,
+ FILEOP_COPY_SYSTEM_PROVIDER,
+ FILEOP_COPY_EXTERNAL_PROVIDER,
+ FILEOP_MOVE_INTRA_PROVIDER,
+ FILEOP_MOVE_SYSTEM_PROVIDER,
+ FILEOP_MOVE_EXTERNAL_PROVIDER,
+ FILEOP_DELETE,
+ FILEOP_OTHER_ERROR,
+ FILEOP_COPY_ERROR,
+ FILEOP_MOVE_ERROR,
+ FILEOP_DELETE_ERROR
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FileOp {}
+
+ // Codes representing different provider types. Used for sorting file operations when logging.
+ private static final int PROVIDER_INTRA = 0;
+ private static final int PROVIDER_SYSTEM = 1;
+ private static final int PROVIDER_EXTERNAL = 2;
+
+ @IntDef(flag = true, value = {
+ PROVIDER_INTRA,
+ PROVIDER_SYSTEM,
+ PROVIDER_EXTERNAL
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Provider {}
+
/**
* Logs when DocumentsUI is started, and how. Call this when DocumentsUI first starts up.
*
@@ -140,6 +228,99 @@
}
/**
+ * Logs file operation stats. Call this when a file operation has completed. The given
+ * DocumentInfo is only used to distinguish broad categories of actions (e.g. copying from one
+ * provider to another vs copying within a given provider). No PII is logged.
+ *
+ * @param context
+ * @param operationType
+ * @param srcs
+ * @param dst
+ */
+ public static void logFileOperation(
+ Context context,
+ @OpType int operationType,
+ List<DocumentInfo> srcs,
+ @Nullable DocumentInfo dst) {
+ ProviderCounts counts = countProviders(srcs, dst);
+
+ if (counts.intraProvider > 0) {
+ logIntraProviderFileOps(context, dst.authority, operationType);
+ }
+ if (counts.systemProvider > 0) {
+ // Log file operations on system providers.
+ logInterProviderFileOps(context, COUNT_FILEOP_SYSTEM, dst, operationType);
+ }
+ if (counts.externalProvider > 0) {
+ // Log file operations on external providers.
+ logInterProviderFileOps(context, COUNT_FILEOP_EXTERNAL, dst, operationType);
+ }
+ }
+
+ /**
+ * Logs some kind of file operation error. Call this when a file operation (e.g. copy, delete)
+ * fails.
+ *
+ * @param context
+ * @param operationType
+ * @param failedFiles
+ */
+ public static void logFileOperationErrors(Context context, @OpType int operationType,
+ List<DocumentInfo> failedFiles) {
+ ProviderCounts counts = countProviders(failedFiles, null);
+
+ @FileOp int opCode = FILEOP_OTHER_ERROR;
+ switch (operationType) {
+ case FileOperationService.OPERATION_COPY:
+ opCode = FILEOP_COPY_ERROR;
+ break;
+ case FileOperationService.OPERATION_DELETE:
+ opCode = FILEOP_DELETE_ERROR;
+ break;
+ case FileOperationService.OPERATION_MOVE:
+ opCode = FILEOP_MOVE_ERROR;
+ break;
+ }
+ if (counts.systemProvider > 0) {
+ logHistogram(context, COUNT_FILEOP_SYSTEM, opCode);
+ }
+ if (counts.externalProvider > 0) {
+ logHistogram(context, COUNT_FILEOP_EXTERNAL, opCode);
+ }
+ }
+
+ /**
+ * Log the cancellation of a file operation. Call this when a Job is canceled.
+ * @param context
+ * @param operationType
+ */
+ public static void logFileOperationCancelled(Context context, @OpType int operationType) {
+ logHistogram(context, COUNT_FILEOP_CANCELED, operationType);
+ }
+
+ private static void logInterProviderFileOps(
+ Context context,
+ String histogram,
+ DocumentInfo dst,
+ @OpType int operationType) {
+ if (operationType == FileOperationService.OPERATION_DELETE) {
+ logHistogram(context, histogram, FILEOP_DELETE);
+ } else {
+ checkArgument(dst != null);
+ @Provider int providerType =
+ isSystemProvider(dst.authority) ? PROVIDER_SYSTEM : PROVIDER_EXTERNAL;
+ logHistogram(context, histogram, getOpCode(operationType, providerType));
+ }
+ }
+
+ private static void logIntraProviderFileOps(
+ Context context, String authority, @OpType int operationType) {
+ // Find the right histogram to log to, then log the operation.
+ String histogram = isSystemProvider(authority) ? COUNT_FILEOP_SYSTEM : COUNT_FILEOP_EXTERNAL;
+ logHistogram(context, histogram, getOpCode(operationType, PROVIDER_INTRA));
+ }
+
+ /**
* Internal method for making a MetricsLogger.count call. Increments the given counter by 1.
*
* @param context
@@ -167,7 +348,7 @@
* small set of hard-coded roots (ones provided by the system). Other roots are all grouped into
* a single ROOT_OTHER bucket.
*/
- private static int sanitizeRoot(Uri uri) {
+ private static @Root int sanitizeRoot(Uri uri) {
if (LauncherActivity.isLaunchUri(uri)) {
return ROOT_NONE;
}
@@ -198,7 +379,7 @@
}
/** @see #sanitizeRoot(Uri) */
- private static int sanitizeRoot(RootInfo root) {
+ private static @Root int sanitizeRoot(RootInfo root) {
if (root.isRecents()) {
// Recents root is special and only identifiable via this method call. Other roots are
// identified by URI.
@@ -209,7 +390,7 @@
}
/** @see #sanitizeRoot(Uri) */
- private static int sanitizeRoot(ResolveInfo info) {
+ private static @Root int sanitizeRoot(ResolveInfo info) {
// Log all apps under a single bucket in the roots histogram.
return ROOT_THIRD_PARTY_APP;
}
@@ -221,7 +402,7 @@
* @param mimeType
* @return
*/
- private static int sanitizeMime(String mimeType) {
+ private static @Mime int sanitizeMime(String mimeType) {
if (mimeType == null) {
return MIME_NONE;
} else if ("*/*".equals(mimeType)) {
@@ -248,4 +429,75 @@
// Bucket all other types into one bucket.
return MIME_OTHER;
}
+
+ private static boolean isSystemProvider(String authority) {
+ switch (authority) {
+ case AUTHORITY_MEDIA:
+ case AUTHORITY_STORAGE:
+ case AUTHORITY_DOWNLOADS:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * @param operation
+ * @param providerType
+ * @return An opcode, suitable for use as histogram bucket, for the given operation/provider
+ * combination.
+ */
+ private static @FileOp int getOpCode(@OpType int operation, @Provider int providerType) {
+ switch (operation) {
+ case FileOperationService.OPERATION_COPY:
+ switch (providerType) {
+ case PROVIDER_INTRA:
+ return FILEOP_COPY_INTRA_PROVIDER;
+ case PROVIDER_SYSTEM:
+ return FILEOP_COPY_SYSTEM_PROVIDER;
+ case PROVIDER_EXTERNAL:
+ return FILEOP_COPY_EXTERNAL_PROVIDER;
+ }
+ case FileOperationService.OPERATION_MOVE:
+ switch (providerType) {
+ case PROVIDER_INTRA:
+ return FILEOP_MOVE_INTRA_PROVIDER;
+ case PROVIDER_SYSTEM:
+ return FILEOP_MOVE_SYSTEM_PROVIDER;
+ case PROVIDER_EXTERNAL:
+ return FILEOP_MOVE_EXTERNAL_PROVIDER;
+ }
+ case FileOperationService.OPERATION_DELETE:
+ return FILEOP_DELETE;
+ default:
+ Log.w(TAG, "Unrecognized operation type when logging a file operation");
+ return FILEOP_OTHER;
+ }
+ }
+
+ /**
+ * Count the given src documents and provide a tally of how many come from the same provider as
+ * the dst document (if a dst is provided), how many come from system providers, and how many
+ * come from external 3rd-party providers.
+ */
+ private static ProviderCounts countProviders(
+ List<DocumentInfo> srcs, @Nullable DocumentInfo dst) {
+ ProviderCounts counts = new ProviderCounts();
+ for (DocumentInfo doc: srcs) {
+ if (dst != null && doc.authority.equals(dst.authority)) {
+ counts.intraProvider++;
+ } else if (isSystemProvider(doc.authority)){
+ counts.systemProvider++;
+ } else {
+ counts.externalProvider++;
+ }
+ }
+ return counts;
+ }
+
+ private static class ProviderCounts {
+ int intraProvider;
+ int systemProvider;
+ int externalProvider;
+ }
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java b/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java
index e5e66f8..7c08ba7 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java
@@ -50,6 +50,7 @@
import android.util.Log;
import android.webkit.MimeTypeMap;
+import com.android.documentsui.Metrics;
import com.android.documentsui.R;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentStack;
@@ -214,10 +215,9 @@
mBatchSize = calculateSize(mSrcs);
DocumentInfo srcInfo;
- DocumentInfo dstInfo;
+ DocumentInfo dstInfo = stack.peek();
for (int i = 0; i < mSrcs.size() && !isCanceled(); ++i) {
srcInfo = mSrcs.get(i);
- dstInfo = stack.peek();
// Guard unsupported recursive operation.
if (dstInfo.equals(srcInfo) || isDescendentOf(srcInfo, dstInfo)) {
@@ -232,6 +232,7 @@
processDocument(srcInfo, null, dstInfo);
}
+ Metrics.logFileOperation(service, operationType, mSrcs, dstInfo);
}
@Override
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/DeleteJob.java b/packages/DocumentsUI/src/com/android/documentsui/services/DeleteJob.java
index 24eb987..11c3a29 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/DeleteJob.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/DeleteJob.java
@@ -25,6 +25,7 @@
import android.os.RemoteException;
import android.util.Log;
+import com.android.documentsui.Metrics;
import com.android.documentsui.R;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentStack;
@@ -90,6 +91,7 @@
onFileFailed(doc);
}
}
+ Metrics.logFileOperation(service, operationType, mSrcs, null);
}
@Override
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/Job.java b/packages/DocumentsUI/src/com/android/documentsui/services/Job.java
index 7b8011a..f2c8763 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/Job.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/Job.java
@@ -41,6 +41,7 @@
import android.util.Log;
import com.android.documentsui.FilesActivity;
+import com.android.documentsui.Metrics;
import com.android.documentsui.OperationDialogFragment;
import com.android.documentsui.R;
import com.android.documentsui.Shared;
@@ -114,6 +115,7 @@
// ensure the service is shut down and notifications
// shown/closed.
Log.e(TAG, "Operation failed due to an exception.", e);
+ Metrics.logFileOperationErrors(service, operationType, failedFiles);
} finally {
listener.onFinished(this);
}
@@ -150,6 +152,7 @@
final void cancel() {
mCanceled = true;
+ Metrics.logFileOperationCancelled(service, operationType);
}
final boolean isCanceled() {