Skip to content

Commit

Permalink
Fix #615 Add Meta Quest support (#704)
Browse files Browse the repository at this point in the history
* Add Meta Quest support

* Add Meta Quest support

* Add Meta Quest support

* Add Meta Quest support

* Add Meta Quest support

* Add Meta Quest support

* Fix tests

* Add Meta Quest support

* Update README.md

* Add Meta Quest support
  • Loading branch information
FluorescentHallucinogen authored Jun 29, 2022
1 parent 69ed1dd commit f8563a8
Show file tree
Hide file tree
Showing 9 changed files with 88 additions and 14 deletions.
10 changes: 7 additions & 3 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,13 @@ parse the Web manifest and generate default values for the Android project, wher
will prompt the user to confirm or input values where one could not be generated.

```
bubblewrap init --manifest="<web-manifest-url>" [--directory="<path-to-output-location>"] [--chromeosonly]
bubblewrap init --manifest="<web-manifest-url>" [--directory="<path-to-output-location>"] [--chromeosonly] [--metaquest]
```

Options:
- `--directory`: path where to generate the project. Defaults to the current directory.
- `--chromeosonly`: this flag specifies that the build will be used for Chrome OS only and prevents non-Chrome OS devices from installing the app.
- `--metaquest`: this flag specifies that the build will be compatible with Meta Quest devices.
- `--alphaDependencies`: enables features that depend on upcoming version of the Android library
for Trusted Web Activity or that are still unstable.

Expand Down Expand Up @@ -351,6 +352,7 @@ Fields:
|host|string|true|The origin that will be opened in the Trusted Web Activity.|
|iconUrl|string|true|Full URL to an the icon used for the application launcher and splash screen. Must be at least 512x512 px.|
|isChromeOSOnly|boolean|false|Generates an application that targets only ChromeOS devices. Defaults to `false`.|
|isMetaQuest|boolean|false|Generates an application that compatible with Meta Quest devices. Defaults to `false`.|
|launcherName|string|false|A short name for the Android application, displayed on the Android launcher|
|maskableIconUrl|string|false|Full URL to an the icon used for maskable icons, when supported by the device.|
|monochromeIconUrl|string|false|Full URL to a monochrome icon, used when displaying notifications.|
Expand All @@ -369,8 +371,10 @@ Fields:
|splashScreenFadeOutDuration|number|true|Duration for the splash screen fade out animation.|
|startUrl|string|true|The start path for the TWA. Must be relative to the domain.|
|themeColor|string|true|The color used for the status bar.|
|webManifestUrl|string|false|Full URL to the PWA Web Manifest. Required for the application to be compatible with Chrome OS devices.|

|webManifestUrl|string|false|Full URL to the PWA Web Manifest. Required for the application to be compatible with Chrome OS and Meta Quest devices.|
|fullScopeUrl|string|false|The navigation scope that the browser considers to be within the app. If the user navigates outside the scope, it reverts to a normal web page inside a browser tab or window. Must be a full URL. Required and used only by Meta Quest devices.|
|minSdkVersion|number|false|The minimum [Android API Level](https://developer.android.com/guide/topics/manifest/uses-sdk-element#ApiLevels) required for the application to run. Defaults to `23`, if `isMetaQuest` is `true`, and `19` otherwise.|

### Features

Developers can enable additional features in their Android application. Some features may include more dependencies into the application and increase the binary size.
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/lib/cmds/help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const HELP_MESSAGES = new Map<string, string>(
' directory',
'--chromeosonly ........ specifies that the build will be used for Chrome OS only and' +
' prevents non-Chrome OS devices from installing the app.',
'--metaquest ........... specifies that the build will be compatible with Meta Quest' +
' devices.',
'--alphaDependencies ... enables features that depend on upcoming version of the ' +
' Android library for Trusted Web Activity or that are still unstable.',
].join('\n')],
Expand Down
8 changes: 8 additions & 0 deletions packages/cli/src/lib/cmds/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export interface InitArgs {
manifest?: string;
directory?: string;
chromeosonly?: boolean;
metaquest?: boolean;
alphaDependencies?: boolean;
}

Expand Down Expand Up @@ -245,6 +246,13 @@ export async function init(
twaManifest.isChromeOSOnly = true;
}

