/* * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Codename One designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Codename One through http://www.codenameone.com/ if you * need additional information or have any questions. */ package com.codename1.impl.android; import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Rect; import android.util.Log; import android.view.*; import android.view.inputmethod.EditorInfo; import com.codename1.ui.Component; import com.codename1.ui.Display; import com.codename1.ui.Form; import com.codename1.ui.PeerComponent; import com.codename1.ui.TextArea; import com.codename1.ui.events.ActionEvent; import com.codename1.ui.events.ActionListener; /** * * @author Chen */ public class CodenameOneView { int width = 1; int height = 1; Bitmap bitmap; AndroidGraphics buffy = null; private Canvas canvas; private AndroidImplementation implementation = null; private final KeyCharacterMap keyCharacterMap; private final Rect bounds = new Rect(); private boolean fireKeyDown = false; //private volatile boolean created = false; private boolean drawing; public CodenameOneView(Activity activity, View androidView, AndroidImplementation implementation, boolean drawing) { this.implementation = implementation; this.drawing = drawing; androidView.setLayoutParams(new ViewGroup.LayoutParams( ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); androidView.setFocusable(true); androidView.setFocusableInTouchMode(true); androidView.setEnabled(true); androidView.setClickable(true); androidView.setLongClickable(false); /** * tell the system that we do our own caching and it does not need to * use an extra offscreen bitmap. */ if(!drawing) { androidView.setWillNotCacheDrawing(false); androidView.setWillNotDraw(true); this.buffy = new AndroidGraphics(implementation, null, false); } this.keyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD); /** * From the docs: "Change whether this view is one of the set of * scrollable containers in its window. This will be used to determine * whether the window can resize or must pan when a soft input area is * open -- scrollable containers allow the window to use resize mode * since the container will appropriately shrink. " */ androidView.setScrollContainer(true); android.view.Display androidDisplay = ((WindowManager) activity.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); width = androidDisplay.getWidth(); height = androidDisplay.getHeight(); initBitmaps(width, height); } public boolean isOpaque() { return true; } public void onSurfaceChanged(final int w, final int h) { if(!Display.isInitialized()) { return; } Display.getInstance().callSerially(new Runnable() { public void run() { handleSizeChange(w, h); } }); } public void onSurfaceCreated() { this.visibilityChangedTo(true); } public void onSurfaceDestroyed() { this.visibilityChangedTo(false); } private void initBitmaps(int w, int h) { if(!drawing) { this.bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); this.canvas = new Canvas(this.bitmap); this.buffy.setCanvas(this.canvas); } } public void visibilityChangedTo(boolean visible) { boolean changed = visible; if (this.implementation.getCurrentForm() != null && changed) { if (visible) { this.implementation.showNotifyPublic(); /** * request a full repaint as our surfaceview is most likely * black if this app comes back from the background. */ this.implementation.getCurrentForm().repaint(); //android.os.Debug.startMethodTracing("calc"); } else { this.implementation.hideNotifyPublic(); //android.os.Debug.stopMethodTracing(); } } //flushGraphics(); } public void handleSizeChange(int w, int h) { if(!drawing) { if ((this.width != w && (this.width < w || this.height < h)) || (bitmap.getHeight() < h)) { this.initBitmaps(w, h); } } if (this.width == w && this.height == h) { return; } this.width = w; this.height = h; Log.d("Codename One", "sizechanged: " + width + " " + height + " " + this); if (this.implementation.getCurrentForm() == null) { /** * make sure a form has been set before we can send events to the * EDT. if we send events before the form has been set we might * deadlock! */ return; } if (InPlaceEditView.isEditing()) { final Form f = this.implementation.getCurrentForm(); ActionListener sizeChanged = new ActionListener() { @Override public void actionPerformed(ActionEvent evt) { CodenameOneView.this.implementation.getActivity().runOnUiThread(new Runnable() { @Override public void run() { InPlaceEditView.reLayoutEdit(); InPlaceEditView.scrollActiveTextfieldToVisible(); } }); f.removeSizeChangedListener(this); } }; f.addSizeChangedListener(sizeChanged); } Display.getInstance().sizeChanged(w, h); } //@Override protected void d(Canvas canvas) { if(!drawing) { boolean empty = canvas.getClipBounds(bounds); if (empty) { // ?? canvas.drawBitmap(bitmap, 0, 0, null); } else { bounds.intersect(0, 0, width, height); canvas.drawBitmap(bitmap, bounds, bounds, null); } } } /** * some info from the MIDP docs about keycodes: * * "Applications receive keystroke events in which the individual keys are * named within a space of key codes. Every key for which events are * reported to MIDP applications is assigned a key code. The key code values * are unique for each hardware key unless two keys are obvious synonyms for * each other. MIDP defines the following key codes: KEY_NUM0, KEY_NUM1, * KEY_NUM2, KEY_NUM3, KEY_NUM4, KEY_NUM5, KEY_NUM6, KEY_NUM7, KEY_NUM8, * KEY_NUM9, KEY_STAR, and KEY_POUND. (These key codes correspond to keys on * a ITU-T standard telephone keypad.) Other keys may be present on the * keyboard, and they will generally have key codes distinct from those list * above. In order to guarantee portability, applications should use only * the standard key codes. * * The standard key codes' values are equal to the Unicode encoding for the * character that represents the key. If the device includes any other keys * that have an obvious correspondence to a Unicode character, their key * code values should equal the Unicode encoding for that character. For * keys that have no corresponding Unicode character, the implementation * must use negative values. Zero is defined to be an invalid key code." * * Because the MIDP implementation is our reference and that implementation * does not interpret the given keycodes we behave alike and pass on the * unicode values. */ final static int internalKeyCodeTranslate(int keyCode) { /** * make sure these important keys have a negative value when passed to * Codename One or they might be interpreted as characters. */ switch (keyCode) { case KeyEvent.KEYCODE_DPAD_DOWN: return AndroidImplementation.DROID_IMPL_KEY_DOWN; case KeyEvent.KEYCODE_DPAD_UP: return AndroidImplementation.DROID_IMPL_KEY_UP; case KeyEvent.KEYCODE_DPAD_LEFT: return AndroidImplementation.DROID_IMPL_KEY_LEFT; case KeyEvent.KEYCODE_DPAD_RIGHT: return AndroidImplementation.DROID_IMPL_KEY_RIGHT; case KeyEvent.KEYCODE_DPAD_CENTER: return AndroidImplementation.DROID_IMPL_KEY_FIRE; case KeyEvent.KEYCODE_MENU: return AndroidImplementation.DROID_IMPL_KEY_MENU; case KeyEvent.KEYCODE_CLEAR: return AndroidImplementation.DROID_IMPL_KEY_CLEAR; case KeyEvent.KEYCODE_DEL: return AndroidImplementation.DROID_IMPL_KEY_BACKSPACE; case KeyEvent.KEYCODE_BACK: return AndroidImplementation.DROID_IMPL_KEY_BACK; default: return keyCode; } } public boolean onKeyUpDown(boolean down, int keyCode, KeyEvent event) { keyCode = this.internalKeyCodeTranslate(keyCode); switch (keyCode) { case KeyEvent.KEYCODE_VOLUME_DOWN: case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_SEARCH: case KeyEvent.KEYCODE_SHIFT_LEFT: case KeyEvent.KEYCODE_SHIFT_RIGHT: case KeyEvent.KEYCODE_ALT_LEFT: case KeyEvent.KEYCODE_ALT_RIGHT: case KeyEvent.KEYCODE_SYM: case KeyEvent.KEYCODE_ENTER: return false; default: } if (event.getRepeatCount() > 0) { // skip repeats return true; } if (this.implementation.getCurrentForm() == null) { /** * make sure a form has been set before we can send events to the * EDT. if we send events before the form has been set we might * deadlock! */ return true; } if (keyCode == AndroidImplementation.DROID_IMPL_KEY_FIRE) { this.fireKeyDown = down; } else if (keyCode == AndroidImplementation.DROID_IMPL_KEY_DOWN || keyCode == AndroidImplementation.DROID_IMPL_KEY_UP || keyCode == AndroidImplementation.DROID_IMPL_KEY_LEFT || keyCode == AndroidImplementation.DROID_IMPL_KEY_RIGHT) { if (this.fireKeyDown) { /** * we keep track of trackball press/release. while it is pressed * we drop directional movements. these movements are most * likely not intended. if the device has no trackball i see no * situation where this additional behavior could hurt. */ return true; } } switch (keyCode) { case AndroidImplementation.DROID_IMPL_KEY_MENU: //if the native commands are used don't handle the keycode if (Display.getInstance().getCommandBehavior() == Display.COMMAND_BEHAVIOR_NATIVE) { return false; } case AndroidImplementation.DROID_IMPL_KEY_BACK: case AndroidImplementation.DROID_IMPL_KEY_DOWN: case AndroidImplementation.DROID_IMPL_KEY_UP: case AndroidImplementation.DROID_IMPL_KEY_LEFT: case AndroidImplementation.DROID_IMPL_KEY_RIGHT: case AndroidImplementation.DROID_IMPL_KEY_FIRE: case AndroidImplementation.DROID_IMPL_KEY_CLEAR: case AndroidImplementation.DROID_IMPL_KEY_BACKSPACE: // directly pass to display. if (down) { Display.getInstance().keyPressed(keyCode); } else { Display.getInstance().keyReleased(keyCode); } return true; default: /** * Codename One's TextField does not seem to work well if two * keyup-keydown sequences of different keys are not strictly * sequential. so we pass the up event of a character right * after the down event. this is exactly the behavior of the * BlackBerry implementation from this repository and has worked * well for me. i guess this should be changed as soon as the * TextField changes. */ int meta = 0; if (event.isShiftPressed()) { meta |= KeyEvent.META_SHIFT_ON; } if (event.isAltPressed()) { meta |= KeyEvent.META_ALT_ON; } if (event.isSymPressed()) { meta |= KeyEvent.META_SYM_ON; } final int nextchar = this.keyCharacterMap.get(keyCode, meta); if (down) { Display.getInstance().keyPressed(nextchar); } else { Display.getInstance().keyReleased(nextchar); } return true; } } private boolean cn1GrabbedPointer = false; private boolean nativePeerGrabbedPointer = false; public boolean onTouchEvent(MotionEvent event) { if (this.implementation.getCurrentForm() == null) { /** * make sure a form has been set before we can send events to the * EDT. if we send events before the form has been set we might * deadlock! */ return true; } int[] x = null; int[] y = null; int size = event.getPointerCount(); if (size > 1) { x = new int[size]; y = new int[size]; for (int i = 0; i < size; i++) { x[i] = (int) event.getX(i); y[i] = (int) event.getY(i); } } if (!cn1GrabbedPointer) { if (x == null) { Component componentAt = this.implementation.getCurrentForm().getComponentAt((int)event.getX(), (int)event.getY()); if (componentAt != null && (componentAt instanceof PeerComponent)) { if (event.getAction() == MotionEvent.ACTION_DOWN) { nativePeerGrabbedPointer = true; } else if (event.getAction() == MotionEvent.ACTION_UP) { nativePeerGrabbedPointer = false; } return false; } } else { Component componentAt = this.implementation.getCurrentForm().getComponentAt((int)x[0], (int)y[0]); if (componentAt != null && (componentAt instanceof PeerComponent)) { if (event.getAction() == MotionEvent.ACTION_DOWN) { nativePeerGrabbedPointer = true; } else if (event.getAction() == MotionEvent.ACTION_UP) { nativePeerGrabbedPointer = false; } return false; } } } if (nativePeerGrabbedPointer) { return false; } switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (x == null) { this.implementation.pointerPressed((int) event.getX(), (int) event.getY()); } else { this.implementation.pointerPressed(x, y); } cn1GrabbedPointer = true; break; case MotionEvent.ACTION_UP: if (x == null) { this.implementation.pointerReleased((int) event.getX(), (int) event.getY()); } else { this.implementation.pointerReleased(x, y); } cn1GrabbedPointer = false; break; case MotionEvent.ACTION_MOVE: if (x == null) { this.implementation.pointerDragged((int) event.getX(), (int) event.getY()); } else { this.implementation.pointerDragged(x, y); } break; } return true; } public AndroidGraphics getGraphics() { return buffy; } public int getViewHeight() { return height; } public int getViewWidth() { return width; } public void setInputType(EditorInfo editorInfo) { /** * do not use the enter key to fire some kind of action! */ // editorInfo.imeOptions |= EditorInfo.IME_ACTION_NONE; Component txtCmp = Display.getInstance().getCurrent().getFocused(); if (txtCmp != null && txtCmp instanceof TextArea) { TextArea txt = (TextArea) txtCmp; if (txt.isSingleLineTextArea()) { editorInfo.imeOptions |= EditorInfo.IME_ACTION_DONE; } else { editorInfo.imeOptions |= EditorInfo.IME_ACTION_NONE; } int inputType = 0; int constraint = txt.getConstraint(); if ((constraint & TextArea.PASSWORD) == TextArea.PASSWORD) { constraint = constraint ^ TextArea.PASSWORD; } switch (constraint) { case TextArea.NUMERIC: inputType = EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_FLAG_SIGNED; break; case TextArea.DECIMAL: inputType = EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_FLAG_DECIMAL; break; case TextArea.PHONENUMBER: inputType = EditorInfo.TYPE_CLASS_PHONE; break; case TextArea.EMAILADDR: inputType = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS; break; case TextArea.URL: inputType = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_URI; break; default: inputType = EditorInfo.TYPE_CLASS_TEXT; break; } editorInfo.inputType = inputType; } } }