1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.chrome.browser.media;
6 
7 import android.app.Activity;
8 import android.content.ContentResolver;
9 import android.content.Intent;
10 import android.net.Uri;
11 import android.os.Bundle;
12 import android.webkit.MimeTypeMap;
13 
14 import androidx.annotation.IntDef;
15 
16 import org.chromium.base.IntentUtils;
17 import org.chromium.base.Log;
18 import org.chromium.base.metrics.RecordHistogram;
19 import org.chromium.chrome.browser.customtabs.CustomTabIntentDataProvider;
20 
21 import java.lang.annotation.Retention;
22 import java.lang.annotation.RetentionPolicy;
23 import java.util.Locale;
24 
25 /**
26  * The MediaLauncherActivity handles media-viewing Intents from other apps. It takes the given
27  * content:// URI from the Intent and properly routes it to a media-viewing CustomTabActivity.
28  */
29 public class MediaLauncherActivity extends Activity {
30     private static final String TAG = "MediaLauncher";
31 
32     // UMA histogram values for media types the user can open.
33     // Keep in sync with MediaLauncherActivityMediaType enum in enums.xml.
34     @IntDef({MediaType.AUDIO, MediaType.IMAGE, MediaType.VIDEO})
35     @Retention(RetentionPolicy.SOURCE)
36     @interface MediaType {
37         int AUDIO = 0;
38         int IMAGE = 1;
39         int VIDEO = 2;
40         int UNKNOWN = 3;
41         int NUM_ENTRIES = 4;
42     }
43 
44     @Override
onCreate(Bundle savedInstanceState)45     public void onCreate(Bundle savedInstanceState) {
46         super.onCreate(savedInstanceState);
47 
48         Intent input = IntentUtils.sanitizeIntent(getIntent());
49         Uri contentUri = input.getData();
50         String mimeType = getMIMEType(contentUri);
51         int mediaType = MediaViewerUtils.getMediaTypeFromMIMEType(mimeType);
52 
53         RecordHistogram.recordEnumeratedHistogram(
54                 "MediaLauncherActivity.MediaType", mediaType, MediaType.NUM_ENTRIES);
55 
56         if (mediaType == MediaType.UNKNOWN) {
57             // With our intent-filter, we should only receive implicit intents with media MIME
58             // types. If we receive a non-media MIME type, it is likely a malicious explicit intent,
59             // so we should not proceed.
60             finish();
61             return;
62         }
63 
64         // TODO(https://crbug.com/800880): Determine file:// URI when possible.
65         Intent intent = MediaViewerUtils.getMediaViewerIntent(
66                 contentUri, contentUri, mimeType, false /* allowExternalAppHandlers */);
67         intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
68         intent.putExtra(CustomTabIntentDataProvider.EXTRA_BROWSER_LAUNCH_SOURCE,
69                 CustomTabIntentDataProvider.LaunchSourceType.MEDIA_LAUNCHER_ACTIVITY);
70 
71         boolean success = false;
72         try {
73             startActivity(intent);
74             success = true;
75         } catch (SecurityException e) {
76             Log.w(TAG, "Cannot open content URI: " + contentUri.toString(), e);
77         }
78 
79         RecordHistogram.recordBooleanHistogram("MediaLauncherActivity.LaunchResult", success);
80 
81         finish();
82     }
83 
getMIMEType(Uri uri)84     private String getMIMEType(Uri uri) {
85         // With a content URI, we can just query the ContentResolver.
86         if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
87             return getContentResolver().getType(uri);
88         }
89 
90         // Otherwise, use the file extension.
91         String filteredUri = filterURI(uri);
92         String fileExtension = MimeTypeMap.getFileExtensionFromUrl(filteredUri);
93         return MimeTypeMap.getSingleton().getMimeTypeFromExtension(
94                 fileExtension.toLowerCase(Locale.ROOT));
95     }
96 
97     // MimeTypeMap.getFileExtensionFromUrl fails when the file name includes certain special
98     // characters, so we filter those out of the URI when determining the MIME type.
filterURI(Uri uri)99     protected static String filterURI(Uri uri) {
100         String uriString = uri.toString();
101         int filterIndex = uriString.length();
102 
103         int fragmentIndex = uriString.lastIndexOf('#', filterIndex);
104         if (fragmentIndex >= 0) filterIndex = fragmentIndex;
105 
106         int queryIndex = uriString.lastIndexOf('?', filterIndex);
107         if (queryIndex >= 0) filterIndex = queryIndex;
108 
109         int extensionIndex = uriString.lastIndexOf('.', filterIndex);
110         if (extensionIndex >= 0) filterIndex = extensionIndex;
111 
112         return uriString.substring(0, filterIndex).replaceAll("['$!]", "")
113                 + uriString.substring(filterIndex);
114     }
115 }
116