/* * Copyright (C) 2011 Steven Luo * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jackpal.androidterm; import java.util.Iterator; import java.util.LinkedList; import android.content.Context; import android.graphics.Canvas; import android.graphics.Rect; import android.os.Handler; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; import android.widget.Toast; import android.widget.ViewFlipper; import jackpal.androidterm.emulatorview.EmulatorView; import jackpal.androidterm.emulatorview.TermSession; import jackpal.androidterm.emulatorview.UpdateCallback; import jackpal.androidterm.compat.AndroidCompat; import jackpal.androidterm.util.TermSettings; public class TermViewFlipper extends ViewFlipper implements Iterable<View> { private Context context; private Toast mToast; private LinkedList<UpdateCallback> callbacks; private boolean mStatusBarVisible = false; private int mCurWidth; private int mCurHeight; private Rect mVisibleRect = new Rect(); private Rect mWindowRect = new Rect(); private LayoutParams mChildParams = null; private boolean mRedoLayout = false; /** * True if we must poll to discover if the view has changed size. * This is the only known way to detect the view changing size due to * the IME being shown or hidden in API level <= 7. */ private final boolean mbPollForWindowSizeChange = (AndroidCompat.SDK < 8); private static final int SCREEN_CHECK_PERIOD = 1000; private final Handler mHandler = new Handler(); private Runnable mCheckSize = new Runnable() { public void run() { adjustChildSize(); mHandler.postDelayed(this, SCREEN_CHECK_PERIOD); } }; class ViewFlipperIterator implements Iterator<View> { int pos = 0; public boolean hasNext() { return (pos < getChildCount()); } public View next() { return getChildAt(pos++); } public void remove() { throw new UnsupportedOperationException(); } } public TermViewFlipper(Context context) { super(context); commonConstructor(context); } public TermViewFlipper(Context context, AttributeSet attrs) { super(context, attrs); commonConstructor(context); } private void commonConstructor(Context context) { this.context = context; callbacks = new LinkedList<UpdateCallback>(); updateVisibleRect(); Rect visible = mVisibleRect; mChildParams = new LayoutParams(visible.width(), visible.height(), Gravity.TOP|Gravity.LEFT); } public void updatePrefs(TermSettings settings) { boolean statusBarVisible = settings.showStatusBar(); int[] colorScheme = settings.getColorScheme(); setBackgroundColor(colorScheme[1]); mStatusBarVisible = statusBarVisible; } public Iterator<View> iterator() { return new ViewFlipperIterator(); } public void addCallback(UpdateCallback callback) { callbacks.add(callback); } public void removeCallback(UpdateCallback callback) { callbacks.remove(callback); } private void notifyChange() { for (UpdateCallback callback : callbacks) { callback.onUpdate(); } } public void onPause() { if (mbPollForWindowSizeChange) { mHandler.removeCallbacks(mCheckSize); } pauseCurrentView(); } public void onResume() { if (mbPollForWindowSizeChange) { mCheckSize.run(); } resumeCurrentView(); } public void pauseCurrentView() { EmulatorView view = (EmulatorView) getCurrentView(); if (view == null) { return; } view.onPause(); } public void resumeCurrentView() { EmulatorView view = (EmulatorView) getCurrentView(); if (view == null) { return; } view.onResume(); view.requestFocus(); } private void showTitle() { if (getChildCount() == 0) { return; } EmulatorView view = (EmulatorView) getCurrentView(); if (view == null) { return; } TermSession session = view.getTermSession(); if (session == null) { return; } String title = context.getString(R.string.window_title,getDisplayedChild()+1); if (session instanceof GenericTermSession) { title = ((GenericTermSession) session).getTitle(title); } if (mToast == null) { mToast = Toast.makeText(context, title, Toast.LENGTH_SHORT); mToast.setGravity(Gravity.CENTER, 0, 0); } else { mToast.setText(title); } mToast.show(); } @Override public void showPrevious() { pauseCurrentView(); super.showPrevious(); showTitle(); resumeCurrentView(); notifyChange(); } @Override public void showNext() { pauseCurrentView(); super.showNext(); showTitle(); resumeCurrentView(); notifyChange(); } @Override public void setDisplayedChild(int position) { pauseCurrentView(); super.setDisplayedChild(position); showTitle(); resumeCurrentView(); notifyChange(); } @Override public void addView(View v, int index) { super.addView(v, index, mChildParams); } @Override public void addView(View v) { super.addView(v, mChildParams); } private void updateVisibleRect() { Rect visible = mVisibleRect; Rect window = mWindowRect; /* Get rectangle representing visible area of this view, as seen by the activity (takes other views in the layout into account, but not space used by the IME) */ getGlobalVisibleRect(visible); /* Get rectangle representing visible area of this window (takes IME into account, but not other views in the layout) */ getWindowVisibleDisplayFrame(window); /* Work around bug in getWindowVisibleDisplayFrame on API < 10, and avoid a distracting height change as status bar hides otherwise */ if (!mStatusBarVisible) { window.top = 0; } // Clip visible rectangle's top to the visible portion of the window if (visible.width() == 0 && visible.height() == 0) { visible.left = window.left; visible.top = window.top; } else { if (visible.left < window.left) { visible.left = window.left; } if (visible.top < window.top) { visible.top = window.top; } } // Always set the bottom of the rectangle to the window bottom /* XXX This breaks with a split action bar, but if we don't do this, it's possible that the view won't resize correctly on IME hide */ visible.right = window.right; visible.bottom = window.bottom; } private void adjustChildSize() { updateVisibleRect(); Rect visible = mVisibleRect; int width = visible.width(); int height = visible.height(); if (mCurWidth != width || mCurHeight != height) { mCurWidth = width; mCurHeight = height; LayoutParams params = mChildParams; params.width = width; params.height = height; for (View v : this) { updateViewLayout(v, params); } mRedoLayout = true; EmulatorView currentView = (EmulatorView) getCurrentView(); if (currentView != null) { currentView.updateSize(false); } } } /** * Called when the view changes size. * (Note: Not always called on Android < 2.2) */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { adjustChildSize(); super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onDraw(Canvas canvas) { if (mRedoLayout) { requestLayout(); mRedoLayout = false; } super.onDraw(canvas); } }