That fuck shit the fascists are using
at master 335 lines 11 kB view raw
1/** 2 * Copyright (C) 2014 Open Whisper Systems 3 * <p> 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * <p> 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * <p> 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17package org.tm.archive.components; 18 19import android.content.Context; 20import android.graphics.Rect; 21import android.os.Build; 22import android.preference.PreferenceManager; 23import android.util.AttributeSet; 24import android.util.DisplayMetrics; 25import android.view.Surface; 26import android.view.View; 27import android.view.WindowInsets; 28 29import androidx.appcompat.widget.LinearLayoutCompat; 30 31import org.signal.core.util.logging.Log; 32import org.tm.archive.R; 33import org.tm.archive.util.ServiceUtil; 34import org.tm.archive.util.Util; 35import org.tm.archive.util.ViewUtil; 36 37import java.lang.reflect.Field; 38import java.util.HashSet; 39import java.util.Set; 40 41/** 42 * LinearLayout that, when a view container, will report back when it thinks a soft keyboard 43 * has been opened and what its height would be. 44 */ 45public class KeyboardAwareLinearLayout extends LinearLayoutCompat { 46 private static final String TAG = Log.tag(KeyboardAwareLinearLayout.class); 47 48 private static final long KEYBOARD_DEBOUNCE = 150; 49 50 private final Rect rect = new Rect(); 51 private final Set<OnKeyboardHiddenListener> hiddenListeners = new HashSet<>(); 52 private final Set<OnKeyboardShownListener> shownListeners = new HashSet<>(); 53 private final DisplayMetrics displayMetrics = new DisplayMetrics(); 54 55 private final int minKeyboardSize; 56 private final int minCustomKeyboardSize; 57 private final int defaultCustomKeyboardSize; 58 private final int minCustomKeyboardTopMarginPortrait; 59 private final int minCustomKeyboardTopMarginLandscape; 60 private final int minCustomKeyboardTopMarginLandscapeBubble; 61 private final int statusBarHeight; 62 63 private int viewInset; 64 65 private boolean keyboardOpen = false; 66 private int rotation = 0; 67 private boolean isBubble = false; 68 private long openedAt = 0; 69 70 public KeyboardAwareLinearLayout(Context context) { 71 this(context, null); 72 } 73 74 public KeyboardAwareLinearLayout(Context context, AttributeSet attrs) { 75 this(context, attrs, 0); 76 } 77 78 public KeyboardAwareLinearLayout(Context context, AttributeSet attrs, int defStyle) { 79 super(context, attrs, defStyle); 80 minKeyboardSize = getResources().getDimensionPixelSize(R.dimen.min_keyboard_size); 81 minCustomKeyboardSize = getResources().getDimensionPixelSize(R.dimen.min_custom_keyboard_size); 82 defaultCustomKeyboardSize = getResources().getDimensionPixelSize(R.dimen.default_custom_keyboard_size); 83 minCustomKeyboardTopMarginPortrait = getResources().getDimensionPixelSize(R.dimen.min_custom_keyboard_top_margin_portrait); 84 minCustomKeyboardTopMarginLandscape = getResources().getDimensionPixelSize(R.dimen.min_custom_keyboard_top_margin_portrait); 85 minCustomKeyboardTopMarginLandscapeBubble = getResources().getDimensionPixelSize(R.dimen.min_custom_keyboard_top_margin_landscape_bubble); 86 statusBarHeight = ViewUtil.getStatusBarHeight(this); 87 viewInset = getViewInset(); 88 } 89 90 @Override 91 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 92 updateRotation(); 93 updateKeyboardState(); 94 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 95 } 96 97 public void setIsBubble(boolean isBubble) { 98 this.isBubble = isBubble; 99 } 100 101 private void updateRotation() { 102 int oldRotation = rotation; 103 rotation = getDeviceRotation(); 104 if (oldRotation != rotation) { 105 Log.i(TAG, "rotation changed"); 106 onKeyboardClose(); 107 } 108 } 109 110 private void updateKeyboardState() { 111 if (viewInset == 0) viewInset = getViewInset(); 112 113 getWindowVisibleDisplayFrame(rect); 114 115 final int availableHeight = getAvailableHeight(); 116 final int keyboardHeight = availableHeight - rect.bottom; 117 118 if (keyboardHeight > minKeyboardSize) { 119 if (getKeyboardHeight() != keyboardHeight) { 120 if (isLandscape()) { 121 setKeyboardLandscapeHeight(keyboardHeight); 122 } else { 123 setKeyboardPortraitHeight(keyboardHeight); 124 } 125 } 126 if (!keyboardOpen) { 127 onKeyboardOpen(keyboardHeight); 128 } 129 } else if (keyboardOpen) { 130 onKeyboardClose(); 131 } 132 } 133 134 @Override 135 protected void onAttachedToWindow() { 136 super.onAttachedToWindow(); 137 rotation = getDeviceRotation(); 138 if (Build.VERSION.SDK_INT >= 23 && getRootWindowInsets() != null) { 139 int bottomInset; 140 WindowInsets windowInsets = getRootWindowInsets(); 141 142 if (Build.VERSION.SDK_INT >= 30) { 143 bottomInset = windowInsets.getInsets(WindowInsets.Type.navigationBars()).bottom; 144 } else { 145 bottomInset = windowInsets.getStableInsetBottom(); 146 } 147 148 if (bottomInset != 0 && (viewInset == 0 || viewInset == statusBarHeight)) { 149 Log.i(TAG, "Updating view inset based on WindowInsets. viewInset: " + viewInset + " windowInset: " + bottomInset); 150 viewInset = bottomInset; 151 } 152 } 153 } 154 155 private int getViewInset() { 156 try { 157 Field attachInfoField = View.class.getDeclaredField("mAttachInfo"); 158 attachInfoField.setAccessible(true); 159 Object attachInfo = attachInfoField.get(this); 160 if (attachInfo != null) { 161 Field stableInsetsField = attachInfo.getClass().getDeclaredField("mStableInsets"); 162 stableInsetsField.setAccessible(true); 163 Rect insets = (Rect) stableInsetsField.get(attachInfo); 164 if (insets != null) { 165 return insets.bottom; 166 } 167 } 168 } catch (NoSuchFieldException | IllegalAccessException e) { 169 // Do nothing 170 } 171 return statusBarHeight; 172 } 173 174 private int getAvailableHeight() { 175 final int availableHeight = this.getRootView().getHeight() - viewInset; 176 final int availableWidth = this.getRootView().getWidth(); 177 178 if (isLandscape() && availableHeight > availableWidth) { 179 //noinspection SuspiciousNameCombination 180 return availableWidth; 181 } 182 183 return availableHeight; 184 } 185 186 protected void onKeyboardOpen(int keyboardHeight) { 187 Log.i(TAG, "onKeyboardOpen(" + keyboardHeight + ")"); 188 keyboardOpen = true; 189 openedAt = System.currentTimeMillis(); 190 191 notifyShownListeners(); 192 } 193 194 protected void onKeyboardClose() { 195 if (System.currentTimeMillis() - openedAt < KEYBOARD_DEBOUNCE) { 196 Log.i(TAG, "Delaying onKeyboardClose()"); 197 postDelayed(this::updateKeyboardState, KEYBOARD_DEBOUNCE); 198 return; 199 } 200 201 Log.i(TAG, "onKeyboardClose()"); 202 keyboardOpen = false; 203 openedAt = 0; 204 notifyHiddenListeners(); 205 } 206 207 public boolean isKeyboardOpen() { 208 return keyboardOpen; 209 } 210 211 public int getKeyboardHeight() { 212 return isLandscape() ? getKeyboardLandscapeHeight() : getKeyboardPortraitHeight(); 213 } 214 215 public boolean isLandscape() { 216 int rotation = getDeviceRotation(); 217 return rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270; 218 } 219 220 private int getDeviceRotation() { 221 if (isInEditMode()) { 222 return Surface.ROTATION_0; 223 } 224 225 if (Build.VERSION.SDK_INT >= 30) { 226 getContext().getDisplay().getRealMetrics(displayMetrics); 227 } else { 228 ServiceUtil.getWindowManager(getContext()).getDefaultDisplay().getRealMetrics(displayMetrics); 229 } 230 return displayMetrics.widthPixels > displayMetrics.heightPixels ? Surface.ROTATION_90 : Surface.ROTATION_0; 231 } 232 233 private int getKeyboardLandscapeHeight() { 234 if (isBubble) { 235 return getRootView().getHeight() - minCustomKeyboardTopMarginLandscapeBubble; 236 } 237 238 int keyboardHeight = PreferenceManager.getDefaultSharedPreferences(getContext()) 239 .getInt("keyboard_height_landscape", defaultCustomKeyboardSize); 240 return Util.clamp(keyboardHeight, minCustomKeyboardSize, getRootView().getHeight() - minCustomKeyboardTopMarginLandscape); 241 } 242 243 private int getKeyboardPortraitHeight() { 244 if (isBubble) { 245 int height = getRootView().getHeight(); 246 return height - (int)(height * 0.45); 247 } 248 249 int keyboardHeight = PreferenceManager.getDefaultSharedPreferences(getContext()) 250 .getInt("keyboard_height_portrait", defaultCustomKeyboardSize); 251 return Util.clamp(keyboardHeight, minCustomKeyboardSize, getRootView().getHeight() - minCustomKeyboardTopMarginPortrait); 252 } 253 254 private void setKeyboardPortraitHeight(int height) { 255 if (isBubble) { 256 return; 257 } 258 259 PreferenceManager.getDefaultSharedPreferences(getContext()) 260 .edit().putInt("keyboard_height_portrait", height).apply(); 261 } 262 263 private void setKeyboardLandscapeHeight(int height) { 264 if (isBubble) { 265 return; 266 } 267 268 PreferenceManager.getDefaultSharedPreferences(getContext()) 269 .edit().putInt("keyboard_height_landscape", height).apply(); 270 } 271 272 public void postOnKeyboardClose(final Runnable runnable) { 273 if (keyboardOpen) { 274 addOnKeyboardHiddenListener(new OnKeyboardHiddenListener() { 275 @Override public void onKeyboardHidden() { 276 removeOnKeyboardHiddenListener(this); 277 runnable.run(); 278 } 279 }); 280 } else { 281 runnable.run(); 282 } 283 } 284 285 public void postOnKeyboardOpen(final Runnable runnable) { 286 if (!keyboardOpen) { 287 addOnKeyboardShownListener(new OnKeyboardShownListener() { 288 @Override public void onKeyboardShown() { 289 removeOnKeyboardShownListener(this); 290 runnable.run(); 291 } 292 }); 293 } else { 294 runnable.run(); 295 } 296 } 297 298 public void addOnKeyboardHiddenListener(OnKeyboardHiddenListener listener) { 299 hiddenListeners.add(listener); 300 } 301 302 public void removeOnKeyboardHiddenListener(OnKeyboardHiddenListener listener) { 303 hiddenListeners.remove(listener); 304 } 305 306 public void addOnKeyboardShownListener(OnKeyboardShownListener listener) { 307 shownListeners.add(listener); 308 } 309 310 public void removeOnKeyboardShownListener(OnKeyboardShownListener listener) { 311 shownListeners.remove(listener); 312 } 313 314 private void notifyHiddenListeners() { 315 final Set<OnKeyboardHiddenListener> listeners = new HashSet<>(hiddenListeners); 316 for (OnKeyboardHiddenListener listener : listeners) { 317 listener.onKeyboardHidden(); 318 } 319 } 320 321 private void notifyShownListeners() { 322 final Set<OnKeyboardShownListener> listeners = new HashSet<>(shownListeners); 323 for (OnKeyboardShownListener listener : listeners) { 324 listener.onKeyboardShown(); 325 } 326 } 327 328 public interface OnKeyboardHiddenListener { 329 void onKeyboardHidden(); 330 } 331 332 public interface OnKeyboardShownListener { 333 void onKeyboardShown(); 334 } 335}