org.navitproject.navit.NavitGraphics.java Source code

Java tutorial

Introduction

Here is the source code for org.navitproject.navit.NavitGraphics.java

Source

/**
 * Navit, a modular navigation system.
 * Copyright (C) 2005-2008 Navit Team
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * version 2 as published by the Free Software Foundation.
 *
 * This program 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 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA  02110-1301, USA.
 */

package org.navitproject.navit;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.support.v4.view.ViewConfigurationCompat;
import android.util.Log;
import android.view.ContextMenu;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup.LayoutParams;
import android.view.inputmethod.InputMethodManager;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import java.io.File;
import java.lang.reflect.Method;
import java.util.ArrayList;

public class NavitGraphics {
    private static final String TAG = "NavitGraphics";
    private final NavitGraphics parent_graphics;
    private final ArrayList<NavitGraphics> overlays = new ArrayList<NavitGraphics>();
    private int bitmap_w;
    private int bitmap_h;
    private int pos_x;
    private int pos_y;
    private int pos_wraparound;
    private int overlay_disabled;
    private int bgcolor;
    private float trackball_x;
    private float trackball_y;
    private View view;
    private SystemBarTintView navigationTintView;
    private SystemBarTintView statusTintView;
    private FrameLayout frameLayout;
    private RelativeLayout relativelayout;
    private NavitCamera camera;
    private Navit activity;
    private static Boolean in_map = false;
    // for menu key
    private static final long time_for_long_press = 300L;

    private Handler timer_handler = new Handler();

    public void setBackgroundColor(int bgcolor) {
        this.bgcolor = bgcolor;
        if (navigationTintView != null) {
            navigationTintView.setBackgroundColor(bgcolor);
        }
        if (statusTintView != null) {
            statusTintView.setBackgroundColor(bgcolor);
        }
    }

    private void SetCamera(int use_camera) {
        if (use_camera != 0 && camera == null) {
            // activity.requestWindowFeature(Window.FEATURE_NO_TITLE);
            camera = new NavitCamera(activity);
            relativelayout.addView(camera);
            relativelayout.bringChildToFront(view);
        }
    }

    private Rect get_rect() {
        Rect ret = new Rect();
        ret.left = pos_x;
        ret.top = pos_y;
        if (pos_wraparound != 0) {
            if (ret.left < 0) {
                ret.left += parent_graphics.bitmap_w;
            }
            if (ret.top < 0) {
                ret.top += parent_graphics.bitmap_h;
            }
        }
        ret.right = ret.left + bitmap_w;
        ret.bottom = ret.top + bitmap_h;
        if (pos_wraparound != 0) {
            if (bitmap_w < 0) {
                ret.right = ret.left + bitmap_w + parent_graphics.bitmap_w;
            }
            if (bitmap_h < 0) {
                ret.bottom = ret.top + bitmap_h + parent_graphics.bitmap_h;
            }
        }
        return ret;
    }

    private class NavitView extends View implements Runnable, MenuItem.OnMenuItemClickListener {
        int touch_mode = NONE;
        float oldDist = 0;
        static final int NONE = 0;
        static final int DRAG = 1;
        static final int ZOOM = 2;
        static final int PRESSED = 3;

        Method eventGetX = null;
        Method eventGetY = null;

        PointF mPressedPosition = null;

        public NavitView(Context context) {
            super(context);
            try {
                eventGetX = android.view.MotionEvent.class.getMethod("getX", int.class);
                eventGetY = android.view.MotionEvent.class.getMethod("getY", int.class);
            } catch (Exception e) {
                Log.e(TAG, "Multitouch zoom not supported");
            }
        }

        @Override
        protected void onCreateContextMenu(ContextMenu menu) {
            super.onCreateContextMenu(menu);

            menu.setHeaderTitle(activity.getTstring(R.string.position_popup_title) + "..");
            menu.add(1, 1, NONE, activity.getTstring(R.string.position_popup_drive_here))
                    .setOnMenuItemClickListener(this);
            menu.add(1, 2, NONE, activity.getTstring(R.string.cancel)).setOnMenuItemClickListener(this);
        }

