2014-05-28 20:53:34 -07:00
|
|
|
/**
|
|
|
|
* Copyright (C) 2014 Open Whisper Systems
|
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* 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, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
package org.thoughtcrime.securesms.components;
|
|
|
|
|
|
|
|
import android.content.Context;
|
|
|
|
import android.graphics.Rect;
|
|
|
|
import android.os.Build;
|
2015-06-08 15:11:05 -07:00
|
|
|
import android.os.Build.VERSION_CODES;
|
2014-05-28 20:53:34 -07:00
|
|
|
import android.preference.PreferenceManager;
|
2015-07-02 16:47:03 -07:00
|
|
|
import android.support.v7.widget.LinearLayoutCompat;
|
2014-05-28 20:53:34 -07:00
|
|
|
import android.util.AttributeSet;
|
|
|
|
import android.util.Log;
|
|
|
|
import android.view.Surface;
|
2015-06-08 15:11:05 -07:00
|
|
|
import android.view.View;
|
2014-05-28 20:53:34 -07:00
|
|
|
|
|
|
|
import org.thoughtcrime.securesms.R;
|
2015-07-07 14:25:41 -07:00
|
|
|
import org.thoughtcrime.securesms.util.ServiceUtil;
|
2015-07-17 12:50:00 -07:00
|
|
|
import org.thoughtcrime.securesms.util.Util;
|
2014-05-28 20:53:34 -07:00
|
|
|
|
2015-06-08 15:11:05 -07:00
|
|
|
import java.lang.reflect.Field;
|
2015-07-02 16:47:03 -07:00
|
|
|
import java.util.HashSet;
|
|
|
|
import java.util.Set;
|
2015-06-08 15:11:05 -07:00
|
|
|
|
2014-05-28 20:53:34 -07:00
|
|
|
/**
|
|
|
|
* LinearLayout that, when a view container, will report back when it thinks a soft keyboard
|
|
|
|
* has been opened and what its height would be.
|
|
|
|
*/
|
2015-07-02 16:47:03 -07:00
|
|
|
public class KeyboardAwareLinearLayout extends LinearLayoutCompat {
|
|
|
|
private static final String TAG = KeyboardAwareLinearLayout.class.getSimpleName();
|
|
|
|
|
|
|
|
private final Rect oldRect = new Rect();
|
|
|
|
private final Rect newRect = new Rect();
|
|
|
|
private final Set<OnKeyboardHiddenListener> hiddenListeners = new HashSet<>();
|
|
|
|
private final Set<OnKeyboardShownListener> shownListeners = new HashSet<>();
|
2015-07-07 14:25:41 -07:00
|
|
|
private final int minKeyboardSize;
|
2015-07-17 12:50:00 -07:00
|
|
|
private final int minCustomKeyboardSize;
|
|
|
|
private final int defaultCustomKeyboardSize;
|
|
|
|
private final int minCustomKeyboardTopMargin;
|
2015-07-02 16:47:03 -07:00
|
|
|
|
2015-07-07 14:25:41 -07:00
|
|
|
private boolean keyboardOpen = false;
|
|
|
|
private int rotation = -1;
|
2014-05-28 20:53:34 -07:00
|
|
|
|
|
|
|
public KeyboardAwareLinearLayout(Context context) {
|
2015-07-02 16:47:03 -07:00
|
|
|
this(context, null);
|
2014-05-28 20:53:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
public KeyboardAwareLinearLayout(Context context, AttributeSet attrs) {
|
2015-07-02 16:47:03 -07:00
|
|
|
this(context, attrs, 0);
|
2014-05-28 20:53:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
public KeyboardAwareLinearLayout(Context context, AttributeSet attrs, int defStyle) {
|
|
|
|
super(context, attrs, defStyle);
|
2015-07-17 12:50:00 -07:00
|
|
|
minKeyboardSize = getResources().getDimensionPixelSize(R.dimen.min_keyboard_size);
|
|
|
|
minCustomKeyboardSize = getResources().getDimensionPixelSize(R.dimen.min_custom_keyboard_size);
|
|
|
|
defaultCustomKeyboardSize = getResources().getDimensionPixelSize(R.dimen.default_custom_keyboard_size);
|
|
|
|
minCustomKeyboardTopMargin = getResources().getDimensionPixelSize(R.dimen.min_custom_keyboard_top_margin);
|
2014-05-28 20:53:34 -07:00
|
|
|
}
|
|
|
|
|
2015-07-02 16:47:03 -07:00
|
|
|
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
2015-07-07 14:25:41 -07:00
|
|
|
updateRotation();
|
|
|
|
updateKeyboardState();
|
2015-07-02 16:47:03 -07:00
|
|
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
2015-07-07 14:25:41 -07:00
|
|
|
}
|
2015-07-02 16:47:03 -07:00
|
|
|
|
2015-07-07 14:25:41 -07:00
|
|
|
private void updateRotation() {
|
|
|
|
int oldRotation = rotation;
|
|
|
|
rotation = getDeviceRotation();
|
|
|
|
if (oldRotation != rotation) {
|
|
|
|
onKeyboardClose();
|
|
|
|
oldRect.setEmpty();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void updateKeyboardState() {
|
|
|
|
int res = getResources().getIdentifier("status_bar_height", "dimen", "android");
|
2014-05-28 20:53:34 -07:00
|
|
|
int statusBarHeight = res > 0 ? getResources().getDimensionPixelSize(res) : 0;
|
|
|
|
|
2015-06-08 15:11:05 -07:00
|
|
|
final int availableHeight = this.getRootView().getHeight() - statusBarHeight - getViewInset();
|
2015-07-02 16:47:03 -07:00
|
|
|
getWindowVisibleDisplayFrame(newRect);
|
2014-05-28 20:53:34 -07:00
|
|
|
|
2015-07-02 16:47:03 -07:00
|
|
|
final int oldKeyboardHeight = availableHeight - (oldRect.bottom - oldRect.top);
|
2015-07-07 14:25:41 -07:00
|
|
|
final int keyboardHeight = availableHeight - (newRect.bottom - newRect.top);
|
2014-05-28 20:53:34 -07:00
|
|
|
|
2015-07-02 16:47:03 -07:00
|
|
|
if (keyboardHeight - oldKeyboardHeight > minKeyboardSize && !keyboardOpen) {
|
|
|
|
onKeyboardOpen(keyboardHeight);
|
|
|
|
} else if (oldKeyboardHeight - keyboardHeight > minKeyboardSize && keyboardOpen) {
|
|
|
|
onKeyboardClose();
|
2014-05-28 20:53:34 -07:00
|
|
|
}
|
|
|
|
|
2015-07-02 16:47:03 -07:00
|
|
|
oldRect.set(newRect);
|
|
|
|
}
|
|
|
|
|
|
|
|
private int getViewInset() {
|
2015-06-08 15:11:05 -07:00
|
|
|
if (Build.VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
Field attachInfoField = View.class.getDeclaredField("mAttachInfo");
|
|
|
|
attachInfoField.setAccessible(true);
|
|
|
|
Object attachInfo = attachInfoField.get(this);
|
|
|
|
if (attachInfo != null) {
|
|
|
|
Field stableInsetsField = attachInfo.getClass().getDeclaredField("mStableInsets");
|
|
|
|
stableInsetsField.setAccessible(true);
|
|
|
|
Rect insets = (Rect)stableInsetsField.get(attachInfo);
|
|
|
|
return insets.bottom;
|
|
|
|
}
|
|
|
|
} catch (NoSuchFieldException nsfe) {
|
|
|
|
Log.w(TAG, "field reflection error when measuring view inset", nsfe);
|
|
|
|
} catch (IllegalAccessException iae) {
|
|
|
|
Log.w(TAG, "access reflection error when measuring view inset", iae);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-07-02 16:47:03 -07:00
|
|
|
protected void onKeyboardOpen(int keyboardHeight) {
|
|
|
|
keyboardOpen = true;
|
2014-05-28 20:53:34 -07:00
|
|
|
|
2015-07-07 14:25:41 -07:00
|
|
|
if (!isLandscape()) {
|
|
|
|
setKeyboardPortraitHeight(keyboardHeight);
|
2014-05-28 20:53:34 -07:00
|
|
|
}
|
2015-07-02 16:47:03 -07:00
|
|
|
notifyShownListeners();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected void onKeyboardClose() {
|
|
|
|
keyboardOpen = false;
|
|
|
|
notifyHiddenListeners();
|
|
|
|
}
|
|
|
|
|
2014-05-28 20:53:34 -07:00
|
|
|
public int getKeyboardHeight() {
|
2015-07-07 14:25:41 -07:00
|
|
|
return isLandscape() ? getKeyboardLandscapeHeight() : getKeyboardPortraitHeight();
|
|
|
|
}
|
2014-05-28 20:53:34 -07:00
|
|
|
|
2015-07-07 14:25:41 -07:00
|
|
|
public boolean isLandscape() {
|
|
|
|
int rotation = getDeviceRotation();
|
|
|
|
return rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270;
|
|
|
|
}
|
|
|
|
private int getDeviceRotation() {
|
|
|
|
return ServiceUtil.getWindowManager(getContext()).getDefaultDisplay().getRotation();
|
2014-05-28 20:53:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
private int getKeyboardLandscapeHeight() {
|
2015-07-07 14:25:41 -07:00
|
|
|
return Math.max(getHeight(), getRootView().getHeight()) / 2;
|
2014-05-28 20:53:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
private int getKeyboardPortraitHeight() {
|
2015-07-07 14:25:41 -07:00
|
|
|
int keyboardHeight = PreferenceManager.getDefaultSharedPreferences(getContext())
|
2015-07-17 12:50:00 -07:00
|
|
|
.getInt("keyboard_height_portrait", defaultCustomKeyboardSize);
|
|
|
|
return Util.clamp(keyboardHeight, minCustomKeyboardSize, getRootView().getHeight() - minCustomKeyboardTopMargin);
|
2014-05-28 20:53:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
private void setKeyboardPortraitHeight(int height) {
|
|
|
|
PreferenceManager.getDefaultSharedPreferences(getContext())
|
|
|
|
.edit().putInt("keyboard_height_portrait", height).apply();
|
|
|
|
}
|
|
|
|
|
2015-07-02 16:47:03 -07:00
|
|
|
public void postOnKeyboardClose(final Runnable runnable) {
|
|
|
|
if (keyboardOpen) {
|
|
|
|
addOnKeyboardHiddenListener(new OnKeyboardHiddenListener() {
|
|
|
|
@Override public void onKeyboardHidden() {
|
|
|
|
removeOnKeyboardHiddenListener(this);
|
|
|
|
runnable.run();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
runnable.run();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-13 15:45:14 -07:00
|
|
|
public void postOnKeyboardOpen(final Runnable runnable) {
|
|
|
|
if (!keyboardOpen) {
|
|
|
|
addOnKeyboardShownListener(new OnKeyboardShownListener() {
|
|
|
|
@Override public void onKeyboardShown() {
|
|
|
|
removeOnKeyboardShownListener(this);
|
|
|
|
runnable.run();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
runnable.run();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-02 16:47:03 -07:00
|
|
|
public void addOnKeyboardHiddenListener(OnKeyboardHiddenListener listener) {
|
|
|
|
hiddenListeners.add(listener);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void removeOnKeyboardHiddenListener(OnKeyboardHiddenListener listener) {
|
|
|
|
hiddenListeners.remove(listener);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void addOnKeyboardShownListener(OnKeyboardShownListener listener) {
|
|
|
|
shownListeners.add(listener);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void removeOnKeyboardShownListener(OnKeyboardShownListener listener) {
|
|
|
|
shownListeners.remove(listener);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void notifyHiddenListeners() {
|
2015-07-13 15:45:14 -07:00
|
|
|
final Set<OnKeyboardHiddenListener> listeners = new HashSet<>(hiddenListeners);
|
|
|
|
for (OnKeyboardHiddenListener listener : listeners) {
|
2015-07-02 16:47:03 -07:00
|
|
|
listener.onKeyboardHidden();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void notifyShownListeners() {
|
2015-07-13 15:45:14 -07:00
|
|
|
final Set<OnKeyboardShownListener> listeners = new HashSet<>(shownListeners);
|
|
|
|
for (OnKeyboardShownListener listener : listeners) {
|
2015-07-02 16:47:03 -07:00
|
|
|
listener.onKeyboardShown();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public interface OnKeyboardHiddenListener {
|
|
|
|
void onKeyboardHidden();
|
|
|
|
}
|
|
|
|
|
|
|
|
public interface OnKeyboardShownListener {
|
|
|
|
void onKeyboardShown();
|
|
|
|
}
|
2014-05-28 20:53:34 -07:00
|
|
|
}
|