Skip to content

Commit 3cb2cd0

Browse files
authored
Add display override to TWA manifest (#947)
1 parent 4855e69 commit 3cb2cd0

5 files changed

Lines changed: 97 additions & 2 deletions

File tree

packages/core/src/lib/TwaManifest.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,12 @@ import {fetchUtils} from './FetchUtils';
2121
import {findSuitableIcon, generatePackageId, validateNotEmpty} from './util';
2222
import Color = require('color');
2323
import {ConsoleLog} from './Log';
24-
import {ShareTarget, WebManifestIcon, WebManifestJson} from './types/WebManifest';
24+
import {
25+
ShareTarget,
26+
WebManifestDisplayOverrideValue,
27+
WebManifestIcon,
28+
WebManifestJson,
29+
} from './types/WebManifest';
2530
import {processProtocolHandlers, ProtocolHandler} from './types/ProtocolHandler';
2631
import {ShortcutInfo} from './ShortcutInfo';
2732
import {AppsFlyerConfig} from './features/AppsFlyerFeature';
@@ -50,6 +55,20 @@ export function asDisplayMode(input: string): DisplayMode | null {
5055
return DISPLAY_MODE_VALUES.includes(input) ? input as DisplayMode : null;
5156
}
5257

58+
// Supported display overrides for TWA
59+
export type DisplayOverrideValue = WebManifestDisplayOverrideValue | 'fullscreen-sticky';
60+
export const DisplayOverrideValues: DisplayOverrideValue[] = ['standalone', 'minimal-ui',
61+
'fullscreen', 'fullscreen-sticky', 'browser', 'window-controls-overlay', 'tabbed'];
62+
63+
export function resolveDisplayOverride(
64+
displayOverride: WebManifestDisplayOverrideValue[]|undefined,
65+
): DisplayOverrideValue[] {
66+
if (!displayOverride) return [];
67+
68+
return displayOverride.filter(
69+
(displayOverrideValue) => DisplayOverrideValues.includes(displayOverrideValue));
70+
}
71+
5372
// Possible values for screen orientation, as defined in `android-browser-helper`:
5473
// https://github.com/GoogleChrome/android-browser-helper/blob/alpha/androidbrowserhelper/src/main/java/com/google/androidbrowserhelper/trusted/LauncherActivityMetadata.java#L191-L216
5574
const ORIENTATION_VALUES = ['default', 'any', 'natural', 'landscape', 'portrait',
@@ -139,6 +158,7 @@ export class TwaManifest {
139158
name: string;
140159
launcherName: string;
141160
display: DisplayMode;
161+
displayOverride: DisplayOverrideValue[];
142162
themeColor: Color;
143163
themeColorDark: Color;
144164
navigationColor: Color;
@@ -228,6 +248,7 @@ export class TwaManifest {
228248
this.protocolHandlers = data.protocolHandlers;
229249
this.fileHandlers = data.fileHandlers;
230250
this.launchHandlerClientMode = data.launchHandlerClientMode;
251+
this.displayOverride = data.displayOverride || [];
231252
}
232253

233254
/**
@@ -340,6 +361,7 @@ export class TwaManifest {
340361
launcherName: webManifest['short_name'] ||
341362
webManifest['name']?.substring(0, SHORT_NAME_MAX_SIZE) || DEFAULT_APP_NAME,
342363
display: asDisplayMode(webManifest['display']!) || DEFAULT_DISPLAY_MODE,
364+
displayOverride: resolveDisplayOverride(webManifest['display_override']),
343365
themeColor: webManifest['theme_color'] || DEFAULT_THEME_COLOR,
344366
themeColorDark: DEFAULT_THEME_COLOR_DARK,
345367
navigationColor: DEFAULT_NAVIGATION_COLOR,
@@ -540,6 +562,8 @@ export class TwaManifest {
540562
webManifest['name']?.substring(0, SHORT_NAME_MAX_SIZE)),
541563
display: this.getNewFieldValue('display', fieldsToIgnore, oldTwaManifest.display,
542564
asDisplayMode(webManifest['display']!)!),
565+
displayOverride: this.getNewFieldValue('displayOverride', fieldsToIgnore,
566+
oldTwaManifest.displayOverride, resolveDisplayOverride(webManifest['display_override'])),
543567
fullScopeUrl: this.getNewFieldValue('fullScopeUrl', fieldsToIgnore,
544568
oldTwaManifest.fullScopeUrl?.toString(), fullScopeUrl.toString()),
545569
themeColor: this.getNewFieldValue('themeColor', fieldsToIgnore,
@@ -571,6 +595,7 @@ export interface TwaManifestJson {
571595
name: string;
572596
launcherName?: string; // Older Manifests may not have this field.
573597
display?: string; // Older Manifests may not have this field.
598+
displayOverride?: DisplayOverrideValue[];
574599
themeColor: string;
575600
themeColorDark?: string;
576601
navigationColor: string;

packages/core/src/lib/types/WebManifest.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ export interface WebManifestShortcutJson {
3535

3636
type WebManifestDisplayMode = 'browser' | 'minimal-ui' | 'standalone' | 'fullscreen';
3737

38+
export type WebManifestDisplayOverrideValue = 'window-controls-overlay' | 'tabbed' | 'browser' |
39+
'minimal-ui' | 'standalone' | 'fullscreen';
40+
3841
export type OrientationLock = 'any' | 'natural' | 'landscape'| 'portrait' | 'portrait-primary'|
3942
'portrait-secondary' | 'landscape-primary' | 'landscape-secondary';
4043

@@ -64,6 +67,7 @@ export interface WebManifestJson {
6467
start_url?: string;
6568
scope?: string;
6669
display?: WebManifestDisplayMode;
70+
display_override?: WebManifestDisplayOverrideValue[];
6771
theme_color?: string;
6872
background_color?: string;
6973
icons?: Array<WebManifestIcon>;

packages/core/src/spec/lib/TwaManifestSpec.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@
1414
* limitations under the License.
1515
*/
1616

17-
import {TwaManifest, TwaManifestJson, asDisplayMode} from '../../lib/TwaManifest';
17+
import {
18+
TwaManifest,
19+
TwaManifestJson,
20+
asDisplayMode,
21+
resolveDisplayOverride,
22+
} from '../../lib/TwaManifest';
1823
import {WebManifestJson} from '../../lib/types/WebManifest';
1924
import Color = require('color');
2025
import {ShortcutInfo} from '../../lib/ShortcutInfo';
@@ -208,6 +213,7 @@ describe('TwaManifest', () => {
208213
startUrl: '/',
209214
iconUrl: 'https://pwa-directory.com/favicons/android-chrome-512x512.png',
210215
display: 'fullscreen',
216+
displayOverride: ['window-controls-overlay'],
211217
orientation: 'landscape',
212218
themeColor: '#00ff00',
213219
themeColorDark: '#000000',
@@ -243,6 +249,7 @@ describe('TwaManifest', () => {
243249
expect(twaManifest.startUrl).toEqual(twaManifest.startUrl);
244250
expect(twaManifest.iconUrl).toEqual(twaManifest.iconUrl);
245251
expect(twaManifest.display).toEqual('fullscreen');
252+
expect(twaManifest.displayOverride).toEqual(['window-controls-overlay']);
246253
expect(twaManifest.orientation).toEqual('landscape');
247254
expect(twaManifest.themeColor).toEqual(new Color('#00ff00'));
248255
expect(twaManifest.themeColorDark).toEqual(new Color('#000000'));
@@ -347,6 +354,39 @@ describe('TwaManifest', () => {
347354
expect(asDisplayMode('')).toBeNull();
348355
});
349356
});
357+
358+
describe('#resolveDisplayOverride', () => {
359+
it('Keeps display override values that are supported', () => {
360+
expect(resolveDisplayOverride([
361+
'browser',
362+
'fullscreen',
363+
'minimal-ui',
364+
'standalone',
365+
'window-controls-overlay',
366+
'tabbed',
367+
])).toEqual([
368+
'browser',
369+
'fullscreen',
370+
'minimal-ui',
371+
'standalone',
372+
'window-controls-overlay',
373+
'tabbed',
374+
]);
375+
});
376+
377+
it('Ignore unsupported values', () => {
378+
expect(resolveDisplayOverride([
379+
'browser',
380+
// @ts-expect-error Unsupported value for testing
381+
'not-supported',
382+
])).toEqual(['browser']);
383+
expect(resolveDisplayOverride([
384+
// @ts-expect-error Unsupported value for testing
385+
'not-supported',
386+
])).toEqual([]);
387+
});
388+
});
389+
350390
describe('#merge', () => {
351391
it('Validates that the merge is done correctly in case which' +
352392
' there are no fields to ignore', async () => {
@@ -380,6 +420,7 @@ describe('TwaManifest', () => {
380420
'name': 'name',
381421
'launcherName': 'name',
382422
'display': 'standalone',
423+
'displayOverride': ['window-controls-overlay'],
383424
'themeColor': '#FFFFFF',
384425
'themeColorDark': '#000000',
385426
'navigationColor': '#000000',
@@ -425,6 +466,7 @@ describe('TwaManifest', () => {
425466
...twaManifest.toJson(),
426467
'launcherName': 'different_name',
427468
'display': 'fullscreen',
469+
'displayOverride': [],
428470
'protocolHandlers': [
429471
{
430472
'protocol': 'web+test-replace',

packages/core/template_project/app/src/main/AndroidManifest.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,12 @@
181181
android:resource="@array/additional_trusted_origins" />
182182
<% } %>
183183

184+
<% if (displayOverride.length > 0) { %>
185+
<meta-data
186+
android:name="android.support.customtabs.trusted.DISPLAY_OVERRIDE"
187+
android:resource="@array/display_override" />
188+
<% } %>
189+
184190
<% if (shareTargetIntentFilter) { %>
185191
<intent-filter>
186192
<% for (const action of shareTargetIntentFilter.actions) { %>

packages/core/template_project/app/src/main/res/values/strings.xml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,24 @@
2323
</string-array>
2424
<% } %>
2525

26+
<!--
27+
The "fullscreen" and "fullscreen-sticky" TWA manifest values correspond to "immersive" and "sticky-immersive"
28+
AndroidManifest values.
29+
-->
30+
<% if (displayOverride.length > 0) { %>
31+
<string-array name="display_override">
32+
<% for (const displayOverrideValue of displayOverride) { %>
33+
<% if (displayOverrideValue === 'fullscreen') { %>
34+
<item>immersive</item>
35+
<% } else if (displayOverrideValue === 'fullscreen-sticky') { %>
36+
<item>sticky-immersive</item>
37+
<% } else { %>
38+
<item><%= displayOverrideValue %></item>
39+
<% } %>
40+
<% } %>
41+
</string-array>
42+
<% } %>
43+
2644
<!--
2745
This variable below expresses the relationship between the app and the site,
2846
as documented in the TWA documentation at

0 commit comments

Comments
 (0)