forked from
lass.pds.lassul.us/superconfig
This repository has no description
6.0 kB
157 lines
1package us.lassul.assist;
2
3import android.app.Activity;
4import android.content.ComponentName;
5import android.content.Context;
6import android.content.Intent;
7import android.content.pm.PackageManager;
8import android.content.pm.ResolveInfo;
9import android.net.Uri;
10import android.os.Bundle;
11import android.util.Log;
12import androidx.browser.customtabs.CustomTabsCallback;
13import androidx.browser.customtabs.CustomTabsClient;
14import androidx.browser.customtabs.CustomTabsIntent;
15import androidx.browser.customtabs.CustomTabsService;
16import androidx.browser.customtabs.CustomTabsServiceConnection;
17import androidx.browser.customtabs.CustomTabsSession;
18import androidx.browser.trusted.TrustedWebActivityIntentBuilder;
19
20/**
21 * The voice page, opened as a Trusted Web Activity, else as a Custom Tab, and its postMessage
22 * channel: the page reports "started" and "ended" for each call, the app sends "hangup". One per
23 * process, used on the main thread only (androidx delivers the callbacks there).
24 */
25final class Link {
26 static final String TAG = "Assist";
27 // The app speaks as this origin; assetlinks.json grants it (use_as_origin).
28 private static final Uri ORIGIN = Uri.parse("https://assist.lassul.us");
29 private static final Uri URL = Uri.parse("https://assist.lassul.us/?listen");
30
31 private static Context app;
32 private static CustomTabsServiceConnection connection;
33 private static CustomTabsSession session;
34 private static boolean twa;
35 // Opened before the browser's service connected: launched once it is.
36 private static Activity pending;
37
38 private static final CustomTabsCallback CALLBACK = new CustomTabsCallback() {
39 @Override
40 public void onRelationshipValidationResult(
41 int relation, Uri origin, boolean result, Bundle extras) {
42 Log.i(TAG, "use_as_origin " + origin + ": " + result);
43 }
44
45 // Every page load needs its own channel; the page takes the port from the first message.
46 @Override
47 public void onNavigationEvent(int event, Bundle extras) {
48 if (event == NAVIGATION_FINISHED && session != null) {
49 boolean requested = session.requestPostMessageChannel(ORIGIN, ORIGIN, new Bundle());
50 Log.i(TAG, "postMessage channel requested: " + requested);
51 }
52 }
53
54 @Override
55 public void onMessageChannelReady(Bundle extras) {
56 Log.i(TAG, "postMessage channel ready: " + session.postMessage("hello", null));
57 }
58
59 @Override
60 public void onPostMessage(String message, Bundle extras) {
61 Log.i(TAG, "page: " + message);
62 if ("started".equals(message)) {
63 CallService.pageStarted();
64 } else if ("ended".equals(message)) {
65 CallService.pageEnded();
66 }
67 }
68 };
69
70 private Link() {}
71
72 static void open(Activity activity) {
73 if (session != null) {
74 launch(activity);
75 return;
76 }
77 pending = activity;
78 if (connection != null) {
79 return;
80 }
81 String provider = twaProvider(activity);
82 twa = provider != null;
83 if (provider == null) {
84 provider = CustomTabsClient.getPackageName(activity, null);
85 }
86 app = activity.getApplicationContext();
87 connection = new CustomTabsServiceConnection() {
88 @Override
89 public void onCustomTabsServiceConnected(ComponentName name, CustomTabsClient client) {
90 client.warmup(0);
91 session = client.newSession(CALLBACK);
92 if (session != null) {
93 session.validateRelationship(
94 CustomTabsService.RELATION_USE_AS_ORIGIN, ORIGIN, null);
95 }
96 Activity waiting = pending;
97 pending = null;
98 if (waiting != null && !waiting.isFinishing()) {
99 launch(waiting);
100 }
101 }
102
103 @Override
104 public void onServiceDisconnected(ComponentName name) {
105 session = null;
106 }
107 };
108 if (provider == null || !CustomTabsClient.bindCustomTabsService(app, provider, connection)) {
109 connection = null;
110 pending = null;
111 launch(activity);
112 }
113 }
114
115 /** Sends "hangup" to the page, if it is connected. */
116 static void hangUp() {
117 if (session != null) {
118 Log.i(TAG, "hangup sent: " + session.postMessage("hangup", null));
119 }
120 }
121
122 static void close() {
123 if (connection != null) {
124 app.unbindService(connection);
125 }
126 connection = null;
127 session = null;
128 pending = null;
129 }
130
131 /** In the session's browser; without one (no Custom Tabs browser) a plain Custom Tab. */
132 private static void launch(Activity activity) {
133 Intent intent;
134 if (session == null) {
135 intent = new CustomTabsIntent.Builder().build().intent.setData(URL);
136 } else if (twa) {
137 intent = new TrustedWebActivityIntentBuilder(URL).build(session).getIntent();
138 } else {
139 intent = new CustomTabsIntent.Builder(session).build().intent.setData(URL);
140 }
141 activity.startActivity(intent);
142 }
143
144 /** The first browser offering Trusted Web Activities, null if none does. */
145 private static String twaProvider(Context context) {
146 Intent intent = new Intent(CustomTabsService.ACTION_CUSTOM_TABS_CONNECTION);
147 for (ResolveInfo info :
148 context.getPackageManager()
149 .queryIntentServices(intent, PackageManager.GET_RESOLVED_FILTER)) {
150 if (info.filter != null
151 && info.filter.hasCategory(CustomTabsService.TRUSTED_WEB_ACTIVITY_CATEGORY)) {
152 return info.serviceInfo.packageName;
153 }
154 }
155 return null;
156 }
157}