if (args.metaquest) {
twaManifest.isMetaQuest = true;
twaManifest.minSdkVersion = 23;
// Warn about increasing the minimum Android API Level
prompt.printMessage(messages.warnIncreasingMinSdkVersion);
}

if (args.alphaDependencies) {
twaManifest.alphaDependencies = {
enabled: true,
Expand Down
5 changes: 5 additions & 0 deletions packages/cli/src/lib/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ type Messages = {
promptVersionMismatch: (currentVersion: string, playStoreVerison: string) => string;
promptUpdateProject: string;
warnFamilyPolicy: string;
warnIncreasingMinSdkVersion: string;
warnPwaFailedQuality: string;
updateConfigUsage: string;
jdkPathIsNotCorrect: string;
Expand Down Expand Up @@ -398,6 +399,10 @@ the PWA:
' Check out the Play for' +
' Families\npolicies to learn more.\n' +
cyan('https://play.google.com/console/about/families/'),
warnIncreasingMinSdkVersion:
bold(yellow('WARNING: ')) + `The minimum Android API Level (${cyan('minSdkVersion')}) has ` +
`been increased\nfrom ${cyan('19')} to ${cyan('23')} because the ${cyan('--metaquest')} ` +
'flag is used.',
warnPwaFailedQuality: red('PWA Quality Criteria check failed.'),
updateConfigUsage: 'Usage: [--jdkPath <path-to-jdk>] [--androidSdkPath <path-to-android-sdk>]' +
'(You can insert one or both of them)',
Expand Down
17 changes: 17 additions & 0 deletions packages/core/src/lib/TwaManifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ const DEFAULT_NAVIGATION_DIVIDER_COLOR = '#00000000';
const DEFAULT_BACKGROUND_COLOR = '#FFFFFF';
const DEFAULT_APP_VERSION_CODE = 1;
const DEFAULT_APP_VERSION_NAME = DEFAULT_APP_VERSION_CODE.toString();
const DEFAULT_MIN_SDK_VERSION = 19;
const DEFAULT_SIGNING_KEY_PATH = './android.keystore';
const DEFAULT_SIGNING_KEY_ALIAS = 'android';
const DEFAULT_ENABLE_NOTIFICATIONS = true;
Expand Down Expand Up @@ -122,6 +123,7 @@ type alphaDependencies = {
* splashScreenFadeOutDuration: 300
* isChromeOSOnly: false, // Setting to true will enable a feature that prevents non-ChromeOS devices
* from installing the app.
* isMetaQuest: false, // Setting to true will generate the build compatible with Meta Quest devices.
* serviceAccountJsonFile: '<%= serviceAccountJsonFile %>', // The service account used to communicate with
* Google Play.
*
Expand Down Expand Up @@ -155,6 +157,9 @@ export class TwaManifest {
alphaDependencies: alphaDependencies;
enableSiteSettingsShortcut: boolean;
isChromeOSOnly: boolean;
isMetaQuest: boolean;
fullScopeUrl?: URL;
minSdkVersion: number;
shareTarget?: ShareTarget;
orientation: Orientation;
fingerprints: Fingerprint[];
Expand Down Expand Up @@ -201,6 +206,9 @@ export class TwaManifest {
this.enableSiteSettingsShortcut = data.enableSiteSettingsShortcut != undefined ?
data.enableSiteSettingsShortcut : true;
this.isChromeOSOnly = data.isChromeOSOnly != undefined ? data.isChromeOSOnly : false;
this.isMetaQuest = data.isMetaQuest != undefined ? data.isMetaQuest : false;
this.fullScopeUrl = data.fullScopeUrl ? new URL(data.fullScopeUrl) : undefined;
this.minSdkVersion = data.minSdkVersion || DEFAULT_MIN_SDK_VERSION;
this.shareTarget = data.shareTarget;
this.orientation = data.orientation || DEFAULT_ORIENTATION;
this.fingerprints = data.fingerprints || [];
Expand All @@ -224,6 +232,7 @@ export class TwaManifest {
backgroundColor: this.backgroundColor.hex(),
appVersion: this.appVersionName,
webManifestUrl: this.webManifestUrl ? this.webManifestUrl.toString() : undefined,
fullScopeUrl: this.fullScopeUrl ? this.fullScopeUrl.toString() : undefined,
});
}

Expand Down Expand Up @@ -291,6 +300,7 @@ export class TwaManifest {
findSuitableIcon(webManifest.icons, 'monochrome', MIN_NOTIFICATION_ICON_SIZE);

const fullStartUrl: URL = new URL(webManifest['start_url'] || '/', webManifestUrl);
const fullScopeUrl: URL = new URL(webManifest['scope'] || '.', webManifestUrl);
const shortcuts: ShortcutInfo[] = this.getShortcuts(webManifestUrl, webManifest);

function resolveIconUrl(icon: WebManifestIcon | null): string | undefined {
Expand Down Expand Up @@ -326,6 +336,7 @@ export class TwaManifest {
features: {},
shareTarget: TwaManifest.verifyShareTarget(webManifestUrl, webManifest.share_target),
orientation: asOrientation(webManifest.orientation) || DEFAULT_ORIENTATION,
fullScopeUrl: fullScopeUrl.toString(),
});
return twaManifest;
}
Expand Down Expand Up @@ -463,6 +474,7 @@ export class TwaManifest {
webManifestUrl);

const fullStartUrl: URL = new URL(webManifest['start_url'] || '/', webManifestUrl);
const fullScopeUrl: URL = new URL(webManifest['scope'] || '.', webManifestUrl);

const twaManifest = new TwaManifest({
...oldTwaManifestJson,
Expand All @@ -473,6 +485,8 @@ export class TwaManifest {
webManifest['name']?.substring(0, SHORT_NAME_MAX_SIZE)),
display: this.getNewFieldValue('display', fieldsToIgnore, oldTwaManifest.display,
asDisplayMode(webManifest['display']!)!),
fullScopeUrl: this.getNewFieldValue('fullScopeUrl', fieldsToIgnore,
oldTwaManifest.fullScopeUrl?.toString(), fullScopeUrl.toString()),
themeColor: this.getNewFieldValue('themeColor', fieldsToIgnore,
oldTwaManifest.themeColor.hex(), webManifest['theme_color']!),
backgroundColor: this.getNewFieldValue('backgroundColor', fieldsToIgnore,
Expand Down Expand Up @@ -528,6 +542,9 @@ export interface TwaManifestJson {
};
enableSiteSettingsShortcut?: boolean;
isChromeOSOnly?: boolean;
isMetaQuest?: boolean; // Older Manifests may not have this field.
fullScopeUrl?: string; // Older Manifests may not have this field.
minSdkVersion?: number; // Older Manifests may not have this field.
shareTarget?: ShareTarget;
orientation?: Orientation;
fingerprints?: Fingerprint[];
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/lib/types/WebManifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export interface WebManifestJson {
name?: string;
short_name?: string;
start_url?: string;
scope?: string;
display?: WebManifestDisplayMode;
theme_color?: string;
background_color?: string;
Expand Down
19 changes: 11 additions & 8 deletions packages/core/src/spec/lib/TwaManifestSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ describe('TwaManifest', () => {
fallbackType: 'webview',
enableSiteSettingsShortcut: false,
isChromeOSOnly: false,
isMetaQuest: false,
serviceAccountJsonFile: '/home/service-account.json',
additionalTrustedOrigins: ['test.com'],
retainedBundles: [3, 4, 5],
Expand Down Expand Up @@ -255,6 +256,7 @@ describe('TwaManifest', () => {
expect(twaManifest.fallbackType).toBe('webview');
expect(twaManifest.enableSiteSettingsShortcut).toEqual(false);
expect(twaManifest.isChromeOSOnly).toEqual(false);
expect(twaManifest.isMetaQuest).toEqual(false);
expect(twaManifest.serviceAccountJsonFile).toEqual(twaManifestJson.serviceAccountJsonFile);
expect(twaManifest.additionalTrustedOrigins).toEqual(['test.com']);
expect(twaManifest.retainedBundles).toEqual([3, 4, 5]);
Expand Down Expand Up @@ -376,11 +378,13 @@ describe('TwaManifest', () => {
'appVersionCode': 1,
'shortcuts': [],
'generatorApp': 'bubblewrap-cli',
'webManifestUrl': 'https://name.github.io/',
'webManifestUrl': 'https://name.github.io/manifest.json',
'fallbackType': 'customtabs',
'features': {},
'enableSiteSettingsShortcut': true,
'isChromeOSOnly': false,
'isMetaQuest': false,
'fullScopeUrl': 'https://name.github.io/',
'appVersion': '1',
'serviceAccountJsonFile': '/home/service-account.json',
});
Expand All @@ -391,7 +395,7 @@ describe('TwaManifest', () => {
'display': 'fullscreen',
});
// A URL to insert as the webManifestUrl.
const url = new URL('https://name.github.io/');
const url = new URL('https://name.github.io/manifest.json');
expect(await TwaManifest.merge([], url, webManifest, twaManifest))
.toEqual(expectedTwaManifest);
});
Expand Down Expand Up @@ -434,21 +438,20 @@ describe('TwaManifest', () => {
'appVersionCode': 1,
'shortcuts': [],
'generatorApp': 'bubblewrap-cli',
'webManifestUrl': 'https://name.github.io/',
'webManifestUrl': 'https://name.github.io/manifest.json',
'fallbackType': 'customtabs',
'features': {},
'enableSiteSettingsShortcut': true,
'isChromeOSOnly': false,
'isMetaQuest': false,
'fullScopeUrl': 'https://name.github.io/',
'appVersion': '1',
'serviceAccountJsonFile': '/home/service-account.json',
});
// The versions shouldn't change because the update happens in `cli`.
const expectedTwaManifest = new TwaManifest({
...twaManifest.toJson(),
'webManifestUrl': 'https://other_url.github.io/',
});
const expectedTwaManifest = twaManifest;
// A URL to insert as the webManifestUrl.
const url = new URL('https://name.github.io/');
const url = new URL('https://name.github.io/manifest.json');
expect(await TwaManifest.merge(['short_name', 'display'], url, webManifest, twaManifest))
.toEqual(expectedTwaManifest);
});
Expand Down
11 changes: 8 additions & 3 deletions packages/core/template_project/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ android {
compileSdkVersion 31
defaultConfig {
applicationId "<%= packageId %>"
minSdkVersion 19
minSdkVersion <%= minSdkVersion %>
targetSdkVersion 31
versionCode <%= appVersionCode %>
versionName "<%= appVersionName %>"
Expand All @@ -70,11 +70,16 @@ android {

<% if (webManifestUrl) { %>
// The URL the Web Manifest for the Progressive Web App that the TWA points to. This
// is used by Chrome OS to open the Web version of the PWA instead of the TWA, as it
// will probably give a better user experience for non-mobile devices.
// is used by Chrome OS and Meta Quest to open the Web version of the PWA instead of
// the TWA, as it will probably give a better user experience for non-mobile devices.
resValue "string", "webManifestUrl", '<%= webManifestUrl %>'
<% } %>

<% if (fullScopeUrl) { %>
// This is used by Meta Quest.
resValue "string", "fullScopeUrl", '<%= fullScopeUrl %>'
<% } %>

<% if (shareTarget) { %>
// The data for the app to support web share target.
resValue "string", "shareTarget", '<%= escapeJsonString(escapeGradleString(JSON.stringify(shareTarget))) %>'
Expand Down
29 changes: 29 additions & 0 deletions packages/core/template_project/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,25 @@
<uses-permission android:name="<%= permission %>"/>
<% } %>

<% if (isMetaQuest) { %>
<uses-permission android:name="com.oculus.permission.HAND_TRACKING" />
<% } %>

<% if (isChromeOSOnly) { %>
<uses-feature android:name="org.chromium.arc" android:required="true" />
<% } %>

<% if (isMetaQuest) { %>
<uses-feature
android:name="android.hardware.vr.headtracking"
android:required="false"
android:version="1" />
<uses-feature
android:name="oculus.software.handtracking"
android:required="false" />
<% } %>

<application
android:name="Application"
android:allowBackup="true"
Expand Down Expand Up @@ -61,6 +76,20 @@
android:value="<%= metadata.value %>" />
<% } %>

<% if (isMetaQuest) { %>
<meta-data
android:name="com.oculus.pwa.NAME"
android:value="@string/appName" />
<meta-data
android:name="com.oculus.pwa.START_URL"
android:value="@string/launchUrl" />
<meta-data
android:name="com.oculus.pwa.SCOPE"
android:value="@string/fullScopeUrl" />
<% } %>

<% if (enableSiteSettingsShortcut) { %>
<activity android:name="com.google.androidbrowserhelper.trusted.ManageDataLauncherActivity">
<meta-data
Expand Down

0 comments on commit f8563a8

Please sign in to comment.