1 /****************************************************************************
2 **
3 ** Copyright (C) 2017 BogDan Vatra <bogdan@kde.org>
4 ** Copyright (C) 2016 The Qt Company Ltd.
5 ** Copyright (C) 2016 Olivier Goffart <ogoffart@woboq.com>
6 ** Contact: https://www.qt.io/licensing/
7 **
8 ** This file is part of the Android port of the Qt Toolkit.
9 **
10 ** $QT_BEGIN_LICENSE:LGPL$
11 ** Commercial License Usage
12 ** Licensees holding valid commercial Qt licenses may use this file in
13 ** accordance with the commercial license agreement provided with the
14 ** Software or, alternatively, in accordance with the terms contained in
15 ** a written agreement between you and The Qt Company. For licensing terms
16 ** and conditions see https://www.qt.io/terms-conditions. For further
17 ** information use the contact form at https://www.qt.io/contact-us.
18 **
19 ** GNU Lesser General Public License Usage
20 ** Alternatively, this file may be used under the terms of the GNU Lesser
21 ** General Public License version 3 as published by the Free Software
22 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
23 ** packaging of this file. Please review the following information to
24 ** ensure the GNU Lesser General Public License version 3 requirements
25 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
26 **
27 ** GNU General Public License Usage
28 ** Alternatively, this file may be used under the terms of the GNU
29 ** General Public License version 2.0 or (at your option) the GNU General
30 ** Public license version 3 or any later version approved by the KDE Free
31 ** Qt Foundation. The licenses are as published by the Free Software
32 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
33 ** included in the packaging of this file. Please review the following
34 ** information to ensure the GNU General Public License requirements will
35 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
36 ** https://www.gnu.org/licenses/gpl-3.0.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 
42 package org.qtproject.qt5.android;
43 
44 import android.app.Activity;
45 import android.content.Context;
46 import android.content.Intent;
47 import android.content.pm.ActivityInfo;
48 import android.content.pm.ApplicationInfo;
49 import android.content.pm.PackageManager;
50 import android.content.pm.PackageManager.NameNotFoundException;
51 import android.content.res.AssetManager;
52 import android.content.res.Configuration;
53 import android.graphics.drawable.ColorDrawable;
54 import android.graphics.drawable.Drawable;
55 import android.graphics.Rect;
56 import android.net.LocalServerSocket;
57 import android.net.LocalSocket;
58 import android.os.Build;
59 import android.os.Bundle;
60 import android.os.Handler;
61 import android.os.ResultReceiver;
62 import android.text.method.MetaKeyKeyListener;
63 import android.util.Base64;
64 import android.util.DisplayMetrics;
65 import android.util.Log;
66 import android.util.TypedValue;
67 import android.view.animation.AccelerateInterpolator;
68 import android.view.animation.AlphaAnimation;
69 import android.view.animation.Animation;
70 import android.view.ContextMenu;
71 import android.view.ContextMenu.ContextMenuInfo;
72 import android.view.KeyCharacterMap;
73 import android.view.KeyEvent;
74 import android.view.Menu;
75 import android.view.MenuItem;
76 import android.view.MotionEvent;
77 import android.view.Surface;
78 import android.view.View;
79 import android.view.ViewConfiguration;
80 import android.view.ViewGroup;
81 import android.view.WindowManager;
82 import android.view.inputmethod.InputMethodManager;
83 import android.view.ViewTreeObserver;
84 import android.widget.ImageView;
85 import android.widget.PopupMenu;
86 import android.hardware.display.DisplayManager;
87 
88 import java.io.BufferedReader;
89 import java.io.DataOutputStream;
90 import java.io.File;
91 import java.io.FileWriter;
92 import java.io.InputStreamReader;
93 import java.io.IOException;
94 import java.lang.reflect.Constructor;
95 import java.lang.reflect.Method;
96 import java.util.ArrayList;
97 import java.util.HashMap;
98 import java.util.Objects;
99 
100 import org.qtproject.qt5.android.accessibility.QtAccessibilityDelegate;
101 
102 public class QtActivityDelegate
103 {
104     private Activity m_activity = null;
105     private Method m_super_dispatchKeyEvent = null;
106     private Method m_super_onRestoreInstanceState = null;
107     private Method m_super_onRetainNonConfigurationInstance = null;
108     private Method m_super_onSaveInstanceState = null;
109     private Method m_super_onKeyDown = null;
110     private Method m_super_onKeyUp = null;
111     private Method m_super_onConfigurationChanged = null;
112     private Method m_super_onActivityResult = null;
113     private Method m_super_dispatchGenericMotionEvent = null;
114     private Method m_super_onWindowFocusChanged = null;
115 
116     private static final String NATIVE_LIBRARIES_KEY = "native.libraries";
117     private static final String BUNDLED_LIBRARIES_KEY = "bundled.libraries";
118     private static final String MAIN_LIBRARY_KEY = "main.library";
119     private static final String ENVIRONMENT_VARIABLES_KEY = "environment.variables";
120     private static final String APPLICATION_PARAMETERS_KEY = "application.parameters";
121     private static final String STATIC_INIT_CLASSES_KEY = "static.init.classes";
122     private static final String NECESSITAS_API_LEVEL_KEY = "necessitas.api.level";
123     private static final String EXTRACT_STYLE_KEY = "extract.android.style";
124     private static final String EXTRACT_STYLE_MINIMAL_KEY = "extract.android.style.option";
125 
126     private static String m_environmentVariables = null;
127     private static String m_applicationParameters = null;
128 
129     private int m_currentRotation = -1; // undefined
130     private int m_nativeOrientation = Configuration.ORIENTATION_UNDEFINED;
131 
132     private String m_mainLib;
133     private long m_metaState;
134     private int m_lastChar = 0;
135     private int m_softInputMode = 0;
136     private boolean m_fullScreen = false;
137     private boolean m_started = false;
138     private HashMap<Integer, QtSurface> m_surfaces = null;
139     private HashMap<Integer, View> m_nativeViews = null;
140     private QtLayout m_layout = null;
141     private ImageView m_splashScreen = null;
142     private boolean m_splashScreenSticky = false;
143     private QtEditText m_editText = null;
144     private InputMethodManager m_imm = null;
145     private boolean m_quitApp = true;
146     private View m_dummyView = null;
147     private boolean m_keyboardIsVisible = false;
148     public boolean m_backKeyPressedSent = false;
149     private long m_showHideTimeStamp = System.nanoTime();
150     private int m_portraitKeyboardHeight = 0;
151     private int m_landscapeKeyboardHeight = 0;
152     private int m_probeKeyboardHeightDelay = 50; // ms
153     private CursorHandle m_cursorHandle;
154     private CursorHandle m_leftSelectionHandle;
155     private CursorHandle m_rightSelectionHandle;
156     private EditPopupMenu m_editPopupMenu;
157 
setFullScreen(boolean enterFullScreen)158     public void setFullScreen(boolean enterFullScreen)
159     {
160         if (m_fullScreen == enterFullScreen)
161             return;
162 
163         if (m_fullScreen = enterFullScreen) {
164             m_activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
165             m_activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
166             try {
167                 int flags = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
168                 flags |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
169                 flags |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
170                 flags |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
171                 flags |= View.SYSTEM_UI_FLAG_FULLSCREEN;
172                 flags |= View.class.getDeclaredField("SYSTEM_UI_FLAG_IMMERSIVE_STICKY").getInt(null);
173                 m_activity.getWindow().getDecorView().setSystemUiVisibility(flags | View.INVISIBLE);
174             } catch (Exception e) {
175                 e.printStackTrace();
176             }
177         } else {
178             m_activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
179             m_activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
180             m_activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
181         }
182         m_layout.requestLayout();
183     }
184 
updateFullScreen()185     public void updateFullScreen()
186     {
187         if (m_fullScreen) {
188             m_fullScreen = false;
189             setFullScreen(true);
190         }
191     }
192 
193     // input method hints - must be kept in sync with QTDIR/src/corelib/global/qnamespace.h
194     private final int ImhHiddenText = 0x1;
195     private final int ImhSensitiveData = 0x2;
196     private final int ImhNoAutoUppercase = 0x4;
197     private final int ImhPreferNumbers = 0x8;
198     private final int ImhPreferUppercase = 0x10;
199     private final int ImhPreferLowercase = 0x20;
200     private final int ImhNoPredictiveText = 0x40;
201 
202     private final int ImhDate = 0x80;
203     private final int ImhTime = 0x100;
204 
205     private final int ImhPreferLatin = 0x200;
206 
207     private final int ImhMultiLine = 0x400;
208 
209     private final int ImhDigitsOnly = 0x10000;
210     private final int ImhFormattedNumbersOnly = 0x20000;
211     private final int ImhUppercaseOnly = 0x40000;
212     private final int ImhLowercaseOnly = 0x80000;
213     private final int ImhDialableCharactersOnly = 0x100000;
214     private final int ImhEmailCharactersOnly = 0x200000;
215     private final int ImhUrlCharactersOnly = 0x400000;
216     private final int ImhLatinOnly = 0x800000;
217 
218     // enter key type - must be kept in sync with QTDIR/src/corelib/global/qnamespace.h
219     private final int EnterKeyDefault = 0;
220     private final int EnterKeyReturn = 1;
221     private final int EnterKeyDone = 2;
222     private final int EnterKeyGo = 3;
223     private final int EnterKeySend = 4;
224     private final int EnterKeySearch = 5;
225     private final int EnterKeyNext = 6;
226     private final int EnterKeyPrevious = 7;
227 
228     // application state
229     public static final int ApplicationSuspended = 0x0;
230     public static final int ApplicationHidden = 0x1;
231     public static final int ApplicationInactive = 0x2;
232     public static final int ApplicationActive = 0x4;
233 
234     private QtAccessibilityDelegate m_accessibilityDelegate = null;
235 
236 
setKeyboardVisibility(boolean visibility, long timeStamp)237     public boolean setKeyboardVisibility(boolean visibility, long timeStamp)
238     {
239         if (m_showHideTimeStamp > timeStamp)
240             return false;
241         m_showHideTimeStamp = timeStamp;
242 
243         if (m_keyboardIsVisible == visibility)
244             return false;
245         m_keyboardIsVisible = visibility;
246         QtNative.keyboardVisibilityChanged(m_keyboardIsVisible);
247 
248         if (visibility == false)
249             updateFullScreen(); // Hiding the keyboard clears the immersive mode, so we need to set it again.
250 
251         return true;
252     }
resetSoftwareKeyboard()253     public void resetSoftwareKeyboard()
254     {
255         if (m_imm == null)
256             return;
257         m_editText.postDelayed(new Runnable() {
258             @Override
259             public void run() {
260                 m_imm.restartInput(m_editText);
261                 m_editText.m_optionsChanged = false;
262             }
263         }, 5);
264     }
265 
showSoftwareKeyboard(final int x, final int y, final int width, final int height, final int inputHints, final int enterKeyType)266     public void showSoftwareKeyboard(final int x, final int y, final int width, final int height, final int inputHints, final int enterKeyType)
267     {
268         if (m_imm == null)
269             return;
270 
271         DisplayMetrics metrics = new DisplayMetrics();
272         m_activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
273 
274         // If the screen is in portrait mode than we estimate that keyboard height will not be higher than 2/5 of the screen.
275         // else than we estimate that keyboard height will not be higher than 2/3 of the screen
276         final int visibleHeight;
277         if (metrics.widthPixels < metrics.heightPixels)
278             visibleHeight = m_portraitKeyboardHeight != 0 ? m_portraitKeyboardHeight : metrics.heightPixels * 3 / 5;
279         else
280             visibleHeight = m_landscapeKeyboardHeight != 0 ? m_landscapeKeyboardHeight : metrics.heightPixels / 3;
281 
282         if (m_softInputMode != 0) {
283             m_activity.getWindow().setSoftInputMode(m_softInputMode);
284             final boolean softInputIsHidden = (m_softInputMode & WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN) != 0;
285             if (softInputIsHidden)
286                 return;
287         } else {
288             if (height > visibleHeight)
289                 m_activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
290             else
291                 m_activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
292         }
293 
294         int initialCapsMode = 0;
295 
296         int imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
297 
298         switch (enterKeyType) {
299         case EnterKeyReturn:
300             imeOptions = android.view.inputmethod.EditorInfo.IME_FLAG_NO_ENTER_ACTION;
301             break;
302         case EnterKeyGo:
303             imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_GO;
304             break;
305         case EnterKeySend:
306             imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_SEND;
307             break;
308         case EnterKeySearch:
309             imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_SEARCH;
310             break;
311         case EnterKeyNext:
312             imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_NEXT;
313             break;
314         case EnterKeyPrevious:
315             imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_PREVIOUS;
316             break;
317         }
318 
319         int inputType = android.text.InputType.TYPE_CLASS_TEXT;
320 
321         if ((inputHints & (ImhPreferNumbers | ImhDigitsOnly | ImhFormattedNumbersOnly)) != 0) {
322             inputType = android.text.InputType.TYPE_CLASS_NUMBER;
323             if ((inputHints & ImhFormattedNumbersOnly) != 0) {
324                 inputType |= (android.text.InputType.TYPE_NUMBER_FLAG_DECIMAL
325                               | android.text.InputType.TYPE_NUMBER_FLAG_SIGNED);
326             }
327 
328             if ((inputHints & ImhHiddenText) != 0)
329                 inputType |= 0x10 /* TYPE_NUMBER_VARIATION_PASSWORD */;
330         } else if ((inputHints & ImhDialableCharactersOnly) != 0) {
331             inputType = android.text.InputType.TYPE_CLASS_PHONE;
332         } else if ((inputHints & (ImhDate | ImhTime)) != 0) {
333             inputType = android.text.InputType.TYPE_CLASS_DATETIME;
334             if ((inputHints & (ImhDate | ImhTime)) != (ImhDate | ImhTime)) {
335                 if ((inputHints & ImhDate) != 0)
336                     inputType |= android.text.InputType.TYPE_DATETIME_VARIATION_DATE;
337                 if ((inputHints & ImhTime) != 0)
338                     inputType |= android.text.InputType.TYPE_DATETIME_VARIATION_TIME;
339             } // else {  TYPE_DATETIME_VARIATION_NORMAL(0) }
340         } else { // CLASS_TEXT
341             if ((inputHints & (ImhEmailCharactersOnly | ImhUrlCharactersOnly)) != 0) {
342                 if ((inputHints & ImhUrlCharactersOnly) != 0) {
343                     inputType |= android.text.InputType.TYPE_TEXT_VARIATION_URI;
344 
345                     if (enterKeyType == 0) // not explicitly overridden
346                         imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_GO;
347                 } else if ((inputHints & ImhEmailCharactersOnly) != 0) {
348                     inputType |= android.text.InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
349                 }
350             } else if ((inputHints & ImhHiddenText) != 0) {
351                 inputType |= android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD;
352             } else if ((inputHints & ImhSensitiveData) != 0 ||
353                 ((inputHints & ImhNoPredictiveText) != 0 &&
354                   System.getenv("QT_ANDROID_ENABLE_WORKAROUND_TO_DISABLE_PREDICTIVE_TEXT") != null)) {
355                 inputType |= android.text.InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
356             }
357 
358             if ((inputHints & ImhMultiLine) != 0)
359                 inputType |= android.text.InputType.TYPE_TEXT_FLAG_MULTI_LINE;
360 
361             if ((inputHints & ImhUppercaseOnly) != 0) {
362                 initialCapsMode |= android.text.TextUtils.CAP_MODE_CHARACTERS;
363                 inputType |= android.text.InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS;
364             } else if ((inputHints & ImhLowercaseOnly) == 0 && (inputHints & ImhNoAutoUppercase) == 0) {
365                 initialCapsMode |= android.text.TextUtils.CAP_MODE_SENTENCES;
366                 inputType |= android.text.InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
367             }
368 
369             if ((inputHints & ImhNoPredictiveText) != 0 || (inputHints & ImhSensitiveData) != 0
370                 || (inputHints & ImhHiddenText) != 0) {
371                 inputType |= android.text.InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
372             }
373         }
374 
375         if (enterKeyType == 0 && (inputHints & ImhMultiLine) != 0)
376             imeOptions = android.view.inputmethod.EditorInfo.IME_FLAG_NO_ENTER_ACTION;
377 
378         m_editText.setInitialCapsMode(initialCapsMode);
379         m_editText.setImeOptions(imeOptions);
380         m_editText.setInputType(inputType);
381 
382         m_layout.setLayoutParams(m_editText, new QtLayout.LayoutParams(width, height, x, y), false);
383         m_editText.requestFocus();
384         m_editText.postDelayed(new Runnable() {
385             @Override
386             public void run() {
387                 m_imm.showSoftInput(m_editText, 0, new ResultReceiver(new Handler()) {
388                     @Override
389                     protected void onReceiveResult(int resultCode, Bundle resultData) {
390                         switch (resultCode) {
391                             case InputMethodManager.RESULT_SHOWN:
392                                 QtNativeInputConnection.updateCursorPosition();
393                                 //FALLTHROUGH
394                             case InputMethodManager.RESULT_UNCHANGED_SHOWN:
395                                 setKeyboardVisibility(true, System.nanoTime());
396                                 if (m_softInputMode == 0) {
397                                     // probe for real keyboard height
398                                     m_layout.postDelayed(new Runnable() {
399                                             @Override
400                                             public void run() {
401                                                 if (!m_keyboardIsVisible)
402                                                     return;
403                                                 DisplayMetrics metrics = new DisplayMetrics();
404                                                 m_activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
405                                                 Rect r = new Rect();
406                                                 m_activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(r);
407                                                 if (metrics.heightPixels != r.bottom) {
408                                                     if (metrics.widthPixels > metrics.heightPixels) { // landscape
409                                                         if (m_landscapeKeyboardHeight != r.bottom) {
410                                                             m_landscapeKeyboardHeight = r.bottom;
411                                                             showSoftwareKeyboard(x, y, width, height, inputHints, enterKeyType);
412                                                         }
413                                                     } else {
414                                                         if (m_portraitKeyboardHeight != r.bottom) {
415                                                             m_portraitKeyboardHeight = r.bottom;
416                                                             showSoftwareKeyboard(x, y, width, height, inputHints, enterKeyType);
417                                                         }
418                                                     }
419                                                 } else {
420                                                     // no luck ?
421                                                     // maybe the delay was too short, so let's make it longer
422                                                     if (m_probeKeyboardHeightDelay < 1000)
423                                                         m_probeKeyboardHeightDelay *= 2;
424                                                 }
425                                             }
426                                         }, m_probeKeyboardHeightDelay);
427                                     }
428                                 break;
429                             case InputMethodManager.RESULT_HIDDEN:
430                             case InputMethodManager.RESULT_UNCHANGED_HIDDEN:
431                                 setKeyboardVisibility(false, System.nanoTime());
432                                 break;
433                         }
434                     }
435                 });
436                 if (m_editText.m_optionsChanged) {
437                     m_imm.restartInput(m_editText);
438                     m_editText.m_optionsChanged = false;
439                 }
440             }
441         }, 15);
442     }
443 
hideSoftwareKeyboard()444     public void hideSoftwareKeyboard()
445     {
446         if (m_imm == null)
447             return;
448         m_imm.hideSoftInputFromWindow(m_editText.getWindowToken(), 0, new ResultReceiver(new Handler()) {
449             @Override
450             protected void onReceiveResult(int resultCode, Bundle resultData) {
451                 switch (resultCode) {
452                     case InputMethodManager.RESULT_SHOWN:
453                     case InputMethodManager.RESULT_UNCHANGED_SHOWN:
454                         setKeyboardVisibility(true, System.nanoTime());
455                         break;
456                     case InputMethodManager.RESULT_HIDDEN:
457                     case InputMethodManager.RESULT_UNCHANGED_HIDDEN:
458                         setKeyboardVisibility(false, System.nanoTime());
459                         break;
460                 }
461             }
462         });
463     }
464 
getAppIconSize(Activity a)465     String getAppIconSize(Activity a)
466     {
467         int size = a.getResources().getDimensionPixelSize(android.R.dimen.app_icon_size);
468         if (size < 36 || size > 512) { // check size sanity
469             DisplayMetrics metrics = new DisplayMetrics();
470             a.getWindowManager().getDefaultDisplay().getMetrics(metrics);
471             size = metrics.densityDpi / 10 * 3;
472             if (size < 36)
473                 size = 36;
474 
475             if (size > 512)
476                 size = 512;
477         }
478         return "\tQT_ANDROID_APP_ICON_SIZE=" + size;
479     }
480 
updateSelection(int selStart, int selEnd, int candidatesStart, int candidatesEnd)481     public void updateSelection(int selStart, int selEnd, int candidatesStart, int candidatesEnd)
482     {
483         if (m_imm == null)
484             return;
485 
486         m_imm.updateSelection(m_editText, selStart, selEnd, candidatesStart, candidatesEnd);
487     }
488 
489     // Values coming from QAndroidInputContext::CursorHandleShowMode
490     private static final int CursorHandleNotShown       = 0;
491     private static final int CursorHandleShowNormal     = 1;
492     private static final int CursorHandleShowSelection  = 2;
493     private static final int CursorHandleShowEdit       = 0x100;
494 
495     /* called from the C++ code when the position of the cursor or selection handles needs to
496        be adjusted.
497        mode is one of QAndroidInputContext::CursorHandleShowMode
498     */
updateHandles(int mode, int editX, int editY, int editButtons, int x1, int y1, int x2, int y2, boolean rtl)499     public void updateHandles(int mode, int editX, int editY, int editButtons, int x1, int y1, int x2, int y2, boolean rtl)
500     {
501         switch (mode & 0xff)
502         {
503             case CursorHandleNotShown:
504                 if (m_cursorHandle != null) {
505                     m_cursorHandle.hide();
506                     m_cursorHandle = null;
507                 }
508                 if (m_rightSelectionHandle != null) {
509                     m_rightSelectionHandle.hide();
510                     m_leftSelectionHandle.hide();
511                     m_rightSelectionHandle = null;
512                     m_leftSelectionHandle = null;
513                 }
514                 if (m_editPopupMenu != null)
515                     m_editPopupMenu.hide();
516                 break;
517 
518             case CursorHandleShowNormal:
519                 if (m_cursorHandle == null) {
520                     m_cursorHandle = new CursorHandle(m_activity, m_layout, QtNative.IdCursorHandle,
521                                                       android.R.attr.textSelectHandle, false);
522                 }
523                 m_cursorHandle.setPosition(x1, y1);
524                 if (m_rightSelectionHandle != null) {
525                     m_rightSelectionHandle.hide();
526                     m_leftSelectionHandle.hide();
527                     m_rightSelectionHandle = null;
528                     m_leftSelectionHandle = null;
529                 }
530                 break;
531 
532             case CursorHandleShowSelection:
533                 if (m_rightSelectionHandle == null) {
534                     m_leftSelectionHandle = new CursorHandle(m_activity, m_layout, QtNative.IdLeftHandle,
535                                                              !rtl ? android.R.attr.textSelectHandleLeft :
536                                                                     android.R.attr.textSelectHandleRight,
537                                                              rtl);
538                     m_rightSelectionHandle = new CursorHandle(m_activity, m_layout, QtNative.IdRightHandle,
539                                                               !rtl ? android.R.attr.textSelectHandleRight :
540                                                                      android.R.attr.textSelectHandleLeft,
541                                                               rtl);
542                 }
543                 m_leftSelectionHandle.setPosition(x1,y1);
544                 m_rightSelectionHandle.setPosition(x2,y2);
545                 if (m_cursorHandle != null) {
546                     m_cursorHandle.hide();
547                     m_cursorHandle = null;
548                 }
549                 mode |= CursorHandleShowEdit;
550                 break;
551         }
552 
553         if (QtNative.hasClipboardText())
554             editButtons |= EditContextView.PASTE_BUTTON;
555         else
556             editButtons &= ~EditContextView.PASTE_BUTTON;
557 
558         if ((mode & CursorHandleShowEdit) == CursorHandleShowEdit && editButtons != 0) {
559             m_editPopupMenu.setPosition(editX, editY, editButtons, m_cursorHandle, m_leftSelectionHandle,
560                                         m_rightSelectionHandle);
561         } else {
562             if (m_editPopupMenu != null)
563                 m_editPopupMenu.hide();
564         }
565     }
566 
loadApplication(Activity activity, ClassLoader classLoader, Bundle loaderParams)567     public boolean loadApplication(Activity activity, ClassLoader classLoader, Bundle loaderParams)
568     {
569         /// check parameters integrity
570         if (!loaderParams.containsKey(NATIVE_LIBRARIES_KEY)
571                 || !loaderParams.containsKey(BUNDLED_LIBRARIES_KEY)
572                 || !loaderParams.containsKey(ENVIRONMENT_VARIABLES_KEY)) {
573             return false;
574         }
575 
576         m_activity = activity;
577         setActionBarVisibility(false);
578         QtNative.setActivity(m_activity, this);
579         QtNative.setClassLoader(classLoader);
580         if (loaderParams.containsKey(STATIC_INIT_CLASSES_KEY)) {
581             for (String className: Objects.requireNonNull(loaderParams.getStringArray(STATIC_INIT_CLASSES_KEY))) {
582                 if (className.length() == 0)
583                     continue;
584 
585                 try {
586                   Class<?> initClass = classLoader.loadClass(className);
587                   Object staticInitDataObject = initClass.newInstance(); // create an instance
588                   try {
589                       Method m = initClass.getMethod("setActivity", Activity.class, Object.class);
590                       m.invoke(staticInitDataObject, m_activity, this);
591                   } catch (Exception e) {
592                       Log.d(QtNative.QtTAG, "Class " + className + " does not implement setActivity method");
593                   }
594 
595                   // For modules that don't need/have setActivity
596                   try {
597                       Method m = initClass.getMethod("setContext", Context.class);
598                       m.invoke(staticInitDataObject, (Context)m_activity);
599                   } catch (Exception e) {
600                       e.printStackTrace();
601                   }
602                 } catch (Exception e) {
603                     e.printStackTrace();
604                 }
605             }
606         }
607         QtNative.loadQtLibraries(loaderParams.getStringArrayList(NATIVE_LIBRARIES_KEY));
608         ArrayList<String> libraries = loaderParams.getStringArrayList(BUNDLED_LIBRARIES_KEY);
609         String nativeLibsDir = QtNativeLibrariesDir.nativeLibrariesDir(m_activity);
610         QtNative.loadBundledLibraries(libraries, nativeLibsDir);
611         m_mainLib = loaderParams.getString(MAIN_LIBRARY_KEY);
612         // older apps provide the main library as the last bundled library; look for this if the main library isn't provided
613         if (null == m_mainLib && libraries.size() > 0) {
614             m_mainLib = libraries.get(libraries.size() - 1);
615             libraries.remove(libraries.size() - 1);
616         }
617 
618         if (loaderParams.containsKey(EXTRACT_STYLE_KEY)) {
619             String path = loaderParams.getString(EXTRACT_STYLE_KEY);
620             new ExtractStyle(m_activity, path, loaderParams.containsKey(EXTRACT_STYLE_MINIMAL_KEY) &&
621                                                loaderParams.getBoolean(EXTRACT_STYLE_MINIMAL_KEY));
622         }
623 
624         try {
625             m_super_dispatchKeyEvent = m_activity.getClass().getMethod("super_dispatchKeyEvent", KeyEvent.class);
626             m_super_onRestoreInstanceState = m_activity.getClass().getMethod("super_onRestoreInstanceState", Bundle.class);
627             m_super_onRetainNonConfigurationInstance = m_activity.getClass().getMethod("super_onRetainNonConfigurationInstance");
628             m_super_onSaveInstanceState = m_activity.getClass().getMethod("super_onSaveInstanceState", Bundle.class);
629             m_super_onKeyDown = m_activity.getClass().getMethod("super_onKeyDown", Integer.TYPE, KeyEvent.class);
630             m_super_onKeyUp = m_activity.getClass().getMethod("super_onKeyUp", Integer.TYPE, KeyEvent.class);
631             m_super_onConfigurationChanged = m_activity.getClass().getMethod("super_onConfigurationChanged", Configuration.class);
632             m_super_onActivityResult = m_activity.getClass().getMethod("super_onActivityResult", Integer.TYPE, Integer.TYPE, Intent.class);
633             m_super_onWindowFocusChanged = m_activity.getClass().getMethod("super_onWindowFocusChanged", Boolean.TYPE);
634             m_super_dispatchGenericMotionEvent = m_activity.getClass().getMethod("super_dispatchGenericMotionEvent", MotionEvent.class);
635         } catch (Exception e) {
636             e.printStackTrace();
637             return false;
638         }
639 
640         int necessitasApiLevel = 1;
641         if (loaderParams.containsKey(NECESSITAS_API_LEVEL_KEY))
642             necessitasApiLevel = loaderParams.getInt(NECESSITAS_API_LEVEL_KEY);
643 
644         m_environmentVariables = loaderParams.getString(ENVIRONMENT_VARIABLES_KEY);
645         String additionalEnvironmentVariables = "QT_ANDROID_FONTS_MONOSPACE=Droid Sans Mono;Droid Sans;Droid Sans Fallback"
646                                               + "\tQT_ANDROID_FONTS_SERIF=Droid Serif"
647                                               + "\tNECESSITAS_API_LEVEL=" + necessitasApiLevel
648                                               + "\tHOME=" + m_activity.getFilesDir().getAbsolutePath()
649                                               + "\tTMPDIR=" + m_activity.getFilesDir().getAbsolutePath();
650 
651         additionalEnvironmentVariables += "\tQT_ANDROID_FONTS=Roboto;Droid Sans;Droid Sans Fallback";
652 
653         additionalEnvironmentVariables += getAppIconSize(activity);
654 
655         if (m_environmentVariables != null && m_environmentVariables.length() > 0)
656             m_environmentVariables = additionalEnvironmentVariables + "\t" + m_environmentVariables;
657         else
658             m_environmentVariables = additionalEnvironmentVariables;
659 
660         if (loaderParams.containsKey(APPLICATION_PARAMETERS_KEY))
661             m_applicationParameters = loaderParams.getString(APPLICATION_PARAMETERS_KEY);
662         else
663             m_applicationParameters = "";
664 
665         try {
666             m_softInputMode = m_activity.getPackageManager().getActivityInfo(m_activity.getComponentName(), 0).softInputMode;
667         } catch (Exception e) {
668             e.printStackTrace();
669         }
670 
671         DisplayManager.DisplayListener displayListener = new DisplayManager.DisplayListener() {
672             @Override
673             public void onDisplayAdded(int displayId) { }
674 
675             @Override
676             public void onDisplayChanged(int displayId) {
677                 m_currentRotation = m_activity.getWindowManager().getDefaultDisplay().getRotation();
678                 QtNative.handleOrientationChanged(m_currentRotation, m_nativeOrientation);
679             }
680 
681             @Override
682             public void onDisplayRemoved(int displayId) { }
683         };
684 
685         try {
686             DisplayManager displayManager = (DisplayManager) m_activity.getSystemService(Context.DISPLAY_SERVICE);
687             displayManager.registerDisplayListener(displayListener, null);
688         } catch (Exception e) {
689             e.printStackTrace();
690         }
691 
692         m_mainLib = QtNative.loadMainLibrary(m_mainLib, nativeLibsDir);
693         return m_mainLib != null;
694     }
695 
startApplication()696     public boolean startApplication()
697     {
698         // start application
699         try {
700 
701             Bundle extras = m_activity.getIntent().getExtras();
702             if (extras != null) {
703                 try {
704                     final boolean isDebuggable = (m_activity.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
705                     if (!isDebuggable)
706                         throw new Exception();
707 
708                     if (extras.containsKey("extraenvvars")) {
709                         try {
710                             m_environmentVariables += "\t" + new String(Base64.decode(extras.getString("extraenvvars"), Base64.DEFAULT), "UTF-8");
711                         } catch (Exception e) {
712                             e.printStackTrace();
713                         }
714                     }
715 
716                     if (extras.containsKey("extraappparams")) {
717                         try {
718                             m_applicationParameters += "\t" + new String(Base64.decode(extras.getString("extraappparams"), Base64.DEFAULT), "UTF-8");
719                         } catch (Exception e) {
720                             e.printStackTrace();
721                         }
722                     }
723                 } catch (Exception e) {
724                     Log.e(QtNative.QtTAG, "Not in debug mode! It is not allowed to use " +
725                                           "extra arguments in non-debug mode.");
726                     // This is not an error, so keep it silent
727                     // e.printStackTrace();
728                 }
729             } // extras != null
730 
731             if (null == m_surfaces)
732                 onCreate(null);
733             return true;
734         } catch (Exception e) {
735             e.printStackTrace();
736             return false;
737         }
738     }
739 
onTerminate()740     public void onTerminate()
741     {
742         QtNative.terminateQt();
743         QtNative.m_qtThread.exit();
744     }
745 
onCreate(Bundle savedInstanceState)746     public void onCreate(Bundle savedInstanceState)
747     {
748         m_quitApp = true;
749         Runnable startApplication = null;
750         if (null == savedInstanceState) {
751             startApplication = new Runnable() {
752                 @Override
753                 public void run() {
754                     try {
755                         QtNative.startApplication(m_applicationParameters, m_environmentVariables, m_mainLib);
756                         m_started = true;
757                     } catch (Exception e) {
758                         e.printStackTrace();
759                         m_activity.finish();
760                     }
761                 }
762             };
763         }
764         m_layout = new QtLayout(m_activity, startApplication);
765 
766         int orientation = m_activity.getResources().getConfiguration().orientation;
767 
768         try {
769             ActivityInfo info = m_activity.getPackageManager().getActivityInfo(m_activity.getComponentName(), PackageManager.GET_META_DATA);
770 
771             String splashScreenKey = "android.app.splash_screen_drawable_"
772                 + (orientation == Configuration.ORIENTATION_LANDSCAPE ? "landscape" : "portrait");
773             if (!info.metaData.containsKey(splashScreenKey))
774                 splashScreenKey = "android.app.splash_screen_drawable";
775 
776             if (info.metaData.containsKey(splashScreenKey)) {
777                 m_splashScreenSticky = info.metaData.containsKey("android.app.splash_screen_sticky") && info.metaData.getBoolean("android.app.splash_screen_sticky");
778                 int id = info.metaData.getInt(splashScreenKey);
779                 m_splashScreen = new ImageView(m_activity);
780                 m_splashScreen.setImageDrawable(m_activity.getResources().getDrawable(id));
781                 m_splashScreen.setScaleType(ImageView.ScaleType.FIT_XY);
782                 m_splashScreen.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
783                 m_layout.addView(m_splashScreen);
784             }
785         } catch (Exception e) {
786             e.printStackTrace();
787         }
788 
789         m_editText = new QtEditText(m_activity, this);
790         m_imm = (InputMethodManager)m_activity.getSystemService(Context.INPUT_METHOD_SERVICE);
791         m_surfaces =  new HashMap<Integer, QtSurface>();
792         m_nativeViews = new HashMap<Integer, View>();
793         m_activity.registerForContextMenu(m_layout);
794         m_activity.setContentView(m_layout,
795                                   new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
796                                                              ViewGroup.LayoutParams.MATCH_PARENT));
797 
798         int rotation = m_activity.getWindowManager().getDefaultDisplay().getRotation();
799         boolean rot90 = (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270);
800         boolean currentlyLandscape = (orientation == Configuration.ORIENTATION_LANDSCAPE);
801         if ((currentlyLandscape && !rot90) || (!currentlyLandscape && rot90))
802             m_nativeOrientation = Configuration.ORIENTATION_LANDSCAPE;
803         else
804             m_nativeOrientation = Configuration.ORIENTATION_PORTRAIT;
805 
806         QtNative.handleOrientationChanged(rotation, m_nativeOrientation);
807         m_currentRotation = rotation;
808 
809         m_layout.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
810             @Override
811             public boolean onPreDraw() {
812                 if (!m_keyboardIsVisible)
813                     return true;
814 
815                 Rect r = new Rect();
816                 m_activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(r);
817                 DisplayMetrics metrics = new DisplayMetrics();
818                 m_activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
819                 final int kbHeight = metrics.heightPixels - r.bottom;
820                 final int[] location = new int[2];
821                 m_layout.getLocationOnScreen(location);
822                 QtNative.keyboardGeometryChanged(location[0], r.bottom - location[1],
823                                                  r.width(), kbHeight);
824                 return true;
825             }
826         });
827         m_editPopupMenu = new EditPopupMenu(m_activity, m_layout);
828     }
829 
hideSplashScreen()830     public void hideSplashScreen()
831     {
832         hideSplashScreen(0);
833     }
834 
hideSplashScreen(final int duration)835     public void hideSplashScreen(final int duration)
836     {
837         if (m_splashScreen == null)
838             return;
839 
840         if (duration <= 0) {
841             m_layout.removeView(m_splashScreen);
842             m_splashScreen = null;
843             return;
844         }
845 
846         final Animation fadeOut = new AlphaAnimation(1, 0);
847         fadeOut.setInterpolator(new AccelerateInterpolator());
848         fadeOut.setDuration(duration);
849 
850         fadeOut.setAnimationListener(new Animation.AnimationListener() {
851             @Override
852             public void onAnimationEnd(Animation animation) { hideSplashScreen(0); }
853 
854             @Override
855             public void onAnimationRepeat(Animation animation) {}
856 
857             @Override
858             public void onAnimationStart(Animation animation) {}
859         });
860 
861         m_splashScreen.startAnimation(fadeOut);
862     }
863 
notifyAccessibilityLocationChange()864     public void notifyAccessibilityLocationChange()
865     {
866         if (m_accessibilityDelegate == null)
867             return;
868         m_accessibilityDelegate.notifyLocationChange();
869     }
870 
notifyObjectHide(int viewId)871     public void notifyObjectHide(int viewId)
872     {
873         if (m_accessibilityDelegate == null)
874             return;
875         m_accessibilityDelegate.notifyObjectHide(viewId);
876     }
877 
notifyObjectFocus(int viewId)878     public void notifyObjectFocus(int viewId)
879     {
880         if (m_accessibilityDelegate == null)
881             return;
882         m_accessibilityDelegate.notifyObjectFocus(viewId);
883     }
884 
initializeAccessibility()885     public void initializeAccessibility()
886     {
887         m_accessibilityDelegate = new QtAccessibilityDelegate(m_activity, m_layout, this);
888     }
889 
onWindowFocusChanged(boolean hasFocus)890     public void onWindowFocusChanged(boolean hasFocus) {
891         try {
892             m_super_onWindowFocusChanged.invoke(m_activity, hasFocus);
893         } catch (Exception e) {
894             e.printStackTrace();
895         }
896         if (hasFocus)
897             updateFullScreen();
898     }
899 
onConfigurationChanged(Configuration configuration)900     public void onConfigurationChanged(Configuration configuration)
901     {
902         try {
903             m_super_onConfigurationChanged.invoke(m_activity, configuration);
904         } catch (Exception e) {
905             e.printStackTrace();
906         }
907     }
908 
onDestroy()909     public void onDestroy()
910     {
911         if (m_quitApp) {
912             QtNative.terminateQt();
913             QtNative.setActivity(null, null);
914             QtNative.m_qtThread.exit();
915             System.exit(0);
916         }
917     }
918 
onPause()919     public void onPause()
920     {
921         if (Build.VERSION.SDK_INT < 24 || !m_activity.isInMultiWindowMode())
922             QtNative.setApplicationState(ApplicationInactive);
923     }
924 
onResume()925     public void onResume()
926     {
927         QtNative.setApplicationState(ApplicationActive);
928         if (m_started) {
929             QtNative.updateWindow();
930             updateFullScreen(); // Suspending the app clears the immersive mode, so we need to set it again.
931         }
932     }
933 
onNewIntent(Intent data)934     public void onNewIntent(Intent data)
935     {
936         QtNative.onNewIntent(data);
937     }
938 
onActivityResult(int requestCode, int resultCode, Intent data)939     public void onActivityResult(int requestCode, int resultCode, Intent data)
940     {
941         try {
942             m_super_onActivityResult.invoke(m_activity, requestCode, resultCode, data);
943         } catch (Exception e) {
944             e.printStackTrace();
945         }
946 
947         QtNative.onActivityResult(requestCode, resultCode, data);
948     }
949 
950 
onStop()951     public void onStop()
952     {
953         QtNative.setApplicationState(ApplicationSuspended);
954     }
955 
onRetainNonConfigurationInstance()956     public Object onRetainNonConfigurationInstance()
957     {
958         try {
959             m_super_onRetainNonConfigurationInstance.invoke(m_activity);
960         } catch (Exception e) {
961             e.printStackTrace();
962         }
963         m_quitApp = false;
964         return true;
965     }
966 
onSaveInstanceState(Bundle outState)967     public void onSaveInstanceState(Bundle outState) {
968         try {
969             m_super_onSaveInstanceState.invoke(m_activity, outState);
970         } catch (Exception e) {
971             e.printStackTrace();
972         }
973         outState.putBoolean("FullScreen", m_fullScreen);
974         outState.putBoolean("Started", m_started);
975         // It should never
976     }
977 
onRestoreInstanceState(Bundle savedInstanceState)978     public void onRestoreInstanceState(Bundle savedInstanceState)
979     {
980         try {
981             m_super_onRestoreInstanceState.invoke(m_activity, savedInstanceState);
982         } catch (Exception e) {
983             e.printStackTrace();
984         }
985         m_started = savedInstanceState.getBoolean("Started");
986         // FIXME restore all surfaces
987 
988     }
989 
onKeyDown(int keyCode, KeyEvent event)990     public boolean onKeyDown(int keyCode, KeyEvent event)
991     {
992         if (!m_started)
993             return false;
994 
995         m_metaState = MetaKeyKeyListener.handleKeyDown(m_metaState, keyCode, event);
996         int c = event.getUnicodeChar(MetaKeyKeyListener.getMetaState(m_metaState) | event.getMetaState());
997         int lc = c;
998         m_metaState = MetaKeyKeyListener.adjustMetaAfterKeypress(m_metaState);
999 
1000         if ((c & KeyCharacterMap.COMBINING_ACCENT) != 0) {
1001             c = c & KeyCharacterMap.COMBINING_ACCENT_MASK;
1002             int composed = KeyEvent.getDeadChar(m_lastChar, c);
1003             c = composed;
1004         }
1005 
1006         if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP
1007             || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
1008             || keyCode == KeyEvent.KEYCODE_MUTE)
1009             && System.getenv("QT_ANDROID_VOLUME_KEYS") == null) {
1010             return false;
1011         }
1012 
1013         m_lastChar = lc;
1014         if (keyCode == KeyEvent.KEYCODE_BACK) {
1015             m_backKeyPressedSent = !m_keyboardIsVisible;
1016             if (!m_backKeyPressedSent)
1017                 return true;
1018         }
1019         QtNative.keyDown(keyCode, c, event.getMetaState(), event.getRepeatCount() > 0);
1020 
1021         return true;
1022     }
1023 
onKeyUp(int keyCode, KeyEvent event)1024     public boolean onKeyUp(int keyCode, KeyEvent event)
1025     {
1026         if (!m_started)
1027             return false;
1028 
1029         if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP
1030             || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
1031             || keyCode == KeyEvent.KEYCODE_MUTE)
1032             && System.getenv("QT_ANDROID_VOLUME_KEYS") == null) {
1033             return false;
1034         }
1035 
1036         if (keyCode == KeyEvent.KEYCODE_BACK && !m_backKeyPressedSent) {
1037             hideSoftwareKeyboard();
1038             setKeyboardVisibility(false, System.nanoTime());
1039             return true;
1040         }
1041 
1042         m_metaState = MetaKeyKeyListener.handleKeyUp(m_metaState, keyCode, event);
1043         QtNative.keyUp(keyCode, event.getUnicodeChar(), event.getMetaState(), event.getRepeatCount() > 0);
1044         return true;
1045     }
1046 
dispatchKeyEvent(KeyEvent event)1047     public boolean dispatchKeyEvent(KeyEvent event)
1048     {
1049         if (m_started
1050                 && event.getAction() == KeyEvent.ACTION_MULTIPLE
1051                 && event.getCharacters() != null
1052                 && event.getCharacters().length() == 1
1053                 && event.getKeyCode() == 0) {
1054             QtNative.keyDown(0, event.getCharacters().charAt(0), event.getMetaState(), event.getRepeatCount() > 0);
1055             QtNative.keyUp(0, event.getCharacters().charAt(0), event.getMetaState(), event.getRepeatCount() > 0);
1056         }
1057 
1058         if (QtNative.dispatchKeyEvent(event))
1059             return true;
1060 
1061         try {
1062             return (Boolean) m_super_dispatchKeyEvent.invoke(m_activity, event);
1063         } catch (Exception e) {
1064             e.printStackTrace();
1065         }
1066         return false;
1067     }
1068 
1069     private boolean m_optionsMenuIsVisible = false;
onCreateOptionsMenu(Menu menu)1070     public boolean onCreateOptionsMenu(Menu menu)
1071     {
1072         menu.clear();
1073         return true;
1074     }
onPrepareOptionsMenu(Menu menu)1075     public boolean onPrepareOptionsMenu(Menu menu)
1076     {
1077         m_optionsMenuIsVisible = true;
1078         boolean res = QtNative.onPrepareOptionsMenu(menu);
1079         setActionBarVisibility(res && menu.size() > 0);
1080         return res;
1081     }
1082 
onOptionsItemSelected(MenuItem item)1083     public boolean onOptionsItemSelected(MenuItem item)
1084     {
1085         return QtNative.onOptionsItemSelected(item.getItemId(), item.isChecked());
1086     }
1087 
onOptionsMenuClosed(Menu menu)1088     public void onOptionsMenuClosed(Menu menu)
1089     {
1090         m_optionsMenuIsVisible = false;
1091         QtNative.onOptionsMenuClosed(menu);
1092     }
1093 
resetOptionsMenu()1094     public void resetOptionsMenu()
1095     {
1096         m_activity.invalidateOptionsMenu();
1097     }
1098 
1099     private boolean m_contextMenuVisible = false;
onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo)1100     public void onCreateContextMenu(ContextMenu menu,
1101                                     View v,
1102                                     ContextMenuInfo menuInfo)
1103     {
1104         menu.clearHeader();
1105         QtNative.onCreateContextMenu(menu);
1106         m_contextMenuVisible = true;
1107     }
1108 
onCreatePopupMenu(Menu menu)1109     public void onCreatePopupMenu(Menu menu)
1110     {
1111         QtNative.fillContextMenu(menu);
1112         m_contextMenuVisible = true;
1113     }
1114 
onContextMenuClosed(Menu menu)1115     public void onContextMenuClosed(Menu menu)
1116     {
1117         if (!m_contextMenuVisible)
1118             return;
1119         m_contextMenuVisible = false;
1120         QtNative.onContextMenuClosed(menu);
1121     }
1122 
onContextItemSelected(MenuItem item)1123     public boolean onContextItemSelected(MenuItem item)
1124     {
1125         m_contextMenuVisible = false;
1126         return QtNative.onContextItemSelected(item.getItemId(), item.isChecked());
1127     }
1128 
openContextMenu(final int x, final int y, final int w, final int h)1129     public void openContextMenu(final int x, final int y, final int w, final int h)
1130     {
1131         m_layout.postDelayed(new Runnable() {
1132                 @Override
1133                 public void run() {
1134                     m_layout.setLayoutParams(m_editText, new QtLayout.LayoutParams(w, h, x, y), false);
1135                     PopupMenu popup = new PopupMenu(m_activity, m_editText);
1136                     QtActivityDelegate.this.onCreatePopupMenu(popup.getMenu());
1137                     popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
1138                         @Override
1139                         public boolean onMenuItemClick(MenuItem menuItem) {
1140                             return QtActivityDelegate.this.onContextItemSelected(menuItem);
1141                         }
1142                     });
1143                     popup.setOnDismissListener(new PopupMenu.OnDismissListener() {
1144                         @Override
1145                         public void onDismiss(PopupMenu popupMenu) {
1146                             QtActivityDelegate.this.onContextMenuClosed(popupMenu.getMenu());
1147                         }
1148                     });
1149                     popup.show();
1150                 }
1151             }, 100);
1152     }
1153 
closeContextMenu()1154     public void closeContextMenu()
1155     {
1156         m_activity.closeContextMenu();
1157     }
1158 
setActionBarVisibility(boolean visible)1159     private void setActionBarVisibility(boolean visible)
1160     {
1161         if (m_activity.getActionBar() == null)
1162             return;
1163         if (ViewConfiguration.get(m_activity).hasPermanentMenuKey() || !visible)
1164             m_activity.getActionBar().hide();
1165         else
1166             m_activity.getActionBar().show();
1167     }
1168 
insertNativeView(int id, View view, int x, int y, int w, int h)1169     public void insertNativeView(int id, View view, int x, int y, int w, int h) {
1170         if (m_dummyView != null) {
1171             m_layout.removeView(m_dummyView);
1172             m_dummyView = null;
1173         }
1174 
1175         if (m_nativeViews.containsKey(id))
1176             m_layout.removeView(m_nativeViews.remove(id));
1177 
1178         if (w < 0 || h < 0) {
1179             view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
1180                                  ViewGroup.LayoutParams.MATCH_PARENT));
1181         } else {
1182             view.setLayoutParams(new QtLayout.LayoutParams(w, h, x, y));
1183         }
1184 
1185         view.setId(id);
1186         m_layout.addView(view);
1187         m_nativeViews.put(id, view);
1188     }
1189 
createSurface(int id, boolean onTop, int x, int y, int w, int h, int imageDepth)1190     public void createSurface(int id, boolean onTop, int x, int y, int w, int h, int imageDepth) {
1191         if (m_surfaces.size() == 0) {
1192             TypedValue attr = new TypedValue();
1193             m_activity.getTheme().resolveAttribute(android.R.attr.windowBackground, attr, true);
1194             if (attr.type >= TypedValue.TYPE_FIRST_COLOR_INT && attr.type <= TypedValue.TYPE_LAST_COLOR_INT) {
1195                 m_activity.getWindow().setBackgroundDrawable(new ColorDrawable(attr.data));
1196             } else {
1197                 m_activity.getWindow().setBackgroundDrawable(m_activity.getResources().getDrawable(attr.resourceId));
1198             }
1199             if (m_dummyView != null) {
1200                 m_layout.removeView(m_dummyView);
1201                 m_dummyView = null;
1202             }
1203         }
1204 
1205         if (m_surfaces.containsKey(id))
1206             m_layout.removeView(m_surfaces.remove(id));
1207 
1208         QtSurface surface = new QtSurface(m_activity, id, onTop, imageDepth);
1209         if (w < 0 || h < 0) {
1210             surface.setLayoutParams( new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
1211                     ViewGroup.LayoutParams.MATCH_PARENT));
1212         } else {
1213             surface.setLayoutParams( new QtLayout.LayoutParams(w, h, x, y));
1214         }
1215 
1216         // Native views are always inserted in the end of the stack (i.e., on top).
1217         // All other views are stacked based on the order they are created.
1218         final int surfaceCount = getSurfaceCount();
1219         m_layout.addView(surface, surfaceCount);
1220 
1221         m_surfaces.put(id, surface);
1222         if (!m_splashScreenSticky)
1223             hideSplashScreen();
1224     }
1225 
setSurfaceGeometry(int id, int x, int y, int w, int h)1226     public void setSurfaceGeometry(int id, int x, int y, int w, int h) {
1227         if (m_surfaces.containsKey(id)) {
1228             QtSurface surface = m_surfaces.get(id);
1229             surface.setLayoutParams(new QtLayout.LayoutParams(w, h, x, y));
1230         } else if (m_nativeViews.containsKey(id)) {
1231             View view = m_nativeViews.get(id);
1232             view.setLayoutParams(new QtLayout.LayoutParams(w, h, x, y));
1233         } else {
1234             Log.e(QtNative.QtTAG, "Surface " + id +" not found!");
1235             return;
1236         }
1237     }
1238 
destroySurface(int id)1239     public void destroySurface(int id) {
1240         View view = null;
1241 
1242         if (m_surfaces.containsKey(id)) {
1243             view = m_surfaces.remove(id);
1244         } else if (m_nativeViews.containsKey(id)) {
1245             view = m_nativeViews.remove(id);
1246         } else {
1247             Log.e(QtNative.QtTAG, "Surface " + id +" not found!");
1248         }
1249 
1250         if (view == null)
1251             return;
1252 
1253         // Keep last frame in stack until it is replaced to get correct
1254         // shutdown transition
1255         if (m_surfaces.size() == 0 && m_nativeViews.size() == 0) {
1256             m_dummyView = view;
1257         } else {
1258             m_layout.removeView(view);
1259         }
1260     }
1261 
getSurfaceCount()1262     public int getSurfaceCount()
1263     {
1264         return m_surfaces.size();
1265     }
1266 
bringChildToFront(int id)1267     public void bringChildToFront(int id)
1268     {
1269         View view = m_surfaces.get(id);
1270         if (view != null) {
1271             final int surfaceCount = getSurfaceCount();
1272             if (surfaceCount > 0)
1273                 m_layout.moveChild(view, surfaceCount - 1);
1274             return;
1275         }
1276 
1277         view = m_nativeViews.get(id);
1278         if (view != null)
1279             m_layout.moveChild(view, -1);
1280     }
1281 
bringChildToBack(int id)1282     public void bringChildToBack(int id)
1283     {
1284         View view = m_surfaces.get(id);
1285         if (view != null) {
1286             m_layout.moveChild(view, 0);
1287             return;
1288         }
1289 
1290         view = m_nativeViews.get(id);
1291         if (view != null) {
1292             final int index = getSurfaceCount();
1293             m_layout.moveChild(view, index);
1294         }
1295     }
1296 
dispatchGenericMotionEvent(MotionEvent ev)1297     public boolean dispatchGenericMotionEvent (MotionEvent ev)
1298     {
1299         if (m_started && QtNative.dispatchGenericMotionEvent(ev))
1300             return true;
1301 
1302         try {
1303             return (Boolean) m_super_dispatchGenericMotionEvent.invoke(m_activity, ev);
1304         } catch (Exception e) {
1305             e.printStackTrace();
1306         }
1307         return false;
1308     }
1309 
onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)1310     public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
1311     {
1312         QtNative.sendRequestPermissionsResult(requestCode, permissions, grantResults);
1313     }
1314 }
1315