@capgo/capacitor-updater 8.48.0 → 8.49.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +186 -8
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +645 -111
- package/android/src/main/java/ee/forgr/capacitor_updater/ShakeMenu.java +203 -30
- package/android/src/main/java/ee/forgr/capacitor_updater/ThreeFingerPinchDetector.java +218 -64
- package/dist/docs.json +528 -17
- package/dist/esm/definitions.d.ts +228 -10
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +7 -1
- package/dist/esm/web.js +24 -0
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +24 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +24 -0
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +538 -77
- package/ios/Sources/CapacitorUpdaterPlugin/ShakeMenu.swift +239 -21
- package/package.json +1 -1
|
@@ -19,10 +19,13 @@ import android.widget.ListView;
|
|
|
19
19
|
import android.widget.ProgressBar;
|
|
20
20
|
import com.getcapacitor.Bridge;
|
|
21
21
|
import com.getcapacitor.BridgeActivity;
|
|
22
|
+
import com.getcapacitor.JSArray;
|
|
23
|
+
import com.getcapacitor.JSObject;
|
|
22
24
|
import java.util.ArrayList;
|
|
23
25
|
import java.util.List;
|
|
24
26
|
import java.util.Map;
|
|
25
27
|
import org.json.JSONArray;
|
|
28
|
+
import org.json.JSONObject;
|
|
26
29
|
|
|
27
30
|
public class ShakeMenu implements ShakeDetector.Listener, ThreeFingerPinchDetector.Listener {
|
|
28
31
|
|
|
@@ -36,11 +39,13 @@ public class ShakeMenu implements ShakeDetector.Listener, ThreeFingerPinchDetect
|
|
|
36
39
|
private ThreeFingerPinchDetector pinchDetector;
|
|
37
40
|
private boolean isShowing = false;
|
|
38
41
|
private Logger logger;
|
|
42
|
+
private String gesture;
|
|
39
43
|
|
|
40
44
|
public ShakeMenu(CapacitorUpdaterPlugin plugin, BridgeActivity activity, Logger logger, String gesture) {
|
|
41
45
|
this.plugin = plugin;
|
|
42
46
|
this.activity = activity;
|
|
43
47
|
this.logger = logger;
|
|
48
|
+
this.gesture = gesture;
|
|
44
49
|
|
|
45
50
|
if (CapacitorUpdaterPlugin.SHAKE_MENU_GESTURE_THREE_FINGER_PINCH.equals(gesture)) {
|
|
46
51
|
this.pinchDetector = new ThreeFingerPinchDetector(this, logger);
|
|
@@ -52,6 +57,10 @@ public class ShakeMenu implements ShakeDetector.Listener, ThreeFingerPinchDetect
|
|
|
52
57
|
}
|
|
53
58
|
}
|
|
54
59
|
|
|
60
|
+
public boolean usesGesture(String gesture) {
|
|
61
|
+
return this.gesture != null && this.gesture.equals(gesture);
|
|
62
|
+
}
|
|
63
|
+
|
|
55
64
|
public void stop() {
|
|
56
65
|
if (shakeDetector != null) {
|
|
57
66
|
shakeDetector.stop();
|
|
@@ -114,21 +123,43 @@ public class ShakeMenu implements ShakeDetector.Listener, ThreeFingerPinchDetect
|
|
|
114
123
|
}
|
|
115
124
|
String appName = activity.getPackageManager().getApplicationLabel(activity.getApplicationInfo()).toString();
|
|
116
125
|
String title = "Preview " + appName + " Menu";
|
|
117
|
-
String message = "Reload
|
|
118
|
-
String
|
|
119
|
-
|
|
120
|
-
|
|
126
|
+
String message = "Reload, switch, or leave the current preview.";
|
|
127
|
+
List<String> actions = new ArrayList<>();
|
|
128
|
+
actions.add("Reload preview");
|
|
129
|
+
if (plugin.previewMenuPreviews().length() > 0) {
|
|
130
|
+
actions.add("Switch preview");
|
|
131
|
+
}
|
|
132
|
+
actions.add("Leave test app");
|
|
133
|
+
final boolean[] openingNestedSelector = { false };
|
|
134
|
+
final boolean[] previewActionRunning = { false };
|
|
121
135
|
|
|
122
136
|
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
|
123
137
|
builder.setTitle(title);
|
|
124
138
|
builder.setMessage(message);
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
139
|
+
builder.setItems(actions.toArray(new String[0]), (dialogInterface, which) -> {
|
|
140
|
+
AlertDialog dialog = (AlertDialog) dialogInterface;
|
|
141
|
+
String action = actions.get(which);
|
|
142
|
+
if ("Reload preview".equals(action)) {
|
|
143
|
+
previewActionRunning[0] = true;
|
|
144
|
+
logger.info("Reloading webview");
|
|
145
|
+
runPreviewMenuAction(dialog, "Could not reload the test app.", "Error reloading test app: ", () ->
|
|
146
|
+
plugin.reloadPreviewSessionFromShakeMenu()
|
|
147
|
+
);
|
|
148
|
+
} else if ("Switch preview".equals(action)) {
|
|
149
|
+
openingNestedSelector[0] = true;
|
|
150
|
+
dialog.dismiss();
|
|
151
|
+
showPreviewSelector();
|
|
152
|
+
} else {
|
|
153
|
+
previewActionRunning[0] = true;
|
|
154
|
+
runPreviewMenuAction(dialog, "Could not leave the test app.", "Error leaving test app: ", () ->
|
|
155
|
+
plugin.leavePreviewSessionFromShakeMenu()
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
128
159
|
|
|
129
160
|
// Cancel button
|
|
130
161
|
builder.setNegativeButton(
|
|
131
|
-
|
|
162
|
+
"Close menu",
|
|
132
163
|
new DialogInterface.OnClickListener() {
|
|
133
164
|
public void onClick(DialogInterface dialog, int id) {
|
|
134
165
|
logger.info("Shake menu cancelled");
|
|
@@ -139,21 +170,12 @@ public class ShakeMenu implements ShakeDetector.Listener, ThreeFingerPinchDetect
|
|
|
139
170
|
);
|
|
140
171
|
|
|
141
172
|
AlertDialog dialog = builder.create();
|
|
142
|
-
dialog.setOnDismissListener((dialogInterface) ->
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
runPreviewMenuAction(dialog, "Could not leave the test app.", "Error leaving test app: ", () ->
|
|
147
|
-
plugin.leavePreviewSessionFromShakeMenu()
|
|
148
|
-
);
|
|
149
|
-
});
|
|
150
|
-
dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener((view) -> {
|
|
151
|
-
setPreviewMenuButtonsEnabled(dialog, false);
|
|
152
|
-
logger.info("Reloading webview");
|
|
153
|
-
runPreviewMenuAction(dialog, "Could not reload the test app.", "Error reloading test app: ", () ->
|
|
154
|
-
plugin.reloadPreviewSessionFromShakeMenu()
|
|
155
|
-
);
|
|
173
|
+
dialog.setOnDismissListener((dialogInterface) -> {
|
|
174
|
+
if (!openingNestedSelector[0] && !previewActionRunning[0]) {
|
|
175
|
+
isShowing = false;
|
|
176
|
+
}
|
|
156
177
|
});
|
|
178
|
+
dialog.show();
|
|
157
179
|
} catch (Exception e) {
|
|
158
180
|
logger.error("Error showing shake menu: " + e.getMessage());
|
|
159
181
|
isShowing = false;
|
|
@@ -165,29 +187,40 @@ public class ShakeMenu implements ShakeDetector.Listener, ThreeFingerPinchDetect
|
|
|
165
187
|
try {
|
|
166
188
|
String appName = activity.getPackageManager().getApplicationLabel(activity.getApplicationInfo()).toString();
|
|
167
189
|
String title = "Preview " + appName + " Menu";
|
|
168
|
-
String message = "Reload or leave the current preview
|
|
169
|
-
String
|
|
170
|
-
|
|
190
|
+
String message = "Reload, switch, or leave the current preview.";
|
|
191
|
+
List<String> actions = new ArrayList<>();
|
|
192
|
+
actions.add("Reload preview");
|
|
193
|
+
if (plugin.previewMenuPreviews().length() > 0) {
|
|
194
|
+
actions.add("Switch preview");
|
|
195
|
+
}
|
|
196
|
+
actions.add("Leave test app");
|
|
197
|
+
actions.add("Switch channel");
|
|
198
|
+
final boolean[] openingNestedSelector = { false };
|
|
171
199
|
final boolean[] previewActionRunning = { false };
|
|
172
200
|
|
|
173
201
|
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
|
174
202
|
builder.setTitle(title);
|
|
175
203
|
builder.setMessage(message);
|
|
176
|
-
builder.setItems(actions, (dialogInterface, which) -> {
|
|
204
|
+
builder.setItems(actions.toArray(new String[0]), (dialogInterface, which) -> {
|
|
177
205
|
AlertDialog dialog = (AlertDialog) dialogInterface;
|
|
178
|
-
|
|
206
|
+
String action = actions.get(which);
|
|
207
|
+
if ("Reload preview".equals(action)) {
|
|
179
208
|
previewActionRunning[0] = true;
|
|
180
209
|
logger.info("Reloading webview");
|
|
181
210
|
runPreviewMenuAction(dialog, "Could not reload the test app.", "Error reloading test app: ", () ->
|
|
182
211
|
plugin.reloadPreviewSessionFromShakeMenu()
|
|
183
212
|
);
|
|
184
|
-
} else if (
|
|
213
|
+
} else if ("Leave test app".equals(action)) {
|
|
185
214
|
previewActionRunning[0] = true;
|
|
186
215
|
runPreviewMenuAction(dialog, "Could not leave the test app.", "Error leaving test app: ", () ->
|
|
187
216
|
plugin.leavePreviewSessionFromShakeMenu()
|
|
188
217
|
);
|
|
218
|
+
} else if ("Switch preview".equals(action)) {
|
|
219
|
+
openingNestedSelector[0] = true;
|
|
220
|
+
dialog.dismiss();
|
|
221
|
+
showPreviewSelector();
|
|
189
222
|
} else {
|
|
190
|
-
|
|
223
|
+
openingNestedSelector[0] = true;
|
|
191
224
|
dialog.dismiss();
|
|
192
225
|
showChannelSelector();
|
|
193
226
|
}
|
|
@@ -200,7 +233,7 @@ public class ShakeMenu implements ShakeDetector.Listener, ThreeFingerPinchDetect
|
|
|
200
233
|
|
|
201
234
|
AlertDialog dialog = builder.create();
|
|
202
235
|
dialog.setOnDismissListener((dialogInterface) -> {
|
|
203
|
-
if (!
|
|
236
|
+
if (!openingNestedSelector[0] && !previewActionRunning[0]) {
|
|
204
237
|
isShowing = false;
|
|
205
238
|
}
|
|
206
239
|
});
|
|
@@ -235,6 +268,146 @@ public class ShakeMenu implements ShakeDetector.Listener, ThreeFingerPinchDetect
|
|
|
235
268
|
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setEnabled(enabled);
|
|
236
269
|
}
|
|
237
270
|
|
|
271
|
+
private void showPreviewSelector() {
|
|
272
|
+
activity.runOnUiThread(() -> {
|
|
273
|
+
try {
|
|
274
|
+
JSArray previewsRaw = plugin.previewMenuPreviews();
|
|
275
|
+
List<JSObject> previews = new ArrayList<>();
|
|
276
|
+
for (int i = 0; i < previewsRaw.length(); i++) {
|
|
277
|
+
Object raw = previewsRaw.opt(i);
|
|
278
|
+
if (raw instanceof JSObject preview) {
|
|
279
|
+
previews.add(preview);
|
|
280
|
+
} else if (raw instanceof JSONObject json) {
|
|
281
|
+
previews.add(JSObject.fromJSONObject(json));
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (previews.isEmpty()) {
|
|
286
|
+
showError("No saved previews available on this device.");
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
presentPreviewPicker(previews);
|
|
291
|
+
} catch (Exception e) {
|
|
292
|
+
logger.error("Error showing preview selector: " + e.getMessage());
|
|
293
|
+
isShowing = false;
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
private String previewLabel(JSObject preview) {
|
|
299
|
+
String name = preview.optString("name", "");
|
|
300
|
+
JSObject bundle = preview.getJSObject("bundle");
|
|
301
|
+
String version = bundle == null ? "" : bundle.optString("version", "");
|
|
302
|
+
String label = !name.isEmpty() ? name : (!version.isEmpty() ? version : preview.optString("id", "Preview"));
|
|
303
|
+
if (preview.optBoolean("isActive", false)) {
|
|
304
|
+
label += " (current)";
|
|
305
|
+
}
|
|
306
|
+
return label;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private void presentPreviewPicker(List<JSObject> previews) {
|
|
310
|
+
try {
|
|
311
|
+
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
|
312
|
+
builder.setTitle("Select Preview");
|
|
313
|
+
|
|
314
|
+
LinearLayout layout = new LinearLayout(activity);
|
|
315
|
+
layout.setOrientation(LinearLayout.VERTICAL);
|
|
316
|
+
int padding = dpToPx(16);
|
|
317
|
+
layout.setPadding(padding, padding, padding, padding);
|
|
318
|
+
|
|
319
|
+
EditText searchField = new EditText(activity);
|
|
320
|
+
searchField.setHint("Search previews...");
|
|
321
|
+
searchField.setSingleLine(true);
|
|
322
|
+
layout.addView(searchField);
|
|
323
|
+
|
|
324
|
+
final List<JSObject> displayedPreviews = new ArrayList<>();
|
|
325
|
+
displayedPreviews.addAll(previews.subList(0, Math.min(5, previews.size())));
|
|
326
|
+
final ArrayAdapter<String> adapter = new ArrayAdapter<>(
|
|
327
|
+
activity,
|
|
328
|
+
android.R.layout.simple_list_item_1,
|
|
329
|
+
previewLabels(displayedPreviews)
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
ListView listView = new ListView(activity);
|
|
333
|
+
listView.setAdapter(adapter);
|
|
334
|
+
LinearLayout.LayoutParams listParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, dpToPx(250));
|
|
335
|
+
listView.setLayoutParams(listParams);
|
|
336
|
+
layout.addView(listView);
|
|
337
|
+
|
|
338
|
+
builder.setView(layout);
|
|
339
|
+
builder.setNegativeButton("Cancel", (dialog, which) -> {
|
|
340
|
+
dialog.dismiss();
|
|
341
|
+
isShowing = false;
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
AlertDialog dialog = builder.create();
|
|
345
|
+
dialog.setOnDismissListener((d) -> isShowing = false);
|
|
346
|
+
|
|
347
|
+
searchField.addTextChangedListener(
|
|
348
|
+
new TextWatcher() {
|
|
349
|
+
@Override
|
|
350
|
+
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
|
351
|
+
|
|
352
|
+
@Override
|
|
353
|
+
public void onTextChanged(CharSequence s, int start, int before, int count) {}
|
|
354
|
+
|
|
355
|
+
@Override
|
|
356
|
+
public void afterTextChanged(Editable s) {
|
|
357
|
+
String query = s.toString().toLowerCase();
|
|
358
|
+
displayedPreviews.clear();
|
|
359
|
+
|
|
360
|
+
for (JSObject preview : previews) {
|
|
361
|
+
if (previewLabel(preview).toLowerCase().contains(query)) {
|
|
362
|
+
displayedPreviews.add(preview);
|
|
363
|
+
if (displayedPreviews.size() >= 5) break;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
adapter.clear();
|
|
368
|
+
adapter.addAll(previewLabels(displayedPreviews));
|
|
369
|
+
adapter.notifyDataSetChanged();
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
listView.setOnItemClickListener((parent, view, position, id) -> {
|
|
375
|
+
JSObject selectedPreview = displayedPreviews.get(position);
|
|
376
|
+
String previewId = selectedPreview.optString("id", "");
|
|
377
|
+
dialog.dismiss();
|
|
378
|
+
selectPreview(previewId);
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
dialog.show();
|
|
382
|
+
} catch (Exception e) {
|
|
383
|
+
logger.error("Error presenting preview picker: " + e.getMessage());
|
|
384
|
+
isShowing = false;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
private List<String> previewLabels(List<JSObject> previews) {
|
|
389
|
+
List<String> labels = new ArrayList<>();
|
|
390
|
+
for (JSObject preview : previews) {
|
|
391
|
+
labels.add(previewLabel(preview));
|
|
392
|
+
}
|
|
393
|
+
return labels;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
private void selectPreview(String previewId) {
|
|
397
|
+
new Thread(() -> {
|
|
398
|
+
try {
|
|
399
|
+
if (!plugin.setPreviewFromShakeMenu(previewId)) {
|
|
400
|
+
activity.runOnUiThread(() -> showError("Could not switch preview."));
|
|
401
|
+
}
|
|
402
|
+
} catch (Exception e) {
|
|
403
|
+
logger.error("Error switching preview: " + e.getMessage());
|
|
404
|
+
activity.runOnUiThread(() -> showError("Error switching preview: " + e.getMessage()));
|
|
405
|
+
} finally {
|
|
406
|
+
isShowing = false;
|
|
407
|
+
}
|
|
408
|
+
}).start();
|
|
409
|
+
}
|
|
410
|
+
|
|
238
411
|
private void showConfiguredDefaultMenu() {
|
|
239
412
|
activity.runOnUiThread(() -> {
|
|
240
413
|
try {
|
|
@@ -6,27 +6,37 @@
|
|
|
6
6
|
|
|
7
7
|
package ee.forgr.capacitor_updater;
|
|
8
8
|
|
|
9
|
+
import android.view.ActionMode;
|
|
10
|
+
import android.view.KeyEvent;
|
|
11
|
+
import android.view.KeyboardShortcutGroup;
|
|
12
|
+
import android.view.Menu;
|
|
13
|
+
import android.view.MenuItem;
|
|
9
14
|
import android.view.MotionEvent;
|
|
15
|
+
import android.view.SearchEvent;
|
|
10
16
|
import android.view.View;
|
|
11
|
-
import
|
|
17
|
+
import android.view.Window;
|
|
18
|
+
import android.view.WindowManager;
|
|
19
|
+
import android.view.accessibility.AccessibilityEvent;
|
|
12
20
|
import com.getcapacitor.BridgeActivity;
|
|
13
|
-
import java.lang.
|
|
21
|
+
import java.lang.ref.WeakReference;
|
|
22
|
+
import java.util.List;
|
|
14
23
|
|
|
15
|
-
public class ThreeFingerPinchDetector
|
|
24
|
+
public class ThreeFingerPinchDetector {
|
|
16
25
|
|
|
17
26
|
public interface Listener {
|
|
18
27
|
void onThreeFingerPinchDetected();
|
|
19
28
|
}
|
|
20
29
|
|
|
21
30
|
private static final int REQUIRED_POINTER_COUNT = 3;
|
|
22
|
-
private static final float MIN_SCALE_DELTA = 0.
|
|
31
|
+
private static final float MIN_SCALE_DELTA = 0.12f;
|
|
23
32
|
private static final long PINCH_TIMEOUT = 1000;
|
|
24
33
|
|
|
25
34
|
private final Listener listener;
|
|
26
35
|
private final Logger logger;
|
|
27
|
-
private
|
|
28
|
-
private
|
|
29
|
-
private
|
|
36
|
+
private Window targetWindow;
|
|
37
|
+
private Window.Callback previousWindowCallback;
|
|
38
|
+
private Window.Callback windowCallback;
|
|
39
|
+
private WeakReference<ThreeFingerPinchDetector> detectorReference;
|
|
30
40
|
private float initialSpan = 0;
|
|
31
41
|
private boolean tracking = false;
|
|
32
42
|
private boolean triggered = false;
|
|
@@ -38,74 +48,68 @@ public class ThreeFingerPinchDetector implements View.OnTouchListener {
|
|
|
38
48
|
}
|
|
39
49
|
|
|
40
50
|
public void start(BridgeActivity activity) {
|
|
41
|
-
if (
|
|
51
|
+
if (targetWindow != null) {
|
|
42
52
|
stop();
|
|
43
53
|
}
|
|
44
54
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
view = bridge.getWebView();
|
|
49
|
-
}
|
|
50
|
-
if (view == null && activity.getWindow() != null) {
|
|
51
|
-
view = activity.getWindow().getDecorView().getRootView();
|
|
52
|
-
}
|
|
53
|
-
if (view == null) {
|
|
54
|
-
logger.warn("Three finger pinch detector could not find a target view");
|
|
55
|
+
Window window = activity.getWindow();
|
|
56
|
+
if (window == null) {
|
|
57
|
+
logger.warn("Three finger pinch detector could not find a target window");
|
|
55
58
|
return;
|
|
56
59
|
}
|
|
57
60
|
|
|
58
|
-
this.
|
|
59
|
-
this.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
this.targetWindow = window;
|
|
62
|
+
this.previousWindowCallback = window.getCallback();
|
|
63
|
+
this.detectorReference = new WeakReference<>(this);
|
|
64
|
+
this.windowCallback = new PinchWindowCallback(this.previousWindowCallback, this.detectorReference);
|
|
65
|
+
window.setCallback(this.windowCallback);
|
|
66
|
+
logger.info("Three finger pinch detector installed on activity window");
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
public void stop() {
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
targetView.setOnTouchListener(previousOnTouchListener);
|
|
70
|
+
if (targetWindow != null) {
|
|
71
|
+
if (windowCallback instanceof PinchWindowCallback) {
|
|
72
|
+
((PinchWindowCallback) windowCallback).disable();
|
|
71
73
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
if (detectorReference != null) {
|
|
75
|
+
detectorReference.clear();
|
|
76
|
+
}
|
|
77
|
+
if (targetWindow.getCallback() == windowCallback) {
|
|
78
|
+
targetWindow.setCallback(previousWindowCallback);
|
|
79
|
+
}
|
|
80
|
+
targetWindow = null;
|
|
81
|
+
previousWindowCallback = null;
|
|
82
|
+
windowCallback = null;
|
|
83
|
+
detectorReference = null;
|
|
75
84
|
}
|
|
76
85
|
reset();
|
|
77
86
|
}
|
|
78
87
|
|
|
79
|
-
|
|
80
|
-
public boolean onTouch(View view, MotionEvent event) {
|
|
81
|
-
boolean consumedByPreviousListener = false;
|
|
82
|
-
if (previousOnTouchListener != null && previousOnTouchListener != this) {
|
|
83
|
-
consumedByPreviousListener = previousOnTouchListener.onTouch(view, event);
|
|
84
|
-
}
|
|
85
|
-
|
|
88
|
+
private void handleTouch(MotionEvent event) {
|
|
86
89
|
int action = event.getActionMasked();
|
|
87
90
|
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP) {
|
|
88
91
|
reset();
|
|
89
|
-
return
|
|
92
|
+
return;
|
|
90
93
|
}
|
|
91
94
|
|
|
92
95
|
if (event.getPointerCount() != REQUIRED_POINTER_COUNT) {
|
|
93
96
|
if (action == MotionEvent.ACTION_POINTER_DOWN) {
|
|
94
97
|
reset();
|
|
95
98
|
}
|
|
96
|
-
return
|
|
99
|
+
return;
|
|
97
100
|
}
|
|
98
101
|
|
|
99
102
|
float span = calculateSpan(event);
|
|
100
103
|
if (span <= 0) {
|
|
101
|
-
return
|
|
104
|
+
return;
|
|
102
105
|
}
|
|
103
106
|
|
|
104
107
|
if (!tracking || action == MotionEvent.ACTION_POINTER_DOWN) {
|
|
105
108
|
initialSpan = span;
|
|
106
109
|
tracking = true;
|
|
107
110
|
triggered = false;
|
|
108
|
-
|
|
111
|
+
logger.info("Three finger pinch tracking started");
|
|
112
|
+
return;
|
|
109
113
|
}
|
|
110
114
|
|
|
111
115
|
if (!triggered && Math.abs(span - initialSpan) / initialSpan >= MIN_SCALE_DELTA) {
|
|
@@ -113,13 +117,12 @@ public class ThreeFingerPinchDetector implements View.OnTouchListener {
|
|
|
113
117
|
if (currentTime - lastPinchTime > PINCH_TIMEOUT) {
|
|
114
118
|
triggered = true;
|
|
115
119
|
lastPinchTime = currentTime;
|
|
120
|
+
logger.info("Three finger pinch threshold reached");
|
|
116
121
|
if (listener != null) {
|
|
117
122
|
listener.onThreeFingerPinchDetected();
|
|
118
123
|
}
|
|
119
124
|
}
|
|
120
125
|
}
|
|
121
|
-
|
|
122
|
-
return consumedByPreviousListener;
|
|
123
126
|
}
|
|
124
127
|
|
|
125
128
|
private float calculateSpan(MotionEvent event) {
|
|
@@ -141,29 +144,180 @@ public class ThreeFingerPinchDetector implements View.OnTouchListener {
|
|
|
141
144
|
return totalDistance / REQUIRED_POINTER_COUNT;
|
|
142
145
|
}
|
|
143
146
|
|
|
144
|
-
private View.OnTouchListener getCurrentOnTouchListener(View view) {
|
|
145
|
-
try {
|
|
146
|
-
Field listenerInfoField = View.class.getDeclaredField("mListenerInfo");
|
|
147
|
-
listenerInfoField.setAccessible(true);
|
|
148
|
-
Object listenerInfo = listenerInfoField.get(view);
|
|
149
|
-
if (listenerInfo == null) {
|
|
150
|
-
return null;
|
|
151
|
-
}
|
|
152
|
-
Field onTouchListenerField = listenerInfo.getClass().getDeclaredField("mOnTouchListener");
|
|
153
|
-
onTouchListenerField.setAccessible(true);
|
|
154
|
-
Object listener = onTouchListenerField.get(listenerInfo);
|
|
155
|
-
if (listener instanceof View.OnTouchListener) {
|
|
156
|
-
return (View.OnTouchListener) listener;
|
|
157
|
-
}
|
|
158
|
-
} catch (ReflectiveOperationException | RuntimeException exception) {
|
|
159
|
-
logger.warn("Three finger pinch detector could not inspect the current touch listener: " + exception.getMessage());
|
|
160
|
-
}
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
147
|
private void reset() {
|
|
165
148
|
initialSpan = 0;
|
|
166
149
|
tracking = false;
|
|
167
150
|
triggered = false;
|
|
168
151
|
}
|
|
152
|
+
|
|
153
|
+
private static class PinchWindowCallback implements Window.Callback {
|
|
154
|
+
|
|
155
|
+
private final Window.Callback delegate;
|
|
156
|
+
private final WeakReference<ThreeFingerPinchDetector> detectorReference;
|
|
157
|
+
private boolean enabled = true;
|
|
158
|
+
|
|
159
|
+
PinchWindowCallback(Window.Callback delegate, WeakReference<ThreeFingerPinchDetector> detectorReference) {
|
|
160
|
+
this.delegate = delegate;
|
|
161
|
+
this.detectorReference = detectorReference;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
void disable() {
|
|
165
|
+
enabled = false;
|
|
166
|
+
if (detectorReference != null) {
|
|
167
|
+
detectorReference.clear();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
@Override
|
|
172
|
+
public boolean dispatchKeyEvent(KeyEvent event) {
|
|
173
|
+
return delegate != null && delegate.dispatchKeyEvent(event);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
@Override
|
|
177
|
+
public boolean dispatchKeyShortcutEvent(KeyEvent event) {
|
|
178
|
+
return delegate != null && delegate.dispatchKeyShortcutEvent(event);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
@Override
|
|
182
|
+
public boolean dispatchTouchEvent(MotionEvent event) {
|
|
183
|
+
boolean handled = delegate != null && delegate.dispatchTouchEvent(event);
|
|
184
|
+
if (enabled) {
|
|
185
|
+
ThreeFingerPinchDetector detector = detectorReference == null ? null : detectorReference.get();
|
|
186
|
+
if (detector != null) {
|
|
187
|
+
detector.handleTouch(event);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return handled;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
@Override
|
|
194
|
+
public boolean dispatchTrackballEvent(MotionEvent event) {
|
|
195
|
+
return delegate != null && delegate.dispatchTrackballEvent(event);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
@Override
|
|
199
|
+
public boolean dispatchGenericMotionEvent(MotionEvent event) {
|
|
200
|
+
return delegate != null && delegate.dispatchGenericMotionEvent(event);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
@Override
|
|
204
|
+
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
|
|
205
|
+
return delegate != null && delegate.dispatchPopulateAccessibilityEvent(event);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
@Override
|
|
209
|
+
public View onCreatePanelView(int featureId) {
|
|
210
|
+
return delegate == null ? null : delegate.onCreatePanelView(featureId);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
@Override
|
|
214
|
+
public boolean onCreatePanelMenu(int featureId, Menu menu) {
|
|
215
|
+
return delegate != null && delegate.onCreatePanelMenu(featureId, menu);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
@Override
|
|
219
|
+
public boolean onPreparePanel(int featureId, View view, Menu menu) {
|
|
220
|
+
return delegate != null && delegate.onPreparePanel(featureId, view, menu);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
@Override
|
|
224
|
+
public boolean onMenuOpened(int featureId, Menu menu) {
|
|
225
|
+
return delegate != null && delegate.onMenuOpened(featureId, menu);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
@Override
|
|
229
|
+
public boolean onMenuItemSelected(int featureId, MenuItem item) {
|
|
230
|
+
return delegate != null && delegate.onMenuItemSelected(featureId, item);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
@Override
|
|
234
|
+
public void onWindowAttributesChanged(WindowManager.LayoutParams attrs) {
|
|
235
|
+
if (delegate != null) {
|
|
236
|
+
delegate.onWindowAttributesChanged(attrs);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
@Override
|
|
241
|
+
public void onContentChanged() {
|
|
242
|
+
if (delegate != null) {
|
|
243
|
+
delegate.onContentChanged();
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
@Override
|
|
248
|
+
public void onWindowFocusChanged(boolean hasFocus) {
|
|
249
|
+
if (delegate != null) {
|
|
250
|
+
delegate.onWindowFocusChanged(hasFocus);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
@Override
|
|
255
|
+
public void onAttachedToWindow() {
|
|
256
|
+
if (delegate != null) {
|
|
257
|
+
delegate.onAttachedToWindow();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
@Override
|
|
262
|
+
public void onDetachedFromWindow() {
|
|
263
|
+
if (delegate != null) {
|
|
264
|
+
delegate.onDetachedFromWindow();
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
@Override
|
|
269
|
+
public void onPanelClosed(int featureId, Menu menu) {
|
|
270
|
+
if (delegate != null) {
|
|
271
|
+
delegate.onPanelClosed(featureId, menu);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
@Override
|
|
276
|
+
public boolean onSearchRequested() {
|
|
277
|
+
return delegate != null && delegate.onSearchRequested();
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
@Override
|
|
281
|
+
public boolean onSearchRequested(SearchEvent searchEvent) {
|
|
282
|
+
return delegate != null && delegate.onSearchRequested(searchEvent);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
@Override
|
|
286
|
+
public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) {
|
|
287
|
+
return delegate == null ? null : delegate.onWindowStartingActionMode(callback);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
@Override
|
|
291
|
+
public ActionMode onWindowStartingActionMode(ActionMode.Callback callback, int type) {
|
|
292
|
+
return delegate == null ? null : delegate.onWindowStartingActionMode(callback, type);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
@Override
|
|
296
|
+
public void onActionModeStarted(ActionMode mode) {
|
|
297
|
+
if (delegate != null) {
|
|
298
|
+
delegate.onActionModeStarted(mode);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
@Override
|
|
303
|
+
public void onActionModeFinished(ActionMode mode) {
|
|
304
|
+
if (delegate != null) {
|
|
305
|
+
delegate.onActionModeFinished(mode);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
@Override
|
|
310
|
+
public void onProvideKeyboardShortcuts(List<KeyboardShortcutGroup> data, Menu menu, int deviceId) {
|
|
311
|
+
if (delegate != null) {
|
|
312
|
+
delegate.onProvideKeyboardShortcuts(data, menu, deviceId);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
@Override
|
|
317
|
+
public void onPointerCaptureChanged(boolean hasCapture) {
|
|
318
|
+
if (delegate != null) {
|
|
319
|
+
delegate.onPointerCaptureChanged(hasCapture);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
169
323
|
}
|