diff options
Diffstat (limited to 'src/android')
9 files changed, 738 insertions, 127 deletions
diff --git a/src/android/jar/jar.pri b/src/android/jar/jar.pri index 19501d7b29..5906231c73 100644 --- a/src/android/jar/jar.pri +++ b/src/android/jar/jar.pri @@ -9,6 +9,7 @@ JAVASOURCES += \ $$PATHPREFIX/QtEditText.java \ $$PATHPREFIX/QtInputConnection.java \ $$PATHPREFIX/QtLayout.java \ + $$PATHPREFIX/QtMessageDialogHelper.java \ $$PATHPREFIX/QtNative.java \ $$PATHPREFIX/QtNativeLibrariesDir.java \ $$PATHPREFIX/QtSurface.java diff --git a/src/android/jar/src/org/qtproject/qt5/android/QtActivityDelegate.java b/src/android/jar/src/org/qtproject/qt5/android/QtActivityDelegate.java index 11af4d4280..3dcffeb07d 100644 --- a/src/android/jar/src/org/qtproject/qt5/android/QtActivityDelegate.java +++ b/src/android/jar/src/org/qtproject/qt5/android/QtActivityDelegate.java @@ -42,13 +42,6 @@ package org.qtproject.qt5.android; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Iterator; - import android.app.Activity; import android.content.Context; import android.content.pm.PackageManager; @@ -57,6 +50,8 @@ import android.content.res.Configuration; import android.graphics.Rect; import android.os.Build; import android.os.Bundle; +import android.os.Handler; +import android.os.ResultReceiver; import android.text.method.MetaKeyKeyListener; import android.util.DisplayMetrics; import android.util.Log; @@ -66,11 +61,19 @@ import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; +import android.view.Surface; import android.view.View; +import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; -import android.view.Surface; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Iterator; public class QtActivityDelegate { @@ -109,8 +112,9 @@ public class QtActivityDelegate private boolean m_quitApp = true; private Process m_debuggerProcess = null; // debugger process - public boolean m_keyboardIsVisible = false; - public boolean m_keyboardIsHiding = false; + private boolean m_keyboardIsVisible = false; + public boolean m_backKeyPressedSent = false; + public QtLayout getQtLayout() { @@ -173,6 +177,13 @@ public class QtActivityDelegate private final int ApplicationInactive = 0x2; private final int ApplicationActive = 0x4; + public void setKeyboardVisibility(boolean visibility) + { + if (m_keyboardIsVisible == visibility) + return; + m_keyboardIsVisible = visibility; + QtNative.keyboardVisibilityChanged(m_keyboardIsVisible); + } public void resetSoftwareKeyboard() { if (m_imm == null) @@ -181,6 +192,7 @@ public class QtActivityDelegate @Override public void run() { m_imm.restartInput(m_editText); + m_editText.m_optionsChanged = false; } }, 5); } @@ -253,15 +265,25 @@ public class QtActivityDelegate m_editText.postDelayed(new Runnable() { @Override public void run() { - m_imm.showSoftInput(m_editText, 0); - m_keyboardIsVisible = true; - m_keyboardIsHiding = false; - m_editText.postDelayed(new Runnable() { + m_imm.showSoftInput(m_editText, 0, new ResultReceiver(new Handler()) { @Override - public void run() { - m_imm.restartInput(m_editText); + protected void onReceiveResult(int resultCode, Bundle resultData) { + switch (resultCode) { + case InputMethodManager.RESULT_SHOWN: + case InputMethodManager.RESULT_UNCHANGED_SHOWN: + setKeyboardVisibility(true); + break; + case InputMethodManager.RESULT_HIDDEN: + case InputMethodManager.RESULT_UNCHANGED_HIDDEN: + setKeyboardVisibility(false); + break; + } } - }, 25); + }); + if (m_editText.m_optionsChanged) { + m_imm.restartInput(m_editText); + m_editText.m_optionsChanged = false; + } } }, 15); } @@ -270,9 +292,21 @@ public class QtActivityDelegate { if (m_imm == null) return; - m_imm.hideSoftInputFromWindow(m_editText.getWindowToken(), 0); - m_keyboardIsVisible = false; - m_keyboardIsHiding = false; + m_imm.hideSoftInputFromWindow(m_editText.getWindowToken(), 0, new ResultReceiver( new Handler()){ + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + switch (resultCode) { + case InputMethodManager.RESULT_SHOWN: + case InputMethodManager.RESULT_UNCHANGED_SHOWN: + setKeyboardVisibility(true); + break; + case InputMethodManager.RESULT_HIDDEN: + case InputMethodManager.RESULT_UNCHANGED_HIDDEN: + setKeyboardVisibility(false); + break; + } + } + }); } public boolean isSoftwareKeyboardVisible() @@ -362,7 +396,7 @@ public class QtActivityDelegate + "\tNECESSITAS_API_LEVEL=" + necessitasApiLevel + "\tHOME=" + m_activity.getFilesDir().getAbsolutePath() + "\tTMPDIR=" + m_activity.getFilesDir().getAbsolutePath(); - if (android.os.Build.VERSION.SDK_INT < 14) + if (Build.VERSION.SDK_INT < 14) additionalEnvironmentVariables += "\tQT_ANDROID_FONTS=Droid Sans;Droid Sans Fallback"; else additionalEnvironmentVariables += "\tQT_ANDROID_FONTS=Roboto;Droid Sans;Droid Sans Fallback"; @@ -378,6 +412,7 @@ public class QtActivityDelegate m_applicationParameters = loaderParams.getString(APPLICATION_PARAMETERS_KEY); else m_applicationParameters = ""; + setActionBarVisibility(false); return true; } @@ -585,9 +620,9 @@ public class QtActivityDelegate } m_layout = new QtLayout(m_activity); m_surface = new QtSurface(m_activity, 0); - m_editText = new QtEditText(m_activity); + m_editText = new QtEditText(m_activity, this); m_imm = (InputMethodManager)m_activity.getSystemService(Context.INPUT_METHOD_SERVICE); - m_layout.addView(m_surface,0); + m_layout.addView(m_surface, 0); m_activity.setContentView(m_layout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); @@ -717,8 +752,12 @@ public class QtActivityDelegate } m_lastChar = lc; - if (keyCode != KeyEvent.KEYCODE_BACK) - QtNative.keyDown(keyCode, c, event.getMetaState()); + if (keyCode == KeyEvent.KEYCODE_BACK) { + m_backKeyPressedSent = !m_keyboardIsVisible; + if (!m_backKeyPressedSent) + return true; + } + QtNative.keyDown(keyCode, c, event.getMetaState()); return true; } @@ -737,8 +776,9 @@ public class QtActivityDelegate } } - if (keyCode == KeyEvent.KEYCODE_BACK && m_keyboardIsVisible && !m_keyboardIsHiding) { + if (keyCode == KeyEvent.KEYCODE_BACK && !m_backKeyPressedSent) { hideSoftwareKeyboard(); + setKeyboardVisibility(false); return true; } @@ -775,7 +815,10 @@ public class QtActivityDelegate public boolean onPrepareOptionsMenu(Menu menu) { m_opionsMenuIsVisible = true; - return QtNative.onPrepareOptionsMenu(menu); + boolean res = QtNative.onPrepareOptionsMenu(menu); + if (!res || menu.size() == 0) + setActionBarVisibility(false); + return res; } public boolean onOptionsItemSelected(MenuItem item) @@ -791,8 +834,17 @@ public class QtActivityDelegate public void resetOptionsMenu() { - if (m_opionsMenuIsVisible) - m_activity.closeOptionsMenu(); + setActionBarVisibility(true); + if (Build.VERSION.SDK_INT > 10) { + try { + Activity.class.getMethod("invalidateOptionsMenu").invoke(m_activity); + } catch (Exception e) { + e.printStackTrace(); + } + } + else + if (m_opionsMenuIsVisible) + m_activity.closeOptionsMenu(); } private boolean m_contextMenuVisible = false; public void onCreateContextMenu(ContextMenu menu, @@ -833,4 +885,46 @@ public class QtActivityDelegate { m_activity.closeContextMenu(); } + + private boolean hasPermanentMenuKey() + { + try { + return Build.VERSION.SDK_INT < 11 || (Build.VERSION.SDK_INT >= 14 && + (Boolean)ViewConfiguration.class.getMethod("hasPermanentMenuKey").invoke(ViewConfiguration.get(m_activity))); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + private Object getActionBar() + { + try { + return Activity.class.getMethod("getActionBar").invoke(m_activity); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + private void setActionBarVisibility(boolean visible) + { + if (hasPermanentMenuKey() || !visible) { + if (Build.VERSION.SDK_INT > 10 && getActionBar() != null) { + try { + Class.forName("android.app.ActionBar").getMethod("hide").invoke(getActionBar()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + } else { + if (Build.VERSION.SDK_INT > 10 && getActionBar() != null) + try { + Class.forName("android.app.ActionBar").getMethod("show").invoke(getActionBar()); + } catch (Exception e) { + e.printStackTrace(); + } + } + } } diff --git a/src/android/jar/src/org/qtproject/qt5/android/QtEditText.java b/src/android/jar/src/org/qtproject/qt5/android/QtEditText.java index b95e0c070c..593746aac9 100644 --- a/src/android/jar/src/org/qtproject/qt5/android/QtEditText.java +++ b/src/android/jar/src/org/qtproject/qt5/android/QtEditText.java @@ -50,33 +50,47 @@ import android.view.inputmethod.InputConnection; public class QtEditText extends View { - QtInputConnection m_inputConnection; int m_initialCapsMode = 0; int m_imeOptions = 0; int m_inputType = InputType.TYPE_CLASS_TEXT; + boolean m_optionsChanged = false; + QtActivityDelegate m_activityDelegate; public void setImeOptions(int m_imeOptions) { + if (m_imeOptions == this.m_imeOptions) + return; this.m_imeOptions = m_imeOptions; + m_optionsChanged = true; } public void setInitialCapsMode(int m_initialCapsMode) { + if (m_initialCapsMode == this.m_initialCapsMode) + return; this.m_initialCapsMode = m_initialCapsMode; + m_optionsChanged = true; } public void setInputType(int m_inputType) { + if (m_inputType == this.m_inputType) + return; this.m_inputType = m_inputType; + m_optionsChanged = true; } - public QtEditText(Context context) + public QtEditText(Context context, QtActivityDelegate activityDelegate) { super(context); setFocusable(true); setFocusableInTouchMode(true); - m_inputConnection = new QtInputConnection(this); + m_activityDelegate = activityDelegate; + } + public QtActivityDelegate getActivityDelegate() + { + return m_activityDelegate; } @Override @@ -86,8 +100,9 @@ public class QtEditText extends View outAttrs.imeOptions = m_imeOptions; outAttrs.initialCapsMode = m_initialCapsMode; outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_EXTRACT_UI; - return m_inputConnection; + return new QtInputConnection(this); } + // // DEBUG CODE // @Override // protected void onDraw(Canvas canvas) { diff --git a/src/android/jar/src/org/qtproject/qt5/android/QtInputConnection.java b/src/android/jar/src/org/qtproject/qt5/android/QtInputConnection.java index f251369737..4b2d50ca1f 100644 --- a/src/android/jar/src/org/qtproject/qt5/android/QtInputConnection.java +++ b/src/android/jar/src/org/qtproject/qt5/android/QtInputConnection.java @@ -43,8 +43,6 @@ package org.qtproject.qt5.android; import android.content.Context; -import android.os.Build; -import android.view.View; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.ExtractedText; @@ -82,6 +80,22 @@ class QtNativeInputConnection static native boolean paste(); } +class HideKeyboardRunnable implements Runnable { + private QtInputConnection m_connection; + HideKeyboardRunnable(QtInputConnection connection) + { + m_connection = connection; + } + + @Override + public void run() { + if (m_connection.getInputState() == QtInputConnection.InputStates.Hiding) { + QtNative.activityDelegate().setKeyboardVisibility(false); + m_connection.reset(); + } + } +} + public class QtInputConnection extends BaseInputConnection { private static final int ID_SELECT_ALL = android.R.id.selectAll; @@ -92,67 +106,83 @@ public class QtInputConnection extends BaseInputConnection private static final int ID_SWITCH_INPUT_METHOD = android.R.id.switchInputMethod; private static final int ID_ADD_TO_DICTIONARY = android.R.id.addToDictionary; - View m_view; - boolean m_closing; - public QtInputConnection(View targetView) + + enum InputStates { Visible, FinishComposing, Hiding }; + + private QtEditText m_view = null; + private InputStates m_inputState = InputStates.Visible; + + public void reset() + { + m_inputState = InputStates.Visible; + } + + public InputStates getInputState() + { + return m_inputState; + } + + private void setClosing(boolean closing) + { + if (closing && m_inputState == InputStates.Hiding) + return; + + if (closing && m_inputState == InputStates.FinishComposing && m_view.getActivityDelegate().isSoftwareKeyboardVisible()) { + m_view.postDelayed(new HideKeyboardRunnable(this), 100); + m_inputState = InputStates.Hiding; + } else { + if (m_inputState == InputStates.Hiding) + QtNative.activityDelegate().setKeyboardVisibility(true); + m_inputState = closing ? InputStates.FinishComposing : InputStates.Visible; + } + } + + public QtInputConnection(QtEditText targetView) { super(targetView, true); m_view = targetView; - m_closing = false; } @Override public boolean beginBatchEdit() { - m_closing = false; + setClosing(false); return true; } @Override public boolean endBatchEdit() { - m_closing = false; +// setClosing(false); return true; } @Override public boolean commitCompletion(CompletionInfo text) { - m_closing = false; + setClosing(false); return QtNativeInputConnection.commitCompletion(text.getText().toString(), text.getPosition()); } @Override public boolean commitText(CharSequence text, int newCursorPosition) { - m_closing = false; + setClosing(false); return QtNativeInputConnection.commitText(text.toString(), newCursorPosition); } @Override public boolean deleteSurroundingText(int leftLength, int rightLength) { - m_closing = false; + setClosing(false); return QtNativeInputConnection.deleteSurroundingText(leftLength, rightLength); } @Override public boolean finishComposingText() { - if (m_closing) { - QtNative.activityDelegate().m_keyboardIsHiding = true; - m_view.postDelayed(new Runnable() { - @Override - public void run() { - if (QtNative.activityDelegate().m_keyboardIsHiding) - QtNative.activityDelegate().m_keyboardIsVisible=false; - } - }, 5000); // it seems finishComposingText comes musch faster than onKeyUp event, - // so we must delay hide notification - m_closing = false; - } else { - m_closing = true; - } + // on some/all android devices hide event is not coming, but instead finishComposingText() is called twice + setClosing(true); return QtNativeInputConnection.finishComposingText(); } @@ -234,18 +264,21 @@ public class QtInputConnection extends BaseInputConnection @Override public boolean setComposingText(CharSequence text, int newCursorPosition) { + setClosing(false); return QtNativeInputConnection.setComposingText(text.toString(), newCursorPosition); } @Override public boolean setComposingRegion(int start, int end) { + setClosing(false); return QtNativeInputConnection.setComposingRegion(start, end); } @Override public boolean setSelection(int start, int end) { + setClosing(false); return QtNativeInputConnection.setSelection(start, end); } } diff --git a/src/android/jar/src/org/qtproject/qt5/android/QtMessageDialogHelper.java b/src/android/jar/src/org/qtproject/qt5/android/QtMessageDialogHelper.java new file mode 100644 index 0000000000..6ee1304c12 --- /dev/null +++ b/src/android/jar/src/org/qtproject/qt5/android/QtMessageDialogHelper.java @@ -0,0 +1,425 @@ +/**************************************************************************** + ** + ** Copyright (C) 2013 BogDan Vatra <bogdan@kde.org> + ** Contact: http://www.qt-project.org/legal + ** + ** This file is part of the Android port of the Qt Toolkit. + ** + ** $QT_BEGIN_LICENSE:LGPL$ + ** Commercial License Usage + ** Licensees holding valid commercial Qt licenses may use this file in + ** accordance with the commercial license agreement provided with the + ** Software or, alternatively, in accordance with the terms contained in + ** a written agreement between you and Digia. For licensing terms and + ** conditions see http://qt.digia.com/licensing. For further information + ** use the contact form at http://qt.digia.com/contact-us. + ** + ** GNU Lesser General Public License Usage + ** Alternatively, this file may be used under the terms of the GNU Lesser + ** General Public License version 2.1 as published by the Free Software + ** Foundation and appearing in the file LICENSE.LGPL included in the + ** packaging of this file. Please review the following information to + ** ensure the GNU Lesser General Public License version 2.1 requirements + ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. + ** + ** In addition, as a special exception, Digia gives you certain additional + ** rights. These rights are described in the Digia Qt LGPL Exception + ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. + ** + ** GNU General Public License Usage + ** Alternatively, this file may be used under the terms of the GNU + ** General Public License version 3.0 as published by the Free Software + ** Foundation and appearing in the file LICENSE.GPL included in the + ** packaging of this file. Please review the following information to + ** ensure the GNU General Public License version 3.0 requirements will be + ** met: http://www.gnu.org/copyleft/gpl.html. + ** + ** + ** $QT_END_LICENSE$ + ** + ****************************************************************************/ + + +package org.qtproject.qt5.android; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.text.ClipboardManager; +import android.util.TypedValue; +import android.view.View; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.ScrollView; +import android.widget.TextView; +import android.widget.Toast; + +import java.util.ArrayList; + +class QtNativeDialogHelper +{ + static native void dialogResult(long handler, int buttonID); +} + +class ButtonStruct implements View.OnClickListener +{ + ButtonStruct(QtMessageDialogHelper dialog, int id, String text) + { + m_dialog = dialog; + m_id = id; + m_text = text; + } + QtMessageDialogHelper m_dialog; + private int m_id; + String m_text; + + @Override + public void onClick(View view) { + QtNativeDialogHelper.dialogResult(m_dialog.handler(), m_id); + } +} + +public class QtMessageDialogHelper +{ + + public QtMessageDialogHelper(Activity activity) + { + m_activity = activity; + } + + + public void setIcon(int icon) + { + m_icon = icon; + + } + + private Drawable getIconDrawable() + { + if (m_icon == 0) + return null; + + if (Build.VERSION.SDK_INT > 10) { + try { + TypedValue typedValue = new TypedValue(); + m_theme.resolveAttribute(Class.forName("android.R$attr").getDeclaredField("alertDialogIcon").getInt(null), typedValue, true); + return m_activity.getResources().getDrawable(typedValue.resourceId); + } catch (Exception e) { + e.printStackTrace(); + } + } + + // Information, Warning, Critical, Question + switch (m_icon) + { + case 1: // Information + try { + return m_activity.getResources().getDrawable(Class.forName("android.R$drawable").getDeclaredField("ic_dialog_info").getInt(null)); + } catch (Exception e) { + e.printStackTrace(); + } + break; + case 2: // Warning +// try { +// return Class.forName("android.R$drawable").getDeclaredField("stat_sys_warning").getInt(null); +// } catch (Exception e) { +// e.printStackTrace(); +// } +// break; + case 3: // Critical + try { + return m_activity.getResources().getDrawable(Class.forName("android.R$drawable").getDeclaredField("ic_dialog_alert").getInt(null)); + } catch (Exception e) { + e.printStackTrace(); + } + break; + case 4: // Question + try { + return m_activity.getResources().getDrawable(Class.forName("android.R$drawable").getDeclaredField("ic_menu_help").getInt(null)); + } catch (Exception e) { + e.printStackTrace(); + } + break; + } + return null; + } + + public void setTile(String title) + { + m_title = title; + } + + public void setText(String text) + { + m_text = text; + } + + public void setInformativeText(String informativeText) + { + m_informativeText = informativeText; + } + + public void setDetailedText(String text) + { + m_detailedText = text; + } + + public void addButton(int id, String text) + { + if (m_buttonsList == null) + m_buttonsList = new ArrayList<ButtonStruct>(); + m_buttonsList.add(new ButtonStruct(this, id, text)); + } + + private void setTextAppearance(TextView view, String attr, String style) + { + try { + int[] attrs = (int[]) Class.forName("android.R$styleable").getDeclaredField("TextAppearance").get(null); + final TypedArray a = m_theme.obtainStyledAttributes(null, + attrs, + Class.forName("android.R$attr").getDeclaredField(attr).getInt(null), + Class.forName("android.R$style").getDeclaredField(style).getInt(null)); + final int textSize = a.getDimensionPixelSize( + Class.forName("android.R$styleable").getDeclaredField("TextAppearance_textSize").getInt(null), 0); + if (textSize != 0) + view.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); + + final int textColor = a.getColor( + Class.forName("android.R$styleable").getDeclaredField("TextAppearance_textColor").getInt(null), 0x3138); + if (textColor != 0x3138) + view.setTextColor(textColor); + + a.recycle(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private Drawable getStyledDrawable(String drawable) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException + { + int[] attrs = {Class.forName("android.R$attr").getDeclaredField(drawable).getInt(null)}; + final TypedArray a = m_theme.obtainStyledAttributes(attrs); + Drawable d = a.getDrawable(0); + a.recycle(); + return d; + } + + + public void show(long handler) + { + m_handler = handler; + m_activity.runOnUiThread( new Runnable() { + @Override + public void run() { + if (m_dialog != null && m_dialog.isShowing()) + m_dialog.dismiss(); + + m_dialog = new AlertDialog.Builder(m_activity).create(); + m_theme = m_dialog.getWindow().getContext().getTheme(); + + if (m_title != null) + m_dialog.setTitle(m_title); + m_dialog.setOnCancelListener( new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialogInterface) { + QtNativeDialogHelper.dialogResult(handler(), -1); + } + }); + m_dialog.setCancelable(m_buttonsList == null); + m_dialog.setCanceledOnTouchOutside(m_buttonsList == null); + m_dialog.setIcon(getIconDrawable()); + ScrollView scrollView = new ScrollView(m_activity); + RelativeLayout dialogLayout = new RelativeLayout(m_activity); + int id = 1; + View lastView = null; + View.OnLongClickListener copyText = new View.OnLongClickListener() { + @Override + public boolean onLongClick(View view) { + TextView tv = (TextView)view; + if (tv != null) { + ClipboardManager cm = (android.text.ClipboardManager) m_activity.getSystemService(Context.CLIPBOARD_SERVICE); + cm.setText(tv.getText()); + } + return true; + } + }; + if (m_text != null) + { + TextView view = new TextView(m_activity); + view.setId(id++); + view.setOnLongClickListener(copyText); + view.setLongClickable(true); + + view.setText(m_text); + setTextAppearance(view, "textAppearanceMedium", "TextAppearance_Medium"); + + RelativeLayout.LayoutParams layout = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT); + layout.setMargins(16, 8, 16, 8); + layout.addRule(RelativeLayout.ALIGN_PARENT_TOP); + dialogLayout.addView(view, layout); + lastView = view; + } + + if (m_informativeText != null) + { + TextView view= new TextView(m_activity); + view.setId(id++); + view.setOnLongClickListener(copyText); + view.setLongClickable(true); + + view.setText(m_informativeText); + setTextAppearance(view, "textAppearanceMedium", "TextAppearance_Medium"); + + RelativeLayout.LayoutParams layout = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT); + layout.setMargins(16, 8, 16, 8); + if (lastView != null) + layout.addRule(RelativeLayout.BELOW, lastView.getId()); + else + layout.addRule(RelativeLayout.ALIGN_PARENT_TOP); + dialogLayout.addView(view, layout); + lastView = view; + } + + if (m_detailedText != null) + { + TextView view= new TextView(m_activity); + view.setId(id++); + view.setOnLongClickListener(copyText); + view.setLongClickable(true); + + view.setText(m_detailedText); + setTextAppearance(view, "textAppearanceSmall", "TextAppearance_Small"); + + RelativeLayout.LayoutParams layout = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT); + layout.setMargins(16, 8, 16, 8); + if (lastView != null) + layout.addRule(RelativeLayout.BELOW, lastView.getId()); + else + layout.addRule(RelativeLayout.ALIGN_PARENT_TOP); + dialogLayout.addView(view, layout); + lastView = view; + } + + if (m_buttonsList != null) + { + LinearLayout buttonsLayout = new LinearLayout(m_activity); + buttonsLayout.setOrientation(LinearLayout.HORIZONTAL); + buttonsLayout.setId(id++); + boolean firstButton = true; + for (ButtonStruct button: m_buttonsList) + { + Button bv; + if (Build.VERSION.SDK_INT > 10) { + try { + bv = new Button(m_activity, null, Class.forName("android.R$attr").getDeclaredField("borderlessButtonStyle").getInt(null)); + } catch (Exception e) { + bv = new Button(m_activity); + e.printStackTrace(); + } + } else { + bv = new Button(m_activity); + } + + bv.setText(button.m_text); + bv.setOnClickListener(button); + if (!firstButton) // first button + { + LinearLayout.LayoutParams layout = null; + View spacer = new View(m_activity); + if (Build.VERSION.SDK_INT > 10) { + try { + layout = new LinearLayout.LayoutParams(1, RelativeLayout.LayoutParams.MATCH_PARENT); + spacer.setBackgroundDrawable(getStyledDrawable("dividerVertical")); + buttonsLayout.addView(spacer, layout); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + LinearLayout.LayoutParams layout = null; + layout = new LinearLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT, 1.0f); + buttonsLayout.addView(bv, layout); + firstButton = false; + } + + if (Build.VERSION.SDK_INT > 10) { + try { + View horizontalDevider = new View(m_activity); + horizontalDevider.setId(id++); + horizontalDevider.setBackgroundDrawable(getStyledDrawable("dividerHorizontal")); + RelativeLayout.LayoutParams relativeParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, 1); + relativeParams.setMargins(0, 10, 0, 0); + if (lastView != null) { + relativeParams.addRule(RelativeLayout.BELOW, lastView.getId()); + } + else + relativeParams.addRule(RelativeLayout.ALIGN_PARENT_TOP); + dialogLayout.addView(horizontalDevider, relativeParams); + lastView = horizontalDevider; + } catch (Exception e) { + e.printStackTrace(); + } + } + RelativeLayout.LayoutParams relativeParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT); + if (lastView != null) { + relativeParams.addRule(RelativeLayout.BELOW, lastView.getId()); + } + else + relativeParams.addRule(RelativeLayout.ALIGN_PARENT_TOP); + if (Build.VERSION.SDK_INT < 11) + relativeParams.setMargins(2, 12, 2, 4); + else + relativeParams.setMargins(2, 0, 2, 0); + dialogLayout.addView(buttonsLayout, relativeParams); + } + scrollView.addView(dialogLayout); + m_dialog.setView(scrollView); + m_dialog.show(); + } + }); + } + + public void hide() + { + m_activity.runOnUiThread( new Runnable() { + @Override + public void run() { + if (m_dialog != null && m_dialog.isShowing()) + m_dialog.dismiss(); + reset(); + } + }); + } + + public long handler() + { + return m_handler; + } + + public void reset() + { + m_icon = 0; + m_title = null; + m_text = null; + m_informativeText = null; + m_detailedText = null; + m_buttonsList = null; + m_dialog = null; + m_handler = 0; + } + + private Activity m_activity; + private int m_icon = 0; + private String m_title, m_text, m_informativeText, m_detailedText; + private ArrayList<ButtonStruct> m_buttonsList; + private AlertDialog m_dialog; + private long m_handler = 0; + private Resources.Theme m_theme; +} diff --git a/src/android/jar/src/org/qtproject/qt5/android/QtNative.java b/src/android/jar/src/org/qtproject/qt5/android/QtNative.java index 1385c90e3e..57f3642b56 100644 --- a/src/android/jar/src/org/qtproject/qt5/android/QtNative.java +++ b/src/android/jar/src/org/qtproject/qt5/android/QtNative.java @@ -50,7 +50,6 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; import android.net.Uri; -import android.os.Build; import android.text.ClipboardManager; import android.util.Log; import android.view.ContextMenu; @@ -110,9 +109,13 @@ public class QtNative public static void openURL(String url) { - Uri uri = Uri.parse(url); - Intent intent = new Intent(Intent.ACTION_VIEW, uri); - activity().startActivity(intent); + try { + Uri uri = Uri.parse(url); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + activity().startActivity(intent); + } catch (Exception e) { + e.printStackTrace(); + } } // this method loads full path libs @@ -419,30 +422,21 @@ public class QtNative private static boolean isSoftwareKeyboardVisible() { - Semaphore semaphore = new Semaphore(1); - Boolean ret = false; - class RunnableRes implements Runnable { - @SuppressWarnings("unused") - Boolean returnValue = null; - Semaphore semaphore = null; - RunnableRes(Boolean ret, Semaphore sem) { - semaphore = sem; - returnValue = ret; - } + final Semaphore semaphore = new Semaphore(0); + final Boolean[] ret = {false}; + runAction(new Runnable() { @Override public void run() { - returnValue = m_activityDelegate.isSoftwareKeyboardVisible(); + ret[0] = m_activityDelegate.isSoftwareKeyboardVisible(); semaphore.release(); } - } - - runAction(new RunnableRes(ret, semaphore)); + }); try { semaphore.acquire(); } catch (Exception e) { e.printStackTrace(); } - return ret; + return ret[0]; } private static void setFullScreen(final boolean fullScreen) @@ -458,7 +452,7 @@ public class QtNative private static void registerClipboardManager() { - final Semaphore semaphore = new Semaphore(1); + final Semaphore semaphore = new Semaphore(0); runAction(new Runnable() { @Override public void run() { @@ -569,6 +563,7 @@ public class QtNative // keyboard methods public static native void keyDown(int key, int unicode, int modifier); public static native void keyUp(int key, int unicode, int modifier); + public static native void keyboardVisibilityChanged(boolean visibility); // keyboard methods // surface methods diff --git a/src/android/jar/src/org/qtproject/qt5/android/QtSurface.java b/src/android/jar/src/org/qtproject/qt5/android/QtSurface.java index cd0bddf2c8..c499dc3898 100644 --- a/src/android/jar/src/org/qtproject/qt5/android/QtSurface.java +++ b/src/android/jar/src/org/qtproject/qt5/android/QtSurface.java @@ -117,21 +117,23 @@ public class QtSurface extends SurfaceView implements SurfaceHolder.Callback // Initialize Accessibility // The accessibility code depends on android API level 16, so dynamically resolve it - try { - final String a11yDelegateClassName = "org.qtproject.qt5.android.accessibility.QtAccessibilityDelegate"; - Class<?> qtDelegateClass = Class.forName(a11yDelegateClassName); - Constructor constructor = qtDelegateClass.getConstructor(Class.forName("android.view.View")); - m_accessibilityDelegate = constructor.newInstance(this); - - Class a11yDelegateClass = Class.forName("android.view.View$AccessibilityDelegate"); - Method setDelegateMethod = this.getClass().getMethod("setAccessibilityDelegate", a11yDelegateClass); - setDelegateMethod.invoke(this, m_accessibilityDelegate); - } catch (ClassNotFoundException e) { - // Class not found is fine since we are compatible with Android API < 16, but the function will - // only be available with that API level. - } catch (Exception e) { - // Unknown exception means something went wrong. - Log.w("Qt A11y", "Unknown exception: " + e.toString()); + if (android.os.Build.VERSION.SDK_INT >= 16) { + try { + final String a11yDelegateClassName = "org.qtproject.qt5.android.accessibility.QtAccessibilityDelegate"; + Class<?> qtDelegateClass = Class.forName(a11yDelegateClassName); + Constructor constructor = qtDelegateClass.getConstructor(Class.forName("android.view.View")); + m_accessibilityDelegate = constructor.newInstance(this); + + Class a11yDelegateClass = Class.forName("android.view.View$AccessibilityDelegate"); + Method setDelegateMethod = this.getClass().getMethod("setAccessibilityDelegate", a11yDelegateClass); + setDelegateMethod.invoke(this, m_accessibilityDelegate); + } catch (ClassNotFoundException e) { + // Class not found is fine since we are compatible with Android API < 16, but the function will + // only be available with that API level. + } catch (Exception e) { + // Unknown exception means something went wrong. + Log.w("Qt A11y", "Unknown exception: " + e.toString()); + } } } diff --git a/src/android/java/AndroidManifest.xml b/src/android/java/AndroidManifest.xml index 6463793e0b..cfad4553ee 100644 --- a/src/android/java/AndroidManifest.xml +++ b/src/android/java/AndroidManifest.xml @@ -1,7 +1,7 @@ <?xml version='1.0' encoding='utf-8'?> -<manifest package="org.qtproject.example" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.0" android:versionCode="1"> +<manifest package="org.qtproject.example" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.0" android:versionCode="1" android:installLocation="auto"> <application android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="@string/app_name"> - <activity android:configChanges="orientation|locale|fontScale|keyboard|keyboardHidden|navigation|screenSize" + <activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|locale|fontScale|keyboard|keyboardHidden|navigation" android:name="org.qtproject.qt5.android.bindings.QtActivity" android:label="@string/app_name" android:screenOrientation="unspecified"> @@ -34,8 +34,10 @@ <!-- Splash screen --> </activity> </application> - <!-- %%INSERT_USES_SDK%% --> + <uses-sdk android:minSdkVersion="9" android:targetSdkVersion="14"/> <supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/> + <!-- %%INSERT_PERMISSIONS --> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> + <!-- %%INSERT_FEATURES --> </manifest> diff --git a/src/android/java/src/org/qtproject/qt5/android/bindings/QtActivity.java b/src/android/java/src/org/qtproject/qt5/android/bindings/QtActivity.java index 9c7b57a4f5..7c741edfda 100644 --- a/src/android/java/src/org/qtproject/qt5/android/bindings/QtActivity.java +++ b/src/android/java/src/org/qtproject/qt5/android/bindings/QtActivity.java @@ -56,6 +56,7 @@ import android.content.res.AssetManager; import android.graphics.Bitmap; import android.graphics.Canvas; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; @@ -79,6 +80,7 @@ import android.view.ActionMode; import android.view.ActionMode.Callback; //@ANDROID-11 + public class QtActivity extends Activity { private final static int MINISTRO_INSTALL_REQUEST_CODE = 0xf3ee; // request code used to know when Ministro instalation is finished @@ -113,28 +115,37 @@ public class QtActivity extends Activity // for more details. private static final String REPOSITORY_KEY = "repository"; // use this key to overwrite the default ministro repsitory + private static final String ANDROID_THEMES_KEY = "android.themes"; // themes that your application uses + - private static final String APPLICATION_PARAMETERS = null; // use this variable to pass any parameters to your application, + public String APPLICATION_PARAMETERS = null; // use this variable to pass any parameters to your application, // the parameters must not contain any white spaces // and must be separated with "\t" // e.g "-param1\t-param2=value2\t-param3\tvalue3" - private String ENVIRONMENT_VARIABLES = "QT_USE_ANDROID_NATIVE_STYLE=1\t"; + public String ENVIRONMENT_VARIABLES = "QT_USE_ANDROID_NATIVE_STYLE=1\tQT_USE_ANDROID_NATIVE_DIALOGS=1\t"; // use this variable to add any environment variables to your application. // the env vars must be separated with "\t" // e.g. "ENV_VAR1=1\tENV_VAR2=2\t" // Currently the following vars are used by the android plugin: - // * QT_USE_ANDROID_NATIVE_STYLE - 1 to use the android widget style if available, - // note that the android style plugin in Qt 5.1 is not fully functional. - - private static final String QT_ANDROID_THEME = "light"; // sets the default theme to light. Possible values are: - // * "" - for the device default dark theme - // * "light" - for the device default light theme - // * "holo" - for the holo dark theme - // * "holo_light" - for the holo light theme + // * QT_USE_ANDROID_NATIVE_STYLE - 1 to use the android widget style if available. + // * QT_USE_ANDROID_NATIVE_DIALOGS -1 to use the android native dialogs. + + public String[] QT_ANDROID_THEMES = null; // A list with all themes that your application want to use. + // The name of the theme must be the same with any theme from + // http://developer.android.com/reference/android/R.style.html + // The most used themes are: + // * "Theme" - (fallback) check http://developer.android.com/reference/android/R.style.html#Theme + // * "Theme_Black" - check http://developer.android.com/reference/android/R.style.html#Theme_Black + // * "Theme_Light" - (default for API <=10) check http://developer.android.com/reference/android/R.style.html#Theme_Light + // * "Theme_Holo" - check http://developer.android.com/reference/android/R.style.html#Theme_Holo + // * "Theme_Holo_Light" - (default for API 11-13) check http://developer.android.com/reference/android/R.style.html#Theme_Holo_Light + // * "Theme_DeviceDefault" - check http://developer.android.com/reference/android/R.style.html#Theme_DeviceDefault + // * "Theme_DeviceDefault_Light" - (default for API 14+) check http://developer.android.com/reference/android/R.style.html#Theme_DeviceDefault_Light + + public String QT_ANDROID_DEFAULT_THEME = null; // sets the default theme. private static final int INCOMPATIBLE_MINISTRO_VERSION = 1; // Incompatible Ministro version. Ministro needs to be upgraded. - private static final String DISPLAY_DPI_KEY = "display.dpi"; private static final int BUFFER_SIZE = 1024; private ActivityInfo m_activityInfo = null; // activity info object, used to access the libs and the strings @@ -153,6 +164,21 @@ public class QtActivity extends Activity // this repository is used to push Qt snapshots. private String[] m_qtLibs = null; // required qt libs + public QtActivity() + { + if (Build.VERSION.SDK_INT <= 10) { + QT_ANDROID_THEMES = new String[] {"Theme_Light"}; + QT_ANDROID_DEFAULT_THEME = "Theme_Light"; + } + else if (Build.VERSION.SDK_INT >= 11 && Build.VERSION.SDK_INT <= 13) { + QT_ANDROID_THEMES = new String[] {"Theme_Holo_Light"}; + QT_ANDROID_DEFAULT_THEME = "Theme_Holo_Light"; + } else { + QT_ANDROID_THEMES = new String[] {"Theme_DeviceDefault_Light"}; + QT_ANDROID_DEFAULT_THEME = "Theme_DeviceDefault_Light"; + } + } + // this function is used to load and start the loader private void loadApplication(Bundle loaderParams) { @@ -237,23 +263,24 @@ public class QtActivity extends Activity private ServiceConnection m_ministroConnection=new ServiceConnection() { private IMinistro m_service = null; - @Override + @Override public void onServiceConnected(ComponentName name, IBinder service) { m_service = IMinistro.Stub.asInterface(service); try { - if (m_service!=null) { - Bundle parameters= new Bundle(); + if (m_service != null) { + Bundle parameters = new Bundle(); parameters.putStringArray(REQUIRED_MODULES_KEY, m_qtLibs); parameters.putString(APPLICATION_TITLE_KEY, (String)QtActivity.this.getTitle()); parameters.putInt(MINIMUM_MINISTRO_API_KEY, MINISTRO_API_LEVEL); parameters.putInt(MINIMUM_QT_VERSION_KEY, QT_VERSION); parameters.putString(ENVIRONMENT_VARIABLES_KEY, ENVIRONMENT_VARIABLES); - if (null!=APPLICATION_PARAMETERS) + if (APPLICATION_PARAMETERS != null) parameters.putString(APPLICATION_PARAMETERS_KEY, APPLICATION_PARAMETERS); parameters.putStringArray(SOURCES_KEY, m_sources); parameters.putString(REPOSITORY_KEY, m_repository); - parameters.putInt(DISPLAY_DPI_KEY, QtActivity.this.getResources().getDisplayMetrics().densityDpi); + if (QT_ANDROID_THEMES != null) + parameters.putStringArray(ANDROID_THEMES_KEY, QT_ANDROID_THEMES); m_service.requestLoader(m_ministroCallback, parameters); } } catch (RemoteException e) { @@ -261,19 +288,19 @@ public class QtActivity extends Activity } } - private IMinistroCallback m_ministroCallback = new IMinistroCallback.Stub() { - // this function is called back by Ministro. - @Override - public void loaderReady(final Bundle loaderParams) throws RemoteException { - runOnUiThread(new Runnable() { - @Override - public void run() { - unbindService(m_ministroConnection); - loadApplication(loaderParams); - } - }); - } - }; + private IMinistroCallback m_ministroCallback = new IMinistroCallback.Stub() { + // this function is called back by Ministro. + @Override + public void loaderReady(final Bundle loaderParams) throws RemoteException { + runOnUiThread(new Runnable() { + @Override + public void run() { + unbindService(m_ministroConnection); + loadApplication(loaderParams); + } + }); + } + }; @Override public void onServiceDisconnected(ComponentName name) { @@ -683,13 +710,30 @@ public class QtActivity extends Activity public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + try { + setTheme(Class.forName("android.R$style").getDeclaredField(QT_ANDROID_DEFAULT_THEME).getInt(null)); + } catch (Exception e) { + e.printStackTrace(); + } + + if (Build.VERSION.SDK_INT > 10) { + try { + requestWindowFeature(Window.class.getField("FEATURE_ACTION_BAR").getInt(null)); + } catch (Exception e) { + e.printStackTrace(); + } + } else { + requestWindowFeature(Window.FEATURE_NO_TITLE); + } + if (QtApplication.m_delegateObject != null && QtApplication.onCreate != null) { QtApplication.invokeDelegateMethod(QtApplication.onCreate, savedInstanceState); return; } - ENVIRONMENT_VARIABLES += "\tQT_ANDROID_THEME=" + QT_ANDROID_THEME + + ENVIRONMENT_VARIABLES += "\tQT_ANDROID_THEME=" + QT_ANDROID_DEFAULT_THEME + "/\tQT_ANDROID_THEME_DISPLAY_DPI=" + getResources().getDisplayMetrics().densityDpi + "\t"; - requestWindowFeature(Window.FEATURE_NO_TITLE); + try { m_activityInfo = getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); } catch (NameNotFoundException e) { |