| /* |
| * 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.documentsui; |
| |
| import static android.content.ContentResolver.wrap; |
| |
| import static com.android.documentsui.DocumentsApplication.acquireUnstableProviderOrThrow; |
| |
| import android.content.ContentProviderClient; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.ResolveInfo; |
| import android.net.Uri; |
| import android.os.RemoteException; |
| import android.provider.DocumentsContract; |
| import android.provider.DocumentsContract.Path; |
| import android.provider.DocumentsProvider; |
| import android.util.Log; |
| import android.util.StatsEvent; |
| import android.util.StatsLog; |
| |
| import androidx.annotation.Nullable; |
| |
| import com.android.documentsui.base.DocumentInfo; |
| import com.android.documentsui.base.Providers; |
| import com.android.documentsui.base.RootInfo; |
| import com.android.documentsui.base.State; |
| import com.android.documentsui.base.UserId; |
| import com.android.documentsui.files.LauncherActivity; |
| import com.android.documentsui.picker.PickResult; |
| import com.android.documentsui.roots.ProvidersAccess; |
| import com.android.documentsui.services.FileOperationService; |
| import com.android.documentsui.services.FileOperationService.OpType; |
| import com.android.documentsui.util.VersionUtils; |
| |
| import java.io.FileNotFoundException; |
| import java.util.List; |
| |
| /** |
| * Methods for logging metrics. |
| */ |
| public final class Metrics { |
| private static final String TAG = "Metrics"; |
| |
| /** |
| * Logs when DocumentsUI is started, and how. Call this when DocumentsUI first starts up. |
| * |
| * @param state |
| * @param intent |
| */ |
| public static void logActivityLaunch(State state, Intent intent) { |
| Uri uri = intent.getData(); |
| DocumentsStatsLog.write(DocumentsStatsLog.DOCS_UI_LAUNCH_REPORTED, |
| toMetricsAction(state.action), false, |
| sanitizeMime(intent.getType()), sanitizeRoot(uri)); |
| } |
| |
| /** |
| * Logs when DocumentsUI are launched with {@link DocumentsContract#EXTRA_INITIAL_URI}. |
| * |
| * @param state used to resolve action |
| * @param rootUri the resolved rootUri, or {@code null} if the provider doesn't |
| * support {@link DocumentsProvider#findDocumentPath(String, String)} |
| */ |
| public static void logLaunchAtLocation(State state, @Nullable Uri rootUri) { |
| DocumentsStatsLog.write(DocumentsStatsLog.DOCS_UI_LAUNCH_REPORTED, |
| toMetricsAction(state.action), true, |
| MetricConsts.MIME_UNKNOWN, sanitizeRoot(rootUri)); |
| } |
| |
| /** |
| * Logs a root visited event in file managers. Call this when the user |
| * taps on a root in {@link com.android.documentsui.sidebar.RootsFragment}. |
| * @param scope |
| * @param info |
| */ |
| public static void logRootVisited(@MetricConsts.ContextScope int scope, RootInfo info) { |
| DocumentsStatsLog.write(DocumentsStatsLog.DOCS_UI_ROOT_VISITED, scope, sanitizeRoot(info)); |
| } |
| |
| public static void logLaunchOtherApp(boolean acrossProfile) { |
| DevicePolicyEventLogger.write(DevicePolicyMetricConsts.EVENT_ID_DOCSUI_LAUNCH_OTHER_APP, |
| acrossProfile); |
| } |
| |
| public static void logCrossProfileEmptyState(CrossProfileException e) { |
| int eventId; |
| if (e instanceof CrossProfileQuietModeException) { |
| eventId = DevicePolicyMetricConsts.EVENT_ID_DOCSUI_EMPTY_STATE_QUIET_MODE; |
| } else if (e instanceof CrossProfileNoPermissionException) { |
| eventId = DevicePolicyMetricConsts.EVENT_ID_DOCSUI_EMPTY_STATE_NO_PERMISSION; |
| } else { |
| Log.d(TAG, "logCrossProfileEmptyState: Unexpected exception " + e); |
| return; |
| } |
| DevicePolicyEventLogger.write(eventId, /* booleanValue= */ true); |
| } |
| |
| /** |
| * Logs an app visited event in file pickers. Call this when the user visits |
| * on an app in the RootsFragment. |
| * |
| * @param info |
| */ |
| public static void logAppVisited(ResolveInfo info) { |
| DocumentsStatsLog.write( |
| DocumentsStatsLog.DOCS_UI_ROOT_VISITED, |
| MetricConsts.PICKER_SCOPE, sanitizeRoot(info)); |
| } |
| |
| /** |
| * 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 operationType |
| * @param srcs |
| * @param dst |
| */ |
| public static void logFileOperation( |
| @OpType int operationType, |
| List<DocumentInfo> srcs, |
| @Nullable DocumentInfo dst) { |
| ProviderCounts counts = new ProviderCounts(); |
| countProviders(counts, srcs, dst); |
| if (counts.intraProvider > 0) { |
| logIntraProviderFileOps(dst.authority, operationType); |
| } |
| if (counts.systemProvider > 0) { |
| // Log file operations on system providers. |
| logInterProviderFileOps(MetricConsts.PROVIDER_SYSTEM, dst, operationType); |
| } |
| if (counts.externalProvider > 0) { |
| // Log file operations on external providers. |
| logInterProviderFileOps(MetricConsts.PROVIDER_EXTERNAL, dst, operationType); |
| } |
| } |
| |
| public static void logFileOperated( |
| @OpType int operationType, @MetricConsts.FileOpMode int approach) { |
| switch (operationType) { |
| case FileOperationService.OPERATION_COPY: |
| DocumentsStatsLog.write(DocumentsStatsLog.DOCS_UI_FILE_OP_COPY_MOVE_MODE_REPORTED, |
| MetricConsts.FILEOP_COPY, approach); |
| break; |
| case FileOperationService.OPERATION_MOVE: |
| DocumentsStatsLog.write(DocumentsStatsLog.DOCS_UI_FILE_OP_COPY_MOVE_MODE_REPORTED, |
| MetricConsts.FILEOP_MOVE, approach); |
| break; |
| } |
| } |
| |
| /** |
| * Logs create directory operation. It is a part of file operation stats. We do not |
| * differentiate between internal and external locations, all create directory operations are |
| * logged under COUNT_FILEOP_SYSTEM. Call this when a create directory operation has completed. |
| */ |
| public static void logCreateDirOperation() { |
| DocumentsStatsLog.write(DocumentsStatsLog.DOCS_UI_PROVIDER_FILE_OP, |
| MetricConsts.PROVIDER_SYSTEM, MetricConsts.FILEOP_CREATE_DIR); |
| } |
| |
| /** |
| * Logs rename file operation. It is a part of file operation stats. We do not differentiate |
| * between internal and external locations, all rename operations are logged under |
| * COUNT_FILEOP_SYSTEM. Call this when a rename file operation has completed. |
| */ |
| public static void logRenameFileOperation() { |
| DocumentsStatsLog.write(DocumentsStatsLog.DOCS_UI_PROVIDER_FILE_OP, |
| MetricConsts.PROVIDER_SYSTEM, MetricConsts.FILEOP_RENAME); |
| } |
| |
| /** |
| * Logs some kind of file operation error. Call this when a file operation (e.g. copy, delete) |
| * fails. |
| * |
| * @param operationType |
| * @param failedFiles |
| */ |
| public static void logFileOperationErrors(@OpType int operationType, |
| List<DocumentInfo> failedFiles, List<Uri> failedUris) { |
| ProviderCounts counts = new ProviderCounts(); |
| countProviders(counts, failedFiles, null); |
| // TODO: Report URI errors separate from file operation errors. |
| countProviders(counts, failedUris); |
| @MetricConsts.FileOp int opCode = MetricConsts.FILEOP_OTHER_ERROR; |
| switch (operationType) { |
| case FileOperationService.OPERATION_COPY: |
| opCode = MetricConsts.FILEOP_COPY_ERROR; |
| break; |
| case FileOperationService.OPERATION_COMPRESS: |
| opCode = MetricConsts.FILEOP_COMPRESS_ERROR; |
| break; |
| case FileOperationService.OPERATION_EXTRACT: |
| opCode = MetricConsts.FILEOP_EXTRACT_ERROR; |
| break; |
| case FileOperationService.OPERATION_DELETE: |
| opCode = MetricConsts.FILEOP_DELETE_ERROR; |
| break; |
| case FileOperationService.OPERATION_MOVE: |
| opCode = MetricConsts.FILEOP_MOVE_ERROR; |
| break; |
| } |
| if (counts.systemProvider > 0) { |
| DocumentsStatsLog.write( |
| DocumentsStatsLog.DOCS_UI_PROVIDER_FILE_OP, |
| MetricConsts.PROVIDER_SYSTEM, opCode); |
| } |
| if (counts.externalProvider > 0) { |
| DocumentsStatsLog.write( |
| DocumentsStatsLog.DOCS_UI_PROVIDER_FILE_OP, |
| MetricConsts.PROVIDER_EXTERNAL, opCode); |
| } |
| } |
| |
| public static void logFileOperationFailure( |
| Context context, @MetricConsts.SubFileOp int subFileOp, Uri docUri) { |
| final String authority = docUri.getAuthority(); |
| switch (authority) { |
| case Providers.AUTHORITY_MEDIA: |
| DocumentsStatsLog.write( |
| DocumentsStatsLog.DOCS_UI_FILE_OP_FAILURE, |
| MetricConsts.AUTH_MEDIA, subFileOp); |
| break; |
| case Providers.AUTHORITY_STORAGE: |
| logStorageFileOperationFailure(context, subFileOp, docUri); |
| break; |
| case Providers.AUTHORITY_DOWNLOADS: |
| DocumentsStatsLog.write( |
| DocumentsStatsLog.DOCS_UI_FILE_OP_FAILURE, |
| MetricConsts.AUTH_DOWNLOADS, subFileOp); |
| break; |
| case Providers.AUTHORITY_MTP: |
| DocumentsStatsLog.write( |
| DocumentsStatsLog.DOCS_UI_FILE_OP_FAILURE, |
| MetricConsts.AUTH_MTP, subFileOp); |
| break; |
| default: |
| DocumentsStatsLog.write( |
| DocumentsStatsLog.DOCS_UI_FILE_OP_FAILURE, |
| MetricConsts.AUTH_OTHER, subFileOp); |
| break; |
| } |
| } |
| |
| /** |
| * Logs create directory operation error. We do not differentiate between internal and external |
| * locations, all create directory errors are logged under COUNT_FILEOP_SYSTEM. Call this when a |
| * create directory operation fails. |
| */ |
| public static void logCreateDirError() { |
| DocumentsStatsLog.write(DocumentsStatsLog.DOCS_UI_PROVIDER_FILE_OP, |
| MetricConsts.PROVIDER_SYSTEM, MetricConsts.FILEOP_CREATE_DIR_ERROR); |
| } |
| |
| /** |
| * Logs rename file operation error. We do not differentiate between internal and external |
| * locations, all rename errors are logged under COUNT_FILEOP_SYSTEM. Call this |
| * when a rename file operation fails. |
| */ |
| public static void logRenameFileError() { |
| DocumentsStatsLog.write(DocumentsStatsLog.DOCS_UI_PROVIDER_FILE_OP, |
| MetricConsts.PROVIDER_SYSTEM, MetricConsts.FILEOP_RENAME_ERROR); |
| } |
| |
| /** |
| * Logs the cancellation of a file operation. Call this when a Job is canceled. |
| * |
| * @param operationType |
| */ |
| public static void logFileOperationCancelled(@OpType int operationType) { |
| DocumentsStatsLog.write( |
| DocumentsStatsLog.DOCS_UI_FILE_OP_CANCELED, toMetricsOpType(operationType)); |
| } |
| |
| /** |
| * Logs startup time in milliseconds. |
| * |
| * @param startupMs Startup time in milliseconds. |
| */ |
| public static void logStartupMs(int startupMs) { |
| DocumentsStatsLog.write(DocumentsStatsLog.DOCS_UI_STARTUP_MS, startupMs); |
| } |
| |
| private static void logInterProviderFileOps( |
| @MetricConsts.Provider int providerType, |
| DocumentInfo dst, |
| @OpType int operationType) { |
| if (operationType == FileOperationService.OPERATION_DELETE) { |
| DocumentsStatsLog.write(DocumentsStatsLog.DOCS_UI_PROVIDER_FILE_OP, |
| providerType, MetricConsts.FILEOP_DELETE); |
| } else { |
| assert(dst != null); |
| @MetricConsts.Provider int opProviderType = isSystemProvider(dst.authority) |
| ? MetricConsts.PROVIDER_SYSTEM : MetricConsts.PROVIDER_EXTERNAL; |
| DocumentsStatsLog.write(DocumentsStatsLog.DOCS_UI_PROVIDER_FILE_OP, |
| providerType, getOpCode(operationType, opProviderType)); |
| } |
| } |
| |
| private static void logIntraProviderFileOps(String authority, @OpType int operationType) { |
| @MetricConsts.Provider int providerType = isSystemProvider(authority) |
| ? MetricConsts.PROVIDER_SYSTEM : MetricConsts.PROVIDER_EXTERNAL; |
| DocumentsStatsLog.write(DocumentsStatsLog.DOCS_UI_PROVIDER_FILE_OP, |
| providerType, getOpCode(operationType, MetricConsts.PROVIDER_INTRA)); |
| } |
| |
| /** |
| * Logs the action that was started by user. |
| * |
| * @param userAction |
| */ |
| public static void logUserAction(@MetricConsts.UserAction int userAction) { |
| DocumentsStatsLog.write(DocumentsStatsLog.DOCS_UI_USER_ACTION_REPORTED, userAction); |
| } |
| |
| public static void logPickerLaunchedFrom(String packgeName) { |
| DocumentsStatsLog.write( |
| DocumentsStatsLog.DOCS_UI_PICKER_LAUNCHED_FROM_REPORTED, packgeName); |
| } |
| |
| public static void logSearchType(int searchType) { |
| DocumentsStatsLog.write(DocumentsStatsLog.DOCS_UI_SEARCH_TYPE_REPORTED, searchType); |
| } |
| |
| public static void logSearchMode(boolean isKeywordSearch, boolean isChipsSearch) { |
| DocumentsStatsLog.write(DocumentsStatsLog.DOCS_UI_SEARCH_MODE_REPORTED, |
| getSearchMode(isKeywordSearch, isChipsSearch)); |
| } |
| |
| /** |
| * Logs drag initiated from which app, documentsUI or another app. |
| */ |
| public static void logDragInitiated(boolean isDragInitatedFromDocsUI) { |
| DocumentsStatsLog.write(DocumentsStatsLog.DOCS_UI_DRAG_AND_DROP_REPORTED, |
| isDragInitatedFromDocsUI); |
| } |
| |
| public static void logPickResult(PickResult result) { |
| DocumentsStatsLog.write( |
| DocumentsStatsLog.DOCS_UI_PICK_RESULT_REPORTED, |
| result.getActionCount(), |
| result.getDuration(), |
| result.getFileCount(), |
| result.isSearching(), |
| result.getRoot(), |
| result.getMimeType(), |
| result.getRepeatedPickTimes()); |
| |
| DevicePolicyEventLogger.write(DevicePolicyMetricConsts.EVENT_ID_DOCSUI_PICK_RESULT, |
| result.hasCrossProfileUri()); |
| } |
| |
| private static void logStorageFileOperationFailure( |
| Context context, @MetricConsts.SubFileOp int subFileOp, Uri docUri) { |
| assert(Providers.AUTHORITY_STORAGE.equals(docUri.getAuthority())); |
| boolean isInternal; |
| try (ContentProviderClient client = acquireUnstableProviderOrThrow( |
| context.getContentResolver(), Providers.AUTHORITY_STORAGE)) { |
| final Path path = DocumentsContract.findDocumentPath(wrap(client), docUri); |
| final ProvidersAccess providers = DocumentsApplication.getProvidersCache(context); |
| final RootInfo root = providers.getRootOneshot(UserId.DEFAULT_USER, |
| Providers.AUTHORITY_STORAGE, path.getRootId()); |
| isInternal = !root.supportsEject(); |
| } catch (FileNotFoundException | RemoteException | RuntimeException e) { |
| Log.e(TAG, "Failed to obtain its root info. Log the metrics as internal.", e); |
| // It's not very likely to have an external storage so log it as internal. |
| isInternal = true; |
| } |
| @MetricConsts.MetricsAuth final int authority = isInternal |
| ? MetricConsts.AUTH_STORAGE_INTERNAL : MetricConsts.AUTH_STORAGE_EXTERNAL; |
| DocumentsStatsLog.write(DocumentsStatsLog.DOCS_UI_FILE_OP_FAILURE, authority, subFileOp); |
| } |
| |
| /** |
| * Generates an integer identifying the given root. For privacy, this function only recognizes a |
| * small set of hard-coded roots (ones provided by the system). Other roots are all grouped into |
| * a single ROOT_OTHER bucket. |
| */ |
| private static @MetricConsts.Root int sanitizeRoot(Uri uri) { |
| if (uri == null || uri.getAuthority() == null || LauncherActivity.isLaunchUri(uri)) { |
| return MetricConsts.ROOT_NONE; |
| } |
| switch (uri.getAuthority()) { |
| case Providers.AUTHORITY_MEDIA: |
| String rootId = getRootIdSafely(uri); |
| if (rootId == null) { |
| return MetricConsts.ROOT_NONE; |
| } |
| switch (rootId) { |
| case Providers.ROOT_ID_AUDIO: |
| return MetricConsts.ROOT_AUDIO; |
| case Providers.ROOT_ID_IMAGES: |
| return MetricConsts.ROOT_IMAGES; |
| case Providers.ROOT_ID_VIDEOS: |
| return MetricConsts.ROOT_VIDEOS; |
| case Providers.ROOT_ID_DOCUMENTS: |
| return MetricConsts.ROOT_DOCUMENTS; |
| default: |
| return MetricConsts.ROOT_OTHER_DOCS_PROVIDER; |
| } |
| case Providers.AUTHORITY_STORAGE: |
| rootId = getRootIdSafely(uri); |
| if (rootId == null) { |
| return MetricConsts.ROOT_NONE; |
| } |
| if (Providers.ROOT_ID_HOME.equals(rootId)) { |
| return MetricConsts.ROOT_HOME; |
| } else { |
| return MetricConsts.ROOT_DEVICE_STORAGE; |
| } |
| case Providers.AUTHORITY_DOWNLOADS: |
| return MetricConsts.ROOT_DOWNLOADS; |
| case Providers.AUTHORITY_MTP: |
| return MetricConsts.ROOT_MTP; |
| default: |
| return MetricConsts.ROOT_OTHER_DOCS_PROVIDER; |
| } |
| } |
| |
| /** @see #sanitizeRoot(Uri) */ |
| public static @MetricConsts.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. |
| return MetricConsts.ROOT_RECENTS; |
| } else { |
| return sanitizeRoot(root.getUri()); |
| } |
| } |
| |
| /** @see #sanitizeRoot(Uri) */ |
| public static @MetricConsts.Root int sanitizeRoot(ResolveInfo info) { |
| // Log all apps under a single bucket in the roots histogram. |
| return MetricConsts.ROOT_THIRD_PARTY_APP; |
| } |
| |
| /** |
| * Generates an int identifying a mime type. For privacy, this function only recognizes a small |
| * set of hard-coded types. For any other type, this function returns "other". |
| * |
| * @param mimeType |
| * @return |
| */ |
| public static @MetricConsts.Mime int sanitizeMime(String mimeType) { |
| if (mimeType == null) { |
| return MetricConsts.MIME_NONE; |
| } else if ("*/*".equals(mimeType)) { |
| return MetricConsts.MIME_ANY; |
| } else { |
| String type = mimeType.substring(0, mimeType.indexOf('/')); |
| switch (type) { |
| case "application": |
| return MetricConsts.MIME_APPLICATION; |
| case "audio": |
| return MetricConsts.MIME_AUDIO; |
| case "image": |
| return MetricConsts.MIME_IMAGE; |
| case "message": |
| return MetricConsts.MIME_MESSAGE; |
| case "multipart": |
| return MetricConsts.MIME_MULTIPART; |
| case "text": |
| return MetricConsts.MIME_TEXT; |
| case "video": |
| return MetricConsts.MIME_VIDEO; |
| } |
| } |
| // Bucket all other types into one bucket. |
| return MetricConsts.MIME_OTHER; |
| } |
| |
| private static boolean isSystemProvider(String authority) { |
| switch (authority) { |
| case Providers.AUTHORITY_MEDIA: |
| case Providers.AUTHORITY_STORAGE: |
| case Providers.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 @MetricConsts.FileOp int getOpCode( |
| @OpType int operation, @MetricConsts.Provider int providerType) { |
| switch (operation) { |
| case FileOperationService.OPERATION_COPY: |
| switch (providerType) { |
| case MetricConsts.PROVIDER_INTRA: |
| return MetricConsts.FILEOP_COPY_INTRA_PROVIDER; |
| case MetricConsts.PROVIDER_SYSTEM: |
| return MetricConsts.FILEOP_COPY_SYSTEM_PROVIDER; |
| case MetricConsts.PROVIDER_EXTERNAL: |
| return MetricConsts.FILEOP_COPY_EXTERNAL_PROVIDER; |
| } |
| case FileOperationService.OPERATION_COMPRESS: |
| switch (providerType) { |
| case MetricConsts.PROVIDER_INTRA: |
| return MetricConsts.FILEOP_COMPRESS_INTRA_PROVIDER; |
| case MetricConsts.PROVIDER_SYSTEM: |
| return MetricConsts.FILEOP_COMPRESS_SYSTEM_PROVIDER; |
| case MetricConsts.PROVIDER_EXTERNAL: |
| return MetricConsts.FILEOP_COMPRESS_EXTERNAL_PROVIDER; |
| } |
| case FileOperationService.OPERATION_EXTRACT: |
| switch (providerType) { |
| case MetricConsts.PROVIDER_INTRA: |
| return MetricConsts.FILEOP_EXTRACT_INTRA_PROVIDER; |
| case MetricConsts.PROVIDER_SYSTEM: |
| return MetricConsts.FILEOP_EXTRACT_SYSTEM_PROVIDER; |
| case MetricConsts.PROVIDER_EXTERNAL: |
| return MetricConsts.FILEOP_EXTRACT_EXTERNAL_PROVIDER; |
| } |
| case FileOperationService.OPERATION_MOVE: |
| switch (providerType) { |
| case MetricConsts.PROVIDER_INTRA: |
| return MetricConsts.FILEOP_MOVE_INTRA_PROVIDER; |
| case MetricConsts.PROVIDER_SYSTEM: |
| return MetricConsts.FILEOP_MOVE_SYSTEM_PROVIDER; |
| case MetricConsts.PROVIDER_EXTERNAL: |
| return MetricConsts.FILEOP_MOVE_EXTERNAL_PROVIDER; |
| } |
| case FileOperationService.OPERATION_DELETE: |
| return MetricConsts.FILEOP_DELETE; |
| default: |
| Log.w(TAG, "Unrecognized operation type when logging a file operation"); |
| return MetricConsts.FILEOP_OTHER; |
| } |
| } |
| |
| /** |
| * Maps FileOperationService OpType values, to MetricsOpType values. |
| */ |
| private static @MetricConsts.FileOp int toMetricsOpType(@OpType int operation) { |
| switch (operation) { |
| case FileOperationService.OPERATION_COPY: |
| return MetricConsts.FILEOP_COPY; |
| case FileOperationService.OPERATION_MOVE: |
| return MetricConsts.FILEOP_MOVE; |
| case FileOperationService.OPERATION_DELETE: |
| return MetricConsts.FILEOP_DELETE; |
| case FileOperationService.OPERATION_UNKNOWN: |
| default: |
| return MetricConsts.FILEOP_UNKNOWN; |
| } |
| } |
| |
| private static @MetricConsts.MetricsAction int toMetricsAction(int action) { |
| switch(action) { |
| case State.ACTION_OPEN: |
| return MetricConsts.ACTION_OPEN; |
| case State.ACTION_CREATE: |
| return MetricConsts.ACTION_CREATE; |
| case State.ACTION_GET_CONTENT: |
| return MetricConsts.ACTION_GET_CONTENT; |
| case State.ACTION_OPEN_TREE: |
| return MetricConsts.ACTION_OPEN_TREE; |
| case State.ACTION_BROWSE: |
| return MetricConsts.ACTION_BROWSE; |
| case State.ACTION_PICK_COPY_DESTINATION: |
| return MetricConsts.ACTION_PICK_COPY_DESTINATION; |
| default: |
| return MetricConsts.ACTION_OTHER; |
| } |
| } |
| |
| private static int getSearchMode(boolean isKeyword, boolean isChip) { |
| if (isKeyword && isChip) { |
| return MetricConsts.SEARCH_KEYWORD_N_CHIPS; |
| } else if (isKeyword) { |
| return MetricConsts.SEARCH_KEYWORD; |
| } else if (isChip) { |
| return MetricConsts.SEARCH_CHIPS; |
| } else { |
| return MetricConsts.SEARCH_UNKNOWN; |
| } |
| } |
| |
| /** |
| * 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 void countProviders( |
| ProviderCounts counts, List<DocumentInfo> srcs, @Nullable DocumentInfo dst) { |
| for (DocumentInfo doc: srcs) { |
| countForAuthority(counts, doc.authority, dst); |
| } |
| } |
| |
| /** |
| * Count the given uris 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 void countProviders(ProviderCounts counts, List<Uri> uris) { |
| for (Uri uri: uris) { |
| countForAuthority(counts, uri.getAuthority(), null); |
| } |
| } |
| |
| private static void countForAuthority( |
| ProviderCounts counts, String authority, @Nullable DocumentInfo dst) { |
| if (dst != null && authority.equals(dst.authority)) { |
| counts.intraProvider++; |
| } else if (isSystemProvider(authority)){ |
| counts.systemProvider++; |
| } else { |
| counts.externalProvider++; |
| } |
| } |
| |
| private static class ProviderCounts { |
| int intraProvider; |
| int systemProvider; |
| int externalProvider; |
| } |
| |
| private static String getRootIdSafely(Uri uri) { |
| try { |
| return DocumentsContract.getRootId(uri); |
| } catch (IllegalArgumentException iae) { |
| Log.w(TAG, "Invalid root Uri " + uri.toSafeString()); |
| } |
| return null; |
| } |
| |
| /** |
| * The implementation is copied from StatsLogInternal for the DEVICE_POLICY_EVENT. This is a |
| * no-op pre-R. |
| */ |
| private static class DevicePolicyEventLogger { |
| public static void write(@DevicePolicyMetricConsts.EventId int eventId, |
| boolean booleanValue) { |
| if (!VersionUtils.isAtLeastR()) { |
| return; |
| } |
| final StatsEvent.Builder builder = StatsEvent.newBuilder(); |
| builder.setAtomId(DevicePolicyMetricConsts.ATOM_DEVICE_POLICY_EVENT); |
| builder.writeInt(eventId); // eventId |
| builder.writeString(null); // adminPackageName |
| builder.writeInt(0); // intValue |
| builder.writeBoolean(booleanValue); // booleanValue |
| builder.writeLong(0); // timePeriodMs |
| builder.writeByteArray(new byte[0]); // bytes |
| |
| builder.usePooledBuffer(); |
| StatsLog.write(builder.build()); |
| } |
| } |
| } |