diff --git a/shell/platform/android/io/flutter/embedding/engine/android/FlutterSurfaceView.java b/shell/platform/android/io/flutter/embedding/engine/android/FlutterSurfaceView.java index dc52f9e6d46f9..cbff0b4ac828c 100644 --- a/shell/platform/android/io/flutter/embedding/engine/android/FlutterSurfaceView.java +++ b/shell/platform/android/io/flutter/embedding/engine/android/FlutterSurfaceView.java @@ -134,7 +134,7 @@ public void detachFromRenderer() { if (flutterRenderer != null) { // If we're attached to an Android window then we were rendering a Flutter UI. Now that // this FlutterSurfaceView is detached from the FlutterRenderer, we need to stop rendering. - if (isAttachedToWindow()) { + if (getWindowToken() != null) { disconnectSurfaceFromRenderer(); } diff --git a/shell/platform/android/io/flutter/embedding/engine/android/FlutterTextureView.java b/shell/platform/android/io/flutter/embedding/engine/android/FlutterTextureView.java index c12c2fa4274fc..f39c6d4e77a82 100644 --- a/shell/platform/android/io/flutter/embedding/engine/android/FlutterTextureView.java +++ b/shell/platform/android/io/flutter/embedding/engine/android/FlutterTextureView.java @@ -145,7 +145,7 @@ public void detachFromRenderer() { if (flutterRenderer != null) { // If we're attached to an Android window then we were rendering a Flutter UI. Now that // this FlutterTextureView is detached from the FlutterRenderer, we need to stop rendering. - if (isAttachedToWindow()) { + if (getWindowToken() != null) { disconnectSurfaceFromRenderer(); } diff --git a/shell/platform/android/io/flutter/embedding/engine/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/engine/android/FlutterView.java index c7d86cbf2e6ab..7e586670e477f 100644 --- a/shell/platform/android/io/flutter/embedding/engine/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/engine/android/FlutterView.java @@ -4,6 +4,7 @@ package io.flutter.embedding.engine.android; +import android.annotation.TargetApi; import android.content.Context; import android.content.res.Configuration; import android.graphics.Rect; @@ -12,6 +13,7 @@ import android.provider.Settings; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.RequiresApi; import android.text.format.DateFormat; import android.util.AttributeSet; import android.util.Log; @@ -179,6 +181,8 @@ protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) * the wider than expected padding when the status and navigation bars are hidden. */ @Override + @TargetApi(20) + @RequiresApi(20) public final WindowInsets onApplyWindowInsets(WindowInsets insets) { WindowInsets newInsets = super.onApplyWindowInsets(insets); diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java b/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java index e9d44001ef0f5..e1e5f9f9cdbaa 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java @@ -132,12 +132,15 @@ private void setSystemChromeApplicationSwitcherDescription(PlatformChannel.AppSw return; } - @SuppressWarnings("deprecation") - TaskDescription taskDescription = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) - ? new TaskDescription(description.label, 0, description.color) - : new TaskDescription(description.label, null, description.color); - - activity.setTaskDescription(taskDescription); + // Linter refuses to believe we're only executing this code in API 28 unless we use distinct if blocks and + // hardcode the API 28 constant. + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P && Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) { + activity.setTaskDescription(new TaskDescription(description.label)); + } + if (Build.VERSION.SDK_INT >= 28) { + TaskDescription taskDescription = new TaskDescription(description.label, 0, description.color); + activity.setTaskDescription(taskDescription); + } } private void setSystemChromeEnabledSystemUIOverlays(List overlaysToShow) { diff --git a/shell/platform/android/io/flutter/view/AccessibilityBridge.java b/shell/platform/android/io/flutter/view/AccessibilityBridge.java index ac5e2f8f3d995..add434080c054 100644 --- a/shell/platform/android/io/flutter/view/AccessibilityBridge.java +++ b/shell/platform/android/io/flutter/view/AccessibilityBridge.java @@ -4,12 +4,15 @@ package io.flutter.view; +import android.annotation.TargetApi; import android.app.Activity; import android.graphics.Rect; import android.opengl.Matrix; import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; +import android.support.annotation.NonNull; +import android.support.annotation.RequiresApi; import android.util.Log; import android.view.View; import android.view.accessibility.AccessibilityEvent; @@ -84,10 +87,12 @@ enum Action { SHOW_ON_SCREEN(1 << 8), MOVE_CURSOR_FORWARD_BY_CHARACTER(1 << 9), MOVE_CURSOR_BACKWARD_BY_CHARACTER(1 << 10), + /** These actions are only supported on Android 4.3 and above. */ SET_SELECTION(1 << 11), COPY(1 << 12), CUT(1 << 13), PASTE(1 << 14), + /** End 4.3 only supported actions. */ DID_GAIN_ACCESSIBILITY_FOCUS(1 << 15), DID_LOSE_ACCESSIBILITY_FOCUS(1 << 16), CUSTOM_ACTION(1 << 17), @@ -233,17 +238,22 @@ public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) { } result.setMovementGranularities(granularities); } - if (object.hasAction(Action.SET_SELECTION)) { - result.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION); - } - if (object.hasAction(Action.COPY)) { - result.addAction(AccessibilityNodeInfo.ACTION_COPY); - } - if (object.hasAction(Action.CUT)) { - result.addAction(AccessibilityNodeInfo.ACTION_CUT); - } - if (object.hasAction(Action.PASTE)) { - result.addAction(AccessibilityNodeInfo.ACTION_PASTE); + + // 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 (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2) { + if (object.hasAction(Action.SET_SELECTION)) { + result.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION); + } + if (object.hasAction(Action.COPY)) { + result.addAction(AccessibilityNodeInfo.ACTION_COPY); + } + if (object.hasAction(Action.CUT)) { + result.addAction(AccessibilityNodeInfo.ACTION_CUT); + } + if (object.hasAction(Action.PASTE)) { + result.addAction(AccessibilityNodeInfo.ACTION_PASTE); + } } if (object.hasFlag(Flag.IS_BUTTON)) { @@ -312,11 +322,14 @@ public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) { // We should prefer setCollectionInfo to the class names, as this way we get "In List" // and "Out of list" announcements. But we don't always know the counts, so we // can fallback to the generic scroll view class names. + // + // On older APIs, we always fall back to the generic scroll view class names here. + // // TODO(dnfield): We should add semantics properties for rows and columns in 2 dimensional lists, e.g. // GridView. Right now, we're only supporting ListViews and only if they have scroll children. if (object.hasFlag(Flag.HAS_IMPLICIT_SCROLLING)) { if (object.hasAction(Action.SCROLL_LEFT) || object.hasAction(Action.SCROLL_RIGHT)) { - if (shouldSetCollectionInfo(object)) { + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT && shouldSetCollectionInfo(object)) { result.setCollectionInfo(AccessibilityNodeInfo.CollectionInfo.obtain( 0, // rows object.scrollChildren, // columns @@ -325,7 +338,7 @@ public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) { result.setClassName("android.widget.HorizontalScrollView"); } } else { - if (shouldSetCollectionInfo(object)) { + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2 && shouldSetCollectionInfo(object)) { result.setCollectionInfo(AccessibilityNodeInfo.CollectionInfo.obtain( object.scrollChildren, // rows 0, // columns @@ -465,9 +478,21 @@ public boolean performAction(int virtualViewId, int action, Bundle arguments) { return true; } 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(object, 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(object, virtualViewId, arguments, true); } case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: { @@ -502,6 +527,12 @@ public boolean performAction(int virtualViewId, int action, Bundle arguments) { return true; } 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 selection = new HashMap<>(); final boolean hasSelection = arguments != null && arguments.containsKey( @@ -553,6 +584,8 @@ public boolean performAction(int virtualViewId, int action, Bundle arguments) { return false; } + @RequiresApi(18) + @TargetApi(18) boolean performCursorMoveAction( SemanticsObject object, int virtualViewId, Bundle arguments, boolean forward) { final int granularity = diff --git a/shell/platform/android/io/flutter/view/FlutterView.java b/shell/platform/android/io/flutter/view/FlutterView.java index 8df25207422ba..6161a23c5f4af 100644 --- a/shell/platform/android/io/flutter/view/FlutterView.java +++ b/shell/platform/android/io/flutter/view/FlutterView.java @@ -4,6 +4,7 @@ package io.flutter.view; +import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.content.res.Configuration; @@ -17,6 +18,7 @@ import android.os.Handler; import android.os.LocaleList; import android.provider.Settings; +import android.support.annotation.RequiresApi; import android.text.format.DateFormat; import android.util.AttributeSet; import android.util.Log; @@ -654,6 +656,8 @@ else if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) { // be padded. When the on-screen keyboard is detected, we want to include the full inset // but when the inset is just the hidden nav bar, we want to provide a zero inset so the space // can be used. + @TargetApi(20) + @RequiresApi(20) int calculateBottomKeyboardInset(WindowInsets insets) { int screenHeight = getRootView().getHeight(); // Magic number due to this being a heuristic. This should be replaced, but we have not @@ -672,6 +676,8 @@ int calculateBottomKeyboardInset(WindowInsets insets) { // This callback is not present in API < 20, which means lower API devices will see // the wider than expected padding when the status and navigation bars are hidden. @Override + @TargetApi(20) + @RequiresApi(20) public final WindowInsets onApplyWindowInsets(WindowInsets insets) { boolean statusBarHidden = (SYSTEM_UI_FLAG_FULLSCREEN & getWindowSystemUiVisibility()) != 0; @@ -963,8 +969,9 @@ public void onChange(boolean selfChange) { @Override public void onChange(boolean selfChange, Uri uri) { - String value = Settings.Global.getString(getContext().getContentResolver(), - Settings.Global.TRANSITION_ANIMATION_SCALE); + String value = Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1 ? null + : Settings.Global.getString(getContext().getContentResolver(), + Settings.Global.TRANSITION_ANIMATION_SCALE); if (value != null && value.equals("0")) { mAccessibilityFeatureFlags |= AccessibilityFeature.DISABLE_ANIMATIONS.value; } else { @@ -974,6 +981,9 @@ public void onChange(boolean selfChange, Uri uri) { } } + // This is guarded at instantiation time. + @TargetApi(19) + @RequiresApi(19) class TouchExplorationListener implements AccessibilityManager.TouchExplorationStateChangeListener { @Override public void onTouchExplorationStateChanged(boolean enabled) { diff --git a/shell/platform/android/io/flutter/view/ResourceExtractor.java b/shell/platform/android/io/flutter/view/ResourceExtractor.java index 40fc75982a972..ecbd026c9db09 100644 --- a/shell/platform/android/io/flutter/view/ResourceExtractor.java +++ b/shell/platform/android/io/flutter/view/ResourceExtractor.java @@ -4,6 +4,8 @@ package io.flutter.view; +import static java.util.Arrays.asList; + import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; @@ -16,6 +18,7 @@ import org.json.JSONObject; import java.io.*; +import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.concurrent.CancellationException; @@ -30,10 +33,12 @@ class ResourceExtractor { private static final String TAG = "ResourceExtractor"; private static final String TIMESTAMP_PREFIX = "res_timestamp-"; + private static final String[] SUPPORTED_ABIS = getSupportedAbis(); @SuppressWarnings("deprecation") static long getVersionCode(PackageInfo packageInfo) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + // Linter needs P (28) hardcoded or else it will fail these lines. + if (Build.VERSION.SDK_INT >= 28) { return packageInfo.getLongVersionCode(); } else { return packageInfo.versionCode; @@ -244,7 +249,7 @@ private boolean extractUpdate(File dataDir) { ZipEntry entry = null; if (asset.endsWith(".so")) { // Replicate library lookup logic. - for (String abi : Build.SUPPORTED_ABIS) { + for (String abi : SUPPORTED_ABIS) { resource = "lib/" + abi + "/" + asset; entry = zipFile.getEntry(resource); if (entry == null) { @@ -403,4 +408,14 @@ private String getAPKPath() { return null; } } + + private static String[] getSupportedAbis() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && Build.SUPPORTED_ABIS.length > 0) { + return Build.SUPPORTED_ABIS; + } else { + ArrayList cpuAbis = new ArrayList(asList(Build.CPU_ABI, Build.CPU_ABI2)); + cpuAbis.removeAll(asList(null, "")); + return cpuAbis.toArray(new String[0]); + } + } } diff --git a/tools/android_lint/baseline.xml b/tools/android_lint/baseline.xml index 2a81ee20380c5..93bc535f5737d 100644 --- a/tools/android_lint/baseline.xml +++ b/tools/android_lint/baseline.xml @@ -8,7 +8,7 @@ errorLine2=" ~~~~~~"> @@ -19,7 +19,7 @@ errorLine2=" ~~~~~~"> @@ -30,7 +30,7 @@ errorLine2=" ~~~~~~"> @@ -41,7 +41,7 @@ errorLine2=" ~~~~~~"> @@ -52,7 +52,7 @@ errorLine2=" ~~~~~~"> @@ -63,7 +63,7 @@ errorLine2=" ~~~~~~"> @@ -74,7 +74,7 @@ errorLine2=" ~~~~~~"> @@ -85,7 +85,7 @@ errorLine2=" ~~~~~~"> @@ -96,7 +96,7 @@ errorLine2=" ~~~~~~"> @@ -107,7 +107,7 @@ errorLine2=" ~~~~~~"> @@ -118,7 +118,7 @@ errorLine2=" ~~~~~~"> @@ -129,7 +129,7 @@ errorLine2=" ~~~~~~"> @@ -155,116 +155,6 @@ column="9"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -342,336 +232,6 @@ column="71"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -767,7 +327,7 @@ errorLine2=" ^"> @@ -800,7 +360,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -811,7 +371,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -877,7 +437,7 @@ errorLine2=" ~~~~~~~~~~~~~~~"> @@ -888,7 +448,7 @@ errorLine2=" ~~~~~~~~~~~~~~~"> @@ -921,7 +481,7 @@ errorLine2=" ~~~~~~~~~~~~"> @@ -932,7 +492,7 @@ errorLine2=" ~~~~~~~~~~~~"> diff --git a/tools/android_lint/lint.xml b/tools/android_lint/lint.xml new file mode 100644 index 0000000000000..241207161d5d6 --- /dev/null +++ b/tools/android_lint/lint.xml @@ -0,0 +1,11 @@ + + + + + + + \ No newline at end of file