forked from firka/flutter
Reverts "Bump minSdk to 19 for Android tests" (flutter/engine#47935)
Reverts flutter/engine#47686 Initiated by: zanderso This change reverts the following previous change: Original Description: The latest Robolectric version, 4.11.x, removed the support for SDKs < 19 by following AndroidX strategy. Also following https://github.com/flutter/buildroot/pull/750, and removing obsolete SDK checking in code to pass android_lint checking after bumping minSdk to 19 for testing target. [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.flutter.app" android:versionCode="1" android:versionName="0.0.1">
|
||||
|
||||
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="34" />
|
||||
<uses-sdk android:minSdkVersion="16" android:targetSdkVersion="34" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-feature android:name="android.hardware.sensor.accelerometer" android:required="true" />
|
||||
|
||||
@@ -198,7 +198,9 @@ public class AndroidTouchProcessor {
|
||||
public boolean onGenericMotionEvent(@NonNull MotionEvent event, @NonNull Context context) {
|
||||
// Method isFromSource is only available in API 18+ (Jelly Bean MR2)
|
||||
// Mouse hover support is not implemented for API < 18.
|
||||
boolean isPointerEvent = event.isFromSource(InputDevice.SOURCE_CLASS_POINTER);
|
||||
boolean isPointerEvent =
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2
|
||||
&& event.isFromSource(InputDevice.SOURCE_CLASS_POINTER);
|
||||
boolean isMovementEvent =
|
||||
(event.getActionMasked() == MotionEvent.ACTION_HOVER_MOVE
|
||||
|| event.getActionMasked() == MotionEvent.ACTION_SCROLL);
|
||||
|
||||
@@ -8,6 +8,7 @@ import android.app.Activity;
|
||||
import android.content.ComponentCallbacks2;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -171,14 +172,16 @@ public class FlutterFragment extends Fragment
|
||||
|
||||
@RequiresApi(18)
|
||||
private final OnWindowFocusChangeListener onWindowFocusChangeListener =
|
||||
new OnWindowFocusChangeListener() {
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
if (stillAttachedForEvent("onWindowFocusChanged")) {
|
||||
delegate.onWindowFocusChanged(hasFocus);
|
||||
Build.VERSION.SDK_INT >= 18
|
||||
? new OnWindowFocusChangeListener() {
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
if (stillAttachedForEvent("onWindowFocusChanged")) {
|
||||
delegate.onWindowFocusChanged(hasFocus);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
: null;
|
||||
|
||||
/**
|
||||
* Creates a {@code FlutterFragment} with a default configuration.
|
||||
@@ -1125,16 +1128,20 @@ public class FlutterFragment extends Fragment
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
view.getViewTreeObserver().addOnWindowFocusChangeListener(onWindowFocusChangeListener);
|
||||
if (Build.VERSION.SDK_INT >= 18) {
|
||||
view.getViewTreeObserver().addOnWindowFocusChangeListener(onWindowFocusChangeListener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
// onWindowFocusChangeListener is API 18+ only.
|
||||
requireView()
|
||||
.getViewTreeObserver()
|
||||
.removeOnWindowFocusChangeListener(onWindowFocusChangeListener);
|
||||
if (Build.VERSION.SDK_INT >= 18) {
|
||||
// onWindowFocusChangeListener is API 18+ only.
|
||||
requireView()
|
||||
.getViewTreeObserver()
|
||||
.removeOnWindowFocusChangeListener(onWindowFocusChangeListener);
|
||||
}
|
||||
if (stillAttachedForEvent("onDestroyView")) {
|
||||
delegate.onDestroyView();
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ import java.util.Locale;
|
||||
* an {@link android.media.Image} and renders it to the {@link android.graphics.Canvas} in {@code
|
||||
* onDraw}.
|
||||
*/
|
||||
@TargetApi(19)
|
||||
public class FlutterImageView extends View implements RenderSurface {
|
||||
private static final String TAG = "FlutterImageView";
|
||||
|
||||
@@ -98,6 +99,7 @@ public class FlutterImageView extends View implements RenderSurface {
|
||||
Log.w(TAG, String.format(Locale.US, format, args));
|
||||
}
|
||||
|
||||
@TargetApi(19)
|
||||
@SuppressLint("WrongConstant") // RGBA_8888 is a valid constant.
|
||||
@NonNull
|
||||
private static ImageReader createImageReader(int width, int height) {
|
||||
@@ -192,6 +194,7 @@ public class FlutterImageView extends View implements RenderSurface {
|
||||
* Acquires the next image to be drawn to the {@link android.graphics.Canvas}. Returns true if
|
||||
* there's an image available in the queue.
|
||||
*/
|
||||
@TargetApi(19)
|
||||
public boolean acquireLatestImage() {
|
||||
if (!isAttachedToFlutterRenderer) {
|
||||
return false;
|
||||
|
||||
@@ -290,6 +290,7 @@ public class FlutterView extends FrameLayout
|
||||
* <p>{@code FlutterView} requires an {@code Activity} instead of a generic {@code Context} to be
|
||||
* compatible with {@link PlatformViewsController}.
|
||||
*/
|
||||
@TargetApi(19)
|
||||
public FlutterView(@NonNull Context context, @NonNull FlutterImageView flutterImageView) {
|
||||
this(context, null, flutterImageView);
|
||||
}
|
||||
@@ -356,6 +357,7 @@ public class FlutterView extends FrameLayout
|
||||
init();
|
||||
}
|
||||
|
||||
@TargetApi(19)
|
||||
private FlutterView(
|
||||
@NonNull Context context,
|
||||
@Nullable AttributeSet attrs,
|
||||
|
||||
@@ -16,6 +16,7 @@ import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.SystemClock;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.WindowManager;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import io.flutter.BuildConfig;
|
||||
@@ -169,9 +170,17 @@ public class FlutterLoader {
|
||||
flutterApplicationInfo = ApplicationInfoLoader.load(appContext);
|
||||
|
||||
VsyncWaiter waiter;
|
||||
final DisplayManager dm =
|
||||
(DisplayManager) appContext.getSystemService(Context.DISPLAY_SERVICE);
|
||||
waiter = VsyncWaiter.getInstance(dm, flutterJNI);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 /* 17 */) {
|
||||
final DisplayManager dm =
|
||||
(DisplayManager) appContext.getSystemService(Context.DISPLAY_SERVICE);
|
||||
waiter = VsyncWaiter.getInstance(dm, flutterJNI);
|
||||
} else {
|
||||
float fps =
|
||||
((WindowManager) appContext.getSystemService(Context.WINDOW_SERVICE))
|
||||
.getDefaultDisplay()
|
||||
.getRefreshRate();
|
||||
waiter = VsyncWaiter.getInstance(fps, flutterJNI);
|
||||
}
|
||||
waiter.init();
|
||||
|
||||
// Use a background thread for initialization tasks that require disk access.
|
||||
|
||||
@@ -3,6 +3,7 @@ package io.flutter.embedding.engine.mutatorsstack;
|
||||
import static android.view.View.OnFocusChangeListener;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Matrix;
|
||||
@@ -22,6 +23,7 @@ import io.flutter.util.ViewUtils;
|
||||
* A view that applies the {@link io.flutter.embedding.engine.mutatorsstack.FlutterMutatorsStack} to
|
||||
* its children.
|
||||
*/
|
||||
@TargetApi(19)
|
||||
public class FlutterMutatorView extends FrameLayout {
|
||||
private FlutterMutatorsStack mutatorsStack;
|
||||
private float screenDensity;
|
||||
|
||||
@@ -355,6 +355,7 @@ public class FlutterRenderer implements TextureRegistry {
|
||||
}
|
||||
|
||||
@Override
|
||||
@TargetApi(19)
|
||||
public void release() {
|
||||
if (released) {
|
||||
return;
|
||||
@@ -368,6 +369,7 @@ public class FlutterRenderer implements TextureRegistry {
|
||||
}
|
||||
|
||||
@Override
|
||||
@TargetApi(19)
|
||||
public void pushImage(Image image) {
|
||||
if (released) {
|
||||
return;
|
||||
@@ -431,6 +433,7 @@ public class FlutterRenderer implements TextureRegistry {
|
||||
}
|
||||
|
||||
@Override
|
||||
@TargetApi(19)
|
||||
protected void finalize() throws Throwable {
|
||||
try {
|
||||
if (released) {
|
||||
|
||||
@@ -7,6 +7,7 @@ package io.flutter.plugin.localization;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Build;
|
||||
import android.os.LocaleList;
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -39,9 +40,18 @@ public class LocalizationPlugin {
|
||||
Locale locale = localeFromString(localeString);
|
||||
|
||||
// setLocale and createConfigurationContext is only available on API >= 17
|
||||
Configuration config = new Configuration(context.getResources().getConfiguration());
|
||||
config.setLocale(locale);
|
||||
localContext = context.createConfigurationContext(config);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
Configuration config = new Configuration(context.getResources().getConfiguration());
|
||||
config.setLocale(locale);
|
||||
localContext = context.createConfigurationContext(config);
|
||||
} else {
|
||||
// In API < 17, we have to update the locale in Configuration.
|
||||
Resources resources = context.getResources();
|
||||
Configuration config = resources.getConfiguration();
|
||||
savedLocale = config.locale;
|
||||
config.locale = locale;
|
||||
resources.updateConfiguration(config, null);
|
||||
}
|
||||
}
|
||||
|
||||
String packageName = context.getPackageName();
|
||||
@@ -51,6 +61,14 @@ public class LocalizationPlugin {
|
||||
stringToReturn = localContext.getResources().getString(resId);
|
||||
}
|
||||
|
||||
// In API < 17, we had to restore the original locale after using.
|
||||
if (localeString != null && Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
Resources resources = context.getResources();
|
||||
Configuration config = resources.getConfiguration();
|
||||
config.locale = savedLocale;
|
||||
resources.updateConfiguration(config, null);
|
||||
}
|
||||
|
||||
return stringToReturn;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -282,7 +282,8 @@ public class PlatformPlugin {
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
| View.SYSTEM_UI_FLAG_FULLSCREEN;
|
||||
} else if (systemUiMode == PlatformChannel.SystemUiMode.IMMERSIVE) {
|
||||
} else if (systemUiMode == PlatformChannel.SystemUiMode.IMMERSIVE
|
||||
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
// IMMERSIVE
|
||||
// Available starting at 19
|
||||
// Should not show overlays, swipe from edges to reveal overlays, needs onChange callback
|
||||
@@ -297,7 +298,8 @@ public class PlatformPlugin {
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
| View.SYSTEM_UI_FLAG_FULLSCREEN;
|
||||
} else if (systemUiMode == PlatformChannel.SystemUiMode.IMMERSIVE_STICKY) {
|
||||
} else if (systemUiMode == PlatformChannel.SystemUiMode.IMMERSIVE_STICKY
|
||||
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
// STICKY IMMERSIVE
|
||||
// Available starting at 19
|
||||
// Should not show overlays, swipe from edges to reveal overlays. The app will also receive
|
||||
@@ -344,7 +346,7 @@ public class PlatformPlugin {
|
||||
// The SYSTEM_UI_FLAG_IMMERSIVE_STICKY flag was introduced in API 19, so we
|
||||
// apply it
|
||||
// if desired, and if the current Android version is 19 or greater.
|
||||
if (overlaysToShow.size() == 0) {
|
||||
if (overlaysToShow.size() == 0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
enabledOverlays |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
|
||||
}
|
||||
|
||||
|
||||
@@ -152,6 +152,7 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
|
||||
private final PlatformViewsChannel.PlatformViewsHandler channelHandler =
|
||||
new PlatformViewsChannel.PlatformViewsHandler() {
|
||||
|
||||
@TargetApi(19)
|
||||
@Override
|
||||
// TODO(egarciad): Remove the need for this.
|
||||
// https://github.com/flutter/flutter/issues/96679
|
||||
@@ -414,6 +415,7 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
|
||||
view.dispatchTouchEvent(event);
|
||||
}
|
||||
|
||||
@TargetApi(17)
|
||||
@Override
|
||||
public void setDirection(int viewId, int direction) {
|
||||
if (!validateDirection(direction)) {
|
||||
@@ -498,6 +500,7 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
|
||||
|
||||
// Creates a platform view based on `request`, performs configuration that's common to
|
||||
// all display modes, and adds it to `platformViews`.
|
||||
@TargetApi(19)
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||
public PlatformView createPlatformView(
|
||||
@NonNull PlatformViewsChannel.PlatformViewCreationRequest request, boolean wrapContext) {
|
||||
@@ -1081,6 +1084,7 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
|
||||
* testing.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||
void initializePlatformViewIfNeeded(int viewId) {
|
||||
final PlatformView platformView = platformViews.get(viewId);
|
||||
if (platformView == null) {
|
||||
@@ -1292,6 +1296,7 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
|
||||
* for public use, and is only visible for testing.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
@TargetApi(19)
|
||||
@NonNull
|
||||
public FlutterOverlaySurface createOverlaySurface(@NonNull PlatformOverlayView imageView) {
|
||||
final int id = nextOverlayLayerId++;
|
||||
@@ -1306,6 +1311,7 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
|
||||
*
|
||||
* <p>This member is not intended for public use, and is only visible for testing.
|
||||
*/
|
||||
@TargetApi(19)
|
||||
@NonNull
|
||||
public FlutterOverlaySurface createOverlaySurface() {
|
||||
// Overlay surfaces have the same size as the background surface.
|
||||
|
||||
@@ -7,6 +7,7 @@ package io.flutter.plugin.platform;
|
||||
import static android.content.Context.WINDOW_SERVICE;
|
||||
import static android.view.View.OnFocusChangeListener;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Presentation;
|
||||
import android.content.Context;
|
||||
@@ -14,6 +15,7 @@ import android.content.ContextWrapper;
|
||||
import android.content.MutableContextWrapper;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.Display;
|
||||
import android.view.Gravity;
|
||||
@@ -49,6 +51,7 @@ import java.lang.reflect.Proxy;
|
||||
* EmbeddedView
|
||||
*/
|
||||
@Keep
|
||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||
class SingleViewPresentation extends Presentation {
|
||||
private static final String TAG = "PlatformViewsController";
|
||||
|
||||
@@ -120,7 +123,9 @@ class SingleViewPresentation extends Presentation {
|
||||
.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
|
||||
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
|
||||
getWindow().setType(WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
getWindow().setType(WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,6 +7,7 @@ package io.flutter.util;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.os.Build;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -67,7 +68,10 @@ public final class ViewUtils {
|
||||
* @return the view id.
|
||||
*/
|
||||
public static int generateViewId(int fallbackId) {
|
||||
return View.generateViewId();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
return View.generateViewId();
|
||||
}
|
||||
return fallbackId;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -379,6 +379,7 @@ public class AccessibilityBridge extends AccessibilityNodeProvider {
|
||||
|
||||
// Listener that is notified when accessibility touch exploration is turned on/off.
|
||||
// This is guarded at instantiation time.
|
||||
@TargetApi(19)
|
||||
@RequiresApi(19)
|
||||
private final AccessibilityManager.TouchExplorationStateChangeListener
|
||||
touchExplorationStateChangeListener;
|
||||
@@ -399,8 +400,10 @@ public class AccessibilityBridge extends AccessibilityNodeProvider {
|
||||
}
|
||||
// Retrieve the current value of TRANSITION_ANIMATION_SCALE from the OS.
|
||||
String value =
|
||||
Settings.Global.getString(
|
||||
contentResolver, Settings.Global.TRANSITION_ANIMATION_SCALE);
|
||||
Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1
|
||||
? null
|
||||
: Settings.Global.getString(
|
||||
contentResolver, Settings.Global.TRANSITION_ANIMATION_SCALE);
|
||||
|
||||
boolean shouldAnimationsBeDisabled = value != null && value.equals("0");
|
||||
if (shouldAnimationsBeDisabled) {
|
||||
@@ -448,34 +451,40 @@ public class AccessibilityBridge extends AccessibilityNodeProvider {
|
||||
|
||||
// Tell Flutter whether touch exploration is initially active or not. Then register a listener
|
||||
// to be notified of changes in the future.
|
||||
touchExplorationStateChangeListener =
|
||||
new AccessibilityManager.TouchExplorationStateChangeListener() {
|
||||
@Override
|
||||
public void onTouchExplorationStateChanged(boolean isTouchExplorationEnabled) {
|
||||
if (isReleased) {
|
||||
return;
|
||||
}
|
||||
if (!isTouchExplorationEnabled) {
|
||||
setAccessibleNavigation(false);
|
||||
onTouchExplorationExit();
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
touchExplorationStateChangeListener =
|
||||
new AccessibilityManager.TouchExplorationStateChangeListener() {
|
||||
@Override
|
||||
public void onTouchExplorationStateChanged(boolean isTouchExplorationEnabled) {
|
||||
if (isReleased) {
|
||||
return;
|
||||
}
|
||||
if (!isTouchExplorationEnabled) {
|
||||
setAccessibleNavigation(false);
|
||||
onTouchExplorationExit();
|
||||
}
|
||||
|
||||
if (onAccessibilityChangeListener != null) {
|
||||
onAccessibilityChangeListener.onAccessibilityChanged(
|
||||
accessibilityManager.isEnabled(), isTouchExplorationEnabled);
|
||||
if (onAccessibilityChangeListener != null) {
|
||||
onAccessibilityChangeListener.onAccessibilityChanged(
|
||||
accessibilityManager.isEnabled(), isTouchExplorationEnabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
touchExplorationStateChangeListener.onTouchExplorationStateChanged(
|
||||
accessibilityManager.isTouchExplorationEnabled());
|
||||
this.accessibilityManager.addTouchExplorationStateChangeListener(
|
||||
touchExplorationStateChangeListener);
|
||||
};
|
||||
touchExplorationStateChangeListener.onTouchExplorationStateChanged(
|
||||
accessibilityManager.isTouchExplorationEnabled());
|
||||
this.accessibilityManager.addTouchExplorationStateChangeListener(
|
||||
touchExplorationStateChangeListener);
|
||||
} else {
|
||||
touchExplorationStateChangeListener = null;
|
||||
}
|
||||
|
||||
// Tell Flutter whether animations should initially be enabled or disabled. Then register a
|
||||
// listener to be notified of changes in the future.
|
||||
animationScaleObserver.onChange(false);
|
||||
Uri transitionUri = Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE);
|
||||
this.contentResolver.registerContentObserver(transitionUri, false, animationScaleObserver);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
animationScaleObserver.onChange(false);
|
||||
Uri transitionUri = Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE);
|
||||
this.contentResolver.registerContentObserver(transitionUri, false, animationScaleObserver);
|
||||
}
|
||||
|
||||
// Tells Flutter whether the text should be bolded or not. If the user changes bold text
|
||||
// setting, the configuration will change and trigger a re-build of the accesibiltyBridge.
|
||||
@@ -498,8 +507,10 @@ public class AccessibilityBridge extends AccessibilityNodeProvider {
|
||||
platformViewsAccessibilityDelegate.detachAccessibilityBridge();
|
||||
setOnAccessibilityChangeListener(null);
|
||||
accessibilityManager.removeAccessibilityStateChangeListener(accessibilityStateChangeListener);
|
||||
accessibilityManager.removeTouchExplorationStateChangeListener(
|
||||
touchExplorationStateChangeListener);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
accessibilityManager.removeTouchExplorationStateChangeListener(
|
||||
touchExplorationStateChangeListener);
|
||||
}
|
||||
contentResolver.unregisterContentObserver(animationScaleObserver);
|
||||
accessibilityChannel.setAccessibilityMessageHandler(null);
|
||||
}
|
||||
@@ -658,7 +669,9 @@ public class AccessibilityBridge extends AccessibilityNodeProvider {
|
||||
}
|
||||
|
||||
// Work around for https://github.com/flutter/flutter/issues/2101
|
||||
result.setViewIdResourceName("");
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
result.setViewIdResourceName("");
|
||||
}
|
||||
result.setPackageName(rootAccessibilityView.getContext().getPackageName());
|
||||
result.setClassName("android.view.View");
|
||||
result.setSource(rootAccessibilityView, virtualViewId);
|
||||
@@ -676,16 +689,20 @@ public class AccessibilityBridge extends AccessibilityNodeProvider {
|
||||
if (!semanticsNode.hasFlag(Flag.IS_READ_ONLY)) {
|
||||
result.setClassName("android.widget.EditText");
|
||||
}
|
||||
result.setEditable(!semanticsNode.hasFlag(Flag.IS_READ_ONLY));
|
||||
if (semanticsNode.textSelectionBase != -1 && semanticsNode.textSelectionExtent != -1) {
|
||||
result.setTextSelection(semanticsNode.textSelectionBase, semanticsNode.textSelectionExtent);
|
||||
}
|
||||
// Text fields will always be created as a live region when they have input focus,
|
||||
// so that updates to the label trigger polite announcements. This makes it easy to
|
||||
// follow a11y guidelines for text fields on Android.
|
||||
if (accessibilityFocusedSemanticsNode != null
|
||||
&& accessibilityFocusedSemanticsNode.id == virtualViewId) {
|
||||
result.setLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
result.setEditable(!semanticsNode.hasFlag(Flag.IS_READ_ONLY));
|
||||
if (semanticsNode.textSelectionBase != -1 && semanticsNode.textSelectionExtent != -1) {
|
||||
result.setTextSelection(
|
||||
semanticsNode.textSelectionBase, semanticsNode.textSelectionExtent);
|
||||
}
|
||||
// Text fields will always be created as a live region when they have input focus,
|
||||
// so that updates to the label trigger polite announcements. This makes it easy to
|
||||
// follow a11y guidelines for text fields on Android.
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2
|
||||
&& accessibilityFocusedSemanticsNode != null
|
||||
&& accessibilityFocusedSemanticsNode.id == virtualViewId) {
|
||||
result.setLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE);
|
||||
}
|
||||
}
|
||||
|
||||
// Cursor movements
|
||||
@@ -720,17 +737,19 @@ public class AccessibilityBridge extends AccessibilityNodeProvider {
|
||||
|
||||
// These are non-ops on older devices. Attempting to interact with the text will cause Talkback
|
||||
// to read the contents of the text box instead.
|
||||
if (semanticsNode.hasAction(Action.SET_SELECTION)) {
|
||||
result.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
|
||||
}
|
||||
if (semanticsNode.hasAction(Action.COPY)) {
|
||||
result.addAction(AccessibilityNodeInfo.ACTION_COPY);
|
||||
}
|
||||
if (semanticsNode.hasAction(Action.CUT)) {
|
||||
result.addAction(AccessibilityNodeInfo.ACTION_CUT);
|
||||
}
|
||||
if (semanticsNode.hasAction(Action.PASTE)) {
|
||||
result.addAction(AccessibilityNodeInfo.ACTION_PASTE);
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
if (semanticsNode.hasAction(Action.SET_SELECTION)) {
|
||||
result.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
|
||||
}
|
||||
if (semanticsNode.hasAction(Action.COPY)) {
|
||||
result.addAction(AccessibilityNodeInfo.ACTION_COPY);
|
||||
}
|
||||
if (semanticsNode.hasAction(Action.CUT)) {
|
||||
result.addAction(AccessibilityNodeInfo.ACTION_CUT);
|
||||
}
|
||||
if (semanticsNode.hasAction(Action.PASTE)) {
|
||||
result.addAction(AccessibilityNodeInfo.ACTION_PASTE);
|
||||
}
|
||||
}
|
||||
|
||||
// Set text API isn't available until API 21.
|
||||
@@ -748,7 +767,8 @@ public class AccessibilityBridge extends AccessibilityNodeProvider {
|
||||
// TODO(jonahwilliams): Figure out a way conform to the expected id from TalkBack's
|
||||
// CustomLabelManager. talkback/src/main/java/labeling/CustomLabelManager.java#L525
|
||||
}
|
||||
if (semanticsNode.hasAction(Action.DISMISS)) {
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2
|
||||
&& semanticsNode.hasAction(Action.DISMISS)) {
|
||||
result.setDismissable(true);
|
||||
result.addAction(AccessibilityNodeInfo.ACTION_DISMISS);
|
||||
}
|
||||
@@ -842,7 +862,8 @@ public class AccessibilityBridge extends AccessibilityNodeProvider {
|
||||
result.setClassName("android.widget.HorizontalScrollView");
|
||||
}
|
||||
} else {
|
||||
if (shouldSetCollectionInfo(semanticsNode)) {
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2
|
||||
&& shouldSetCollectionInfo(semanticsNode)) {
|
||||
result.setCollectionInfo(
|
||||
AccessibilityNodeInfo.CollectionInfo.obtain(
|
||||
semanticsNode.scrollChildren, // rows
|
||||
@@ -877,7 +898,8 @@ public class AccessibilityBridge extends AccessibilityNodeProvider {
|
||||
result.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
|
||||
}
|
||||
}
|
||||
if (semanticsNode.hasFlag(Flag.IS_LIVE_REGION)) {
|
||||
if (semanticsNode.hasFlag(Flag.IS_LIVE_REGION)
|
||||
&& Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
result.setLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE);
|
||||
}
|
||||
|
||||
@@ -1089,10 +1111,26 @@ public class AccessibilityBridge extends AccessibilityNodeProvider {
|
||||
}
|
||||
case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY:
|
||||
{
|
||||
// Text selection APIs aren't available until API 18. We can't handle the case here so
|
||||
// return false
|
||||
// instead. It's extremely unlikely that this case would ever be triggered in the first
|
||||
// place in API <
|
||||
// 18.
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
return false;
|
||||
}
|
||||
return performCursorMoveAction(semanticsNode, virtualViewId, arguments, false);
|
||||
}
|
||||
case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
|
||||
{
|
||||
// Text selection APIs aren't available until API 18. We can't handle the case here so
|
||||
// return false
|
||||
// instead. It's extremely unlikely that this case would ever be triggered in the first
|
||||
// place in API <
|
||||
// 18.
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
return false;
|
||||
}
|
||||
return performCursorMoveAction(semanticsNode, virtualViewId, arguments, true);
|
||||
}
|
||||
case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
|
||||
@@ -1145,6 +1183,14 @@ public class AccessibilityBridge extends AccessibilityNodeProvider {
|
||||
}
|
||||
case AccessibilityNodeInfo.ACTION_SET_SELECTION:
|
||||
{
|
||||
// Text selection APIs aren't available until API 18. We can't handle the case here so
|
||||
// return false
|
||||
// instead. It's extremely unlikely that this case would ever be triggered in the first
|
||||
// place in API <
|
||||
// 18.
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
return false;
|
||||
}
|
||||
final Map<String, Integer> selection = new HashMap<>();
|
||||
final boolean hasSelection =
|
||||
arguments != null
|
||||
@@ -1220,6 +1266,7 @@ public class AccessibilityBridge extends AccessibilityNodeProvider {
|
||||
* Handles the responsibilities of {@link #performAction(int, int, Bundle)} for the specific
|
||||
* scenario of cursor movement.
|
||||
*/
|
||||
@TargetApi(18)
|
||||
@RequiresApi(18)
|
||||
private boolean performCursorMoveAction(
|
||||
@NonNull SemanticsNode semanticsNode,
|
||||
@@ -1949,7 +1996,9 @@ public class AccessibilityBridge extends AccessibilityNodeProvider {
|
||||
private void sendWindowContentChangeEvent(int virtualViewId) {
|
||||
AccessibilityEvent event =
|
||||
obtainAccessibilityEvent(virtualViewId, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
|
||||
event.setContentChangeTypes(AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
event.setContentChangeTypes(AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
|
||||
}
|
||||
sendAccessibilityEvent(event);
|
||||
}
|
||||
|
||||
@@ -2000,6 +2049,7 @@ public class AccessibilityBridge extends AccessibilityNodeProvider {
|
||||
* Hook called just before a {@link SemanticsNode} is removed from the Android cache of Flutter's
|
||||
* semantics tree.
|
||||
*/
|
||||
@TargetApi(19)
|
||||
@RequiresApi(19)
|
||||
private void willRemoveSemanticsNode(SemanticsNode semanticsNodeToBeRemoved) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
|
||||
@@ -230,16 +230,20 @@ class AccessibilityViewEmbedder {
|
||||
output.setText(input.getText());
|
||||
output.setVisibleToUser(input.isVisibleToUser());
|
||||
|
||||
output.setEditable(input.isEditable());
|
||||
output.setCanOpenPopup(input.canOpenPopup());
|
||||
output.setCollectionInfo(input.getCollectionInfo());
|
||||
output.setCollectionItemInfo(input.getCollectionItemInfo());
|
||||
output.setContentInvalid(input.isContentInvalid());
|
||||
output.setDismissable(input.isDismissable());
|
||||
output.setInputType(input.getInputType());
|
||||
output.setLiveRegion(input.getLiveRegion());
|
||||
output.setMultiLine(input.isMultiLine());
|
||||
output.setRangeInfo(input.getRangeInfo());
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
output.setEditable(input.isEditable());
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
output.setCanOpenPopup(input.canOpenPopup());
|
||||
output.setCollectionInfo(input.getCollectionInfo());
|
||||
output.setCollectionItemInfo(input.getCollectionItemInfo());
|
||||
output.setContentInvalid(input.isContentInvalid());
|
||||
output.setDismissable(input.isDismissable());
|
||||
output.setInputType(input.getInputType());
|
||||
output.setLiveRegion(input.getLiveRegion());
|
||||
output.setMultiLine(input.isMultiLine());
|
||||
output.setRangeInfo(input.getRangeInfo());
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
output.setError(input.getError());
|
||||
output.setMaxTextLength(input.getMaxTextLength());
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
package io.flutter.view;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.hardware.display.DisplayManager;
|
||||
import android.view.Choreographer;
|
||||
import android.view.Display;
|
||||
@@ -13,6 +14,7 @@ import io.flutter.embedding.engine.FlutterJNI;
|
||||
|
||||
// TODO(mattcarroll): add javadoc.
|
||||
public class VsyncWaiter {
|
||||
@TargetApi(17)
|
||||
class DisplayListener implements DisplayManager.DisplayListener {
|
||||
DisplayListener(DisplayManager displayManager) {
|
||||
this.displayManager = displayManager;
|
||||
@@ -57,6 +59,7 @@ public class VsyncWaiter {
|
||||
return instance;
|
||||
}
|
||||
|
||||
@TargetApi(17)
|
||||
@NonNull
|
||||
public static VsyncWaiter getInstance(
|
||||
@NonNull DisplayManager displayManager, @NonNull FlutterJNI flutterJNI) {
|
||||
|
||||
@@ -64,6 +64,7 @@ import org.robolectric.annotation.Config;
|
||||
|
||||
@Config(manifest = Config.NONE)
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@TargetApi(19)
|
||||
public class AccessibilityBridgeTest {
|
||||
|
||||
@Test
|
||||
|
||||
@@ -14,6 +14,7 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.robolectric.Shadows.shadowOf;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.hardware.display.DisplayManager;
|
||||
import android.os.Looper;
|
||||
import android.view.Display;
|
||||
@@ -49,6 +50,7 @@ public class VsyncWaiterTest {
|
||||
verify(mockFlutterJNI, times(1)).onVsync(anyLong(), eq(1000000000l / 10l), eq(1l));
|
||||
}
|
||||
|
||||
@TargetApi(17)
|
||||
@Test
|
||||
public void itSetsFpsWhenDisplayManagerUpdates() {
|
||||
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
|
||||
@@ -84,6 +86,7 @@ public class VsyncWaiterTest {
|
||||
verify(mockFlutterJNI, times(1)).onVsync(anyLong(), eq(1000000000l / 60l), eq(1l));
|
||||
}
|
||||
|
||||
@TargetApi(17)
|
||||
@Test
|
||||
public void itSetsFpsWhenDisplayManagerDoesNotUpdate() {
|
||||
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
|
||||
|
||||
Reference in New Issue
Block a user