        @Override
        public boolean onMenuItemClick(MenuItem item) {
            switch (item.getItemId()) {
            case 1:
                Message msg = Message.obtain(callback_handler, msg_type.CLB_SET_DISPLAY_DESTINATION.ordinal(),
                        (int) mPressedPosition.x, (int) mPressedPosition.y);
                msg.sendToTarget();
                break;
            }
            return false;
        }

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            canvas.drawBitmap(draw_bitmap, pos_x, pos_y, null);
            if (overlay_disabled == 0) {
                // assume we ARE in map view mode!
                in_map = true;
                for (NavitGraphics overlay : overlays) {
                    if (overlay.overlay_disabled == 0) {
                        Rect r = overlay.get_rect();
                        canvas.drawBitmap(overlay.draw_bitmap, r.left, r.top, null);
                    }
                }
            } else {
                if (Navit.show_soft_keyboard) {
                    if (Navit.mgr != null) {
                        Navit.mgr.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT);
                        Navit.show_soft_keyboard_now_showing = true;
                        // clear the variable now, keyboard will stay on screen until backbutton pressed
                        Navit.show_soft_keyboard = false;
                    }
                }
            }
        }

        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            Log.d(TAG, "onSizeChanged pixels x=" + w + " pixels y=" + h);
            Log.d(TAG, "onSizeChanged density=" + Navit.metrics.density);
            Log.d(TAG, "onSizeChanged scaledDensity=" + Navit.metrics.scaledDensity);
            super.onSizeChanged(w, h, oldw, oldh);

            handleResize(w, h);
        }

        void do_longpress_action() {
            Log.d(TAG, "do_longpress_action enter");

            activity.openContextMenu(this);
        }

        private int getActionField(String fieldname, Object obj) {
            int ret_value = -999;
            try {
                java.lang.reflect.Field field = android.view.MotionEvent.class.getField(fieldname);
                try {
                    ret_value = field.getInt(obj);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } catch (NoSuchFieldException ex) {
                ex.printStackTrace();
            }
            return ret_value;
        }

        @SuppressLint("ClickableViewAccessibility")
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            super.onTouchEvent(event);
            int x = (int) event.getX();
            int y = (int) event.getY();

            int _ACTION_POINTER_UP_ = getActionField("ACTION_POINTER_UP", event);
            int _ACTION_POINTER_DOWN_ = getActionField("ACTION_POINTER_DOWN", event);
            int _ACTION_MASK_ = getActionField("ACTION_MASK", event);

            int switch_value = event.getAction();
            if (_ACTION_MASK_ != -999) {
                switch_value = (event.getAction() & _ACTION_MASK_);
            }

            if (switch_value == MotionEvent.ACTION_DOWN) {
                touch_mode = PRESSED;
                if (!in_map) {
                    ButtonCallback(ButtonCallbackID, 1, 1, x, y); // down
                }
                mPressedPosition = new PointF(x, y);
                postDelayed(this, time_for_long_press);
            } else if ((switch_value == MotionEvent.ACTION_UP) || (switch_value == _ACTION_POINTER_UP_)) {
                Log.d(TAG, "ACTION_UP");

                switch (touch_mode) {
                case DRAG:
                    Log.d(TAG, "onTouch move");

                    MotionCallback(MotionCallbackID, x, y);
                    ButtonCallback(ButtonCallbackID, 0, 1, x, y); // up

                    break;
                case ZOOM:
                    float newDist = spacing(getFloatValue(event, 0), getFloatValue(event, 1));
                    float scale = 0;
                    if (newDist > 10f) {
                        scale = newDist / oldDist;
                    }

                    if (scale > 1.3) {
                        // zoom in
                        CallbackMessageChannel(1, null);
                    } else if (scale < 0.8) {
                        // zoom out
                        CallbackMessageChannel(2, null);
                    }
                    break;
                case PRESSED:
                    if (in_map) {
                        ButtonCallback(ButtonCallbackID, 1, 1, x, y); // down
                    }
                    ButtonCallback(ButtonCallbackID, 0, 1, x, y); // up

                    break;
                }
                touch_mode = NONE;
            } else if (switch_value == MotionEvent.ACTION_MOVE) {
                switch (touch_mode) {
                case DRAG:
                    MotionCallback(MotionCallbackID, x, y);
                    break;
                case ZOOM:
                    float newDist = spacing(getFloatValue(event, 0), getFloatValue(event, 1));
                    float scale = newDist / oldDist;
                    Log.d(TAG, "New scale = " + scale);
                    if (scale > 1.2) {
                        // zoom in
                        CallbackMessageChannel(1, "");
                        oldDist = newDist;
                    } else if (scale < 0.8) {
                        oldDist = newDist;
                        // zoom out
                        CallbackMessageChannel(2, "");
                    }
                    break;
                case PRESSED:
                    Log.d(TAG, "Start drag mode");
                    if (spacing(mPressedPosition, new PointF(event.getX(), event.getY())) > 20f) {
                        ButtonCallback(ButtonCallbackID, 1, 1, x, y); // down
                        touch_mode = DRAG;
                    }
                    break;
                }
            } else if (switch_value == _ACTION_POINTER_DOWN_) {
                oldDist = spacing(getFloatValue(event, 0), getFloatValue(event, 1));
                if (oldDist > 2f) {
                    touch_mode = ZOOM;
                }
            }
            return true;
        }

        private float spacing(PointF a, PointF b) {
            float x = a.x - b.x;
            float y = a.y - b.y;
            return (float) Math.sqrt(x * x + y * y);
        }

        private PointF getFloatValue(Object instance, Object argument) {
            PointF pos = new PointF(0, 0);

            if (eventGetX != null && eventGetY != null) {
                try {
                    Float x = (java.lang.Float) eventGetX.invoke(instance, argument);
                    Float y = (java.lang.Float) eventGetY.invoke(instance, argument);
                    pos.set(x, y);

                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return pos;
        }

        @Override
        public boolean onKeyDown(int keyCode, KeyEvent event) {
            int i;
            String s = null;
            long interval_for_long_press = 200L;
            i = event.getUnicodeChar();
            if (i == 0) {
                switch (keyCode) {
                case KeyEvent.KEYCODE_DEL:
                    s = String.valueOf((char) 8);
                    break;
                case KeyEvent.KEYCODE_MENU:
                    if (!in_map) {
                        // if last menukeypress is less than 0.2 seconds away then count longpress
                        if ((System.currentTimeMillis() - Navit.last_pressed_menu_key) < interval_for_long_press) {
                            Navit.time_pressed_menu_key = Navit.time_pressed_menu_key
                                    + (System.currentTimeMillis() - Navit.last_pressed_menu_key);
                            // on long press let softkeyboard popup
                            if (Navit.time_pressed_menu_key > time_for_long_press) {
                                Navit.show_soft_keyboard = true;
                                Navit.time_pressed_menu_key = 0L;
                                // need to draw to get the keyboard showing
                                this.postInvalidate();
                            }
                        } else {
                            Navit.time_pressed_menu_key = 0L;
                        }
                        Navit.last_pressed_menu_key = System.currentTimeMillis();
                        // if in menu view:
                        // use as OK (Enter) key
                        // dont use menu key here (use it in onKeyUp)
                        return true;
                    } else {
                        // if on map view:
                        // volume UP
                        //s = java.lang.String.valueOf((char) 1);
                        return true;
                    }
                case KeyEvent.KEYCODE_SEARCH:
                    /* Handle event in Main Activity if map is shown */
                    if (in_map) {
                        return false;
                    }

                    s = String.valueOf((char) 19);
                    break;
                case KeyEvent.KEYCODE_BACK:
                    s = String.valueOf((char) 27);
                    break;
                case KeyEvent.KEYCODE_CALL:
                    s = String.valueOf((char) 3);
                    break;
                case KeyEvent.KEYCODE_VOLUME_UP:
                    if (!in_map) {
                        // if in menu view:
                        // use as UP key
                        s = String.valueOf((char) 16);
                    } else {
                        // if on map view:
                        // volume UP
                        //s = java.lang.String.valueOf((char) 21);
                        return false;
                    }
                    break;
                case KeyEvent.KEYCODE_VOLUME_DOWN:
                    if (!in_map) {
                        // if in menu view:
                        // use as DOWN key
                        s = String.valueOf((char) 14);
                    } else {
                        // if on map view:
                        // volume DOWN
                        //s = java.lang.String.valueOf((char) 4);
                        return false;
                    }
                    break;
                case KeyEvent.KEYCODE_DPAD_CENTER:
                    s = String.valueOf((char) 13);
                    break;
                case KeyEvent.KEYCODE_DPAD_DOWN:
                    s = String.valueOf((char) 14);
                    break;
                case KeyEvent.KEYCODE_DPAD_LEFT:
                    s = String.valueOf((char) 2);
                    break;
                case KeyEvent.KEYCODE_DPAD_RIGHT:
                    s = String.valueOf((char) 6);
                    break;
                case KeyEvent.KEYCODE_DPAD_UP:
                    s = String.valueOf((char) 16);
                    break;
                }
            } else if (i == 10) {
                s = java.lang.String.valueOf((char) 13);
            }

            if (s != null) {
                KeypressCallback(KeypressCallbackID, s);
            }
            return true;
        }

        @Override
        public boolean onKeyUp(int keyCode, KeyEvent event) {
            int i;
            String s = null;
            i = event.getUnicodeChar();

            if (i == 0) {
                switch (keyCode) {
                case KeyEvent.KEYCODE_VOLUME_UP:
                    return (!in_map);
                case KeyEvent.KEYCODE_VOLUME_DOWN:
                    return (!in_map);
                case KeyEvent.KEYCODE_SEARCH:
                    /* Handle event in Main Activity if map is shown */
                    if (in_map) {
                        return false;
                    }
                    break;
                case KeyEvent.KEYCODE_BACK:
                    if (Navit.show_soft_keyboard_now_showing) {
                        Navit.show_soft_keyboard_now_showing = false;
                    }
                    //s = java.lang.String.valueOf((char) 27);
                    return true;
                case KeyEvent.KEYCODE_MENU:
                    if (!in_map) {
                        if (Navit.show_soft_keyboard_now_showing) {
                            // if soft keyboard showing on screen, dont use menu button as select key
                        } else {
                            // if in menu view:
                            // use as OK (Enter) key
                            s = String.valueOf((char) 13);
                        }
                    } else {
                        // if on map view:
                        // volume UP
                        //s = java.lang.String.valueOf((char) 1);
                        return false;
                    }
                    break;
                }
            } else if (i != 10) {
                s = java.lang.String.valueOf((char) i);
            }

            if (s != null) {
                KeypressCallback(KeypressCallbackID, s);
            }
            return true;
        }

        @Override
        public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) {
            String s;
            if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {
                s = event.getCharacters();
                KeypressCallback(KeypressCallbackID, s);
                return true;
            }
            return super.onKeyMultiple(keyCode, count, event);
        }

        @Override
        public boolean onTrackballEvent(MotionEvent event) {
            String s = null;
            if (event.getAction() == android.view.MotionEvent.ACTION_DOWN) {
                s = java.lang.String.valueOf((char) 13);
            }
            if (event.getAction() == android.view.MotionEvent.ACTION_MOVE) {
                trackball_x += event.getX();
                trackball_y += event.getY();
                if (trackball_x <= -1) {
                    s = java.lang.String.valueOf((char) 2);
                    trackball_x += 1;
                }
                if (trackball_x >= 1) {
                    s = java.lang.String.valueOf((char) 6);
                    trackball_x -= 1;
                }
                if (trackball_y <= -1) {
                    s = java.lang.String.valueOf((char) 16);
                    trackball_y += 1;
                }
                if (trackball_y >= 1) {
                    s = java.lang.String.valueOf((char) 14);
                    trackball_y -= 1;
                }
            }
            if (s != null) {
                KeypressCallback(KeypressCallbackID, s);
            }
            return true;
        }

        public void run() {
            if (in_map && touch_mode == PRESSED) {
                do_longpress_action();
                touch_mode = NONE;
            }
        }

    }

    private class SystemBarTintView extends View {

        public SystemBarTintView(Context context) {
            super(context);
            this.setBackgroundColor(bgcolor);
        }

    }

    public NavitGraphics(final Activity activity, NavitGraphics parent, int x, int y, int w, int h, int wraparound,
            int use_camera) {
        if (parent == null) {
            this.activity = (Navit) activity;
            view = new NavitView(activity);
            //activity.registerForContextMenu(view);
            view.setClickable(false);
            view.setFocusable(true);
            view.setFocusableInTouchMode(true);
            view.setKeepScreenOn(true);
            relativelayout = new RelativeLayout(activity);
            if (use_camera != 0) {
                SetCamera(use_camera);
            }
            relativelayout.addView(view);

            /* The navigational and status bar tinting code is meaningful only on API19+ */
            if (Build.VERSION.SDK_INT >= 19) {
                frameLayout = new FrameLayout(activity);
                frameLayout.addView(relativelayout);
                navigationTintView = new SystemBarTintView(activity);
                statusTintView = new SystemBarTintView(activity);
                frameLayout.addView(navigationTintView);
                frameLayout.addView(statusTintView);
                activity.setContentView(frameLayout);
            } else {
                activity.setContentView(relativelayout);
            }

            view.requestFocus();
        } else {
            draw_bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
            bitmap_w = w;
            bitmap_h = h;
            pos_x = x;
            pos_y = y;
            pos_wraparound = wraparound;
            draw_canvas = new Canvas(draw_bitmap);
            parent.overlays.add(this);
        }
        parent_graphics = parent;
    }

    public enum msg_type {
        CLB_ZOOM_IN, CLB_ZOOM_OUT, CLB_REDRAW, CLB_MOVE, CLB_BUTTON_UP, CLB_BUTTON_DOWN, CLB_SET_DESTINATION, CLB_SET_DISPLAY_DESTINATION, CLB_CALL_CMD, CLB_COUNTRY_CHOOSER, CLB_LOAD_MAP, CLB_UNLOAD_MAP, CLB_DELETE_MAP
    }

    static private final msg_type[] msg_values = msg_type.values();

    public final Handler callback_handler = new Handler() {
        public void handleMessage(Message msg) {
            switch (msg_values[msg.what]) {
            case CLB_ZOOM_IN:
                CallbackMessageChannel(1, "");
                break;
            case CLB_ZOOM_OUT:
                CallbackMessageChannel(2, "");
                break;
            case CLB_MOVE:
                MotionCallback(MotionCallbackID, msg.getData().getInt("x"), msg.getData().getInt("y"));
                break;
            case CLB_SET_DESTINATION:
                String lat = Float.toString(msg.getData().getFloat("lat"));
                String lon = Float.toString(msg.getData().getFloat("lon"));
                String q = msg.getData().getString(("q"));
                CallbackMessageChannel(3, lat + "#" + lon + "#" + q);
                break;
            case CLB_SET_DISPLAY_DESTINATION:
                int x = msg.arg1;
                int y = msg.arg2;
                CallbackMessageChannel(4, "" + x + "#" + y);
                break;
            case CLB_CALL_CMD:
                String cmd = msg.getData().getString(("cmd"));
                CallbackMessageChannel(5, cmd);
                break;
            case CLB_BUTTON_UP:
                ButtonCallback(ButtonCallbackID, 0, 1, msg.getData().getInt("x"), msg.getData().getInt("y")); // up
                break;
            case CLB_BUTTON_DOWN:
                // down
                ButtonCallback(ButtonCallbackID, 1, 1, msg.getData().getInt("x"), msg.getData().getInt("y"));
                break;
            case CLB_COUNTRY_CHOOSER:
                break;
            case CLB_LOAD_MAP:
                CallbackMessageChannel(6, msg.getData().getString(("title")));
                break;
            case CLB_DELETE_MAP:
                File toDelete = new File(msg.getData().getString(("title")));
                toDelete.delete();
                //fallthrough
            case CLB_UNLOAD_MAP:
                CallbackMessageChannel(7, msg.getData().getString(("title")));
                break;
            }
        }
    };

    public native void SizeChangedCallback(int id, int x, int y);

    public native void PaddingChangedCallback(int id, int left, int right, int top, int bottom);

    public native void KeypressCallback(int id, String s);

    public native int CallbackMessageChannel(int i, String s);

    public native void ButtonCallback(int id, int pressed, int button, int x, int y);

    public native void MotionCallback(int id, int x, int y);

    public native String GetDefaultCountry(int id, String s);

    public static native String[][] GetAllCountries();

    private Canvas draw_canvas;
    private Bitmap draw_bitmap;
    private int SizeChangedCallbackID;
    private int PaddingChangedCallbackID;
    private int ButtonCallbackID;
    private int MotionCallbackID;
    private int KeypressCallbackID;

    /**
     * @brief Adjust views used to tint navigation and status bars.
     *
     * This method is called from handleResize.
     *
     * It (re-)evaluates if and where the navigation bar is going to be shown, and calculates the
     * padding for objects which should not be obstructed.
     *
     */
    private void adjustSystemBarsTintingViews() {

        /* frameLayout is only created on platforms supporting navigation and status bar tinting */
        if (frameLayout == null) {
            return;
        }
        if (activity == null) {
            Log.w(TAG, "Main Activity is not a Navit instance, cannot update padding");
            return;
        }

        Navit navit = activity;

        /*
         * Determine visibility of status bar.
         * The status bar is always visible unless we are in fullscreen mode.
         */
        final Boolean isStatusShowing = !navit.isFullscreen;

        /*
         * Determine visibility of navigation bar.
         * This logic is based on the presence of a hardware menu button and is known to work on
         * devices which allow switching between hw and sw buttons (OnePlus One running CyanogenMod).
         */
        final Boolean isNavShowing = !ViewConfigurationCompat.hasPermanentMenuKey(ViewConfiguration.get(navit));
        Log.d(TAG, String.format("isStatusShowing=%b isNavShowing=%b", isStatusShowing, isNavShowing));

        /*
         * Determine where the navigation bar would be displayed.
         * Logic is taken from AOSP RenderSessionImpl.findNavigationBar()
         * platform/frameworks/base/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
         */
        final Boolean isLandscape = (navit.getResources()
                .getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE);
        final Boolean isNavAtBottom = (!isLandscape)
                || (navit.getResources().getConfiguration().smallestScreenWidthDp >= 600);
        Log.d(TAG, String.format("isNavAtBottom=%b (Configuration.smallestScreenWidthDp=%d, isLandscape=%b)",
                isNavAtBottom, navit.getResources().getConfiguration().smallestScreenWidthDp, isLandscape));

        int left = 0;
        int top = isStatusShowing ? Navit.status_bar_height : 0;
        int right = (isNavShowing && !isNavAtBottom) ? Navit.navigation_bar_width : 0;
        final int bottom = (!(isNavShowing && isNavAtBottom)) ? 0
                : (isLandscape ? Navit.navigation_bar_height_landscape : Navit.navigation_bar_height);

        /* hide tint bars during update to prevent ugly effects */
        statusTintView.setVisibility(View.GONE);
        navigationTintView.setVisibility(View.GONE);

        frameLayout.post(new Runnable() {
            @Override
            public void run() {
                statusTintView.setVisibility(isStatusShowing ? View.VISIBLE : View.GONE);
                FrameLayout.LayoutParams statusLayoutParams = new FrameLayout.LayoutParams(
                        LayoutParams.MATCH_PARENT, Navit.status_bar_height, Gravity.TOP);

                /* Prevent tint views from overlapping when navigation is on the right */
                statusLayoutParams.setMargins(0, 0,
                        (isNavShowing && !isNavAtBottom) ? Navit.navigation_bar_width : 0, 0);
                statusTintView.setLayoutParams(statusLayoutParams);
                Log.d(TAG, String.format("statusTintView: width=%d height=%d", statusTintView.getWidth(),
                        statusTintView.getHeight()));
                navigationTintView.setVisibility(isNavShowing ? View.VISIBLE : View.GONE);
                LayoutParams navigationLayoutParams = new FrameLayout.LayoutParams(
                        isNavAtBottom ? LayoutParams.MATCH_PARENT : Navit.navigation_bar_width, // X
                        isNavAtBottom ? bottom : LayoutParams.MATCH_PARENT, // Y
                        Gravity.BOTTOM | Gravity.RIGHT);
                navigationTintView.setLayoutParams(navigationLayoutParams);
                Log.d(TAG, String.format("navigationTintView: width=%d height=%d", navigationTintView.getWidth(),
                        navigationTintView.getHeight()));
            }
        });

        Log.d(TAG, String.format("Padding left=%d top=%d right=%d bottom=%d", left, top, right, bottom));

        PaddingChangedCallback(PaddingChangedCallbackID, left, top, right, bottom);
    }

    /**
     * @brief Handles resize events.
     *
     * This method is called whenever the main View is resized in any way. This is the case when its
     * {@code onSizeChanged()} event handler fires or when toggling Fullscreen mode.
     *
     */
    public void handleResize(int w, int h) {
        if (this.parent_graphics != null) {
            this.parent_graphics.handleResize(w, h);
        } else {
            Log.d(TAG, String.format("handleResize w=%d h=%d", w, h));

            adjustSystemBarsTintingViews();

            draw_bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
            draw_canvas = new Canvas(draw_bitmap);
            bitmap_w = w;
            bitmap_h = h;
            SizeChangedCallback(SizeChangedCallbackID, w, h);
        }
    }

    /**
     * @brief Returns whether the device has a hardware menu button.
     *
     * Only Android versions starting with ICS (API version 14) support the API call to detect the presence of a
     * Menu button. On earlier Android versions, the following assumptions will be made: On API levels up to 10,
     * this method will always return {@code true}, as these Android versions relied on devices having a physical
     * Menu button. On API levels 11 through 13 (Honeycomb releases), this method will always return
     * {@code false}, as Honeycomb was a tablet-only release and did not require devices to have a Menu button.
     *
     * Note that this method is not aware of non-standard mechanisms on some customized builds of Android. For
     * example, CyanogenMod has an option to add a menu button to the navigation bar. Even with that option,
     * this method will still return `false`.
     */
    public boolean hasMenuButton() {
        if (Build.VERSION.SDK_INT <= 10) {
            return true;
        } else {
            if (Build.VERSION.SDK_INT <= 13) {
                return false;
            } else {
                return ViewConfiguration.get(activity.getApplication()).hasPermanentMenuKey();
            }
        }
    }

    public void setSizeChangedCallback(int id) {
        SizeChangedCallbackID = id;
    }

    public void setPaddingChangedCallback(int id) {
        PaddingChangedCallbackID = id;
    }

    public void setButtonCallback(int id) {
        ButtonCallbackID = id;
    }

    public void setMotionCallback(int id) {
        MotionCallbackID = id;
        if (activity != null) {
            activity.setMotionCallback(id, this);
        }
    }

    public void setKeypressCallback(int id) {
        KeypressCallbackID = id;
        // set callback id also in main intent (for menus)
        if (activity != null) {
            activity.setKeypressCallback(id, this);
        }
    }

    protected void draw_polyline(Paint paint, int[] c) {
        paint.setStrokeWidth(c[0]);
        paint.setARGB(c[1], c[2], c[3], c[4]);
        paint.setStyle(Paint.Style.STROKE);
        //paint.setAntiAlias(true);
        //paint.setStrokeWidth(0);
        int ndashes = c[5];
        float[] intervals = new float[ndashes + (ndashes % 2)];
        for (int i = 0; i < ndashes; i++) {
            intervals[i] = c[6 + i];
        }

        if ((ndashes % 2) == 1) {
            intervals[ndashes] = intervals[ndashes - 1];
        }

        if (ndashes > 0) {
            paint.setPathEffect(new android.graphics.DashPathEffect(intervals, 0.0f));
        }

        Path path = new Path();
        path.moveTo(c[6 + ndashes], c[7 + ndashes]);
        for (int i = 8 + ndashes; i < c.length; i += 2) {
            path.lineTo(c[i], c[i + 1]);
        }
        //global_path.close();
        draw_canvas.drawPath(path, paint);
        paint.setPathEffect(null);
    }

    protected void draw_polygon(Paint paint, int[] c) {
        paint.setStrokeWidth(c[0]);
        paint.setARGB(c[1], c[2], c[3], c[4]);
        paint.setStyle(Paint.Style.FILL);
        //paint.setAntiAlias(true);
        //paint.setStrokeWidth(0);
        Path path = new Path();
        path.moveTo(c[5], c[6]);
        for (int i = 7; i < c.length; i += 2) {
            path.lineTo(c[i], c[i + 1]);
        }
        //global_path.close();
        draw_canvas.drawPath(path, paint);
    }

    protected void draw_rectangle(Paint paint, int x, int y, int w, int h) {
        Rect r = new Rect(x, y, x + w, y + h);
        paint.setStyle(Paint.Style.FILL);
        paint.setAntiAlias(true);
        //paint.setStrokeWidth(0);
        draw_canvas.drawRect(r, paint);
    }

    protected void draw_circle(Paint paint, int x, int y, int r) {
        paint.setStyle(Paint.Style.STROKE);
        draw_canvas.drawCircle(x, y, r / 2, paint);
    }

    protected void draw_text(Paint paint, int x, int y, String text, int size, int dx, int dy, int bgcolor) {
        int oldcolor = paint.getColor();
        Path path = null;

        paint.setTextSize(size / 15);
        paint.setStyle(Paint.Style.FILL);

        if (dx != 0x10000 || dy != 0) {
            path = new Path();
            path.moveTo(x, y);
            path.rLineTo(dx, dy);
            paint.setTextAlign(android.graphics.Paint.Align.LEFT);
        }

        if (bgcolor != 0) {
            paint.setStrokeWidth(3);
            paint.setColor(bgcolor);
            paint.setStyle(Paint.Style.STROKE);
            if (path == null) {
                draw_canvas.drawText(text, x, y, paint);
            } else {
                draw_canvas.drawTextOnPath(text, path, 0, 0, paint);
            }
            paint.setStyle(Paint.Style.FILL);
            paint.setColor(oldcolor);
        }

        if (path == null) {
            draw_canvas.drawText(text, x, y, paint);
        } else {
            draw_canvas.drawTextOnPath(text, path, 0, 0, paint);
        }
        paint.clearShadowLayer();
    }

    protected void draw_image(Paint paint, int x, int y, Bitmap bitmap) {
        draw_canvas.drawBitmap(bitmap, x, y, null);
    }

    /* takes an image and draws it on the screen as a prerendered maptile
     *
     *
     *
     * @param paint     Paint object used to draw the image
     * @param count     the number of points specified
     * @param p0x and p0y   specifying the top left point
     * @param p1x and p1y   specifying the top right point
     * @param p2x and p2y   specifying the bottom left point, not yet used but kept
     *                      for compatibility with the linux port
     * @param bitmap    Bitmap object holding the image to draw
     *
     * TODO make it work with 4 points specified to make it work for 3D mapview, so it can be used
     *      for small but very detailed maps as well as for large maps with very little detail but large
     *      coverage.
     * TODO make it work with rectangular tiles as well ?
     */
    protected void draw_image_warp(Paint paint, int count, int p0x, int p0y, int p1x, int p1y, int p2x, int p2y,
            Bitmap bitmap) {

        float width;
        float scale;
        float deltaY;
        float deltaX;
        float angle;
        Matrix matrix;

        if (count == 3) {
            matrix = new Matrix();
            deltaX = p1x - p0x;
            deltaY = p1y - p0y;
            width = (float) (Math.sqrt((deltaX * deltaX) + (deltaY * deltaY)));
            angle = (float) (Math.atan2(deltaY, deltaX) * 180d / Math.PI);
            scale = width / bitmap.getWidth();
            matrix.preScale(scale, scale);
            matrix.postTranslate(p0x, p0y);
            matrix.postRotate(angle, p0x, p0y);
            draw_canvas.drawBitmap(bitmap, matrix, paint);
        }
    }

    /* These constants must be synchronized with enum draw_mode_num in graphics.h. */
    private static final int draw_mode_begin = 0;
    private static final int draw_mode_end = 1;

    protected void draw_mode(int mode) {
        if (mode == draw_mode_end) {
            if (parent_graphics == null) {
                view.invalidate();
            } else {
                parent_graphics.view.invalidate(get_rect());
            }
        }
        if (mode == draw_mode_begin && parent_graphics != null) {
            draw_bitmap.eraseColor(0);
        }

    }

    protected void draw_drag(int x, int y) {
        pos_x = x;
        pos_y = y;
    }

    protected void overlay_disable(int disable) {
        Log.d(TAG, "overlay_disable: " + disable + "Parent: " + (parent_graphics != null));
        // assume we are NOT in map view mode!
        if (parent_graphics == null) {
            in_map = (disable == 0);
        }
        if (overlay_disabled != disable) {
            overlay_disabled = disable;
            if (parent_graphics != null) {
                parent_graphics.view.invalidate(get_rect());
            }
        }
    }

    protected void overlay_resize(int x, int y, int w, int h, int wraparound) {
        draw_bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        bitmap_w = w;
        bitmap_h = h;
        pos_x = x;
        pos_y = y;
        pos_wraparound = wraparound;
        draw_canvas.setBitmap(draw_bitmap);
    }

    public static native String CallbackLocalizedString(String s);
}