diff --git a/apps/app/App_Resources/Android/AndroidManifest.xml b/apps/app/App_Resources/Android/AndroidManifest.xml
index 72e4464c2..b73635900 100644
--- a/apps/app/App_Resources/Android/AndroidManifest.xml
+++ b/apps/app/App_Resources/Android/AndroidManifest.xml
@@ -27,6 +27,17 @@
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true" >
+
+
+
+
+
+
+
+
diff --git a/apps/app/ui-tests-app/intent/main-page.ts b/apps/app/ui-tests-app/intent/main-page.ts
new file mode 100644
index 000000000..fba42084f
--- /dev/null
+++ b/apps/app/ui-tests-app/intent/main-page.ts
@@ -0,0 +1,17 @@
+import { EventData } from "tns-core-modules/data/observable";
+import { SubMainPageViewModel } from "../sub-main-page-view-model";
+import { WrapLayout } from "tns-core-modules/ui/layouts/wrap-layout";
+import { Page } from "tns-core-modules/ui/page";
+
+export function pageLoaded(args: EventData) {
+ const page = args.object;
+ const wrapLayout = page.getViewById("wrapLayoutWithExamples");
+ page.bindingContext = new SubMainPageViewModel(wrapLayout, loadExamples());
+}
+
+export function loadExamples() {
+ const examples = new Map();
+ examples.set("open-file", "intent/open-file");
+
+ return examples;
+}
\ No newline at end of file
diff --git a/apps/app/ui-tests-app/intent/main-page.xml b/apps/app/ui-tests-app/intent/main-page.xml
new file mode 100644
index 000000000..33306f0d0
--- /dev/null
+++ b/apps/app/ui-tests-app/intent/main-page.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/app/ui-tests-app/intent/open-file.ts b/apps/app/ui-tests-app/intent/open-file.ts
new file mode 100644
index 000000000..d95ccc75f
--- /dev/null
+++ b/apps/app/ui-tests-app/intent/open-file.ts
@@ -0,0 +1,15 @@
+import { isIOS, isAndroid } from "tns-core-modules/platform";
+import * as fs from "tns-core-modules/file-system/file-system";
+import * as utils from "tns-core-modules/utils/utils";
+
+export function openFile() {
+ let directory;
+ if (isIOS) {
+ directory = fs.knownFolders.ios.downloads();
+ } else if (isAndroid) {
+ directory = android.os.Environment.getExternalStorageDirectory().getAbsolutePath().toString();
+ }
+
+ const filePath = fs.path.join(directory, "Test_File_Open.txt");
+ utils.openFile(filePath);
+}
\ No newline at end of file
diff --git a/apps/app/ui-tests-app/intent/open-file.xml b/apps/app/ui-tests-app/intent/open-file.xml
new file mode 100644
index 000000000..aa41c4042
--- /dev/null
+++ b/apps/app/ui-tests-app/intent/open-file.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/app/ui-tests-app/main-page.ts b/apps/app/ui-tests-app/main-page.ts
index 2e6c4fd49..88d6d06d9 100644
--- a/apps/app/ui-tests-app/main-page.ts
+++ b/apps/app/ui-tests-app/main-page.ts
@@ -36,6 +36,7 @@ export function pageLoaded(args: EventData) {
examples.set("progress-bar", "progress-bar/main-page");
examples.set("date-picker", "date-picker/date-picker");
examples.set("nested-frames", "nested-frames/main-page");
+ examples.set("intent", "intent/main-page");
page.bindingContext = new MainPageViewModel(wrapLayout, examples);
const parent = page.getViewById("parentLayout");
diff --git a/tests/app/App_Resources/Android/AndroidManifest.xml b/tests/app/App_Resources/Android/AndroidManifest.xml
index dea9961e6..2c9be1067 100644
--- a/tests/app/App_Resources/Android/AndroidManifest.xml
+++ b/tests/app/App_Resources/Android/AndroidManifest.xml
@@ -27,6 +27,7 @@
android:label="@string/app_name"
android:largeHeap="true"
android:theme="@style/AppTheme" >
+
10) {
- allTests["SAFEAREALAYOUT"] = safeAreaLayoutTests;
- allTests["SAFEAREA-LISTVIEW"] = safeAreaListViewtTests;
- allTests["SAFEAREA-SCROLL-VIEW"] = scrollViewSafeAreaTests;
- allTests["SAFEAREA-REPEATER"] = repeaterSafeAreaTests;
- allTests["SAFEAREA-WEBVIEW"] = webViewSafeAreaTests;
-}
+// if (platform.isIOS && ios.MajorVersion > 10) {
+// allTests["SAFEAREALAYOUT"] = safeAreaLayoutTests;
+// allTests["SAFEAREA-LISTVIEW"] = safeAreaListViewtTests;
+// allTests["SAFEAREA-SCROLL-VIEW"] = scrollViewSafeAreaTests;
+// allTests["SAFEAREA-REPEATER"] = repeaterSafeAreaTests;
+// allTests["SAFEAREA-WEBVIEW"] = webViewSafeAreaTests;
+// }
-import * as stylePropertiesTests from "./ui/styling/style-properties-tests";
-allTests["STYLE-PROPERTIES"] = stylePropertiesTests;
+// import * as stylePropertiesTests from "./ui/styling/style-properties-tests";
+// allTests["STYLE-PROPERTIES"] = stylePropertiesTests;
-import * as frameTests from "./ui/frame/frame-tests";
-allTests["FRAME"] = frameTests;
+// import * as frameTests from "./ui/frame/frame-tests";
+// allTests["FRAME"] = frameTests;
-import * as viewTests from "./ui/view/view-tests";
-allTests["VIEW"] = viewTests;
+// import * as viewTests from "./ui/view/view-tests";
+// allTests["VIEW"] = viewTests;
-import * as viewLayoutChangedEventTests from "./ui/view/view-tests-layout-event";
-allTests["VIEW-LAYOUT-EVENT"] = viewLayoutChangedEventTests;
+// import * as viewLayoutChangedEventTests from "./ui/view/view-tests-layout-event";
+// allTests["VIEW-LAYOUT-EVENT"] = viewLayoutChangedEventTests;
-import * as styleTests from "./ui/styling/style-tests";
-allTests["STYLE"] = styleTests;
+// import * as styleTests from "./ui/styling/style-tests";
+// allTests["STYLE"] = styleTests;
-import * as visualStateTests from "./ui/styling/visual-state-tests";
-allTests["VISUAL-STATE"] = visualStateTests;
+// import * as visualStateTests from "./ui/styling/visual-state-tests";
+// allTests["VISUAL-STATE"] = visualStateTests;
-import * as valueSourceTests from "./ui/styling/value-source-tests";
-allTests["VALUE-SOURCE"] = valueSourceTests;
+// import * as valueSourceTests from "./ui/styling/value-source-tests";
+// allTests["VALUE-SOURCE"] = valueSourceTests;
-import * as buttonTests from "./ui/button/button-tests";
-allTests["BUTTON"] = buttonTests;
+// import * as buttonTests from "./ui/button/button-tests";
+// allTests["BUTTON"] = buttonTests;
-import * as borderTests from "./ui/border/border-tests";
-allTests["BORDER"] = borderTests;
+// import * as borderTests from "./ui/border/border-tests";
+// allTests["BORDER"] = borderTests;
-import * as labelTests from "./ui/label/label-tests";
-allTests["LABEL"] = labelTests;
+// import * as labelTests from "./ui/label/label-tests";
+// allTests["LABEL"] = labelTests;
-import * as tabViewTests from "./ui/tab-view/tab-view-tests";
-allTests["TAB-VIEW"] = tabViewTests;
+// import * as tabViewTests from "./ui/tab-view/tab-view-tests";
+// allTests["TAB-VIEW"] = tabViewTests;
-import * as tabViewNavigationTests from "./ui/tab-view/tab-view-navigation-tests";
-allTests["TAB-VIEW-NAVIGATION"] = tabViewNavigationTests;
+// import * as tabViewNavigationTests from "./ui/tab-view/tab-view-navigation-tests";
+// allTests["TAB-VIEW-NAVIGATION"] = tabViewNavigationTests;
-import * as imageTests from "./ui/image/image-tests";
-allTests["IMAGE"] = imageTests;
+// import * as imageTests from "./ui/image/image-tests";
+// allTests["IMAGE"] = imageTests;
-import * as sliderTests from "./ui/slider/slider-tests";
-allTests["SLIDER"] = sliderTests;
+// import * as sliderTests from "./ui/slider/slider-tests";
+// allTests["SLIDER"] = sliderTests;
-import * as switchTests from "./ui/switch/switch-tests";
-allTests["SWITCH"] = switchTests;
+// import * as switchTests from "./ui/switch/switch-tests";
+// allTests["SWITCH"] = switchTests;
-import * as progressTests from "./ui/progress/progress-tests";
-allTests["PROGRESS"] = progressTests;
+// import * as progressTests from "./ui/progress/progress-tests";
+// allTests["PROGRESS"] = progressTests;
-import * as placeholderTests from "./ui/placeholder/placeholder-tests";
-allTests["PLACEHOLDER"] = placeholderTests;
+// import * as placeholderTests from "./ui/placeholder/placeholder-tests";
+// allTests["PLACEHOLDER"] = placeholderTests;
-import * as pageTests from "./ui/page/page-tests";
-allTests["PAGE"] = pageTests;
+// import * as pageTests from "./ui/page/page-tests";
+// allTests["PAGE"] = pageTests;
-import * as listViewTests from "./ui/list-view/list-view-tests";
-allTests["LISTVIEW"] = listViewTests;
+// import * as listViewTests from "./ui/list-view/list-view-tests";
+// allTests["LISTVIEW"] = listViewTests;
-import * as activityIndicatorTests from "./ui/activity-indicator/activity-indicator-tests";
-allTests["ACTIVITY-INDICATOR"] = activityIndicatorTests;
+// import * as activityIndicatorTests from "./ui/activity-indicator/activity-indicator-tests";
+// allTests["ACTIVITY-INDICATOR"] = activityIndicatorTests;
-import * as textFieldTests from "./ui/text-field/text-field-tests";
-allTests["TEXT-FIELD"] = textFieldTests;
+// import * as textFieldTests from "./ui/text-field/text-field-tests";
+// allTests["TEXT-FIELD"] = textFieldTests;
-import * as textViewTests from "./ui/text-view/text-view-tests";
-allTests["TEXT-VIEW"] = textViewTests;
+// import * as textViewTests from "./ui/text-view/text-view-tests";
+// allTests["TEXT-VIEW"] = textViewTests;
-import * as listPickerTests from "./ui/list-picker/list-picker-tests";
-allTests["LIST-PICKER"] = listPickerTests;
+// import * as listPickerTests from "./ui/list-picker/list-picker-tests";
+// allTests["LIST-PICKER"] = listPickerTests;
-import * as datePickerTests from "./ui/date-picker/date-picker-tests";
-allTests["DATE-PICKER"] = datePickerTests;
+// import * as datePickerTests from "./ui/date-picker/date-picker-tests";
+// allTests["DATE-PICKER"] = datePickerTests;
-import * as timePickerTests from "./ui/time-picker/time-picker-tests";
-allTests["TIME-PICKER"] = timePickerTests;
+// import * as timePickerTests from "./ui/time-picker/time-picker-tests";
+// allTests["TIME-PICKER"] = timePickerTests;
-import * as webViewTests from "./ui/web-view/web-view-tests";
-allTests["WEB-VIEW"] = webViewTests;
+// import * as webViewTests from "./ui/web-view/web-view-tests";
+// allTests["WEB-VIEW"] = webViewTests;
-import * as htmlViewTests from "./ui/html-view/html-view-tests";
-allTests["HTML-VIEW"] = htmlViewTests;
+// import * as htmlViewTests from "./ui/html-view/html-view-tests";
+// allTests["HTML-VIEW"] = htmlViewTests;
-import * as repeaterTests from "./ui/repeater/repeater-tests";
-allTests["REPEATER"] = repeaterTests;
+// import * as repeaterTests from "./ui/repeater/repeater-tests";
+// allTests["REPEATER"] = repeaterTests;
-import * as segmentedBarTests from "./ui/segmented-bar/segmented-bar-tests";
-allTests["SEGMENTED-BAR"] = segmentedBarTests;
+// import * as segmentedBarTests from "./ui/segmented-bar/segmented-bar-tests";
+// allTests["SEGMENTED-BAR"] = segmentedBarTests;
-import * as animationTests from "./ui/animation/animation-tests";
-allTests["ANIMATION"] = animationTests;
+// import * as animationTests from "./ui/animation/animation-tests";
+// allTests["ANIMATION"] = animationTests;
-import * as lifecycle from "./ui/lifecycle/lifecycle-tests";
-allTests["LIFECYCLE"] = lifecycle;
+// import * as lifecycle from "./ui/lifecycle/lifecycle-tests";
+// allTests["LIFECYCLE"] = lifecycle;
-import * as cssAnimationTests from "./ui/animation/css-animation-tests";
-allTests["CSS-ANIMATION"] = cssAnimationTests;
+// import * as cssAnimationTests from "./ui/animation/css-animation-tests";
+// allTests["CSS-ANIMATION"] = cssAnimationTests;
-import * as transitionTests from "./navigation/transition-tests";
-allTests["TRANSITIONS"] = transitionTests;
+// import * as transitionTests from "./navigation/transition-tests";
+// allTests["TRANSITIONS"] = transitionTests;
-import * as searchBarTests from "./ui/search-bar/search-bar-tests";
-allTests["SEARCH-BAR"] = searchBarTests;
+// import * as searchBarTests from "./ui/search-bar/search-bar-tests";
+// allTests["SEARCH-BAR"] = searchBarTests;
-import * as navigationTests from "./navigation/navigation-tests";
-allTests["NAVIGATION"] = navigationTests;
+// import * as navigationTests from "./navigation/navigation-tests";
+// allTests["NAVIGATION"] = navigationTests;
-import * as livesyncTests from "./livesync/livesync-tests";
-allTests["LIVESYNC"] = livesyncTests;
+// import * as livesyncTests from "./livesync/livesync-tests";
+// allTests["LIVESYNC"] = livesyncTests;
-import * as tabViewRootTests from "./ui/tab-view/tab-view-root-tests";
-allTests["TAB-VIEW-ROOT"] = tabViewRootTests;
+// import * as tabViewRootTests from "./ui/tab-view/tab-view-root-tests";
+// allTests["TAB-VIEW-ROOT"] = tabViewRootTests;
-import * as resetRootViewTests from "./ui/root-view/reset-root-view-tests";
-allTests["RESET-ROOT-VIEW"] = resetRootViewTests;
+// import * as resetRootViewTests from "./ui/root-view/reset-root-view-tests";
+// allTests["RESET-ROOT-VIEW"] = resetRootViewTests;
-import * as rootViewTests from "./ui/root-view/root-view-tests";
-allTests["ROOT-VIEW"] = rootViewTests;
+// import * as rootViewTests from "./ui/root-view/root-view-tests";
+// allTests["ROOT-VIEW"] = rootViewTests;
import * as utilsTests from "./utils/utils-tests";
allTests["UTILS"] = utilsTests;
diff --git a/tests/package.json b/tests/package.json
index 3f6cb8562..a36530bc3 100644
--- a/tests/package.json
+++ b/tests/package.json
@@ -28,4 +28,4 @@
"scripts": {
"check-circular-deps": "node circular-check.js"
}
-}
\ No newline at end of file
+}
diff --git a/tns-core-modules/utils/utils.android.ts b/tns-core-modules/utils/utils.android.ts
index c2b9db852..2b3845652 100644
--- a/tns-core-modules/utils/utils.android.ts
+++ b/tns-core-modules/utils/utils.android.ts
@@ -1,10 +1,16 @@
import {
- write as traceWrite, categories as traceCategories, messageType as traceMessageType
+ write as traceWrite,
+ categories as traceCategories,
+ messageType as traceMessageType,
} from "../trace";
export * from "./utils-common";
import { getNativeApplication, android as androidApp } from "../application";
+import { device } from "../platform";
+import { FileSystemAccess } from "../file-system/file-system-access";
+
+const MIN_URI_SHARE_RESTRICTED_APK_VERSION = 24;
export module layout {
let density: number;
@@ -224,3 +230,145 @@ export function openUrl(location: string): boolean {
}
return true;
}
+
+/**
+ * Check whether external storage is read only
+ *
+ * @returns {boolean} whether the external storage is read only
+ */
+function isExternalStorageReadOnly(): boolean {
+ const extStorageState = android.os.Environment.getExternalStorageState();
+ if (android.os.Environment.MEDIA_MOUNTED_READ_ONLY === extStorageState) {
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Checks whether external storage is available
+ *
+ * @returns {boolean} whether external storage is available
+ */
+function isExternalStorageAvailable(): boolean {
+ const extStorageState = android.os.Environment.getExternalStorageState();
+ if (android.os.Environment.MEDIA_MOUNTED === extStorageState) {
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Detect the mimetype of a file at a given path
+ *
+ * @param {string} filePath
+ * @returns {string} mimetype
+ */
+function getMimeTypeNameFromExtension(filePath: string): string {
+ const mimeTypeMap = android.webkit.MimeTypeMap.getSingleton();
+ const extension = new FileSystemAccess()
+ .getFileExtension(filePath)
+ .replace(".", "")
+ .toLowerCase();
+
+ return mimeTypeMap.getMimeTypeFromExtension(extension);
+}
+
+/**
+ * Open a file
+ *
+ * @param {string} filePath
+ * @returns {boolean} whether opening the file succeeded or not
+ */
+export function openFile(filePath: string): boolean {
+ const context = ad.getApplicationContext();
+ try {
+ // Ensure external storage is available
+ if (!isExternalStorageAvailable()) {
+ traceWrite(
+ `
+External storage is unavailable (please check app permissions).
+Applications cannot access internal storage of other application on Android (see: https://developer.android.com/guide/topics/data/data-storage).
+`,
+ traceCategories.Error,
+ traceMessageType.error,
+ );
+
+ return false;
+ }
+
+ // Ensure external storage is available
+ if (isExternalStorageReadOnly()) {
+ traceWrite("External storage is read only", traceCategories.Error, traceMessageType.error);
+ return false;
+ }
+
+ // Determine file mimetype & start creating intent
+ const mimeType = getMimeTypeNameFromExtension(filePath);
+ const intent = new android.content.Intent(android.content.Intent.ACTION_VIEW);
+ const chooserIntent = android.content.Intent.createChooser(intent, "Open File...");
+
+ intent.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK);
+ chooserIntent.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ // Android SDK <28 only requires starting the chooser Intent straight forwardly
+ const sdkVersion = parseInt(device.sdkVersion, 10);
+ if (sdkVersion && sdkVersion < MIN_URI_SHARE_RESTRICTED_APK_VERSION) {
+ traceWrite(
+ `detected sdk version ${sdkVersion} (< ${MIN_URI_SHARE_RESTRICTED_APK_VERSION}), using simple openFile`,
+ traceCategories.Debug
+ );
+ intent.setDataAndType(android.net.Uri.fromFile(new java.io.File(filePath)), mimeType);
+ context.startActivity(chooserIntent);
+ return true;
+ }
+
+ traceWrite(
+ `detected sdk version ${sdkVersion} (>= ${MIN_URI_SHARE_RESTRICTED_APK_VERSION}), using URI openFile`,
+ traceCategories.Debug
+ );
+
+ // Android SDK 24+ introduced file system permissions changes that disallow
+ // exposing URIs between applications
+ //
+ // see: https://developer.android.com/reference/android/os/FileUriExposedException
+ // see: https://github.com/NativeScript/NativeScript/issues/5661#issuecomment-456405380
+ const providerName = `${context.getPackageName()}.provider`;
+ traceWrite(`fully-qualified provider name [${providerName}]`, traceCategories.Debug);
+
+ const apkURI = android.support.v4.content.FileProvider.getUriForFile(
+ context,
+ providerName,
+ new java.io.File(filePath),
+ );
+
+ // Set flags & URI as data type on the view action
+ intent.addFlags(android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ chooserIntent.addFlags(android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+ // Finish intent setup
+ intent.setDataAndType(apkURI, mimeType);
+
+ context.startActivity(chooserIntent);
+
+ return true;
+ } catch (err) {
+ const msg = err.message ? `: ${err.message}` : "";
+ traceWrite(`Error in openFile${msg}`, traceCategories.Error, traceMessageType.error);
+
+ if (msg &&
+ msg.includes("Attempt to invoke virtual method") &&
+ msg.includes("android.content.pm.ProviderInfo.loadXmlMetaData") &&
+ msg.includes("on a null object reference")) {
+ // Alert user to possible fix
+ traceWrite(
+ `
+Please ensure you have your manifest correctly configured with the FileProvider.
+(see: https://developer.android.com/reference/android/support/v4/content/FileProvider#ProviderDefinition)
+`,
+ traceCategories.Error,
+ );
+ }
+
+ return false;
+ }
+}
diff --git a/tns-core-modules/utils/utils.d.ts b/tns-core-modules/utils/utils.d.ts
index 7ae9e8257..2e4e06fca 100644
--- a/tns-core-modules/utils/utils.d.ts
+++ b/tns-core-modules/utils/utils.d.ts
@@ -285,6 +285,12 @@ export function isDataURI(uri: string): boolean
*/
export function openUrl(url: string): boolean
+/**
+ * Opens file.
+ * @param {string} filePath The file.
+ */
+export function openFile(filePath: string): boolean
+
/**
* Escapes special regex symbols (., *, ^, $ and so on) in string in order to create a valid regex from it.
* @param source The original value.