summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt22
-rw-r--r--api/system-current.txt24
-rw-r--r--cmds/am/src/com/android/commands/am/Am.java393
-rw-r--r--core/java/android/accessibilityservice/AccessibilityService.java435
-rw-r--r--core/java/android/accessibilityservice/AccessibilityServiceInfo.java16
-rw-r--r--core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl3
-rw-r--r--core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl16
-rw-r--r--core/java/android/app/TimePickerDialog.java16
-rw-r--r--core/java/android/app/UiAutomation.java8
-rw-r--r--core/java/android/bluetooth/BluetoothHeadset.java42
-rwxr-xr-xcore/java/android/bluetooth/IBluetoothHeadset.aidl2
-rw-r--r--core/java/android/content/Intent.java440
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java15
-rw-r--r--core/java/android/content/res/Resources.java337
-rw-r--r--core/java/android/os/IDeviceIdleController.aidl2
-rw-r--r--core/java/android/os/Process.java3
-rw-r--r--core/java/android/os/ShellCommand.java26
-rw-r--r--core/java/android/os/UserHandle.java17
-rw-r--r--core/java/android/os/storage/IMountService.java35
-rw-r--r--core/java/android/os/storage/StorageManager.java20
-rw-r--r--core/java/android/view/MagnificationSpec.java41
-rw-r--r--core/java/android/view/ViewRootImpl.java18
-rw-r--r--core/java/android/view/WindowManager.java13
-rw-r--r--core/java/android/view/WindowManagerInternal.java8
-rw-r--r--core/java/android/widget/PopupMenu.java147
-rw-r--r--core/java/android/widget/TimePicker.java96
-rw-r--r--core/java/android/widget/TimePickerClockDelegate.java143
-rw-r--r--core/java/android/widget/TimePickerSpinnerDelegate.java85
-rw-r--r--core/java/com/android/internal/os/BaseCommand.java60
-rw-r--r--core/java/com/android/internal/os/Zygote.java4
-rw-r--r--core/java/com/android/internal/os/ZygoteConnection.java4
-rw-r--r--core/java/com/android/internal/policy/DecorView.java1632
-rw-r--r--core/java/com/android/internal/policy/PhoneWindow.java1612
-rwxr-xr-xcore/jni/android/graphics/Bitmap.cpp1
-rw-r--r--core/jni/android_util_Log.cpp4
-rw-r--r--core/res/res/values-ja/strings.xml2
-rw-r--r--core/res/res/values/attrs.xml8
-rw-r--r--core/res/res/values/public.xml1
-rw-r--r--core/res/res/values/strings.xml6
-rw-r--r--core/res/res/values/symbols.xml2
-rw-r--r--docs/html/guide/topics/manifest/uses-feature-element.jd4
-rw-r--r--graphics/java/android/graphics/drawable/Drawable.java52
-rw-r--r--libs/hwui/Android.mk4
-rw-r--r--libs/hwui/BakedOpRenderer.cpp43
-rw-r--r--libs/hwui/BakedOpRenderer.h22
-rw-r--r--libs/hwui/Caches.cpp1
-rw-r--r--libs/hwui/DeviceInfo.cpp1
-rw-r--r--libs/hwui/Extensions.cpp2
-rw-r--r--libs/hwui/Extensions.h2
-rw-r--r--libs/hwui/OpReorderer.cpp62
-rw-r--r--libs/hwui/OpReorderer.h20
-rw-r--r--libs/hwui/RecordedOp.h14
-rw-r--r--libs/hwui/RecordingCanvas.cpp2
-rw-r--r--libs/hwui/RenderNode.cpp6
-rw-r--r--libs/hwui/Snapshot.cpp4
-rw-r--r--libs/hwui/microbench/OpReordererBench.cpp8
-rw-r--r--libs/hwui/renderstate/OffscreenBufferPool.h15
-rw-r--r--libs/hwui/renderthread/CanvasContext.cpp21
-rw-r--r--libs/hwui/renderthread/CanvasContext.h9
-rw-r--r--libs/hwui/unit_tests/OpReordererTests.cpp287
-rw-r--r--libs/hwui/unit_tests/TestUtils.h14
-rw-r--r--media/java/android/media/tv/TvContentRating.java2
-rw-r--r--packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java9
-rw-r--r--packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java2
-rw-r--r--packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DirectoryFragmentModelTest.java2
-rw-r--r--packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java3
-rw-r--r--packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java2
-rw-r--r--packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_SelectionTest.java3
-rw-r--r--packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java120
-rw-r--r--packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java6
-rw-r--r--packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java39
-rw-r--r--packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java3
-rw-r--r--packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java113
-rw-r--r--packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java10
-rw-r--r--packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java8
-rw-r--r--packages/MtpDocumentsProvider/tests/AndroidManifest.xml3
-rw-r--r--packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java22
-rw-r--r--packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java203
-rw-r--r--packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java11
-rw-r--r--packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java5
-rw-r--r--packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java2
-rw-r--r--packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResultInstrumentation.java14
-rw-r--r--packages/PrintSpooler/res/drawable/ic_add.xml25
-rw-r--r--packages/PrintSpooler/res/drawable/ic_search.xml43
-rw-r--r--packages/PrintSpooler/res/layout/printer_dropdown_prompt.xml28
-rw-r--r--packages/PrintSpooler/res/menu/select_printer_activity.xml4
-rw-r--r--packages/PrintSpooler/res/values/strings.xml3
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java118
-rw-r--r--packages/SettingsLib/res/values/strings.xml3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java172
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/ExitRecentsWindowFirstAnimationFrameEvent.java28
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java8
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java18
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java295
-rw-r--r--services/accessibility/java/com/android/server/accessibility/MagnificationController.java851
-rw-r--r--services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java (renamed from services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java)496
-rw-r--r--services/core/java/com/android/server/AlarmManagerService.java18
-rw-r--r--services/core/java/com/android/server/DeviceIdleController.java181
-rw-r--r--services/core/java/com/android/server/IntentResolver.java2
-rw-r--r--services/core/java/com/android/server/MountService.java7
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java11
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerShellCommand.java20
-rw-r--r--services/core/java/com/android/server/am/ActivityStack.java295
-rw-r--r--services/core/java/com/android/server/am/ActivityStackSupervisor.java7
-rw-r--r--services/core/java/com/android/server/content/SyncManager.java31
-rw-r--r--services/core/java/com/android/server/job/JobSchedulerService.java42
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java156
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerShellCommand.java123
-rw-r--r--services/core/java/com/android/server/pm/SELinuxMMAC.java6
-rw-r--r--services/core/java/com/android/server/pm/Settings.java2
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java413
-rw-r--r--services/core/java/com/android/server/wm/AccessibilityController.java62
-rw-r--r--services/core/java/com/android/server/wm/AppTransition.java25
-rw-r--r--services/core/java/com/android/server/wm/AppWindowAnimator.java20
-rw-r--r--services/core/java/com/android/server/wm/AppWindowToken.java2
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java32
-rw-r--r--services/core/java/com/android/server/wm/DragState.java1
-rw-r--r--services/core/java/com/android/server/wm/InputMonitor.java8
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java185
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java125
-rw-r--r--services/core/java/com/android/server/wm/WindowStateAnimator.java46
-rw-r--r--services/core/java/com/android/server/wm/WindowSurfacePlacer.java17
-rw-r--r--tools/aapt2/ResourceParser.cpp30
-rw-r--r--tools/aapt2/ResourceUtils.cpp43
-rw-r--r--tools/aapt2/ResourceUtils.h10
-rw-r--r--tools/aapt2/ResourceUtils_test.cpp20
-rw-r--r--tools/aapt2/XmlDom.h7
-rw-r--r--tools/aapt2/link/Link.cpp31
-rw-r--r--tools/aapt2/link/ReferenceLinkerVisitor.h4
-rw-r--r--tools/aapt2/link/XmlReferenceLinker.cpp23
-rw-r--r--tools/aapt2/process/SymbolTable.cpp48
-rw-r--r--tools/aapt2/unflatten/BinaryResourceParser.cpp5
-rw-r--r--tools/aapt2/util/Util.h35
-rw-r--r--tools/aapt2/util/Util_test.cpp9
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java23
-rw-r--r--wifi/java/android/net/wifi/WifiEnterpriseConfig.java4
-rw-r--r--wifi/java/android/net/wifi/WifiScanner.java22
143 files changed, 6953 insertions, 4280 deletions
diff --git a/api/current.txt b/api/current.txt
index 96ecad617a91..cca8293486e3 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -332,6 +332,7 @@ package android {
field public static final int calendarTextColor = 16843931; // 0x101049b
field public static final int calendarViewShown = 16843596; // 0x101034c
field public static final int calendarViewStyle = 16843613; // 0x101035d
+ field public static final int canControlMagnification = 16844040; // 0x1010508
field public static final int canRequestEnhancedWebAccessibility = 16843736; // 0x10103d8
field public static final int canRequestFilterKeyEvents = 16843737; // 0x10103d9
field public static final int canRequestTouchExplorationMode = 16843735; // 0x10103d7
@@ -2619,6 +2620,7 @@ package android.accessibilityservice {
public abstract class AccessibilityService extends android.app.Service {
ctor public AccessibilityService();
method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
+ method public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController();
method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo();
method public java.util.List<android.view.accessibility.AccessibilityWindowInfo> getWindows();
@@ -2656,6 +2658,23 @@ package android.accessibilityservice {
field public static final java.lang.String SERVICE_META_DATA = "android.accessibilityservice";
}
+ public static final class AccessibilityService.MagnificationController {
+ method public void addListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener);
+ method public void addListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener, android.os.Handler);
+ method public float getCenterX();
+ method public float getCenterY();
+ method public android.graphics.Region getMagnifiedRegion();
+ method public float getScale();
+ method public boolean removeListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener);
+ method public boolean reset(boolean);
+ method public boolean setCenter(float, float, boolean);
+ method public boolean setScale(float, boolean);
+ }
+
+ public static abstract interface AccessibilityService.MagnificationController.OnMagnificationChangedListener {
+ method public abstract void onMagnificationChanged(android.accessibilityservice.AccessibilityService.MagnificationController, android.graphics.Region, float, float, float);
+ }
+
public class AccessibilityServiceInfo implements android.os.Parcelable {
ctor public AccessibilityServiceInfo();
method public static java.lang.String capabilityToString(int);
@@ -2670,6 +2689,7 @@ package android.accessibilityservice {
method public java.lang.String getSettingsActivityName();
method public java.lang.String loadDescription(android.content.pm.PackageManager);
method public void writeToParcel(android.os.Parcel, int);
+ field public static final int CAPABILITY_CAN_CONTROL_MAGNIFICATION = 16; // 0x10
field public static final int CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 4; // 0x4
field public static final int CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS = 8; // 0x8
field public static final int CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 2; // 0x2
@@ -9920,6 +9940,7 @@ package android.content.res {
public static class Resources.NotFoundException extends java.lang.RuntimeException {
ctor public Resources.NotFoundException();
ctor public Resources.NotFoundException(java.lang.String);
+ ctor public Resources.NotFoundException(java.lang.String, java.lang.Exception);
}
public final class Resources.Theme {
@@ -17776,6 +17797,7 @@ package android.media.session {
package android.media.tv {
public final class TvContentRating {
+ method public final boolean contains(android.media.tv.TvContentRating);
method public static android.media.tv.TvContentRating createRating(java.lang.String, java.lang.String, java.lang.String, java.lang.String...);
method public java.lang.String flattenToString();
method public java.lang.String getDomain();
diff --git a/api/system-current.txt b/api/system-current.txt
index 0605851f058d..3946d4abeae3 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -424,6 +424,7 @@ package android {
field public static final int calendarTextColor = 16843931; // 0x101049b
field public static final int calendarViewShown = 16843596; // 0x101034c
field public static final int calendarViewStyle = 16843613; // 0x101035d
+ field public static final int canControlMagnification = 16844040; // 0x1010508
field public static final int canRequestEnhancedWebAccessibility = 16843736; // 0x10103d8
field public static final int canRequestFilterKeyEvents = 16843737; // 0x10103d9
field public static final int canRequestTouchExplorationMode = 16843735; // 0x10103d7
@@ -2718,6 +2719,7 @@ package android.accessibilityservice {
public abstract class AccessibilityService extends android.app.Service {
ctor public AccessibilityService();
method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
+ method public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController();
method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo();
method public java.util.List<android.view.accessibility.AccessibilityWindowInfo> getWindows();
@@ -2755,6 +2757,23 @@ package android.accessibilityservice {
field public static final java.lang.String SERVICE_META_DATA = "android.accessibilityservice";
}
+ public static final class AccessibilityService.MagnificationController {
+ method public void addListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener);
+ method public void addListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener, android.os.Handler);
+ method public float getCenterX();
+ method public float getCenterY();
+ method public android.graphics.Region getMagnifiedRegion();
+ method public float getScale();
+ method public boolean removeListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener);
+ method public boolean reset(boolean);
+ method public boolean setCenter(float, float, boolean);
+ method public boolean setScale(float, boolean);
+ }
+
+ public static abstract interface AccessibilityService.MagnificationController.OnMagnificationChangedListener {
+ method public abstract void onMagnificationChanged(android.accessibilityservice.AccessibilityService.MagnificationController, android.graphics.Region, float, float, float);
+ }
+
public class AccessibilityServiceInfo implements android.os.Parcelable {
ctor public AccessibilityServiceInfo();
method public static java.lang.String capabilityToString(int);
@@ -2769,6 +2788,7 @@ package android.accessibilityservice {
method public java.lang.String getSettingsActivityName();
method public java.lang.String loadDescription(android.content.pm.PackageManager);
method public void writeToParcel(android.os.Parcel, int);
+ field public static final int CAPABILITY_CAN_CONTROL_MAGNIFICATION = 16; // 0x10
field public static final int CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 4; // 0x4
field public static final int CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS = 8; // 0x8
field public static final int CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 2; // 0x2
@@ -10261,6 +10281,7 @@ package android.content.res {
public static class Resources.NotFoundException extends java.lang.RuntimeException {
ctor public Resources.NotFoundException();
ctor public Resources.NotFoundException(java.lang.String);
+ ctor public Resources.NotFoundException(java.lang.String, java.lang.Exception);
}
public final class Resources.Theme {
@@ -21494,10 +21515,13 @@ package android.net.wifi {
method public void writeToParcel(android.os.Parcel, int);
field public int band;
field public android.net.wifi.WifiScanner.ChannelSpec[] channels;
+ field public int exponent;
+ field public int maxPeriodInMs;
field public int maxScansToCache;
field public int numBssidsPerScan;
field public int periodInMs;
field public int reportEvents;
+ field public int stepCount;
}
public static abstract interface WifiScanner.WifiChangeListener implements android.net.wifi.WifiScanner.ActionListener {
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 7f33cb5df1a3..62e0919a3d84 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -55,6 +55,7 @@ import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.SELinux;
import android.os.ServiceManager;
+import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -72,6 +73,7 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
+import java.io.PrintWriter;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
@@ -117,7 +119,8 @@ public class Am extends BaseCommand {
@Override
public void onShowUsage(PrintStream out) {
- out.println(
+ PrintWriter pw = new PrintWriter(out);
+ pw.println(
"usage: am [subcommand] [options]\n" +
"usage: am start [-D] [-W] [-P <FILE>] [--start-profiler <FILE>]\n" +
" [--sampling INTERVAL] [-R COUNT] [-S]\n" +
@@ -337,52 +340,10 @@ public class Am extends BaseCommand {
"am send-trim-memory: send a memory trim event to a <PROCESS>.\n" +
"\n" +
"am get-current-user: returns id of the current foreground user.\n" +
- "\n" +
- "<INTENT> specifications include these flags and arguments:\n" +
- " [-a <ACTION>] [-d <DATA_URI>] [-t <MIME_TYPE>]\n" +
- " [-c <CATEGORY> [-c <CATEGORY>] ...]\n" +
- " [-e|--es <EXTRA_KEY> <EXTRA_STRING_VALUE> ...]\n" +
- " [--esn <EXTRA_KEY> ...]\n" +
- " [--ez <EXTRA_KEY> <EXTRA_BOOLEAN_VALUE> ...]\n" +
- " [--ei <EXTRA_KEY> <EXTRA_INT_VALUE> ...]\n" +
- " [--el <EXTRA_KEY> <EXTRA_LONG_VALUE> ...]\n" +
- " [--ef <EXTRA_KEY> <EXTRA_FLOAT_VALUE> ...]\n" +
- " [--eu <EXTRA_KEY> <EXTRA_URI_VALUE> ...]\n" +
- " [--ecn <EXTRA_KEY> <EXTRA_COMPONENT_NAME_VALUE>]\n" +
- " [--eia <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...]]\n" +
- " (mutiple extras passed as Integer[])\n" +
- " [--eial <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...]]\n" +
- " (mutiple extras passed as List<Integer>)\n" +
- " [--ela <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]]\n" +
- " (mutiple extras passed as Long[])\n" +
- " [--elal <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]]\n" +
- " (mutiple extras passed as List<Long>)\n" +
- " [--efa <EXTRA_KEY> <EXTRA_FLOAT_VALUE>[,<EXTRA_FLOAT_VALUE...]]\n" +
- " (mutiple extras passed as Float[])\n" +
- " [--efal <EXTRA_KEY> <EXTRA_FLOAT_VALUE>[,<EXTRA_FLOAT_VALUE...]]\n" +
- " (mutiple extras passed as List<Float>)\n" +
- " [--esa <EXTRA_KEY> <EXTRA_STRING_VALUE>[,<EXTRA_STRING_VALUE...]]\n" +
- " (mutiple extras passed as String[]; to embed a comma into a string,\n" +
- " escape it using \"\\,\")\n" +
- " [--esal <EXTRA_KEY> <EXTRA_STRING_VALUE>[,<EXTRA_STRING_VALUE...]]\n" +
- " (mutiple extras passed as List<String>; to embed a comma into a string,\n" +
- " escape it using \"\\,\")\n" +
- " [--grant-read-uri-permission] [--grant-write-uri-permission]\n" +
- " [--grant-persistable-uri-permission] [--grant-prefix-uri-permission]\n" +
- " [--debug-log-resolution] [--exclude-stopped-packages]\n" +
- " [--include-stopped-packages]\n" +
- " [--activity-brought-to-front] [--activity-clear-top]\n" +
- " [--activity-clear-when-task-reset] [--activity-exclude-from-recents]\n" +
- " [--activity-launched-from-history] [--activity-multiple-task]\n" +
- " [--activity-no-animation] [--activity-no-history]\n" +
- " [--activity-no-user-action] [--activity-previous-is-top]\n" +
- " [--activity-reorder-to-front] [--activity-reset-task-if-needed]\n" +
- " [--activity-single-top] [--activity-clear-task]\n" +
- " [--activity-task-on-home]\n" +
- " [--receiver-registered-only] [--receiver-replace-pending]\n" +
- " [--selector]\n" +
- " [<URI> | <PACKAGE> | <COMPONENT>]\n"
- );
+ "\n"
+ );
+ Intent.printIntentArgsHelp(pw, "");
+ pw.flush();
}
@Override
@@ -486,10 +447,6 @@ public class Am extends BaseCommand {
}
private Intent makeIntent(int defUser) throws URISyntaxException {
- Intent intent = new Intent();
- Intent baseIntent = intent;
- boolean hasIntentInfo = false;
-
mStartFlags = 0;
mWaitOption = false;
mStopOption = false;
@@ -498,316 +455,38 @@ public class Am extends BaseCommand {
mSamplingInterval = 0;
mAutoStop = false;
mUserId = defUser;
- Uri data = null;
- String type = null;
- String opt;
- while ((opt=nextOption()) != null) {
- if (opt.equals("-a")) {
- intent.setAction(nextArgRequired());
- if (intent == baseIntent) {
- hasIntentInfo = true;
- }
- } else if (opt.equals("-d")) {
- data = Uri.parse(nextArgRequired());
- if (intent == baseIntent) {
- hasIntentInfo = true;
- }
- } else if (opt.equals("-t")) {
- type = nextArgRequired();
- if (intent == baseIntent) {
- hasIntentInfo = true;
- }
- } else if (opt.equals("-c")) {
- intent.addCategory(nextArgRequired());
- if (intent == baseIntent) {
- hasIntentInfo = true;
- }
- } else if (opt.equals("-e") || opt.equals("--es")) {
- String key = nextArgRequired();
- String value = nextArgRequired();
- intent.putExtra(key, value);
- } else if (opt.equals("--esn")) {
- String key = nextArgRequired();
- intent.putExtra(key, (String) null);
- } else if (opt.equals("--ei")) {
- String key = nextArgRequired();
- String value = nextArgRequired();
- intent.putExtra(key, Integer.decode(value));
- } else if (opt.equals("--eu")) {
- String key = nextArgRequired();
- String value = nextArgRequired();
- intent.putExtra(key, Uri.parse(value));
- } else if (opt.equals("--ecn")) {
- String key = nextArgRequired();
- String value = nextArgRequired();
- ComponentName cn = ComponentName.unflattenFromString(value);
- if (cn == null) throw new IllegalArgumentException("Bad component name: " + value);
- intent.putExtra(key, cn);
- } else if (opt.equals("--eia")) {
- String key = nextArgRequired();
- String value = nextArgRequired();
- String[] strings = value.split(",");
- int[] list = new int[strings.length];
- for (int i = 0; i < strings.length; i++) {
- list[i] = Integer.decode(strings[i]);
- }
- intent.putExtra(key, list);
- } else if (opt.equals("--eial")) {
- String key = nextArgRequired();
- String value = nextArgRequired();
- String[] strings = value.split(",");
- ArrayList<Integer> list = new ArrayList<>(strings.length);
- for (int i = 0; i < strings.length; i++) {
- list.add(Integer.decode(strings[i]));
- }
- intent.putExtra(key, list);
- } else if (opt.equals("--el")) {
- String key = nextArgRequired();
- String value = nextArgRequired();
- intent.putExtra(key, Long.valueOf(value));
- } else if (opt.equals("--ela")) {
- String key = nextArgRequired();
- String value = nextArgRequired();
- String[] strings = value.split(",");
- long[] list = new long[strings.length];
- for (int i = 0; i < strings.length; i++) {
- list[i] = Long.valueOf(strings[i]);
- }
- intent.putExtra(key, list);
- hasIntentInfo = true;
- } else if (opt.equals("--elal")) {
- String key = nextArgRequired();
- String value = nextArgRequired();
- String[] strings = value.split(",");
- ArrayList<Long> list = new ArrayList<>(strings.length);
- for (int i = 0; i < strings.length; i++) {
- list.add(Long.valueOf(strings[i]));
- }
- intent.putExtra(key, list);
- hasIntentInfo = true;
- } else if (opt.equals("--ef")) {
- String key = nextArgRequired();
- String value = nextArgRequired();
- intent.putExtra(key, Float.valueOf(value));
- hasIntentInfo = true;
- } else if (opt.equals("--efa")) {
- String key = nextArgRequired();
- String value = nextArgRequired();
- String[] strings = value.split(",");
- float[] list = new float[strings.length];
- for (int i = 0; i < strings.length; i++) {
- list[i] = Float.valueOf(strings[i]);
- }
- intent.putExtra(key, list);
- hasIntentInfo = true;
- } else if (opt.equals("--efal")) {
- String key = nextArgRequired();
- String value = nextArgRequired();
- String[] strings = value.split(",");
- ArrayList<Float> list = new ArrayList<>(strings.length);
- for (int i = 0; i < strings.length; i++) {
- list.add(Float.valueOf(strings[i]));
- }
- intent.putExtra(key, list);
- hasIntentInfo = true;
- } else if (opt.equals("--esa")) {
- String key = nextArgRequired();
- String value = nextArgRequired();
- // Split on commas unless they are preceeded by an escape.
- // The escape character must be escaped for the string and
- // again for the regex, thus four escape characters become one.
- String[] strings = value.split("(?<!\\\\),");
- intent.putExtra(key, strings);
- hasIntentInfo = true;
- } else if (opt.equals("--esal")) {
- String key = nextArgRequired();
- String value = nextArgRequired();
- // Split on commas unless they are preceeded by an escape.
- // The escape character must be escaped for the string and
- // again for the regex, thus four escape characters become one.
- String[] strings = value.split("(?<!\\\\),");
- ArrayList<String> list = new ArrayList<>(strings.length);
- for (int i = 0; i < strings.length; i++) {
- list.add(strings[i]);
- }
- intent.putExtra(key, list);
- hasIntentInfo = true;
- } else if (opt.equals("--ez")) {
- String key = nextArgRequired();
- String value = nextArgRequired().toLowerCase();
- // Boolean.valueOf() results in false for anything that is not "true", which is
- // error-prone in shell commands
- boolean arg;
- if ("true".equals(value) || "t".equals(value)) {
- arg = true;
- } else if ("false".equals(value) || "f".equals(value)) {
- arg = false;
+ return Intent.parseCommandArgs(mArgs, new Intent.CommandOptionHandler() {
+ @Override
+ public boolean handleOption(String opt, ShellCommand cmd) {
+ if (opt.equals("-D")) {
+ mStartFlags |= ActivityManager.START_FLAG_DEBUG;
+ } else if (opt.equals("-W")) {
+ mWaitOption = true;
+ } else if (opt.equals("-P")) {
+ mProfileFile = nextArgRequired();
+ mAutoStop = true;
+ } else if (opt.equals("--start-profiler")) {
+ mProfileFile = nextArgRequired();
+ mAutoStop = false;
+ } else if (opt.equals("--sampling")) {
+ mSamplingInterval = Integer.parseInt(nextArgRequired());
+ } else if (opt.equals("-R")) {
+ mRepeat = Integer.parseInt(nextArgRequired());
+ } else if (opt.equals("-S")) {
+ mStopOption = true;
+ } else if (opt.equals("--track-allocation")) {
+ mStartFlags |= ActivityManager.START_FLAG_TRACK_ALLOCATION;
+ } else if (opt.equals("--user")) {
+ mUserId = parseUserArg(nextArgRequired());
+ } else if (opt.equals("--receiver-permission")) {
+ mReceiverPermission = nextArgRequired();
} else {
- try {
- arg = Integer.decode(value) != 0;
- } catch (NumberFormatException ex) {
- throw new IllegalArgumentException("Invalid boolean value: " + value);
- }
- }
-
- intent.putExtra(key, arg);
- } else if (opt.equals("-n")) {
- String str = nextArgRequired();
- ComponentName cn = ComponentName.unflattenFromString(str);
- if (cn == null) throw new IllegalArgumentException("Bad component name: " + str);
- intent.setComponent(cn);
- if (intent == baseIntent) {
- hasIntentInfo = true;
- }
- } else if (opt.equals("-p")) {
- String str = nextArgRequired();
- intent.setPackage(str);
- if (intent == baseIntent) {
- hasIntentInfo = true;
- }
- } else if (opt.equals("-f")) {
- String str = nextArgRequired();
- intent.setFlags(Integer.decode(str).intValue());
- } else if (opt.equals("--grant-read-uri-permission")) {
- intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- } else if (opt.equals("--grant-write-uri-permission")) {
- intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
- } else if (opt.equals("--grant-persistable-uri-permission")) {
- intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
- } else if (opt.equals("--grant-prefix-uri-permission")) {
- intent.addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
- } else if (opt.equals("--exclude-stopped-packages")) {
- intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
- } else if (opt.equals("--include-stopped-packages")) {
- intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
- } else if (opt.equals("--debug-log-resolution")) {
- intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION);
- } else if (opt.equals("--activity-brought-to-front")) {
- intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
- } else if (opt.equals("--activity-clear-top")) {
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- } else if (opt.equals("--activity-clear-when-task-reset")) {
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
- } else if (opt.equals("--activity-exclude-from-recents")) {
- intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
- } else if (opt.equals("--activity-launched-from-history")) {
- intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY);
- } else if (opt.equals("--activity-multiple-task")) {
- intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
- } else if (opt.equals("--activity-no-animation")) {
- intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
- } else if (opt.equals("--activity-no-history")) {
- intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
- } else if (opt.equals("--activity-no-user-action")) {
- intent.addFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION);
- } else if (opt.equals("--activity-previous-is-top")) {
- intent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
- } else if (opt.equals("--activity-reorder-to-front")) {
- intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
- } else if (opt.equals("--activity-reset-task-if-needed")) {
- intent.addFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
- } else if (opt.equals("--activity-single-top")) {
- intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
- } else if (opt.equals("--activity-clear-task")) {
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
- } else if (opt.equals("--activity-task-on-home")) {
- intent.addFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME);
- } else if (opt.equals("--receiver-registered-only")) {
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- } else if (opt.equals("--receiver-replace-pending")) {
- intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
- } else if (opt.equals("--selector")) {
- intent.setDataAndType(data, type);
- intent = new Intent();
- } else if (opt.equals("-D")) {
- mStartFlags |= ActivityManager.START_FLAG_DEBUG;
- } else if (opt.equals("-W")) {
- mWaitOption = true;
- } else if (opt.equals("-P")) {
- mProfileFile = nextArgRequired();
- mAutoStop = true;
- } else if (opt.equals("--start-profiler")) {
- mProfileFile = nextArgRequired();
- mAutoStop = false;
- } else if (opt.equals("--sampling")) {
- mSamplingInterval = Integer.parseInt(nextArgRequired());
- } else if (opt.equals("-R")) {
- mRepeat = Integer.parseInt(nextArgRequired());
- } else if (opt.equals("-S")) {
- mStopOption = true;
- } else if (opt.equals("--track-allocation")) {
- mStartFlags |= ActivityManager.START_FLAG_TRACK_ALLOCATION;
- } else if (opt.equals("--user")) {
- mUserId = parseUserArg(nextArgRequired());
- } else if (opt.equals("--receiver-permission")) {
- mReceiverPermission = nextArgRequired();
- } else {
- throw new IllegalArgumentException("Unknown option: " + opt);
- }
- }
- intent.setDataAndType(data, type);
-
- final boolean hasSelector = intent != baseIntent;
- if (hasSelector) {
- // A selector was specified; fix up.
- baseIntent.setSelector(intent);
- intent = baseIntent;
- }
-
- String arg = nextArg();
- baseIntent = null;
- if (arg == null) {
- if (hasSelector) {
- // If a selector has been specified, and no arguments
- // have been supplied for the main Intent, then we can
- // assume it is ACTION_MAIN CATEGORY_LAUNCHER; we don't
- // need to have a component name specified yet, the
- // selector will take care of that.
- baseIntent = new Intent(Intent.ACTION_MAIN);
- baseIntent.addCategory(Intent.CATEGORY_LAUNCHER);
- }
- } else if (arg.indexOf(':') >= 0) {
- // The argument is a URI. Fully parse it, and use that result
- // to fill in any data not specified so far.
- baseIntent = Intent.parseUri(arg, Intent.URI_INTENT_SCHEME
- | Intent.URI_ANDROID_APP_SCHEME | Intent.URI_ALLOW_UNSAFE);
- } else if (arg.indexOf('/') >= 0) {
- // The argument is a component name. Build an Intent to launch
- // it.
- baseIntent = new Intent(Intent.ACTION_MAIN);
- baseIntent.addCategory(Intent.CATEGORY_LAUNCHER);
- baseIntent.setComponent(ComponentName.unflattenFromString(arg));
- } else {
- // Assume the argument is a package name.
- baseIntent = new Intent(Intent.ACTION_MAIN);
- baseIntent.addCategory(Intent.CATEGORY_LAUNCHER);
- baseIntent.setPackage(arg);
- }
- if (baseIntent != null) {
- Bundle extras = intent.getExtras();
- intent.replaceExtras((Bundle)null);
- Bundle uriExtras = baseIntent.getExtras();
- baseIntent.replaceExtras((Bundle)null);
- if (intent.getAction() != null && baseIntent.getCategories() != null) {
- HashSet<String> cats = new HashSet<String>(baseIntent.getCategories());
- for (String c : cats) {
- baseIntent.removeCategory(c);
+ return false;
}
+ return true;
}
- intent.fillIn(baseIntent, Intent.FILL_IN_COMPONENT | Intent.FILL_IN_SELECTOR);
- if (extras == null) {
- extras = uriExtras;
- } else if (uriExtras != null) {
- uriExtras.putAll(extras);
- extras = uriExtras;
- }
- intent.replaceExtras(extras);
- hasIntentInfo = true;
- }
-
- if (!hasIntentInfo) throw new IllegalArgumentException("No intent supplied");
- return intent;
+ });
}
private void runStartService() throws Exception {
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 9d6aa132759c..273483a6db8a 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -17,14 +17,19 @@
package android.accessibilityservice;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
+import android.graphics.Region;
+import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
+import android.util.ArrayMap;
import android.util.Log;
+import android.util.Slog;
import android.view.KeyEvent;
import android.view.WindowManager;
import android.view.WindowManagerImpl;
@@ -36,7 +41,10 @@ import android.view.accessibility.AccessibilityWindowInfo;
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
+import java.util.ArrayList;
import java.util.List;
+import java.util.Map.Entry;
+import java.util.Set;
/**
* An accessibility service runs in the background and receives callbacks by the system
@@ -373,6 +381,8 @@ public abstract class AccessibilityService extends Service {
public void init(int connectionId, IBinder windowToken);
public boolean onGesture(int gestureId);
public boolean onKeyEvent(KeyEvent event);
+ public void onMagnificationChanged(@NonNull Region region,
+ float scale, float centerX, float centerY);
}
private int mConnectionId;
@@ -383,6 +393,8 @@ public abstract class AccessibilityService extends Service {
private WindowManager mWindowManager;
+ private MagnificationController mMagnificationController;
+
/**
* Callback for {@link android.view.accessibility.AccessibilityEvent}s.
*
@@ -396,6 +408,20 @@ public abstract class AccessibilityService extends Service {
public abstract void onInterrupt();
/**
+ * Dispatches service connection to internal components first, then the
+ * client code.
+ */
+ private void dispatchServiceConnected() {
+ if (mMagnificationController != null) {
+ mMagnificationController.onServiceConnected();
+ }
+
+ // The client gets to handle service connection last, after we've set
+ // up any state upon which their code may rely.
+ onServiceConnected();
+ }
+
+ /**
* This method is a part of the {@link AccessibilityService} lifecycle and is
* called after the system has successfully bound to the service. If is
* convenient to use this method for setting the {@link AccessibilityServiceInfo}.
@@ -513,6 +539,385 @@ public abstract class AccessibilityService extends Service {
}
/**
+ * Returns the magnification controller, which may be used to query and
+ * modify the state of display magnification.
+ * <p>
+ * <strong>Note:</strong> In order to control magnification, your service
+ * must declare the capability by setting the
+ * {@link android.R.styleable#AccessibilityService_canControlMagnification}
+ * property in its meta-data. For more information, see
+ * {@link #SERVICE_META_DATA}.
+ *
+ * @return the magnification controller
+ */
+ @NonNull
+ public final MagnificationController getMagnificationController() {
+ if (mMagnificationController == null) {
+ mMagnificationController = new MagnificationController(this);
+ }
+ return mMagnificationController;
+ }
+
+ private void onMagnificationChanged(@NonNull Region region, float scale,
+ float centerX, float centerY) {
+ if (mMagnificationController != null) {
+ mMagnificationController.dispatchMagnificationChanged(
+ region, scale, centerX, centerY);
+ }
+ }
+
+ /**
+ * Used to control and query the state of display magnification.
+ */
+ public static final class MagnificationController {
+ private final AccessibilityService mService;
+
+ /**
+ * Map of listeners to their handlers. Lazily created when adding the
+ * first magnification listener.
+ */
+ private ArrayMap<OnMagnificationChangedListener, Handler> mListeners;
+
+ MagnificationController(@NonNull AccessibilityService service) {
+ mService = service;
+ }
+
+ /**
+ * Called when the service is connected.
+ */
+ void onServiceConnected() {
+ if (mListeners != null && !mListeners.isEmpty()) {
+ setMagnificationCallbackEnabled(true);
+ }
+ }
+
+ /**
+ * Adds the specified change listener to the list of magnification
+ * change listeners. The callback will occur on the service's main
+ * thread.
+ *
+ * @param listener the listener to add, must be non-{@code null}
+ */
+ public void addListener(@NonNull OnMagnificationChangedListener listener) {
+ addListener(listener, null);
+ }
+
+ /**
+ * Adds the specified change listener to the list of magnification
+ * change listeners. The callback will occur on the specified
+ * {@link Handler}'s thread, or on the service's main thread if the
+ * handler is {@code null}.
+ *
+ * @param listener the listener to add, must be non-null
+ * @param handler the handler on which the callback should execute, or
+ * {@code null} to execute on the service's main thread
+ */
+ public void addListener(@NonNull OnMagnificationChangedListener listener,
+ @Nullable Handler handler) {
+ if (mListeners == null) {
+ mListeners = new ArrayMap<>();
+ }
+
+ final boolean shouldEnableCallback = mListeners.isEmpty();
+ mListeners.put(listener, handler);
+
+ if (shouldEnableCallback) {
+ // This may fail if the service is not connected yet, but if we
+ // still have listeners when it connects then we can try again.
+ setMagnificationCallbackEnabled(true);
+ }
+ }
+
+ /**
+ * Removes all instances of the specified change listener from the list
+ * of magnification change listeners.
+ *
+ * @param listener the listener to remove, must be non-null
+ * @return {@code true} if at least one instance of the listener was
+ * removed
+ */
+ public boolean removeListener(@NonNull OnMagnificationChangedListener listener) {
+ if (mListeners == null) {
+ return false;
+ }
+
+ final int keyIndex = mListeners.indexOfKey(listener);
+ final boolean hasKey = keyIndex >= 0;
+ if (hasKey) {
+ mListeners.removeAt(keyIndex);
+ }
+
+ if (hasKey && mListeners.isEmpty()) {
+ // We just removed the last listener, so we don't need
+ // callbacks from the service anymore.
+ setMagnificationCallbackEnabled(false);
+ }
+
+ return hasKey;
+ }
+
+ private void setMagnificationCallbackEnabled(boolean enabled) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance().getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ connection.setMagnificationCallbackEnabled(enabled);
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ }
+ }
+
+ /**
+ * Dispatches magnification changes to any registered listeners. This
+ * should be called on the service's main thread.
+ */
+ void dispatchMagnificationChanged(final @NonNull Region region, final float scale,
+ final float centerX, final float centerY) {
+ if (mListeners == null || mListeners.isEmpty()) {
+ Slog.d(LOG_TAG, "Received magnification changed "
+ + "callback with no listeners registered!");
+ setMagnificationCallbackEnabled(false);
+ return;
+ }
+
+ // Listeners may remove themselves. Perform a shallow copy to avoid
+ // concurrent modification.
+ final ArrayMap<OnMagnificationChangedListener, Handler> entries =
+ new ArrayMap<>(mListeners);
+
+ for (int i = 0, count = entries.size(); i < count; i++) {
+ final OnMagnificationChangedListener listener = entries.keyAt(i);
+ final Handler handler = entries.valueAt(i);
+ if (handler != null) {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ listener.onMagnificationChanged(MagnificationController.this,
+ region, scale, centerX, centerY);
+ }
+ });
+ } else {
+ // We're already on the main thread, just run the listener.
+ listener.onMagnificationChanged(this, region, scale, centerX, centerY);
+ }
+ }
+ }
+
+ /**
+ * Returns the current magnification scale.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been
+ * called) or the service has been disconnected, this method will
+ * return a default value of {@code 1.0f}.
+ *
+ * @return the current magnification scale
+ */
+ public float getScale() {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance().getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.getMagnificationScale();
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to obtain scale", re);
+ }
+ }
+ return 1.0f;
+ }
+
+ /**
+ * Returns the unscaled screen-relative X coordinate of the focal
+ * center of the magnified region. This is the point around which
+ * zooming occurs and is guaranteed to lie within the magnified
+ * region.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been
+ * called) or the service has been disconnected, this method will
+ * return a default value of {@code 0.0f}.
+ *
+ * @return the unscaled screen-relative X coordinate of the center of
+ * the magnified region
+ */
+ public float getCenterX() {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance().getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.getMagnificationCenterX();
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to obtain center X", re);
+ }
+ }
+ return 0.0f;
+ }
+
+ /**
+ * Returns the unscaled screen-relative Y coordinate of the focal
+ * center of the magnified region. This is the point around which
+ * zooming occurs and is guaranteed to lie within the magnified
+ * region.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been
+ * called) or the service has been disconnected, this method will
+ * return a default value of {@code 0.0f}.
+ *
+ * @return the unscaled screen-relative Y coordinate of the center of
+ * the magnified region
+ */
+ public float getCenterY() {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance().getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.getMagnificationCenterY();
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to obtain center Y", re);
+ }
+ }
+ return 0.0f;
+ }
+
+ /**
+ * Returns the region of the screen currently being magnified. If
+ * magnification is not enabled, the returned region will be empty.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been
+ * called) or the service has been disconnected, this method will
+ * return an empty region.
+ *
+ * @return the screen-relative bounds of the magnified region
+ */
+ @NonNull
+ public Region getMagnifiedRegion() {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance().getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.getMagnifiedRegion();
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to obtain magnified region", re);
+ }
+ }
+ return Region.obtain();
+ }
+
+ /**
+ * Resets magnification scale and center to their default (e.g. no
+ * magnification) values.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been
+ * called) or the service has been disconnected, this method will have
+ * no effect and return {@code false}.
+ *
+ * @param animate {@code true} to animate from the current scale and
+ * center or {@code false} to reset the scale and center
+ * immediately
+ * @return {@code true} on success, {@code false} on failure
+ */
+ public boolean reset(boolean animate) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance().getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.resetMagnification(animate);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to reset", re);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Sets the magnification scale.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been
+ * called) or the service has been disconnected, this method will have
+ * no effect and return {@code false}.
+ *
+ * @param scale the magnification scale to set, must be >= 1 and <= 5
+ * @param animate {@code true} to animate from the current scale or
+ * {@code false} to set the scale immediately
+ * @return {@code true} on success, {@code false} on failure
+ */
+ public boolean setScale(float scale, boolean animate) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance().getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.setMagnificationScaleAndCenter(
+ scale, Float.NaN, Float.NaN, animate);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to set scale", re);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Sets the center of the magnified viewport.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been
+ * called) or the service has been disconnected, this method will have
+ * no effect and return {@code false}.
+ *
+ * @param centerX the unscaled screen-relative X coordinate on which to
+ * center the viewport
+ * @param centerY the unscaled screen-relative Y coordinate on which to
+ * center the viewport
+ * @param animate {@code true} to animate from the current viewport
+ * center or {@code false} to set the center immediately
+ * @return {@code true} on success, {@code false} on failure
+ */
+ public boolean setCenter(float centerX, float centerY, boolean animate) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance().getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.setMagnificationScaleAndCenter(
+ Float.NaN, centerX, centerY, animate);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to set center", re);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Listener for changes in the state of magnification.
+ */
+ public interface OnMagnificationChangedListener {
+ /**
+ * Called when the magnified region, scale, or center changes.
+ *
+ * @param controller the magnification controller
+ * @param region the new magnified region, may be empty if
+ * magnification is not enabled (e.g. scale is 1)
+ * @param scale the new scale
+ * @param centerX the new X coordinate around which magnification is focused
+ * @param centerY the new Y coordinate around which magnification is focused
+ */
+ void onMagnificationChanged(@NonNull MagnificationController controller,
+ @NonNull Region region, float scale, float centerX, float centerY);
+ }
+ }
+
+ /**
* Performs a global action. Such an action can be performed
* at any moment regardless of the current application or user
* location in that application. For example going back, going
@@ -645,7 +1050,7 @@ public abstract class AccessibilityService extends Service {
return new IAccessibilityServiceClientWrapper(this, getMainLooper(), new Callbacks() {
@Override
public void onServiceConnected() {
- AccessibilityService.this.onServiceConnected();
+ AccessibilityService.this.dispatchServiceConnected();
}
@Override
@@ -678,6 +1083,12 @@ public abstract class AccessibilityService extends Service {
public boolean onKeyEvent(KeyEvent event) {
return AccessibilityService.this.onKeyEvent(event);
}
+
+ @Override
+ public void onMagnificationChanged(@NonNull Region region,
+ float scale, float centerX, float centerY) {
+ AccessibilityService.this.onMagnificationChanged(region, scale, centerX, centerY);
+ }
});
}
@@ -695,6 +1106,7 @@ public abstract class AccessibilityService extends Service {
private static final int DO_ON_GESTURE = 4;
private static final int DO_CLEAR_ACCESSIBILITY_CACHE = 5;
private static final int DO_ON_KEY_EVENT = 6;
+ private static final int DO_ON_MAGNIFICATION_CHANGED = 7;
private final HandlerCaller mCaller;
@@ -741,6 +1153,18 @@ public abstract class AccessibilityService extends Service {
mCaller.sendMessage(message);
}
+ public void onMagnificationChanged(@NonNull Region region,
+ float scale, float centerX, float centerY) {
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = region;
+ args.arg2 = scale;
+ args.arg3 = centerX;
+ args.arg4 = centerY;
+
+ final Message message = mCaller.obtainMessageO(DO_ON_MAGNIFICATION_CHANGED, args);
+ mCaller.sendMessage(message);
+ }
+
@Override
public void executeMessage(Message message) {
switch (message.what) {
@@ -816,6 +1240,15 @@ public abstract class AccessibilityService extends Service {
}
} return;
+ case DO_ON_MAGNIFICATION_CHANGED: {
+ final SomeArgs args = (SomeArgs) message.obj;
+ final Region region = (Region) args.arg1;
+ final float scale = (float) args.arg2;
+ final float centerX = (float) args.arg3;
+ final float centerY = (float) args.arg4;
+ mCallback.onMagnificationChanged(region, scale, centerX, centerY);
+ } return;
+
default :
Log.w(LOG_TAG, "Unknown message type " + message.what);
}
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 4edb0c6d1b07..2c98fef20cdd 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -104,6 +104,12 @@ public class AccessibilityServiceInfo implements Parcelable {
*/
public static final int CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS = 0x00000008;
+ /**
+ * Capability: This accessibility service can control display magnification.
+ * @see android.R.styleable#AccessibilityService_canControlMagnification
+ */
+ public static final int CAPABILITY_CAN_CONTROL_MAGNIFICATION = 0x00000010;
+
private static final SparseArray<CapabilityInfo> sAvailableCapabilityInfos =
new SparseArray<CapabilityInfo>();
static {
@@ -123,6 +129,10 @@ public class AccessibilityServiceInfo implements Parcelable {
new CapabilityInfo(CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS,
R.string.capability_title_canRequestFilterKeyEvents,
R.string.capability_desc_canRequestFilterKeyEvents));
+ sAvailableCapabilityInfos.put(CAPABILITY_CAN_CONTROL_MAGNIFICATION,
+ new CapabilityInfo(CAPABILITY_CAN_CONTROL_MAGNIFICATION,
+ R.string.capability_title_canControlMagnification,
+ R.string.capability_desc_canControlMagnification));
}
/**
@@ -502,6 +512,10 @@ public class AccessibilityServiceInfo implements Parcelable {
.AccessibilityService_canRequestFilterKeyEvents, false)) {
mCapabilities |= CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS;
}
+ if (asAttributes.getBoolean(com.android.internal.R.styleable
+ .AccessibilityService_canControlMagnification, false)) {
+ mCapabilities |= CAPABILITY_CAN_CONTROL_MAGNIFICATION;
+ }
TypedValue peekedValue = asAttributes.peekValue(
com.android.internal.R.styleable.AccessibilityService_description);
if (peekedValue != null) {
@@ -917,6 +931,8 @@ public class AccessibilityServiceInfo implements Parcelable {
return "CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY";
case CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS:
return "CAPABILITY_CAN_FILTER_KEY_EVENTS";
+ case CAPABILITY_CAN_CONTROL_MAGNIFICATION:
+ return "CAPABILITY_CAN_CONTROL_MAGNIFICATION";
default:
return "UNKNOWN";
}
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
index 8b503ddad947..15666bf03ae8 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
@@ -17,6 +17,7 @@
package android.accessibilityservice;
import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.graphics.Region;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityWindowInfo;
import android.view.KeyEvent;
@@ -39,4 +40,6 @@ import android.view.KeyEvent;
void clearAccessibilityCache();
void onKeyEvent(in KeyEvent event, int sequence);
+
+ void onMagnificationChanged(in Region region, float scale, float centerX, float centerY);
}
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 5f7a17d33bd1..6ac50bd6efbc 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -18,6 +18,7 @@ package android.accessibilityservice;
import android.os.Bundle;
import android.accessibilityservice.AccessibilityServiceInfo;
+import android.graphics.Region;
import android.view.MagnificationSpec;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
@@ -63,4 +64,19 @@ interface IAccessibilityServiceConnection {
boolean performGlobalAction(int action);
oneway void setOnKeyEventResult(boolean handled, int sequence);
+
+ float getMagnificationScale();
+
+ float getMagnificationCenterX();
+
+ float getMagnificationCenterY();
+
+ Region getMagnifiedRegion();
+
+ boolean resetMagnification(boolean animate);
+
+ boolean setMagnificationScaleAndCenter(float scale, float centerX, float centerY,
+ boolean animate);
+
+ void setMagnificationCallbackEnabled(boolean enabled);
}
diff --git a/core/java/android/app/TimePickerDialog.java b/core/java/android/app/TimePickerDialog.java
index a3b3022fc69c..aca07636e26e 100644
--- a/core/java/android/app/TimePickerDialog.java
+++ b/core/java/android/app/TimePickerDialog.java
@@ -23,10 +23,8 @@ import android.os.Bundle;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
-import android.widget.Button;
import android.widget.TimePicker;
import android.widget.TimePicker.OnTimeChangedListener;
-import android.widget.TimePicker.ValidationCallback;
import com.android.internal.R;
@@ -64,7 +62,7 @@ public class TimePickerDialog extends AlertDialog implements OnClickListener,
* @param hourOfDay the hour that was set
* @param minute the minute that was set
*/
- public void onTimeSet(TimePicker view, int hourOfDay, int minute);
+ void onTimeSet(TimePicker view, int hourOfDay, int minute);
}
/**
@@ -115,7 +113,6 @@ public class TimePickerDialog extends AlertDialog implements OnClickListener,
final TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(R.attr.timePickerDialogTheme, outValue, true);
- final int layoutResId = outValue.resourceId;
final LayoutInflater inflater = LayoutInflater.from(themeContext);
final View view = inflater.inflate(R.layout.time_picker_dialog, null);
@@ -129,7 +126,6 @@ public class TimePickerDialog extends AlertDialog implements OnClickListener,
mTimePicker.setCurrentHour(mInitialHourOfDay);
mTimePicker.setCurrentMinute(mInitialMinute);
mTimePicker.setOnTimeChangedListener(this);
- mTimePicker.setValidationCallback(mValidationCallback);
}
@Override
@@ -181,14 +177,4 @@ public class TimePickerDialog extends AlertDialog implements OnClickListener,
mTimePicker.setCurrentHour(hour);
mTimePicker.setCurrentMinute(minute);
}
-
- private final ValidationCallback mValidationCallback = new ValidationCallback() {
- @Override
- public void onValidationChanged(boolean valid) {
- final Button positive = getButton(BUTTON_POSITIVE);
- if (positive != null) {
- positive.setEnabled(valid);
- }
- }
- };
}
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index efed2e09733e..f7848f98b006 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -21,9 +21,11 @@ import android.accessibilityservice.AccessibilityService.IAccessibilityServiceCl
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.annotation.NonNull;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Point;
+import android.graphics.Region;
import android.hardware.display.DisplayManagerGlobal;
import android.os.IBinder;
import android.os.Looper;
@@ -1020,6 +1022,12 @@ public final class UiAutomation {
public boolean onKeyEvent(KeyEvent event) {
return false;
}
+
+ @Override
+ public void onMagnificationChanged(@NonNull Region region,
+ float scale, float centerX, float centerY) {
+ /* do nothing */
+ }
});
}
}
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index 25d9aa9bae67..09a15de8778e 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -685,6 +685,48 @@ public final class BluetoothHeadset implements BluetoothProfile {
}
/**
+ * Sets whether audio routing is allowed. When set to {@code false}, the AG will not route any
+ * audio to the HF unless explicitly told to.
+ * This method should be used in cases where the SCO channel is shared between multiple profiles
+ * and must be delegated by a source knowledgeable
+ * Note: This is an internal function and shouldn't be exposed
+ *
+ * @param allowed {@code true} if the profile can reroute audio, {@code false} otherwise.
+ *
+ * @hide
+ */
+ public void setAudioRouteAllowed(boolean allowed) {
+ if (VDBG) log("setAudioRouteAllowed");
+ if (mService != null && isEnabled()) {
+ try {
+ mService.setAudioRouteAllowed(allowed);
+ } catch (RemoteException e) {Log.e(TAG, e.toString());}
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ }
+
+ /**
+ * Returns whether audio routing is allowed. see {@link #setAudioRouteAllowed(boolean)}.
+ * Note: This is an internal function and shouldn't be exposed
+ *
+ * @hide
+ */
+ public boolean getAudioRouteAllowed() {
+ if (VDBG) log("getAudioRouteAllowed");
+ if (mService != null && isEnabled()) {
+ try {
+ return mService.getAudioRouteAllowed();
+ } catch (RemoteException e) {Log.e(TAG, e.toString());}
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ return false;
+ }
+
+ /**
* Check if Bluetooth SCO audio is connected.
*
* <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
diff --git a/core/java/android/bluetooth/IBluetoothHeadset.aidl b/core/java/android/bluetooth/IBluetoothHeadset.aidl
index 0e23fada8317..0bb4088f62c9 100755
--- a/core/java/android/bluetooth/IBluetoothHeadset.aidl
+++ b/core/java/android/bluetooth/IBluetoothHeadset.aidl
@@ -50,6 +50,8 @@ interface IBluetoothHeadset {
boolean isAudioOn();
boolean connectAudio();
boolean disconnectAudio();
+ void setAudioRouteAllowed(boolean allowed);
+ boolean getAudioRouteAllowed();
boolean startScoUsingVirtualVoiceCall(in BluetoothDevice device);
boolean stopScoUsingVirtualVoiceCall(in BluetoothDevice device);
void phoneStateChanged(int numActive, int numHeld, int callState, String number, int type);
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 68b77fe78673..30fe531c5186 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -18,6 +18,7 @@ package android.content;
import android.content.pm.ApplicationInfo;
import android.os.ResultReceiver;
+import android.os.ShellCommand;
import android.provider.MediaStore;
import android.util.ArraySet;
@@ -57,11 +58,13 @@ import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
+import java.io.PrintWriter;
import java.io.Serializable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.net.URISyntaxException;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
@@ -3054,21 +3057,21 @@ public class Intent implements Parcelable, Cloneable {
/**
* Thermal state when the device is normal. This state is sent in the
- * {@link ACTION_THERMAL_EVENT} broadcast as {@link EXTRA_THERMAL_STATE}.
+ * {@link #ACTION_THERMAL_EVENT} broadcast as {@link #EXTRA_THERMAL_STATE}.
* {@hide}
*/
public static final int EXTRA_THERMAL_STATE_NORMAL = 0;
/**
* Thermal state where the device is approaching its maximum threshold. This state is sent in
- * the {@link ACTION_THERMAL_EVENT} broadcast as {@link EXTRA_THERMAL_STATE}.
+ * the {@link #ACTION_THERMAL_EVENT} broadcast as {@link #EXTRA_THERMAL_STATE}.
* {@hide}
*/
public static final int EXTRA_THERMAL_STATE_WARNING = 1;
/**
* Thermal state where the device has reached its maximum threshold. This state is sent in the
- * {@link ACTION_THERMAL_EVENT} broadcast as {@link EXTRA_THERMAL_STATE}.
+ * {@link #ACTION_THERMAL_EVENT} broadcast as {@link #EXTRA_THERMAL_STATE}.
* {@hide}
*/
public static final int EXTRA_THERMAL_STATE_EXCEEDED = 2;
@@ -5083,6 +5086,437 @@ public class Intent implements Parcelable, Cloneable {
return intent;
}
+ /** @hide */
+ public interface CommandOptionHandler {
+ boolean handleOption(String opt, ShellCommand cmd);
+ }
+
+ /** @hide */
+ public static Intent parseCommandArgs(ShellCommand cmd, CommandOptionHandler optionHandler)
+ throws URISyntaxException {
+ Intent intent = new Intent();
+ Intent baseIntent = intent;
+ boolean hasIntentInfo = false;
+
+ Uri data = null;
+ String type = null;
+
+ String opt;
+ while ((opt=cmd.getNextOption()) != null) {
+ switch (opt) {
+ case "-a":
+ intent.setAction(cmd.getNextArgRequired());
+ if (intent == baseIntent) {
+ hasIntentInfo = true;
+ }
+ break;
+ case "-d":
+ data = Uri.parse(cmd.getNextArgRequired());
+ if (intent == baseIntent) {
+ hasIntentInfo = true;
+ }
+ break;
+ case "-t":
+ type = cmd.getNextArgRequired();
+ if (intent == baseIntent) {
+ hasIntentInfo = true;
+ }
+ break;
+ case "-c":
+ intent.addCategory(cmd.getNextArgRequired());
+ if (intent == baseIntent) {
+ hasIntentInfo = true;
+ }
+ break;
+ case "-e":
+ case "--es": {
+ String key = cmd.getNextArgRequired();
+ String value = cmd.getNextArgRequired();
+ intent.putExtra(key, value);
+ }
+ break;
+ case "--esn": {
+ String key = cmd.getNextArgRequired();
+ intent.putExtra(key, (String) null);
+ }
+ break;
+ case "--ei": {
+ String key = cmd.getNextArgRequired();
+ String value = cmd.getNextArgRequired();
+ intent.putExtra(key, Integer.decode(value));
+ }
+ break;
+ case "--eu": {
+ String key = cmd.getNextArgRequired();
+ String value = cmd.getNextArgRequired();
+ intent.putExtra(key, Uri.parse(value));
+ }
+ break;
+ case "--ecn": {
+ String key = cmd.getNextArgRequired();
+ String value = cmd.getNextArgRequired();
+ ComponentName cn = ComponentName.unflattenFromString(value);
+ if (cn == null)
+ throw new IllegalArgumentException("Bad component name: " + value);
+ intent.putExtra(key, cn);
+ }
+ break;
+ case "--eia": {
+ String key = cmd.getNextArgRequired();
+ String value = cmd.getNextArgRequired();
+ String[] strings = value.split(",");
+ int[] list = new int[strings.length];
+ for (int i = 0; i < strings.length; i++) {
+ list[i] = Integer.decode(strings[i]);
+ }
+ intent.putExtra(key, list);
+ }
+ break;
+ case "--eial": {
+ String key = cmd.getNextArgRequired();
+ String value = cmd.getNextArgRequired();
+ String[] strings = value.split(",");
+ ArrayList<Integer> list = new ArrayList<>(strings.length);
+ for (int i = 0; i < strings.length; i++) {
+ list.add(Integer.decode(strings[i]));
+ }
+ intent.putExtra(key, list);
+ }
+ break;
+ case "--el": {
+ String key = cmd.getNextArgRequired();
+ String value = cmd.getNextArgRequired();
+ intent.putExtra(key, Long.valueOf(value));
+ }
+ break;
+ case "--ela": {
+ String key = cmd.getNextArgRequired();
+ String value = cmd.getNextArgRequired();
+ String[] strings = value.split(",");
+ long[] list = new long[strings.length];
+ for (int i = 0; i < strings.length; i++) {
+ list[i] = Long.valueOf(strings[i]);
+ }
+ intent.putExtra(key, list);
+ hasIntentInfo = true;
+ }
+ break;
+ case "--elal": {
+ String key = cmd.getNextArgRequired();
+ String value = cmd.getNextArgRequired();
+ String[] strings = value.split(",");
+ ArrayList<Long> list = new ArrayList<>(strings.length);
+ for (int i = 0; i < strings.length; i++) {
+ list.add(Long.valueOf(strings[i]));
+ }
+ intent.putExtra(key, list);
+ hasIntentInfo = true;
+ }
+ break;
+ case "--ef": {
+ String key = cmd.getNextArgRequired();
+ String value = cmd.getNextArgRequired();
+ intent.putExtra(key, Float.valueOf(value));
+ hasIntentInfo = true;
+ }
+ break;
+ case "--efa": {
+ String key = cmd.getNextArgRequired();
+ String value = cmd.getNextArgRequired();
+ String[] strings = value.split(",");
+ float[] list = new float[strings.length];
+ for (int i = 0; i < strings.length; i++) {
+ list[i] = Float.valueOf(strings[i]);
+ }
+ intent.putExtra(key, list);
+ hasIntentInfo = true;
+ }
+ break;
+ case "--efal": {
+ String key = cmd.getNextArgRequired();
+ String value = cmd.getNextArgRequired();
+ String[] strings = value.split(",");
+ ArrayList<Float> list = new ArrayList<>(strings.length);
+ for (int i = 0; i < strings.length; i++) {
+ list.add(Float.valueOf(strings[i]));
+ }
+ intent.putExtra(key, list);
+ hasIntentInfo = true;
+ }
+ break;
+ case "--esa": {
+ String key = cmd.getNextArgRequired();
+ String value = cmd.getNextArgRequired();
+ // Split on commas unless they are preceeded by an escape.
+ // The escape character must be escaped for the string and
+ // again for the regex, thus four escape characters become one.
+ String[] strings = value.split("(?<!\\\\),");
+ intent.putExtra(key, strings);
+ hasIntentInfo = true;
+ }
+ break;
+ case "--esal": {
+ String key = cmd.getNextArgRequired();
+ String value = cmd.getNextArgRequired();
+ // Split on commas unless they are preceeded by an escape.
+ // The escape character must be escaped for the string and
+ // again for the regex, thus four escape characters become one.
+ String[] strings = value.split("(?<!\\\\),");
+ ArrayList<String> list = new ArrayList<>(strings.length);
+ for (int i = 0; i < strings.length; i++) {
+ list.add(strings[i]);
+ }
+ intent.putExtra(key, list);
+ hasIntentInfo = true;
+ }
+ break;
+ case "--ez": {
+ String key = cmd.getNextArgRequired();
+ String value = cmd.getNextArgRequired().toLowerCase();
+ // Boolean.valueOf() results in false for anything that is not "true", which is
+ // error-prone in shell commands
+ boolean arg;
+ if ("true".equals(value) || "t".equals(value)) {
+ arg = true;
+ } else if ("false".equals(value) || "f".equals(value)) {
+ arg = false;
+ } else {
+ try {
+ arg = Integer.decode(value) != 0;
+ } catch (NumberFormatException ex) {
+ throw new IllegalArgumentException("Invalid boolean value: " + value);
+ }
+ }
+
+ intent.putExtra(key, arg);
+ }
+ break;
+ case "-n": {
+ String str = cmd.getNextArgRequired();
+ ComponentName cn = ComponentName.unflattenFromString(str);
+ if (cn == null)
+ throw new IllegalArgumentException("Bad component name: " + str);
+ intent.setComponent(cn);
+ if (intent == baseIntent) {
+ hasIntentInfo = true;
+ }
+ }
+ break;
+ case "-p": {
+ String str = cmd.getNextArgRequired();
+ intent.setPackage(str);
+ if (intent == baseIntent) {
+ hasIntentInfo = true;
+ }
+ }
+ break;
+ case "-f":
+ String str = cmd.getNextArgRequired();
+ intent.setFlags(Integer.decode(str).intValue());
+ break;
+ case "--grant-read-uri-permission":
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ break;
+ case "--grant-write-uri-permission":
+ intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ break;
+ case "--grant-persistable-uri-permission":
+ intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
+ break;
+ case "--grant-prefix-uri-permission":
+ intent.addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
+ break;
+ case "--exclude-stopped-packages":
+ intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
+ break;
+ case "--include-stopped-packages":
+ intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
+ break;
+ case "--debug-log-resolution":
+ intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION);
+ break;
+ case "--activity-brought-to-front":
+ intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
+ break;
+ case "--activity-clear-top":
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ break;
+ case "--activity-clear-when-task-reset":
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
+ break;
+ case "--activity-exclude-from-recents":
+ intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ break;
+ case "--activity-launched-from-history":
+ intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY);
+ break;
+ case "--activity-multiple-task":
+ intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+ break;
+ case "--activity-no-animation":
+ intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+ break;
+ case "--activity-no-history":
+ intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
+ break;
+ case "--activity-no-user-action":
+ intent.addFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION);
+ break;
+ case "--activity-previous-is-top":
+ intent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
+ break;
+ case "--activity-reorder-to-front":
+ intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+ break;
+ case "--activity-reset-task-if-needed":
+ intent.addFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ break;
+ case "--activity-single-top":
+ intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ break;
+ case "--activity-clear-task":
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ break;
+ case "--activity-task-on-home":
+ intent.addFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME);
+ break;
+ case "--receiver-registered-only":
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ break;
+ case "--receiver-replace-pending":
+ intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ break;
+ case "--selector":
+ intent.setDataAndType(data, type);
+ intent = new Intent();
+ break;
+ default:
+ if (optionHandler != null && optionHandler.handleOption(opt, cmd)) {
+ // Okay, caller handled this option.
+ } else {
+ throw new IllegalArgumentException("Unknown option: " + opt);
+ }
+ break;
+ }
+ }
+ intent.setDataAndType(data, type);
+
+ final boolean hasSelector = intent != baseIntent;
+ if (hasSelector) {
+ // A selector was specified; fix up.
+ baseIntent.setSelector(intent);
+ intent = baseIntent;
+ }
+
+ String arg = cmd.getNextArg();
+ baseIntent = null;
+ if (arg == null) {
+ if (hasSelector) {
+ // If a selector has been specified, and no arguments
+ // have been supplied for the main Intent, then we can
+ // assume it is ACTION_MAIN CATEGORY_LAUNCHER; we don't
+ // need to have a component name specified yet, the
+ // selector will take care of that.
+ baseIntent = new Intent(Intent.ACTION_MAIN);
+ baseIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+ }
+ } else if (arg.indexOf(':') >= 0) {
+ // The argument is a URI. Fully parse it, and use that result
+ // to fill in any data not specified so far.
+ baseIntent = Intent.parseUri(arg, Intent.URI_INTENT_SCHEME
+ | Intent.URI_ANDROID_APP_SCHEME | Intent.URI_ALLOW_UNSAFE);
+ } else if (arg.indexOf('/') >= 0) {
+ // The argument is a component name. Build an Intent to launch
+ // it.
+ baseIntent = new Intent(Intent.ACTION_MAIN);
+ baseIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+ baseIntent.setComponent(ComponentName.unflattenFromString(arg));
+ } else {
+ // Assume the argument is a package name.
+ baseIntent = new Intent(Intent.ACTION_MAIN);
+ baseIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+ baseIntent.setPackage(arg);
+ }
+ if (baseIntent != null) {
+ Bundle extras = intent.getExtras();
+ intent.replaceExtras((Bundle)null);
+ Bundle uriExtras = baseIntent.getExtras();
+ baseIntent.replaceExtras((Bundle)null);
+ if (intent.getAction() != null && baseIntent.getCategories() != null) {
+ HashSet<String> cats = new HashSet<String>(baseIntent.getCategories());
+ for (String c : cats) {
+ baseIntent.removeCategory(c);
+ }
+ }
+ intent.fillIn(baseIntent, Intent.FILL_IN_COMPONENT | Intent.FILL_IN_SELECTOR);
+ if (extras == null) {
+ extras = uriExtras;
+ } else if (uriExtras != null) {
+ uriExtras.putAll(extras);
+ extras = uriExtras;
+ }
+ intent.replaceExtras(extras);
+ hasIntentInfo = true;
+ }
+
+ if (!hasIntentInfo) throw new IllegalArgumentException("No intent supplied");
+ return intent;
+ }
+
+ /** @hide */
+ public static void printIntentArgsHelp(PrintWriter pw, String prefix) {
+ final String[] lines = new String[] {
+ "<INTENT> specifications include these flags and arguments:",
+ " [-a <ACTION>] [-d <DATA_URI>] [-t <MIME_TYPE>]",
+ " [-c <CATEGORY> [-c <CATEGORY>] ...]",
+ " [-e|--es <EXTRA_KEY> <EXTRA_STRING_VALUE> ...]",
+ " [--esn <EXTRA_KEY> ...]",
+ " [--ez <EXTRA_KEY> <EXTRA_BOOLEAN_VALUE> ...]",
+ " [--ei <EXTRA_KEY> <EXTRA_INT_VALUE> ...]",
+ " [--el <EXTRA_KEY> <EXTRA_LONG_VALUE> ...]",
+ " [--ef <EXTRA_KEY> <EXTRA_FLOAT_VALUE> ...]",
+ " [--eu <EXTRA_KEY> <EXTRA_URI_VALUE> ...]",
+ " [--ecn <EXTRA_KEY> <EXTRA_COMPONENT_NAME_VALUE>]",
+ " [--eia <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...]]",
+ " (mutiple extras passed as Integer[])",
+ " [--eial <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...]]",
+ " (mutiple extras passed as List<Integer>)",
+ " [--ela <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]]",
+ " (mutiple extras passed as Long[])",
+ " [--elal <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]]",
+ " (mutiple extras passed as List<Long>)",
+ " [--efa <EXTRA_KEY> <EXTRA_FLOAT_VALUE>[,<EXTRA_FLOAT_VALUE...]]",
+ " (mutiple extras passed as Float[])",
+ " [--efal <EXTRA_KEY> <EXTRA_FLOAT_VALUE>[,<EXTRA_FLOAT_VALUE...]]",
+ " (mutiple extras passed as List<Float>)",
+ " [--esa <EXTRA_KEY> <EXTRA_STRING_VALUE>[,<EXTRA_STRING_VALUE...]]",
+ " (mutiple extras passed as String[]; to embed a comma into a string,",
+ " escape it using \"\\,\")",
+ " [--esal <EXTRA_KEY> <EXTRA_STRING_VALUE>[,<EXTRA_STRING_VALUE...]]",
+ " (mutiple extras passed as List<String>; to embed a comma into a string,",
+ " escape it using \"\\,\")",
+ " [--grant-read-uri-permission] [--grant-write-uri-permission]",
+ " [--grant-persistable-uri-permission] [--grant-prefix-uri-permission]",
+ " [--debug-log-resolution] [--exclude-stopped-packages]",
+ " [--include-stopped-packages]",
+ " [--activity-brought-to-front] [--activity-clear-top]",
+ " [--activity-clear-when-task-reset] [--activity-exclude-from-recents]",
+ " [--activity-launched-from-history] [--activity-multiple-task]",
+ " [--activity-no-animation] [--activity-no-history]",
+ " [--activity-no-user-action] [--activity-previous-is-top]",
+ " [--activity-reorder-to-front] [--activity-reset-task-if-needed]",
+ " [--activity-single-top] [--activity-clear-task]",
+ " [--activity-task-on-home]",
+ " [--receiver-registered-only] [--receiver-replace-pending]",
+ " [--selector]",
+ " [<URI> | <PACKAGE> | <COMPONENT>]"
+ };
+ for (String line : lines) {
+ pw.print(prefix);
+ pw.println(line);
+ }
+ }
+
/**
* Retrieve the general action to be performed, such as
* {@link #ACTION_VIEW}. The action describes the general way the rest of
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 9c880d3591c3..eda4136b311c 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -479,6 +479,14 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
public static final int PRIVATE_FLAG_ENCRYPTION_AWARE = 1 << 6;
/**
+ * Value for {@link #privateFlags}: set to {@code true} if the application
+ * is AutoPlay.
+ *
+ * {@hide}
+ */
+ public static final int PRIVATE_FLAG_AUTOPLAY = 1 << 7;
+
+ /**
* Private/hidden flags. See {@code PRIVATE_FLAG_...} constants.
* {@hide}
*/
@@ -1049,6 +1057,13 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
/**
* @hide
*/
+ public boolean isAutoPlayApp() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_AUTOPLAY) != 0;
+ }
+
+ /**
+ * @hide
+ */
@Override protected ApplicationInfo getApplicationInfo() {
return this;
}
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 0606e35a081e..7b3dde4262f4 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -48,7 +48,6 @@ import android.icu.text.PluralRules;
import android.os.Build;
import android.os.Bundle;
import android.os.Trace;
-import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.LocaleList;
@@ -68,7 +67,6 @@ import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.io.InputStream;
-import java.lang.ref.WeakReference;
import java.util.Locale;
/**
@@ -120,9 +118,6 @@ public class Resources {
private static final LongSparseArray<android.content.res.ConstantState<ColorStateList>>
sPreloadedColorStateLists = new LongSparseArray<>();
- private static final String CACHE_NOT_THEMED = "";
- private static final String CACHE_NULL_THEME = "null_theme";
-
// Pool of TypedArrays targeted to this Resources object.
final SynchronizedPool<TypedArray> mTypedArrayPool = new SynchronizedPool<>(5);
@@ -130,10 +125,11 @@ public class Resources {
static Resources mSystem = null;
private static boolean sPreloaded;
- private static int sPreloadedDensity;
- // These are protected by mAccessLock.
+ /** Lock object used to protect access to caches and configuration. */
private final Object mAccessLock = new Object();
+
+ // These are protected by mAccessLock.
private final Configuration mTmpConfig = new Configuration();
private final DrawableCache mDrawableCache = new DrawableCache(this);
private final DrawableCache mColorDrawableCache = new DrawableCache(this);
@@ -147,7 +143,12 @@ public class Resources {
/** Used to inflate drawable objects from XML. */
private DrawableInflater mDrawableInflater;
+ /** Lock object used to protect access to {@link #mTmpValue}. */
+ private final Object mTmpValueLock = new Object();
+
+ /** Single-item pool used to minimize TypedValue allocations. */
private TypedValue mTmpValue = new TypedValue();
+
private boolean mPreloading;
private int mLastCachedXmlBlockIndex = -1;
@@ -249,6 +250,10 @@ public class Resources {
public NotFoundException(String name) {
super(name);
}
+
+ public NotFoundException(String name, Exception cause) {
+ super(name, cause);
+ }
}
/**
@@ -621,18 +626,15 @@ public class Resources {
* @see #getDimensionPixelSize
*/
public float getDimension(@DimenRes int id) throws NotFoundException {
- synchronized (mAccessLock) {
- TypedValue value = mTmpValue;
- if (value == null) {
- mTmpValue = value = new TypedValue();
- }
- getValue(id, value, true);
+ final TypedValue value = obtainTempTypedValue(id);
+ try {
if (value.type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimension(value.data, mMetrics);
}
- throw new NotFoundException(
- "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
- + Integer.toHexString(value.type) + " is not valid");
+ throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+ + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+ } finally {
+ releaseTempTypedValue(value);
}
}
@@ -656,19 +658,15 @@ public class Resources {
* @see #getDimensionPixelSize
*/
public int getDimensionPixelOffset(@DimenRes int id) throws NotFoundException {
- synchronized (mAccessLock) {
- TypedValue value = mTmpValue;
- if (value == null) {
- mTmpValue = value = new TypedValue();
- }
- getValue(id, value, true);
+ final TypedValue value = obtainTempTypedValue(id);
+ try {
if (value.type == TypedValue.TYPE_DIMENSION) {
- return TypedValue.complexToDimensionPixelOffset(
- value.data, mMetrics);
+ return TypedValue.complexToDimensionPixelOffset(value.data, mMetrics);
}
- throw new NotFoundException(
- "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
- + Integer.toHexString(value.type) + " is not valid");
+ throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+ + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+ } finally {
+ releaseTempTypedValue(value);
}
}
@@ -693,19 +691,15 @@ public class Resources {
* @see #getDimensionPixelOffset
*/
public int getDimensionPixelSize(@DimenRes int id) throws NotFoundException {
- synchronized (mAccessLock) {
- TypedValue value = mTmpValue;
- if (value == null) {
- mTmpValue = value = new TypedValue();
- }
- getValue(id, value, true);
+ final TypedValue value = obtainTempTypedValue(id);
+ try {
if (value.type == TypedValue.TYPE_DIMENSION) {
- return TypedValue.complexToDimensionPixelSize(
- value.data, mMetrics);
+ return TypedValue.complexToDimensionPixelSize(value.data, mMetrics);
}
- throw new NotFoundException(
- "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
- + Integer.toHexString(value.type) + " is not valid");
+ throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+ + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+ } finally {
+ releaseTempTypedValue(value);
}
}
@@ -727,18 +721,15 @@ public class Resources {
* @throws NotFoundException Throws NotFoundException if the given ID does not exist.
*/
public float getFraction(@FractionRes int id, int base, int pbase) {
- synchronized (mAccessLock) {
- TypedValue value = mTmpValue;
- if (value == null) {
- mTmpValue = value = new TypedValue();
- }
- getValue(id, value, true);
+ final TypedValue value = obtainTempTypedValue(id);
+ try {
if (value.type == TypedValue.TYPE_FRACTION) {
return TypedValue.complexToFraction(value.data, base, pbase);
}
- throw new NotFoundException(
- "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
- + Integer.toHexString(value.type) + " is not valid");
+ throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+ + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+ } finally {
+ releaseTempTypedValue(value);
}
}
@@ -801,24 +792,14 @@ public class Resources {
* not exist.
*/
@Nullable
- public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme) throws NotFoundException {
- TypedValue value;
- synchronized (mAccessLock) {
- value = mTmpValue;
- if (value == null) {
- value = new TypedValue();
- } else {
- mTmpValue = null;
- }
- getValue(id, value, true);
- }
- final Drawable res = loadDrawable(value, id, theme);
- synchronized (mAccessLock) {
- if (mTmpValue == null) {
- mTmpValue = value;
- }
+ public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme)
+ throws NotFoundException {
+ final TypedValue value = obtainTempTypedValue(id);
+ try {
+ return loadDrawable(value, id, theme);
+ } finally {
+ releaseTempTypedValue(value);
}
- return res;
}
/**
@@ -849,7 +830,8 @@ public class Resources {
*/
@Deprecated
@Nullable
- public Drawable getDrawableForDensity(@DrawableRes int id, int density) throws NotFoundException {
+ public Drawable getDrawableForDensity(@DrawableRes int id, int density)
+ throws NotFoundException {
return getDrawableForDensity(id, density, null);
}
@@ -869,14 +851,8 @@ public class Resources {
*/
@Nullable
public Drawable getDrawableForDensity(@DrawableRes int id, int density, @Nullable Theme theme) {
- TypedValue value;
- synchronized (mAccessLock) {
- value = mTmpValue;
- if (value == null) {
- value = new TypedValue();
- } else {
- mTmpValue = null;
- }
+ final TypedValue value = obtainTempTypedValue(id);
+ try {
getValueForDensity(id, density, value, true);
/*
@@ -893,15 +869,11 @@ public class Resources {
value.density = (value.density * mMetrics.densityDpi) / density;
}
}
- }
- final Drawable res = loadDrawable(value, id, theme);
- synchronized (mAccessLock) {
- if (mTmpValue == null) {
- mTmpValue = value;
- }
+ return loadDrawable(value, id, theme);
+ } finally {
+ releaseTempTypedValue(value);
}
- return res;
}
/**
@@ -963,33 +935,21 @@ public class Resources {
*/
@ColorInt
public int getColor(@ColorRes int id, @Nullable Theme theme) throws NotFoundException {
- TypedValue value;
- synchronized (mAccessLock) {
- value = mTmpValue;
- if (value == null) {
- value = new TypedValue();
- }
- getValue(id, value, true);
+ final TypedValue value = obtainTempTypedValue(id);
+ try {
if (value.type >= TypedValue.TYPE_FIRST_INT
&& value.type <= TypedValue.TYPE_LAST_INT) {
- mTmpValue = value;
return value.data;
} else if (value.type != TypedValue.TYPE_STRING) {
- throw new NotFoundException(
- "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
- + Integer.toHexString(value.type) + " is not valid");
+ throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+ + " type #0x" + Integer.toHexString(value.type) + " is not valid");
}
- mTmpValue = null;
- }
- final ColorStateList csl = loadColorStateList(value, id, theme);
- synchronized (mAccessLock) {
- if (mTmpValue == null) {
- mTmpValue = value;
- }
+ final ColorStateList csl = loadColorStateList(value, id, theme);
+ return csl.getDefaultColor();
+ } finally {
+ releaseTempTypedValue(value);
}
-
- return csl.getDefaultColor();
}
/**
@@ -1043,25 +1003,12 @@ public class Resources {
@Nullable
public ColorStateList getColorStateList(@ColorRes int id, @Nullable Theme theme)
throws NotFoundException {
- TypedValue value;
- synchronized (mAccessLock) {
- value = mTmpValue;
- if (value == null) {
- value = new TypedValue();
- } else {
- mTmpValue = null;
- }
- getValue(id, value, true);
- }
-
- final ColorStateList res = loadColorStateList(value, id, theme);
- synchronized (mAccessLock) {
- if (mTmpValue == null) {
- mTmpValue = value;
- }
+ final TypedValue value = obtainTempTypedValue(id);
+ try {
+ return loadColorStateList(value, id, theme);
+ } finally {
+ releaseTempTypedValue(value);
}
-
- return res;
}
/**
@@ -1078,19 +1025,16 @@ public class Resources {
* @return Returns the boolean value contained in the resource.
*/
public boolean getBoolean(@BoolRes int id) throws NotFoundException {
- synchronized (mAccessLock) {
- TypedValue value = mTmpValue;
- if (value == null) {
- mTmpValue = value = new TypedValue();
- }
- getValue(id, value, true);
+ final TypedValue value = obtainTempTypedValue(id);
+ try {
if (value.type >= TypedValue.TYPE_FIRST_INT
- && value.type <= TypedValue.TYPE_LAST_INT) {
+ && value.type <= TypedValue.TYPE_LAST_INT) {
return value.data != 0;
}
- throw new NotFoundException(
- "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
- + Integer.toHexString(value.type) + " is not valid");
+ throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+ + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+ } finally {
+ releaseTempTypedValue(value);
}
}
@@ -1106,19 +1050,16 @@ public class Resources {
* @return Returns the integer value contained in the resource.
*/
public int getInteger(@IntegerRes int id) throws NotFoundException {
- synchronized (mAccessLock) {
- TypedValue value = mTmpValue;
- if (value == null) {
- mTmpValue = value = new TypedValue();
- }
- getValue(id, value, true);
+ final TypedValue value = obtainTempTypedValue(id);
+ try {
if (value.type >= TypedValue.TYPE_FIRST_INT
- && value.type <= TypedValue.TYPE_LAST_INT) {
+ && value.type <= TypedValue.TYPE_LAST_INT) {
return value.data;
}
- throw new NotFoundException(
- "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
- + Integer.toHexString(value.type) + " is not valid");
+ throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+ + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+ } finally {
+ releaseTempTypedValue(value);
}
}
@@ -1136,17 +1077,15 @@ public class Resources {
* @hide Pending API council approval.
*/
public float getFloat(int id) {
- synchronized (mAccessLock) {
- TypedValue value = mTmpValue;
- if (value == null) {
- mTmpValue = value = new TypedValue();
- }
- getValue(id, value, true);
+ final TypedValue value = obtainTempTypedValue(id);
+ try {
if (value.type == TypedValue.TYPE_FLOAT) {
return value.getFloat();
}
- throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id) + " type #0x"
- + Integer.toHexString(value.type) + " is not valid");
+ throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+ + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+ } finally {
+ releaseTempTypedValue(value);
}
}
@@ -1238,22 +1177,60 @@ public class Resources {
*
*/
public InputStream openRawResource(@RawRes int id) throws NotFoundException {
- TypedValue value;
- synchronized (mAccessLock) {
- value = mTmpValue;
- if (value == null) {
- value = new TypedValue();
- } else {
+ final TypedValue value = obtainTempTypedValue();
+ try {
+ return openRawResource(id, value);
+ } finally {
+ releaseTempTypedValue(value);
+ }
+ }
+
+ /**
+ * Returns a TypedValue populated with data for the specified resource ID
+ * that's suitable for temporary use. The obtained TypedValue should be
+ * released using {@link #releaseTempTypedValue(TypedValue)}.
+ *
+ * @param id the resource ID for which data should be obtained
+ * @return a populated typed value suitable for temporary use
+ */
+ private TypedValue obtainTempTypedValue(@AnyRes int id) {
+ final TypedValue value = obtainTempTypedValue();
+ getValue(id, value, true);
+ return value;
+ }
+
+ /**
+ * Returns a TypedValue suitable for temporary use. The obtained TypedValue
+ * should be released using {@link #releaseTempTypedValue(TypedValue)}.
+ *
+ * @return a typed value suitable for temporary use
+ */
+ private TypedValue obtainTempTypedValue() {
+ TypedValue tmpValue = null;
+ synchronized (mTmpValueLock) {
+ if (mTmpValue != null) {
+ tmpValue = mTmpValue;
mTmpValue = null;
}
}
- InputStream res = openRawResource(id, value);
- synchronized (mAccessLock) {
+ if (tmpValue == null) {
+ return new TypedValue();
+ }
+ return tmpValue;
+ }
+
+ /**
+ * Returns a TypedValue to the pool. After calling this method, the
+ * specified TypedValue should no longer be accessed.
+ *
+ * @param value the typed value to return to the pool
+ */
+ private void releaseTempTypedValue(TypedValue value) {
+ synchronized (mTmpValueLock) {
if (mTmpValue == null) {
mTmpValue = value;
}
}
- return res;
}
/**
@@ -1307,32 +1284,14 @@ public class Resources {
*/
public AssetFileDescriptor openRawResourceFd(@RawRes int id)
throws NotFoundException {
- TypedValue value;
- synchronized (mAccessLock) {
- value = mTmpValue;
- if (value == null) {
- value = new TypedValue();
- } else {
- mTmpValue = null;
- }
- getValue(id, value, true);
- }
+ final TypedValue value = obtainTempTypedValue(id);
try {
- return mAssets.openNonAssetFd(
- value.assetCookie, value.string.toString());
+ return mAssets.openNonAssetFd(value.assetCookie, value.string.toString());
} catch (Exception e) {
- NotFoundException rnf = new NotFoundException(
- "File " + value.string.toString()
- + " from drawable resource ID #0x"
- + Integer.toHexString(id));
- rnf.initCause(e);
- throw rnf;
+ throw new NotFoundException("File " + value.string.toString() + " from drawable "
+ + "resource ID #0x" + Integer.toHexString(id), e);
} finally {
- synchronized (mAccessLock) {
- if (mTmpValue == null) {
- mTmpValue = value;
- }
- }
+ releaseTempTypedValue(value);
}
}
@@ -2015,8 +1974,8 @@ public class Resources {
Build.VERSION.RESOURCES_SDK_INT);
if (DEBUG_CONFIG) {
- Slog.i(TAG, "**** Updating config of " + this + ": final config is " + mConfiguration
- + " final compat is " + mCompatibilityInfo);
+ Slog.i(TAG, "**** Updating config of " + this + ": final config is "
+ + mConfiguration + " final compat is " + mCompatibilityInfo);
}
mDrawableCache.onConfigurationChange(configChanges);
@@ -2402,8 +2361,7 @@ public class Resources {
}
sPreloaded = true;
mPreloading = true;
- sPreloadedDensity = DisplayMetrics.DENSITY_DEVICE;
- mConfiguration.densityDpi = sPreloadedDensity;
+ mConfiguration.densityDpi = DisplayMetrics.DENSITY_DEVICE;
updateConfiguration(null, null);
}
}
@@ -2740,19 +2698,16 @@ public class Resources {
/*package*/ XmlResourceParser loadXmlResourceParser(int id, String type)
throws NotFoundException {
- synchronized (mAccessLock) {
- TypedValue value = mTmpValue;
- if (value == null) {
- mTmpValue = value = new TypedValue();
- }
- getValue(id, value, true);
+ final TypedValue value = obtainTempTypedValue(id);
+ try {
if (value.type == TypedValue.TYPE_STRING) {
return loadXmlResourceParser(value.string.toString(), id,
value.assetCookie, type);
}
- throw new NotFoundException(
- "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
- + Integer.toHexString(value.type) + " is not valid");
+ throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+ + " type #0x" + Integer.toHexString(value.type) + " is not valid");
+ } finally {
+ releaseTempTypedValue(value);
}
}
diff --git a/core/java/android/os/IDeviceIdleController.aidl b/core/java/android/os/IDeviceIdleController.aidl
index f55883ae7a70..7a1a6a29ad12 100644
--- a/core/java/android/os/IDeviceIdleController.aidl
+++ b/core/java/android/os/IDeviceIdleController.aidl
@@ -35,4 +35,6 @@ interface IDeviceIdleController {
long addPowerSaveTempWhitelistAppForMms(String name, int userId, String reason);
long addPowerSaveTempWhitelistAppForSms(String name, int userId, String reason);
void exitIdle(String reason);
+ void downloadServiceActive(IBinder token);
+ void downloadServiceInactive();
}
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 4ac361d00682..54bfca365769 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -632,9 +632,6 @@ public class Process {
if ((debugFlags & Zygote.DEBUG_ENABLE_CHECKJNI) != 0) {
argsForZygote.add("--enable-checkjni");
}
- if ((debugFlags & Zygote.DEBUG_ENABLE_JIT) != 0) {
- argsForZygote.add("--enable-jit");
- }
if ((debugFlags & Zygote.DEBUG_GENERATE_DEBUG_INFO) != 0) {
argsForZygote.add("--generate-debug-info");
}
diff --git a/core/java/android/os/ShellCommand.java b/core/java/android/os/ShellCommand.java
index cad482b6bab9..6f12b625318f 100644
--- a/core/java/android/os/ShellCommand.java
+++ b/core/java/android/os/ShellCommand.java
@@ -48,19 +48,35 @@ public abstract class ShellCommand {
private FastPrintWriter mErrPrintWriter;
private InputStream mInputStream;
- public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ResultReceiver resultReceiver) {
+ public void init(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
+ String[] args, int firstArgPos) {
mTarget = target;
mIn = in;
mOut = out;
mErr = err;
mArgs = args;
- mResultReceiver = resultReceiver;
- mCmd = args != null && args.length > 0 ? args[0] : null;
- mArgPos = 1;
+ mResultReceiver = null;
+ mCmd = null;
+ mArgPos = firstArgPos;
mCurArgData = null;
mOutPrintWriter = null;
mErrPrintWriter = null;
+ }
+
+ public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
+ String[] args, ResultReceiver resultReceiver) {
+ String cmd;
+ int start;
+ if (args != null && args.length > 0) {
+ cmd = args[0];
+ start = 1;
+ } else {
+ cmd = null;
+ start = 0;
+ }
+ init(target, in, out, err, args, start);
+ mCmd = cmd;
+ mResultReceiver = resultReceiver;
if (DEBUG) Slog.d(TAG, "Starting command " + mCmd + " on " + mTarget);
int res = -1;
diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java
index 95da438bc869..f946ca7ed415 100644
--- a/core/java/android/os/UserHandle.java
+++ b/core/java/android/os/UserHandle.java
@@ -256,6 +256,23 @@ public final class UserHandle implements Parcelable {
}
}
+ /** @hide */
+ public static int parseUserArg(String arg) {
+ int userId;
+ if ("all".equals(arg)) {
+ userId = UserHandle.USER_ALL;
+ } else if ("current".equals(arg) || "cur".equals(arg)) {
+ userId = UserHandle.USER_CURRENT;
+ } else {
+ try {
+ userId = Integer.parseInt(arg);
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Bad user number: " + arg);
+ }
+ }
+ return userId;
+ }
+
/**
* Returns the user id of the current process
* @return user id of the current process
diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java
index d19c7c979501..2e43ffcbcc4f 100644
--- a/core/java/android/os/storage/IMountService.java
+++ b/core/java/android/os/storage/IMountService.java
@@ -21,8 +21,12 @@ import android.os.Binder;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
import android.os.RemoteException;
+import java.io.FileDescriptor;
+
/**
* WARNING! Update IMountService.h and IMountService.cpp if you change this
* file. In particular, the ordering of the methods below must match the
@@ -1312,6 +1316,25 @@ public interface IMountService extends IInterface {
}
return _result;
}
+
+ @Override
+ public ParcelFileDescriptor mountAppFuse(String name) throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ ParcelFileDescriptor _result = null;
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeString(name);
+ mRemote.transact(Stub.TRANSACTION_mountAppFuse, _data, _reply, 0);
+ _reply.readException();
+ _result = _reply.<ParcelFileDescriptor>readParcelable(
+ ClassLoader.getSystemClassLoader());
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ return _result;
+ }
}
private static final String DESCRIPTOR = "IMountService";
@@ -1439,6 +1462,8 @@ public interface IMountService extends IInterface {
static final int TRANSACTION_isPerUserEncryptionEnabled = IBinder.FIRST_CALL_TRANSACTION + 67;
static final int TRANSACTION_isConvertibleToFBE = IBinder.FIRST_CALL_TRANSACTION + 68;
+ static final int TRANSACTION_mountAppFuse = IBinder.FIRST_CALL_TRANSACTION + 69;
+
/**
* Cast an IBinder object into an IMountService interface, generating a
* proxy if needed.
@@ -2056,6 +2081,14 @@ public interface IMountService extends IInterface {
reply.writeInt(result ? 1 : 0);
return true;
}
+ case TRANSACTION_mountAppFuse: {
+ data.enforceInterface(DESCRIPTOR);
+ String name = data.readString();
+ ParcelFileDescriptor fd = mountAppFuse(name);
+ reply.writeNoException();
+ reply.writeParcelable(fd, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
}
@@ -2379,4 +2412,6 @@ public interface IMountService extends IInterface {
throws RemoteException;
public boolean isPerUserEncryptionEnabled() throws RemoteException;
+
+ public ParcelFileDescriptor mountAppFuse(String name) throws RemoteException;
}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 27df46d7ee0e..2d9090b6f99a 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -30,6 +30,7 @@ import android.os.FileUtils;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.provider.Settings;
@@ -293,7 +294,7 @@ public class StorageManager {
/**
* Constructs a StorageManager object through which an application can
* can communicate with the systems mount service.
- *
+ *
* @param tgtLooper The {@link android.os.Looper} which events will be received on.
*
* <p>Applications can get instance of this class by calling
@@ -409,7 +410,7 @@ public class StorageManager {
* file matches a package ID that is owned by the calling program's UID.
* That is, shared UID applications can attempt to mount any other
* application's OBB that shares its UID.
- *
+ *
* @param rawPath the path to the OBB file
* @param key secret used to encrypt the OBB; may be <code>null</code> if no
* encryption was used on the OBB.
@@ -447,7 +448,7 @@ public class StorageManager {
* That is, shared UID applications can obtain access to any other
* application's OBB that shares its UID.
* <p>
- *
+ *
* @param rawPath path to the OBB file
* @param force whether to kill any programs using this in order to unmount
* it
@@ -471,7 +472,7 @@ public class StorageManager {
/**
* Check whether an Opaque Binary Blob (OBB) is mounted or not.
- *
+ *
* @param rawPath path to OBB image
* @return true if OBB is mounted; false if not mounted or on error
*/
@@ -491,7 +492,7 @@ public class StorageManager {
* Check the mounted path of an Opaque Binary Blob (OBB) file. This will
* give you the path to where you can obtain access to the internals of the
* OBB.
- *
+ *
* @param rawPath path to OBB image
* @return absolute path to mounted OBB image data or <code>null</code> if
* not mounted or exception encountered trying to read status
@@ -1049,6 +1050,15 @@ public class StorageManager {
return path;
}
+ /** {@hide} */
+ public ParcelFileDescriptor mountAppFuse(String name) {
+ try {
+ return mMountService.mountAppFuse(name);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
/// Consts to match the password types in cryptfs.h
/** @hide */
public static final int CRYPT_TYPE_PASSWORD = 0;
diff --git a/core/java/android/view/MagnificationSpec.java b/core/java/android/view/MagnificationSpec.java
index 0ee6714f7b5f..49242bb5aa65 100644
--- a/core/java/android/view/MagnificationSpec.java
+++ b/core/java/android/view/MagnificationSpec.java
@@ -28,10 +28,21 @@ import android.util.Pools.SynchronizedPool;
public class MagnificationSpec implements Parcelable {
private static final int MAX_POOL_SIZE = 20;
private static final SynchronizedPool<MagnificationSpec> sPool =
- new SynchronizedPool<MagnificationSpec>(MAX_POOL_SIZE);
+ new SynchronizedPool<>(MAX_POOL_SIZE);
+ /** The magnification scaling factor. */
public float scale = 1.0f;
+
+ /**
+ * The X coordinate, in unscaled screen-relative pixels, around which
+ * magnification is focused.
+ */
public float offsetX;
+
+ /**
+ * The Y coordinate, in unscaled screen-relative pixels, around which
+ * magnification is focused.
+ */
public float offsetY;
private MagnificationSpec() {
@@ -75,6 +86,12 @@ public class MagnificationSpec implements Parcelable {
offsetY = 0.0f;
}
+ public void setTo(MagnificationSpec other) {
+ scale = other.scale;
+ offsetX = other.offsetX;
+ offsetY = other.offsetY;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -89,6 +106,28 @@ public class MagnificationSpec implements Parcelable {
}
@Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+
+ if (other == null || getClass() != other.getClass()) {
+ return false;
+ }
+
+ final MagnificationSpec s = (MagnificationSpec) other;
+ return scale == s.scale && offsetX == s.offsetX && offsetY == s.offsetY;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = (scale != +0.0f ? Float.floatToIntBits(scale) : 0);
+ result = 31 * result + (offsetX != +0.0f ? Float.floatToIntBits(offsetX) : 0);
+ result = 31 * result + (offsetY != +0.0f ? Float.floatToIntBits(offsetY) : 0);
+ return result;
+ }
+
+ @Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("<scale:");
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index fdea1ba157f4..cd9dd97468b3 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -16,6 +16,8 @@
package android.view;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
+
import android.Manifest;
import android.animation.LayoutTransition;
import android.app.ActivityManagerNative;
@@ -37,7 +39,6 @@ import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
-import android.hardware.input.InputManager;
import android.media.AudioManager;
import android.os.Binder;
import android.os.Build;
@@ -74,7 +75,6 @@ import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
-import android.view.WindowCallbacks;
import android.widget.Scroller;
import com.android.internal.R;
@@ -176,6 +176,11 @@ public final class ViewRootImpl implements ViewParent,
int mViewVisibility;
boolean mAppVisible = true;
+ // For recents to freeform transition we need to keep drawing after the app receives information
+ // that it became invisible. This will ignore that information and depend on the decor view
+ // visibility to control drawing. The decor view visibility will get adjusted when the app get
+ // stopped and that's when the app will stop drawing further frames.
+ private boolean mForceDecorViewVisibility = false;
int mOrigWindowType = -1;
/** Whether the window had focus during the most recent traversal. */
@@ -561,6 +566,8 @@ public final class ViewRootImpl implements ViewParent,
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
mInputChannel = new InputChannel();
}
+ mForceDecorViewVisibility = (mWindowAttributes.privateFlags
+ & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
@@ -1063,7 +1070,7 @@ public final class ViewRootImpl implements ViewParent,
}
int getHostVisibility() {
- return mAppVisible ? mView.getVisibility() : View.GONE;
+ return (mAppVisible || mForceDecorViewVisibility) ? mView.getVisibility() : View.GONE;
}
/**
@@ -1568,7 +1575,7 @@ public final class ViewRootImpl implements ViewParent,
boolean insetsPending = false;
int relayoutResult = 0;
- boolean isViewVisible = viewVisibility == View.VISIBLE;
+ final boolean isViewVisible = viewVisibility == View.VISIBLE;
if (mFirst || windowShouldResize || insetsChanged ||
viewVisibilityChanged || params != null) {
@@ -2471,8 +2478,7 @@ public final class ViewRootImpl implements ViewParent,
if (callbacks != null) {
for (SurfaceHolder.Callback c : callbacks) {
if (c instanceof SurfaceHolder.Callback2) {
- ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded(
- mSurfaceHolder);
+ ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded(mSurfaceHolder);
}
}
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index edf4297afdea..1521f2ed07fe 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -118,8 +118,7 @@ public interface WindowManager extends ViewManager {
*/
public void removeViewImmediate(View view);
- public static class LayoutParams extends ViewGroup.LayoutParams
- implements Parcelable {
+ public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
/**
* X position for this window. With the default gravity it is ignored.
* When using {@link Gravity#LEFT} or {@link Gravity#START} or {@link Gravity#RIGHT} or
@@ -1149,13 +1148,21 @@ public interface WindowManager extends ViewManager {
/**
* Flag indicating that the x, y, width, and height members should be
- * ignored (and thus their previous value preserved). For example
+ * ignored (and thus their previous value preserved). For example
* because they are being managed externally through repositionChild.
*
* {@hide}
*/
public static final int PRIVATE_FLAG_PRESERVE_GEOMETRY = 0x00002000;
+ /**
+ * Flag that will make window ignore app visibility and instead depend purely on the decor
+ * view visibility for determining window visibility. This is used by recents to keep
+ * drawing after it launches an app.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY = 0x00004000;
+
/**
* Control flags that are private to the platform.
diff --git a/core/java/android/view/WindowManagerInternal.java b/core/java/android/view/WindowManagerInternal.java
index 3882bca043e8..89b1eb933025 100644
--- a/core/java/android/view/WindowManagerInternal.java
+++ b/core/java/android/view/WindowManagerInternal.java
@@ -16,6 +16,7 @@
package android.view;
+import android.annotation.Nullable;
import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.display.DisplayManagerInternal;
@@ -55,9 +56,10 @@ public abstract class WindowManagerInternal {
* Called when the bounds of the screen content that is magnified changed.
* Note that not the entire screen is magnified.
*
- * @param bounds The bounds.
+ * @param magnifiedBounds the currently magnified region
+ * @param availableBounds the region available for magnification
*/
- public void onMagnifedBoundsChanged(Region bounds);
+ public void onMagnifiedBoundsChanged(Region magnifiedBounds, Region availableBounds);
/**
* Called when an application requests a rectangle on the screen to allow
@@ -142,7 +144,7 @@ public abstract class WindowManagerInternal {
*
* @param callbacks The callbacks to invoke.
*/
- public abstract void setMagnificationCallbacks(MagnificationCallbacks callbacks);
+ public abstract void setMagnificationCallbacks(@Nullable MagnificationCallbacks callbacks);
/**
* Set by the accessibility layer to specify the magnification and panning to
diff --git a/core/java/android/widget/PopupMenu.java b/core/java/android/widget/PopupMenu.java
index 34a843925700..a53df8835b08 100644
--- a/core/java/android/widget/PopupMenu.java
+++ b/core/java/android/widget/PopupMenu.java
@@ -21,7 +21,6 @@ import com.android.internal.view.menu.MenuBuilder;
import com.android.internal.view.menu.MenuPopupHelper;
import com.android.internal.view.menu.MenuPresenter;
import com.android.internal.view.menu.ShowableListMenu;
-import com.android.internal.view.menu.SubMenuBuilder;
import android.annotation.MenuRes;
import android.content.Context;
@@ -33,35 +32,23 @@ import android.view.View;
import android.view.View.OnTouchListener;
/**
- * A PopupMenu displays a {@link Menu} in a modal popup window anchored to a {@link View}.
- * The popup will appear below the anchor view if there is room, or above it if there is not.
- * If the IME is visible the popup will not overlap it until it is touched. Touching outside
- * of the popup will dismiss it.
+ * A PopupMenu displays a {@link Menu} in a modal popup window anchored to a
+ * {@link View}. The popup will appear below the anchor view if there is room,
+ * or above it if there is not. If the IME is visible the popup will not
+ * overlap it until it is touched. Touching outside of the popup will dismiss
+ * it.
*/
-public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback {
+public class PopupMenu {
private final Context mContext;
private final MenuBuilder mMenu;
private final View mAnchor;
private final MenuPopupHelper mPopup;
- private final boolean mShowCascadingMenus;
private OnMenuItemClickListener mMenuItemClickListener;
private OnDismissListener mDismissListener;
private OnTouchListener mDragListener;
/**
- * Callback interface used to notify the application that the menu has closed.
- */
- public interface OnDismissListener {
- /**
- * Called when the associated menu has been dismissed.
- *
- * @param menu The PopupMenu that was dismissed.
- */
- public void onDismiss(PopupMenu menu);
- }
-
- /**
* Constructor to create a new popup menu with an anchor view.
*
* @param context Context the popup menu is running in, through which it
@@ -108,14 +95,40 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback {
public PopupMenu(Context context, View anchor, int gravity, int popupStyleAttr,
int popupStyleRes) {
mContext = context;
- mShowCascadingMenus = context.getResources().getBoolean(
- com.android.internal.R.bool.config_enableCascadingSubmenus);
- mMenu = new MenuBuilder(context);
- mMenu.setCallback(this);
mAnchor = anchor;
+
+ mMenu = new MenuBuilder(context);
+ mMenu.setCallback(new MenuBuilder.Callback() {
+ @Override
+ public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
+ if (mMenuItemClickListener != null) {
+ return mMenuItemClickListener.onMenuItemClick(item);
+ }
+ return false;
+ }
+
+ @Override
+ public void onMenuModeChange(MenuBuilder menu) {
+ }
+ });
+
mPopup = new MenuPopupHelper(context, mMenu, anchor, false, popupStyleAttr, popupStyleRes);
mPopup.setGravity(gravity);
- mPopup.setCallback(this);
+ mPopup.setCallback(new MenuPresenter.Callback() {
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ if (mDismissListener != null) {
+ mDismissListener.onDismiss(PopupMenu.this);
+ }
+ }
+
+ @Override
+ public boolean onOpenSubMenu(MenuBuilder subMenu) {
+ // The menu presenter will handle opening the submenu itself.
+ // Nothing to do here.
+ return false;
+ }
+ });
}
/**
@@ -125,7 +138,6 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback {
* the next time the popup is shown.
*
* @param gravity the gravity used to align the popup window
- *
* @see #getGravity()
*/
public void setGravity(int gravity) {
@@ -134,7 +146,6 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback {
/**
* @return the gravity used to align the popup window to its anchor view
- *
* @see #setGravity(int)
*/
public int getGravity() {
@@ -146,8 +157,8 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback {
* to implement drag-to-open behavior.
* <p>
* When the listener is set on a view, touching that view and dragging
- * outside of its bounds will open the popup window. Lifting will select the
- * currently touched list item.
+ * outside of its bounds will open the popup window. Lifting will select
+ * the currently touched list item.
* <p>
* Example usage:
* <pre>
@@ -184,9 +195,10 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback {
}
/**
- * @return the {@link Menu} associated with this popup. Populate the returned Menu with
- * items before calling {@link #show()}.
+ * Returns the {@link Menu} associated with this popup. Populate the
+ * returned Menu with items before calling {@link #show()}.
*
+ * @return the {@link Menu} associated with this popup
* @see #show()
* @see #getMenuInflater()
*/
@@ -195,9 +207,8 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback {
}
/**
- * @return a {@link MenuInflater} that can be used to inflate menu items from XML into the
- * menu returned by {@link #getMenu()}.
- *
+ * @return a {@link MenuInflater} that can be used to inflate menu items
+ * from XML into the menu returned by {@link #getMenu()}
* @see #getMenu()
*/
public MenuInflater getMenuInflater() {
@@ -205,8 +216,9 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback {
}
/**
- * Inflate a menu resource into this PopupMenu. This is equivalent to calling
- * popupMenu.getMenuInflater().inflate(menuRes, popupMenu.getMenu()).
+ * Inflate a menu resource into this PopupMenu. This is equivalent to
+ * calling {@code popupMenu.getMenuInflater().inflate(menuRes, popupMenu.getMenu())}.
+ *
* @param menuRes Menu resource to inflate
*/
public void inflate(@MenuRes int menuRes) {
@@ -215,6 +227,7 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback {
/**
* Show the menu popup anchored to the view specified during construction.
+ *
* @see #dismiss()
*/
public void show() {
@@ -223,6 +236,7 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback {
/**
* Dismiss the menu popup.
+ *
* @see #show()
*/
public void dismiss() {
@@ -230,68 +244,49 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback {
}
/**
- * Set a listener that will be notified when the user selects an item from the menu.
+ * Sets a listener that will be notified when the user selects an item from
+ * the menu.
*
- * @param listener Listener to notify
+ * @param listener the listener to notify
*/
public void setOnMenuItemClickListener(OnMenuItemClickListener listener) {
mMenuItemClickListener = listener;
}
/**
- * Set a listener that will be notified when this menu is dismissed.
+ * Sets a listener that will be notified when this menu is dismissed.
*
- * @param listener Listener to notify
+ * @param listener the listener to notify
*/
public void setOnDismissListener(OnDismissListener listener) {
mDismissListener = listener;
}
/**
- * @hide
- */
- public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
- if (mMenuItemClickListener != null) {
- return mMenuItemClickListener.onMenuItemClick(item);
- }
- return false;
- }
-
- /**
- * @hide
+ * Interface responsible for receiving menu item click events if the items
+ * themselves do not have individual item click listeners.
*/
- public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
- if (mDismissListener != null) {
- mDismissListener.onDismiss(this);
- }
- }
-
- /**
- * @hide
- */
- public boolean onOpenSubMenu(MenuBuilder subMenu) {
- // The menu presenter will handle opening the submenu itself. Nothing to do here.
- return false;
- }
-
- /**
- * @hide
- */
- public void onMenuModeChange(MenuBuilder menu) {
+ public interface OnMenuItemClickListener {
+ /**
+ * This method will be invoked when a menu item is clicked if the item
+ * itself did not already handle the event.
+ *
+ * @param item the menu item that was clicked
+ * @return {@code true} if the event was handled, {@code false}
+ * otherwise
+ */
+ boolean onMenuItemClick(MenuItem item);
}
/**
- * Interface responsible for receiving menu item click events if the items themselves
- * do not have individual item click listeners.
+ * Callback interface used to notify the application that the menu has closed.
*/
- public interface OnMenuItemClickListener {
+ public interface OnDismissListener {
/**
- * This method will be invoked when a menu item is clicked if the item itself did
- * not already handle the event.
+ * Called when the associated menu has been dismissed.
*
- * @param item {@link MenuItem} that was clicked
- * @return <code>true</code> if the event was handled, <code>false</code> otherwise.
+ * @param menu the popup menu that was dismissed
*/
- public boolean onMenuItemClick(MenuItem item);
+ void onDismiss(PopupMenu menu);
}
}
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index 8e5af793e2d9..a24d37f4b097 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -100,7 +100,7 @@ public class TimePicker extends FrameLayout {
* @see #getHour()
*/
public void setHour(int hour) {
- mDelegate.setCurrentHour(hour);
+ mDelegate.setHour(hour);
}
/**
@@ -110,7 +110,7 @@ public class TimePicker extends FrameLayout {
* @see #setHour(int)
*/
public int getHour() {
- return mDelegate.getCurrentHour();
+ return mDelegate.getHour();
}
/**
@@ -120,7 +120,7 @@ public class TimePicker extends FrameLayout {
* @see #getMinute()
*/
public void setMinute(int minute) {
- mDelegate.setCurrentMinute(minute);
+ mDelegate.setMinute(minute);
}
/**
@@ -130,7 +130,7 @@ public class TimePicker extends FrameLayout {
* @see #setMinute(int)
*/
public int getMinute() {
- return mDelegate.getCurrentMinute();
+ return mDelegate.getMinute();
}
/**
@@ -150,7 +150,7 @@ public class TimePicker extends FrameLayout {
@NonNull
@Deprecated
public Integer getCurrentHour() {
- return mDelegate.getCurrentHour();
+ return mDelegate.getHour();
}
/**
@@ -160,7 +160,7 @@ public class TimePicker extends FrameLayout {
*/
@Deprecated
public void setCurrentMinute(@NonNull Integer currentMinute) {
- mDelegate.setCurrentMinute(currentMinute);
+ mDelegate.setMinute(currentMinute);
}
/**
@@ -170,7 +170,7 @@ public class TimePicker extends FrameLayout {
@NonNull
@Deprecated
public Integer getCurrentMinute() {
- return mDelegate.getCurrentMinute();
+ return mDelegate.getMinute();
}
/**
@@ -186,7 +186,7 @@ public class TimePicker extends FrameLayout {
return;
}
- mDelegate.setIs24HourView(is24HourView);
+ mDelegate.setIs24Hour(is24HourView);
}
/**
@@ -195,7 +195,7 @@ public class TimePicker extends FrameLayout {
* @see #setIs24HourView(Boolean)
*/
public boolean is24HourView() {
- return mDelegate.is24HourView();
+ return mDelegate.is24Hour();
}
/**
@@ -207,16 +207,6 @@ public class TimePicker extends FrameLayout {
mDelegate.setOnTimeChangedListener(onTimeChangedListener);
}
- /**
- * Sets the callback that indicates the current time is valid.
- *
- * @param callback the callback, may be null
- * @hide
- */
- public void setValidationCallback(@Nullable ValidationCallback callback) {
- mDelegate.setValidationCallback(callback);
- }
-
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
@@ -234,12 +224,6 @@ public class TimePicker extends FrameLayout {
}
@Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- mDelegate.onConfigurationChanged(newConfig);
- }
-
- @Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
return mDelegate.onSaveInstanceState(superState);
@@ -269,25 +253,22 @@ public class TimePicker extends FrameLayout {
* for the real behavior.
*/
interface TimePickerDelegate {
- void setCurrentHour(int currentHour);
- int getCurrentHour();
+ void setHour(int hour);
+ int getHour();
- void setCurrentMinute(int currentMinute);
- int getCurrentMinute();
+ void setMinute(int minute);
+ int getMinute();
- void setIs24HourView(boolean is24HourView);
- boolean is24HourView();
+ void setIs24Hour(boolean is24Hour);
+ boolean is24Hour();
void setOnTimeChangedListener(OnTimeChangedListener onTimeChangedListener);
- void setValidationCallback(ValidationCallback callback);
void setEnabled(boolean enabled);
boolean isEnabled();
int getBaseline();
- void onConfigurationChanged(Configuration newConfig);
-
Parcelable onSaveInstanceState(Parcelable superState);
void onRestoreInstanceState(Parcelable state);
@@ -295,16 +276,6 @@ public class TimePicker extends FrameLayout {
void onPopulateAccessibilityEvent(AccessibilityEvent event);
}
- /**
- * A callback interface for updating input validity when the TimePicker
- * when included into a Dialog.
- *
- * @hide
- */
- public static interface ValidationCallback {
- void onValidationChanged(boolean valid);
- }
-
static String[] getAmPmStrings(Context context) {
final Locale locale = context.getResources().getConfiguration().locale;
final LocaleData d = LocaleData.get(locale);
@@ -319,43 +290,16 @@ public class TimePicker extends FrameLayout {
* An abstract class which can be used as a start for TimePicker implementations
*/
abstract static class AbstractTimePickerDelegate implements TimePickerDelegate {
- // The delegator
- protected TimePicker mDelegator;
-
- // The context
- protected Context mContext;
+ protected final TimePicker mDelegator;
+ protected final Context mContext;
+ protected final Locale mLocale;
- // The current locale
- protected Locale mCurrentLocale;
-
- // Callbacks
protected OnTimeChangedListener mOnTimeChangedListener;
- protected ValidationCallback mValidationCallback;
- public AbstractTimePickerDelegate(TimePicker delegator, Context context) {
+ public AbstractTimePickerDelegate(@NonNull TimePicker delegator, @NonNull Context context) {
mDelegator = delegator;
mContext = context;
-
- // initialization based on locale
- setCurrentLocale(Locale.getDefault());
- }
-
- public void setCurrentLocale(Locale locale) {
- if (locale.equals(mCurrentLocale)) {
- return;
- }
- mCurrentLocale = locale;
- }
-
- @Override
- public void setValidationCallback(ValidationCallback callback) {
- mValidationCallback = callback;
- }
-
- protected void onValidationChanged(boolean valid) {
- if (mValidationCallback != null) {
- mValidationCallback.onValidationChanged(valid);
- }
+ mLocale = context.getResources().getConfiguration().locale;
}
}
}
diff --git a/core/java/android/widget/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java
index 4dc5fd3e5f5c..38ce033470b6 100644
--- a/core/java/android/widget/TimePickerClockDelegate.java
+++ b/core/java/android/widget/TimePickerClockDelegate.java
@@ -19,7 +19,6 @@ package android.widget;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.ColorStateList;
-import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.Parcel;
@@ -89,7 +88,7 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
private boolean mAllowAutoAdvance;
private int mInitialHourOfDay;
private int mInitialMinute;
- private boolean mIs24HourView;
+ private boolean mIs24Hour;
private boolean mIsAmPmAtStart;
// Accessibility strings.
@@ -200,19 +199,19 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
mAllowAutoAdvance = true;
// Updates mHourFormat variables used below.
- updateHourFormat(mCurrentLocale, mIs24HourView);
+ updateHourFormat(mLocale, mIs24Hour);
// Update hour text field.
final int minHour = mHourFormatStartsAtZero ? 0 : 1;
- final int maxHour = (mIs24HourView ? 23 : 11) + minHour;
+ final int maxHour = (mIs24Hour ? 23 : 11) + minHour;
mHourView.setRange(minHour, maxHour);
mHourView.setShowLeadingZeroes(mHourFormatShowLeadingZero);
// Initialize with current time.
- mTempCalendar = Calendar.getInstance(mCurrentLocale);
+ mTempCalendar = Calendar.getInstance(mLocale);
final int currentHour = mTempCalendar.get(Calendar.HOUR_OF_DAY);
final int currentMinute = mTempCalendar.get(Calendar.MINUTE);
- initialize(currentHour, currentMinute, mIs24HourView, HOUR_INDEX);
+ initialize(currentHour, currentMinute, mIs24Hour, HOUR_INDEX);
}
/**
@@ -333,7 +332,7 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
private void initialize(int hourOfDay, int minute, boolean is24HourView, int index) {
mInitialHourOfDay = hourOfDay;
mInitialMinute = minute;
- mIs24HourView = is24HourView;
+ mIs24Hour = is24HourView;
updateUI(index);
}
@@ -352,17 +351,16 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
}
private void updateRadialPicker(int index) {
- mRadialTimePickerView.initialize(mInitialHourOfDay, mInitialMinute, mIs24HourView);
+ mRadialTimePickerView.initialize(mInitialHourOfDay, mInitialMinute, mIs24Hour);
setCurrentItemShowing(index, false, true);
}
private void updateHeaderAmPm() {
-
- if (mIs24HourView) {
+ if (mIs24Hour) {
mAmPmLayout.setVisibility(View.GONE);
} else {
// Ensure that AM/PM layout is in the correct position.
- final String dateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale, "hm");
+ final String dateTimePattern = DateFormat.getBestDateTimePattern(mLocale, "hm");
final boolean isAmPmAtStart = dateTimePattern.startsWith("a");
setAmPmAtStart(isAmPmAtStart);
@@ -395,35 +393,32 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
* Set the current hour.
*/
@Override
- public void setCurrentHour(int currentHour) {
- if (mInitialHourOfDay == currentHour) {
- return;
+ public void setHour(int hour) {
+ if (mInitialHourOfDay != hour) {
+ mInitialHourOfDay = hour;
+ updateHeaderHour(hour, true);
+ updateHeaderAmPm();
+ mRadialTimePickerView.setCurrentHour(hour);
+ mRadialTimePickerView.setAmOrPm(mInitialHourOfDay < 12 ? AM : PM);
+ mDelegator.invalidate();
+ onTimeChanged();
}
- mInitialHourOfDay = currentHour;
- updateHeaderHour(currentHour, true);
- updateHeaderAmPm();
- mRadialTimePickerView.setCurrentHour(currentHour);
- mRadialTimePickerView.setAmOrPm(mInitialHourOfDay < 12 ? AM : PM);
- mDelegator.invalidate();
- onTimeChanged();
}
/**
- * @return The current hour in the range (0-23).
+ * @return the current hour in the range (0-23)
*/
@Override
- public int getCurrentHour() {
- int currentHour = mRadialTimePickerView.getCurrentHour();
- if (mIs24HourView) {
+ public int getHour() {
+ final int currentHour = mRadialTimePickerView.getCurrentHour();
+ if (mIs24Hour) {
return currentHour;
+ }
+
+ if (mRadialTimePickerView.getAmOrPm() == PM) {
+ return (currentHour % HOURS_IN_HALF_DAY) + HOURS_IN_HALF_DAY;
} else {
- switch(mRadialTimePickerView.getAmOrPm()) {
- case PM:
- return (currentHour % HOURS_IN_HALF_DAY) + HOURS_IN_HALF_DAY;
- case AM:
- default:
- return currentHour % HOURS_IN_HALF_DAY;
- }
+ return currentHour % HOURS_IN_HALF_DAY;
}
}
@@ -431,48 +426,48 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
* Set the current minute (0-59).
*/
@Override
- public void setCurrentMinute(int currentMinute) {
- if (mInitialMinute == currentMinute) {
- return;
+ public void setMinute(int minute) {
+ if (mInitialMinute != minute) {
+ mInitialMinute = minute;
+ updateHeaderMinute(minute, true);
+ mRadialTimePickerView.setCurrentMinute(minute);
+ mDelegator.invalidate();
+ onTimeChanged();
}
- mInitialMinute = currentMinute;
- updateHeaderMinute(currentMinute, true);
- mRadialTimePickerView.setCurrentMinute(currentMinute);
- mDelegator.invalidate();
- onTimeChanged();
}
/**
* @return The current minute.
*/
@Override
- public int getCurrentMinute() {
+ public int getMinute() {
return mRadialTimePickerView.getCurrentMinute();
}
/**
- * Set whether in 24 hour or AM/PM mode.
+ * Sets whether time is displayed in 24-hour mode or 12-hour mode with
+ * AM/PM indicators.
*
- * @param is24HourView True = 24 hour mode. False = AM/PM.
+ * @param is24Hour {@code true} to display time in 24-hour mode or
+ * {@code false} for 12-hour mode with AM/PM
*/
- @Override
- public void setIs24HourView(boolean is24HourView) {
- if (is24HourView == mIs24HourView) {
- return;
- }
-
- mIs24HourView = is24HourView;
- mInitialHourOfDay = getCurrentHour();
+ public void setIs24Hour(boolean is24Hour) {
+ if (mIs24Hour != is24Hour) {
+ mIs24Hour = is24Hour;
+ mInitialHourOfDay = getHour();
- updateUI(mRadialTimePickerView.getCurrentItemShowing());
+ updateUI(mRadialTimePickerView.getCurrentItemShowing());
+ }
}
/**
- * @return true if this is in 24 hour view else false.
+ * @return {@code true} if time is displayed in 24-hour mode, or
+ * {@code false} if time is displayed in 12-hour mode with AM/PM
+ * indicators
*/
@Override
- public boolean is24HourView() {
- return mIs24HourView;
+ public boolean is24Hour() {
+ return mIs24Hour;
}
@Override
@@ -502,14 +497,9 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
}
@Override
- public void onConfigurationChanged(Configuration newConfig) {
- updateUI(mRadialTimePickerView.getCurrentItemShowing());
- }
-
- @Override
public Parcelable onSaveInstanceState(Parcelable superState) {
- return new SavedState(superState, getCurrentHour(), getCurrentMinute(),
- is24HourView(), getCurrentItemShowing());
+ return new SavedState(superState, getHour(), getMinute(),
+ is24Hour(), getCurrentItemShowing());
}
@Override
@@ -520,12 +510,6 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
}
@Override
- public void setCurrentLocale(Locale locale) {
- super.setCurrentLocale(locale);
- mTempCalendar = Calendar.getInstance(locale);
- }
-
- @Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
onPopulateAccessibilityEvent(event);
return true;
@@ -534,13 +518,13 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
@Override
public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
int flags = DateUtils.FORMAT_SHOW_TIME;
- if (mIs24HourView) {
+ if (mIs24Hour) {
flags |= DateUtils.FORMAT_24HOUR;
} else {
flags |= DateUtils.FORMAT_12HOUR;
}
- mTempCalendar.set(Calendar.HOUR_OF_DAY, getCurrentHour());
- mTempCalendar.set(Calendar.MINUTE, getCurrentMinute());
+ mTempCalendar.set(Calendar.HOUR_OF_DAY, getHour());
+ mTempCalendar.set(Calendar.MINUTE, getMinute());
String selectedDate = DateUtils.formatDateTime(mContext,
mTempCalendar.getTimeInMillis(), flags);
event.getText().add(selectedDate);
@@ -559,8 +543,7 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
private void onTimeChanged() {
mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
if (mOnTimeChangedListener != null) {
- mOnTimeChangedListener.onTimeChanged(mDelegator,
- getCurrentHour(), getCurrentMinute());
+ mOnTimeChangedListener.onTimeChanged(mDelegator, getHour(), getMinute());
}
}
@@ -666,7 +649,7 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
}
if (mOnTimeChangedListener != null) {
- mOnTimeChangedListener.onTimeChanged(mDelegator, getCurrentHour(), getCurrentMinute());
+ mOnTimeChangedListener.onTimeChanged(mDelegator, getHour(), getMinute());
}
}
@@ -677,14 +660,14 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
* @return a localized hour number
*/
private int getLocalizedHour(int hourOfDay) {
- if (!mIs24HourView) {
+ if (!mIs24Hour) {
// Convert to hour-of-am-pm.
hourOfDay %= 12;
}
if (!mHourFormatStartsAtZero && hourOfDay == 0) {
// Convert to clock-hour (either of-day or of-am-pm).
- hourOfDay = mIs24HourView ? 24 : 12;
+ hourOfDay = mIs24Hour ? 24 : 12;
}
return hourOfDay;
@@ -716,8 +699,8 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
* separator as the character which is just after the hour marker in the returned pattern.
*/
private void updateHeaderSeparator() {
- final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
- (mIs24HourView) ? "Hm" : "hm");
+ final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mLocale,
+ (mIs24Hour) ? "Hm" : "hm");
final String separatorText;
// See http://www.unicode.org/reports/tr35/tr35-dates.html for hour formats
final char[] hourFormats = {'H', 'h', 'K', 'k'};
@@ -819,14 +802,14 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
private final Runnable mCommitHour = new Runnable() {
@Override
public void run() {
- setCurrentHour(mHourView.getValue());
+ setHour(mHourView.getValue());
}
};
private final Runnable mCommitMinute = new Runnable() {
@Override
public void run() {
- setCurrentMinute(mMinuteView.getValue());
+ setMinute(mMinuteView.getValue());
}
};
diff --git a/core/java/android/widget/TimePickerSpinnerDelegate.java b/core/java/android/widget/TimePickerSpinnerDelegate.java
index 8741cc3b784f..2ed230bf1b35 100644
--- a/core/java/android/widget/TimePickerSpinnerDelegate.java
+++ b/core/java/android/widget/TimePickerSpinnerDelegate.java
@@ -17,7 +17,6 @@
package android.widget;
import android.content.Context;
-import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.os.Parcel;
import android.os.Parcelable;
@@ -33,7 +32,6 @@ import android.view.inputmethod.InputMethodManager;
import com.android.internal.R;
import java.util.Calendar;
-import java.util.Locale;
import libcore.icu.LocaleData;
@@ -92,7 +90,7 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate {
mHourSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
public void onValueChange(NumberPicker spinner, int oldVal, int newVal) {
updateInputState();
- if (!is24HourView()) {
+ if (!is24Hour()) {
if ((oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) ||
(oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1)) {
mIsAm = !mIsAm;
@@ -124,14 +122,14 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate {
int maxValue = mMinuteSpinner.getMaxValue();
if (oldVal == maxValue && newVal == minValue) {
int newHour = mHourSpinner.getValue() + 1;
- if (!is24HourView() && newHour == HOURS_IN_HALF_DAY) {
+ if (!is24Hour() && newHour == HOURS_IN_HALF_DAY) {
mIsAm = !mIsAm;
updateAmPmControl();
}
mHourSpinner.setValue(newHour);
} else if (oldVal == minValue && newVal == maxValue) {
int newHour = mHourSpinner.getValue() - 1;
- if (!is24HourView() && newHour == HOURS_IN_HALF_DAY - 1) {
+ if (!is24Hour() && newHour == HOURS_IN_HALF_DAY - 1) {
mIsAm = !mIsAm;
updateAmPmControl();
}
@@ -204,8 +202,8 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate {
updateAmPmControl();
// set to current time
- setCurrentHour(mTempCalendar.get(Calendar.HOUR_OF_DAY));
- setCurrentMinute(mTempCalendar.get(Calendar.MINUTE));
+ setHour(mTempCalendar.get(Calendar.HOUR_OF_DAY));
+ setMinute(mTempCalendar.get(Calendar.MINUTE));
if (!isEnabled()) {
setEnabled(false);
@@ -221,7 +219,7 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate {
}
private void getHourFormatData() {
- final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
+ final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mLocale,
(mIs24HourView) ? "Hm" : "hm");
final int lengthPattern = bestDateTimePattern.length();
mHourWithTwoDigit = false;
@@ -241,7 +239,7 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate {
}
private boolean isAmPmAtStart() {
- final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
+ final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mLocale,
"hm" /* skeleton */);
return bestDateTimePattern.startsWith("a");
@@ -257,7 +255,7 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate {
*/
private void setDividerText() {
final String skeleton = (mIs24HourView) ? "Hm" : "hm";
- final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
+ final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mLocale,
skeleton);
final String separatorText;
int hourIndex = bestDateTimePattern.lastIndexOf('H');
@@ -279,16 +277,16 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate {
}
@Override
- public void setCurrentHour(int currentHour) {
- setCurrentHour(currentHour, true);
+ public void setHour(int hour) {
+ setCurrentHour(hour, true);
}
private void setCurrentHour(int currentHour, boolean notifyTimeChanged) {
// why was Integer used in the first place?
- if (currentHour == getCurrentHour()) {
+ if (currentHour == getHour()) {
return;
}
- if (!is24HourView()) {
+ if (!is24Hour()) {
// convert [0,23] ordinal to wall clock display
if (currentHour >= HOURS_IN_HALF_DAY) {
mIsAm = false;
@@ -310,9 +308,9 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate {
}
@Override
- public int getCurrentHour() {
+ public int getHour() {
int currentHour = mHourSpinner.getValue();
- if (is24HourView()) {
+ if (is24Hour()) {
return currentHour;
} else if (mIsAm) {
return currentHour % HOURS_IN_HALF_DAY;
@@ -322,28 +320,27 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate {
}
@Override
- public void setCurrentMinute(int currentMinute) {
- if (currentMinute == getCurrentMinute()) {
+ public void setMinute(int minute) {
+ if (minute == getMinute()) {
return;
}
- mMinuteSpinner.setValue(currentMinute);
+ mMinuteSpinner.setValue(minute);
onTimeChanged();
}
@Override
- public int getCurrentMinute() {
+ public int getMinute() {
return mMinuteSpinner.getValue();
}
- @Override
- public void setIs24HourView(boolean is24HourView) {
- if (mIs24HourView == is24HourView) {
+ public void setIs24Hour(boolean is24Hour) {
+ if (mIs24HourView == is24Hour) {
return;
}
// cache the current hour since spinner range changes and BEFORE changing mIs24HourView!!
- int currentHour = getCurrentHour();
+ int currentHour = getHour();
// Order is important here.
- mIs24HourView = is24HourView;
+ mIs24HourView = is24Hour;
getHourFormatData();
updateHourControl();
// set value after spinner range is updated
@@ -353,7 +350,7 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate {
}
@Override
- public boolean is24HourView() {
+ public boolean is24Hour() {
return mIs24HourView;
}
@@ -388,20 +385,15 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate {
}
@Override
- public void onConfigurationChanged(Configuration newConfig) {
- setCurrentLocale(newConfig.locale);
- }
-
- @Override
public Parcelable onSaveInstanceState(Parcelable superState) {
- return new SavedState(superState, getCurrentHour(), getCurrentMinute());
+ return new SavedState(superState, getHour(), getMinute());
}
@Override
public void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
- setCurrentHour(ss.getHour());
- setCurrentMinute(ss.getMinute());
+ setHour(ss.getHour());
+ setMinute(ss.getMinute());
}
@Override
@@ -418,8 +410,8 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate {
} else {
flags |= DateUtils.FORMAT_12HOUR;
}
- mTempCalendar.set(Calendar.HOUR_OF_DAY, getCurrentHour());
- mTempCalendar.set(Calendar.MINUTE, getCurrentMinute());
+ mTempCalendar.set(Calendar.HOUR_OF_DAY, getHour());
+ mTempCalendar.set(Calendar.MINUTE, getMinute());
String selectedDateUtterance = DateUtils.formatDateTime(mContext,
mTempCalendar.getTimeInMillis(), flags);
event.getText().add(selectedDateUtterance);
@@ -447,7 +439,7 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate {
}
private void updateAmPmControl() {
- if (is24HourView()) {
+ if (is24Hour()) {
if (mAmPmSpinner != null) {
mAmPmSpinner.setVisibility(View.GONE);
} else {
@@ -466,27 +458,16 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate {
mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
}
- /**
- * Sets the current locale.
- *
- * @param locale The current locale.
- */
- @Override
- public void setCurrentLocale(Locale locale) {
- super.setCurrentLocale(locale);
- mTempCalendar = Calendar.getInstance(locale);
- }
-
private void onTimeChanged() {
mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
if (mOnTimeChangedListener != null) {
- mOnTimeChangedListener.onTimeChanged(mDelegator, getCurrentHour(),
- getCurrentMinute());
+ mOnTimeChangedListener.onTimeChanged(mDelegator, getHour(),
+ getMinute());
}
}
private void updateHourControl() {
- if (is24HourView()) {
+ if (is24Hour()) {
// 'k' means 1-24 hour
if (mHourFormat == 'k') {
mHourSpinner.setMinValue(1);
@@ -509,7 +490,7 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate {
}
private void updateMinuteControl() {
- if (is24HourView()) {
+ if (is24Hour()) {
mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_DONE);
} else {
mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT);
diff --git a/core/java/com/android/internal/os/BaseCommand.java b/core/java/com/android/internal/os/BaseCommand.java
index e26b27d5820c..c067da7e1dec 100644
--- a/core/java/com/android/internal/os/BaseCommand.java
+++ b/core/java/com/android/internal/os/BaseCommand.java
@@ -17,13 +17,19 @@
package com.android.internal.os;
+import android.os.ShellCommand;
+
import java.io.PrintStream;
public abstract class BaseCommand {
- protected String[] mArgs;
- private int mNextArg;
- private String mCurArgData;
+ final protected ShellCommand mArgs = new ShellCommand() {
+ @Override public int onCommand(String cmd) {
+ return 0;
+ }
+ @Override public void onHelp() {
+ }
+ };
// These are magic strings understood by the Eclipse plugin.
public static final String FATAL_ERROR_CODE = "Error type 1";
@@ -39,9 +45,7 @@ public abstract class BaseCommand {
return;
}
- mArgs = args;
- mNextArg = 0;
- mCurArgData = null;
+ mArgs.init(null, null, null, null, args, 0);
try {
onRun();
@@ -87,32 +91,7 @@ public abstract class BaseCommand {
* starts with '-'. If the next argument is not an option, null is returned.
*/
public String nextOption() {
- if (mCurArgData != null) {
- String prev = mArgs[mNextArg - 1];
- throw new IllegalArgumentException("No argument expected after \"" + prev + "\"");
- }
- if (mNextArg >= mArgs.length) {
- return null;
- }
- String arg = mArgs[mNextArg];
- if (!arg.startsWith("-")) {
- return null;
- }
- mNextArg++;
- if (arg.equals("--")) {
- return null;
- }
- if (arg.length() > 1 && arg.charAt(1) != '-') {
- if (arg.length() > 2) {
- mCurArgData = arg.substring(2);
- return arg.substring(0, 2);
- } else {
- mCurArgData = null;
- return arg;
- }
- }
- mCurArgData = null;
- return arg;
+ return mArgs.getNextOption();
}
/**
@@ -120,15 +99,7 @@ public abstract class BaseCommand {
* no arguments left, return null.
*/
public String nextArg() {
- if (mCurArgData != null) {
- String arg = mCurArgData;
- mCurArgData = null;
- return arg;
- } else if (mNextArg < mArgs.length) {
- return mArgs[mNextArg++];
- } else {
- return null;
- }
+ return mArgs.getNextArg();
}
/**
@@ -136,11 +107,6 @@ public abstract class BaseCommand {
* no arguments left, throws an IllegalArgumentException to report this to the user.
*/
public String nextArgRequired() {
- String arg = nextArg();
- if (arg == null) {
- String prev = mArgs[mNextArg - 1];
- throw new IllegalArgumentException("Argument expected after \"" + prev + "\"");
- }
- return arg;
+ return mArgs.getNextArgRequired();
}
}
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 197004c93dcb..8186378bfb6f 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -39,10 +39,8 @@ public final class Zygote {
public static final int DEBUG_ENABLE_SAFEMODE = 1 << 3;
/** Enable logging of third-party JNI activity. */
public static final int DEBUG_ENABLE_JNI_LOGGING = 1 << 4;
- /** enable the JIT compiler */
- public static final int DEBUG_ENABLE_JIT = 1 << 5;
/** Force generation of native debugging information. */
- public static final int DEBUG_GENERATE_DEBUG_INFO = 1 << 6;
+ public static final int DEBUG_GENERATE_DEBUG_INFO = 1 << 5;
/** No external storage should be mounted. */
public static final int MOUNT_EXTERNAL_NONE = 0;
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 3e86fac74f2d..a40f9a82ace7 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -322,7 +322,7 @@ class ZygoteConnection {
/**
* From --enable-debugger, --enable-checkjni, --enable-assert,
- * --enable-safemode, --enable-jit, --generate-debug-info and --enable-jni-logging.
+ * --enable-safemode, --generate-debug-info and --enable-jni-logging.
*/
int debugFlags;
@@ -432,8 +432,6 @@ class ZygoteConnection {
debugFlags |= Zygote.DEBUG_ENABLE_SAFEMODE;
} else if (arg.equals("--enable-checkjni")) {
debugFlags |= Zygote.DEBUG_ENABLE_CHECKJNI;
- } else if (arg.equals("--enable-jit")) {
- debugFlags |= Zygote.DEBUG_ENABLE_JIT;
} else if (arg.equals("--generate-debug-info")) {
debugFlags |= Zygote.DEBUG_GENERATE_DEBUG_INFO;
} else if (arg.equals("--enable-jni-logging")) {
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
new file mode 100644
index 000000000000..4f38ff34e7da
--- /dev/null
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -0,0 +1,1632 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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 com.android.internal.policy;
+
+import com.android.internal.R;
+import com.android.internal.view.FloatingActionMode;
+import com.android.internal.view.RootViewSurfaceTaker;
+import com.android.internal.view.StandaloneActionMode;
+import com.android.internal.view.menu.ContextMenuBuilder;
+import com.android.internal.view.menu.MenuDialogHelper;
+import com.android.internal.view.menu.MenuPopupHelper;
+import com.android.internal.widget.ActionBarContextView;
+import com.android.internal.widget.BackgroundFallback;
+import com.android.internal.widget.FloatingToolbar;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.ActionMode;
+import android.view.ContextThemeWrapper;
+import android.view.Gravity;
+import android.view.InputQueue;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewStub;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.widget.FrameLayout;
+import android.widget.PopupWindow;
+
+import static android.view.View.MeasureSpec.AT_MOST;
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.getMode;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
+import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
+import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
+
+class DecorView extends FrameLayout implements RootViewSurfaceTaker {
+ private static final String TAG = "DecorView";
+
+ private static final boolean SWEEP_OPEN_MENU = false;
+
+ int mDefaultOpacity = PixelFormat.OPAQUE;
+
+ /** The feature ID of the panel, or -1 if this is the application's DecorView */
+ private final int mFeatureId;
+
+ private final Rect mDrawingBounds = new Rect();
+
+ private final Rect mBackgroundPadding = new Rect();
+
+ private final Rect mFramePadding = new Rect();
+
+ private final Rect mFrameOffsets = new Rect();
+
+ // True if a non client area decor exists.
+ private boolean mHasNonClientDecor = false;
+
+ private boolean mChanging;
+
+ private Drawable mMenuBackground;
+ private boolean mWatchingForMenu;
+ private int mDownY;
+
+ ActionMode mPrimaryActionMode;
+ private ActionMode mFloatingActionMode;
+ private ActionBarContextView mPrimaryActionModeView;
+ private PopupWindow mPrimaryActionModePopup;
+ private Runnable mShowPrimaryActionModePopup;
+ private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener;
+ private View mFloatingActionModeOriginatingView;
+ private FloatingToolbar mFloatingToolbar;
+ private ObjectAnimator mFadeAnim;
+
+ // View added at runtime to draw under the status bar area
+ private View mStatusGuard;
+ // View added at runtime to draw under the navigation bar area
+ private View mNavigationGuard;
+
+ private final ColorViewState mStatusColorViewState = new ColorViewState(
+ SYSTEM_UI_FLAG_FULLSCREEN, FLAG_TRANSLUCENT_STATUS,
+ Gravity.TOP, Gravity.LEFT,
+ Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME,
+ com.android.internal.R.id.statusBarBackground,
+ FLAG_FULLSCREEN);
+ private final ColorViewState mNavigationColorViewState = new ColorViewState(
+ SYSTEM_UI_FLAG_HIDE_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION,
+ Gravity.BOTTOM, Gravity.RIGHT,
+ Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME,
+ com.android.internal.R.id.navigationBarBackground,
+ 0 /* hideWindowFlag */);
+
+ private final Interpolator mShowInterpolator;
+ private final Interpolator mHideInterpolator;
+ private final int mBarEnterExitDuration;
+
+ private final BackgroundFallback mBackgroundFallback = new BackgroundFallback();
+
+ private int mLastTopInset = 0;
+ private int mLastBottomInset = 0;
+ private int mLastRightInset = 0;
+ private boolean mLastHasTopStableInset = false;
+ private boolean mLastHasBottomStableInset = false;
+ private boolean mLastHasRightStableInset = false;
+ private int mLastWindowFlags = 0;
+
+ private int mRootScrollY = 0;
+
+ private PhoneWindow mWindow;
+
+ ViewGroup mContentRoot;
+
+ private Rect mTempRect;
+ private Rect mOutsets = new Rect();
+
+ DecorView(Context context, int featureId, PhoneWindow window) {
+ super(context);
+ mFeatureId = featureId;
+
+ mShowInterpolator = AnimationUtils.loadInterpolator(context,
+ android.R.interpolator.linear_out_slow_in);
+ mHideInterpolator = AnimationUtils.loadInterpolator(context,
+ android.R.interpolator.fast_out_linear_in);
+
+ mBarEnterExitDuration = context.getResources().getInteger(
+ R.integer.dock_enter_exit_duration);
+
+ setWindow(window);
+ }
+
+ public void setBackgroundFallback(int resId) {
+ mBackgroundFallback.setDrawable(resId != 0 ? getContext().getDrawable(resId) : null);
+ setWillNotDraw(getBackground() == null && !mBackgroundFallback.hasFallback());
+ }
+
+ @Override
+ public void onDraw(Canvas c) {
+ super.onDraw(c);
+ mBackgroundFallback.draw(mContentRoot, c, mWindow.mContentParent);
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ final int keyCode = event.getKeyCode();
+ final int action = event.getAction();
+ final boolean isDown = action == KeyEvent.ACTION_DOWN;
+
+ if (isDown && (event.getRepeatCount() == 0)) {
+ // First handle chording of panel key: if a panel key is held
+ // but not released, try to execute a shortcut in it.
+ if ((mWindow.mPanelChordingKey > 0) && (mWindow.mPanelChordingKey != keyCode)) {
+ boolean handled = dispatchKeyShortcutEvent(event);
+ if (handled) {
+ return true;
+ }
+ }
+
+ // If a panel is open, perform a shortcut on it without the
+ // chorded panel key
+ if ((mWindow.mPreparedPanel != null) && mWindow.mPreparedPanel.isOpen) {
+ if (mWindow.performPanelShortcut(mWindow.mPreparedPanel, keyCode, event, 0)) {
+ return true;
+ }
+ }
+ }
+
+ if (!mWindow.isDestroyed()) {
+ final Window.Callback cb = mWindow.getCallback();
+ final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
+ : super.dispatchKeyEvent(event);
+ if (handled) {
+ return true;
+ }
+ }
+
+ return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event)
+ : mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event);
+ }
+
+ @Override
+ public boolean dispatchKeyShortcutEvent(KeyEvent ev) {
+ // If the panel is already prepared, then perform the shortcut using it.
+ boolean handled;
+ if (mWindow.mPreparedPanel != null) {
+ handled = mWindow.performPanelShortcut(mWindow.mPreparedPanel, ev.getKeyCode(), ev,
+ Menu.FLAG_PERFORM_NO_CLOSE);
+ if (handled) {
+ if (mWindow.mPreparedPanel != null) {
+ mWindow.mPreparedPanel.isHandled = true;
+ }
+ return true;
+ }
+ }
+
+ // Shortcut not handled by the panel. Dispatch to the view hierarchy.
+ final Window.Callback cb = mWindow.getCallback();
+ handled = cb != null && !mWindow.isDestroyed() && mFeatureId < 0
+ ? cb.dispatchKeyShortcutEvent(ev) : super.dispatchKeyShortcutEvent(ev);
+ if (handled) {
+ return true;
+ }
+
+ // If the panel is not prepared, then we may be trying to handle a shortcut key
+ // combination such as Control+C. Temporarily prepare the panel then mark it
+ // unprepared again when finished to ensure that the panel will again be prepared
+ // the next time it is shown for real.
+ PhoneWindow.PanelFeatureState st =
+ mWindow.getPanelState(Window.FEATURE_OPTIONS_PANEL, false);
+ if (st != null && mWindow.mPreparedPanel == null) {
+ mWindow.preparePanel(st, ev);
+ handled = mWindow.performPanelShortcut(st, ev.getKeyCode(), ev,
+ Menu.FLAG_PERFORM_NO_CLOSE);
+ st.isPrepared = false;
+ if (handled) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ final Window.Callback cb = mWindow.getCallback();
+ return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
+ ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
+ }
+
+ @Override
+ public boolean dispatchTrackballEvent(MotionEvent ev) {
+ final Window.Callback cb = mWindow.getCallback();
+ return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
+ ? cb.dispatchTrackballEvent(ev) : super.dispatchTrackballEvent(ev);
+ }
+
+ @Override
+ public boolean dispatchGenericMotionEvent(MotionEvent ev) {
+ final Window.Callback cb = mWindow.getCallback();
+ return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
+ ? cb.dispatchGenericMotionEvent(ev) : super.dispatchGenericMotionEvent(ev);
+ }
+
+ public boolean superDispatchKeyEvent(KeyEvent event) {
+ // Give priority to closing action modes if applicable.
+ if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+ final int action = event.getAction();
+ // Back cancels action modes first.
+ if (mPrimaryActionMode != null) {
+ if (action == KeyEvent.ACTION_UP) {
+ mPrimaryActionMode.finish();
+ }
+ return true;
+ }
+ }
+
+ return super.dispatchKeyEvent(event);
+ }
+
+ public boolean superDispatchKeyShortcutEvent(KeyEvent event) {
+ return super.dispatchKeyShortcutEvent(event);
+ }
+
+ public boolean superDispatchTouchEvent(MotionEvent event) {
+ return super.dispatchTouchEvent(event);
+ }
+
+ public boolean superDispatchTrackballEvent(MotionEvent event) {
+ return super.dispatchTrackballEvent(event);
+ }
+
+ public boolean superDispatchGenericMotionEvent(MotionEvent event) {
+ return super.dispatchGenericMotionEvent(event);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ return onInterceptTouchEvent(event);
+ }
+
+ private boolean isOutOfInnerBounds(int x, int y) {
+ return x < 0 || y < 0 || x > getWidth() || y > getHeight();
+ }
+
+ private boolean isOutOfBounds(int x, int y) {
+ return x < -5 || y < -5 || x > (getWidth() + 5)
+ || y > (getHeight() + 5);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ int action = event.getAction();
+ if (mHasNonClientDecor && mWindow.mNonClientDecorView.mVisible) {
+ // Don't dispatch ACTION_DOWN to the non client decor if the window is
+ // resizable and the event was (starting) outside the window.
+ // Window resizing events should be handled by WindowManager.
+ // TODO: Investigate how to handle the outside touch in window manager
+ // without generating these events.
+ // Currently we receive these because we need to enlarge the window's
+ // touch region so that the monitor channel receives the events
+ // in the outside touch area.
+ if (action == MotionEvent.ACTION_DOWN) {
+ final int x = (int) event.getX();
+ final int y = (int) event.getY();
+ if (isOutOfInnerBounds(x, y)) {
+ return true;
+ }
+ }
+ }
+
+ if (mFeatureId >= 0) {
+ if (action == MotionEvent.ACTION_DOWN) {
+ int x = (int)event.getX();
+ int y = (int)event.getY();
+ if (isOutOfBounds(x, y)) {
+ mWindow.closePanel(mFeatureId);
+ return true;
+ }
+ }
+ }
+
+ if (!SWEEP_OPEN_MENU) {
+ return false;
+ }
+
+ if (mFeatureId >= 0) {
+ if (action == MotionEvent.ACTION_DOWN) {
+ Log.i(TAG, "Watchiing!");
+ mWatchingForMenu = true;
+ mDownY = (int) event.getY();
+ return false;
+ }
+
+ if (!mWatchingForMenu) {
+ return false;
+ }
+
+ int y = (int)event.getY();
+ if (action == MotionEvent.ACTION_MOVE) {
+ if (y > (mDownY+30)) {
+ Log.i(TAG, "Closing!");
+ mWindow.closePanel(mFeatureId);
+ mWatchingForMenu = false;
+ return true;
+ }
+ } else if (action == MotionEvent.ACTION_UP) {
+ mWatchingForMenu = false;
+ }
+
+ return false;
+ }
+
+ //Log.i(TAG, "Intercept: action=" + action + " y=" + event.getY()
+ // + " (in " + getHeight() + ")");
+
+ if (action == MotionEvent.ACTION_DOWN) {
+ int y = (int)event.getY();
+ if (y >= (getHeight()-5) && !mWindow.hasChildren()) {
+ Log.i(TAG, "Watching!");
+ mWatchingForMenu = true;
+ }
+ return false;
+ }
+
+ if (!mWatchingForMenu) {
+ return false;
+ }
+
+ int y = (int)event.getY();
+ if (action == MotionEvent.ACTION_MOVE) {
+ if (y < (getHeight()-30)) {
+ Log.i(TAG, "Opening!");
+ mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, new KeyEvent(
+ KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU));
+ mWatchingForMenu = false;
+ return true;
+ }
+ } else if (action == MotionEvent.ACTION_UP) {
+ mWatchingForMenu = false;
+ }
+
+ return false;
+ }
+
+ @Override
+ public void sendAccessibilityEvent(int eventType) {
+ if (!AccessibilityManager.getInstance(mContext).isEnabled()) {
+ return;
+ }
+
+ // if we are showing a feature that should be announced and one child
+ // make this child the event source since this is the feature itself
+ // otherwise the callback will take over and announce its client
+ if ((mFeatureId == Window.FEATURE_OPTIONS_PANEL ||
+ mFeatureId == Window.FEATURE_CONTEXT_MENU ||
+ mFeatureId == Window.FEATURE_PROGRESS ||
+ mFeatureId == Window.FEATURE_INDETERMINATE_PROGRESS)
+ && getChildCount() == 1) {
+ getChildAt(0).sendAccessibilityEvent(eventType);
+ } else {
+ super.sendAccessibilityEvent(eventType);
+ }
+ }
+
+ @Override
+ public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
+ final Window.Callback cb = mWindow.getCallback();
+ if (cb != null && !mWindow.isDestroyed()) {
+ if (cb.dispatchPopulateAccessibilityEvent(event)) {
+ return true;
+ }
+ }
+ return super.dispatchPopulateAccessibilityEventInternal(event);
+ }
+
+ @Override
+ protected boolean setFrame(int l, int t, int r, int b) {
+ boolean changed = super.setFrame(l, t, r, b);
+ if (changed) {
+ final Rect drawingBounds = mDrawingBounds;
+ getDrawingRect(drawingBounds);
+
+ Drawable fg = getForeground();
+ if (fg != null) {
+ final Rect frameOffsets = mFrameOffsets;
+ drawingBounds.left += frameOffsets.left;
+ drawingBounds.top += frameOffsets.top;
+ drawingBounds.right -= frameOffsets.right;
+ drawingBounds.bottom -= frameOffsets.bottom;
+ fg.setBounds(drawingBounds);
+ final Rect framePadding = mFramePadding;
+ drawingBounds.left += framePadding.left - frameOffsets.left;
+ drawingBounds.top += framePadding.top - frameOffsets.top;
+ drawingBounds.right -= framePadding.right - frameOffsets.right;
+ drawingBounds.bottom -= framePadding.bottom - frameOffsets.bottom;
+ }
+
+ Drawable bg = getBackground();
+ if (bg != null) {
+ bg.setBounds(drawingBounds);
+ }
+
+ if (SWEEP_OPEN_MENU) {
+ if (mMenuBackground == null && mFeatureId < 0
+ && mWindow.getAttributes().height
+ == WindowManager.LayoutParams.MATCH_PARENT) {
+ mMenuBackground = getContext().getDrawable(
+ R.drawable.menu_background);
+ }
+ if (mMenuBackground != null) {
+ mMenuBackground.setBounds(drawingBounds.left,
+ drawingBounds.bottom-6, drawingBounds.right,
+ drawingBounds.bottom+20);
+ }
+ }
+ }
+ return changed;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
+ final boolean isPortrait = metrics.widthPixels < metrics.heightPixels;
+
+ final int widthMode = getMode(widthMeasureSpec);
+ final int heightMode = getMode(heightMeasureSpec);
+
+ boolean fixedWidth = false;
+ if (widthMode == AT_MOST) {
+ final TypedValue tvw = isPortrait ? mWindow.mFixedWidthMinor
+ : mWindow.mFixedWidthMajor;
+ if (tvw != null && tvw.type != TypedValue.TYPE_NULL) {
+ final int w;
+ if (tvw.type == TypedValue.TYPE_DIMENSION) {
+ w = (int) tvw.getDimension(metrics);
+ } else if (tvw.type == TypedValue.TYPE_FRACTION) {
+ w = (int) tvw.getFraction(metrics.widthPixels, metrics.widthPixels);
+ } else {
+ w = 0;
+ }
+
+ if (w > 0) {
+ final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec(
+ Math.min(w, widthSize), EXACTLY);
+ fixedWidth = true;
+ }
+ }
+ }
+
+ if (heightMode == AT_MOST) {
+ final TypedValue tvh = isPortrait ? mWindow.mFixedHeightMajor
+ : mWindow.mFixedHeightMinor;
+ if (tvh != null && tvh.type != TypedValue.TYPE_NULL) {
+ final int h;
+ if (tvh.type == TypedValue.TYPE_DIMENSION) {
+ h = (int) tvh.getDimension(metrics);
+ } else if (tvh.type == TypedValue.TYPE_FRACTION) {
+ h = (int) tvh.getFraction(metrics.heightPixels, metrics.heightPixels);
+ } else {
+ h = 0;
+ }
+ if (h > 0) {
+ final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+ heightMeasureSpec = MeasureSpec.makeMeasureSpec(
+ Math.min(h, heightSize), EXACTLY);
+ }
+ }
+ }
+
+ getOutsets(mOutsets);
+ if (mOutsets.top > 0 || mOutsets.bottom > 0) {
+ int mode = MeasureSpec.getMode(heightMeasureSpec);
+ if (mode != MeasureSpec.UNSPECIFIED) {
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+ heightMeasureSpec = MeasureSpec.makeMeasureSpec(
+ height + mOutsets.top + mOutsets.bottom, mode);
+ }
+ }
+ if (mOutsets.left > 0 || mOutsets.right > 0) {
+ int mode = MeasureSpec.getMode(widthMeasureSpec);
+ if (mode != MeasureSpec.UNSPECIFIED) {
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec(
+ width + mOutsets.left + mOutsets.right, mode);
+ }
+ }
+
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ int width = getMeasuredWidth();
+ boolean measure = false;
+
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, EXACTLY);
+
+ if (!fixedWidth && widthMode == AT_MOST) {
+ final TypedValue tv = isPortrait ? mWindow.mMinWidthMinor : mWindow.mMinWidthMajor;
+ if (tv.type != TypedValue.TYPE_NULL) {
+ final int min;
+ if (tv.type == TypedValue.TYPE_DIMENSION) {
+ min = (int)tv.getDimension(metrics);
+ } else if (tv.type == TypedValue.TYPE_FRACTION) {
+ min = (int)tv.getFraction(metrics.widthPixels, metrics.widthPixels);
+ } else {
+ min = 0;
+ }
+
+ if (width < min) {
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec(min, EXACTLY);
+ measure = true;
+ }
+ }
+ }
+
+ // TODO: Support height?
+
+ if (measure) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ getOutsets(mOutsets);
+ if (mOutsets.left > 0) {
+ offsetLeftAndRight(-mOutsets.left);
+ }
+ if (mOutsets.top > 0) {
+ offsetTopAndBottom(-mOutsets.top);
+ }
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ super.draw(canvas);
+
+ if (mMenuBackground != null) {
+ mMenuBackground.draw(canvas);
+ }
+ }
+
+ @Override
+ public boolean showContextMenuForChild(View originalView) {
+ // Reuse the context menu builder
+ if (mWindow.mContextMenu == null) {
+ mWindow.mContextMenu = new ContextMenuBuilder(getContext());
+ mWindow.mContextMenu.setCallback(mWindow.mContextMenuCallback);
+ } else {
+ mWindow.mContextMenu.clearAll();
+ }
+
+ final MenuDialogHelper helper = mWindow.mContextMenu.show(originalView,
+ originalView.getWindowToken());
+ if (helper != null) {
+ helper.setPresenterCallback(mWindow.mContextMenuCallback);
+ } else if (mWindow.mContextMenuHelper != null) {
+ // No menu to show, but if we have a menu currently showing it just became blank.
+ // Close it.
+ mWindow.mContextMenuHelper.dismiss();
+ }
+ mWindow.mContextMenuHelper = helper;
+ return helper != null;
+ }
+
+ @Override
+ public boolean showContextMenuForChild(View originalView, float x, float y) {
+ // Reuse the context menu builder
+ if (mWindow.mContextMenu == null) {
+ mWindow.mContextMenu = new ContextMenuBuilder(getContext());
+ mWindow.mContextMenu.setCallback(mWindow.mContextMenuCallback);
+ } else {
+ mWindow.mContextMenu.clearAll();
+ }
+
+ final MenuPopupHelper helper = mWindow.mContextMenu.showPopup(
+ getContext(), originalView, x, y);
+ if (helper != null) {
+ helper.setCallback(mWindow.mContextMenuCallback);
+ } else if (mWindow.mContextMenuPopupHelper != null) {
+ // No menu to show, but if we have a menu currently showing it just became blank.
+ // Close it.
+ mWindow.mContextMenuPopupHelper.dismiss();
+ }
+ mWindow.mContextMenuPopupHelper = helper;
+ return helper != null;
+ }
+
+ @Override
+ public ActionMode startActionModeForChild(View originalView,
+ ActionMode.Callback callback) {
+ return startActionModeForChild(originalView, callback, ActionMode.TYPE_PRIMARY);
+ }
+
+ @Override
+ public ActionMode startActionModeForChild(
+ View child, ActionMode.Callback callback, int type) {
+ return startActionMode(child, callback, type);
+ }
+
+ @Override
+ public ActionMode startActionMode(ActionMode.Callback callback) {
+ return startActionMode(callback, ActionMode.TYPE_PRIMARY);
+ }
+
+ @Override
+ public ActionMode startActionMode(ActionMode.Callback callback, int type) {
+ return startActionMode(this, callback, type);
+ }
+
+ private ActionMode startActionMode(
+ View originatingView, ActionMode.Callback callback, int type) {
+ ActionMode.Callback2 wrappedCallback = new ActionModeCallback2Wrapper(callback);
+ ActionMode mode = null;
+ if (mWindow.getCallback() != null && !mWindow.isDestroyed()) {
+ try {
+ mode = mWindow.getCallback().onWindowStartingActionMode(wrappedCallback, type);
+ } catch (AbstractMethodError ame) {
+ // Older apps might not implement the typed version of this method.
+ if (type == ActionMode.TYPE_PRIMARY) {
+ try {
+ mode = mWindow.getCallback().onWindowStartingActionMode(
+ wrappedCallback);
+ } catch (AbstractMethodError ame2) {
+ // Older apps might not implement this callback method at all.
+ }
+ }
+ }
+ }
+ if (mode != null) {
+ if (mode.getType() == ActionMode.TYPE_PRIMARY) {
+ cleanupPrimaryActionMode();
+ mPrimaryActionMode = mode;
+ } else if (mode.getType() == ActionMode.TYPE_FLOATING) {
+ if (mFloatingActionMode != null) {
+ mFloatingActionMode.finish();
+ }
+ mFloatingActionMode = mode;
+ }
+ } else {
+ mode = createActionMode(type, wrappedCallback, originatingView);
+ if (mode != null && wrappedCallback.onCreateActionMode(mode, mode.getMenu())) {
+ setHandledActionMode(mode);
+ } else {
+ mode = null;
+ }
+ }
+ if (mode != null && mWindow.getCallback() != null && !mWindow.isDestroyed()) {
+ try {
+ mWindow.getCallback().onActionModeStarted(mode);
+ } catch (AbstractMethodError ame) {
+ // Older apps might not implement this callback method.
+ }
+ }
+ return mode;
+ }
+
+ private void cleanupPrimaryActionMode() {
+ if (mPrimaryActionMode != null) {
+ mPrimaryActionMode.finish();
+ mPrimaryActionMode = null;
+ }
+ if (mPrimaryActionModeView != null) {
+ mPrimaryActionModeView.killMode();
+ }
+ }
+
+ private void cleanupFloatingActionModeViews() {
+ if (mFloatingToolbar != null) {
+ mFloatingToolbar.dismiss();
+ mFloatingToolbar = null;
+ }
+ if (mFloatingActionModeOriginatingView != null) {
+ if (mFloatingToolbarPreDrawListener != null) {
+ mFloatingActionModeOriginatingView.getViewTreeObserver()
+ .removeOnPreDrawListener(mFloatingToolbarPreDrawListener);
+ mFloatingToolbarPreDrawListener = null;
+ }
+ mFloatingActionModeOriginatingView = null;
+ }
+ }
+
+ public void startChanging() {
+ mChanging = true;
+ }
+
+ public void finishChanging() {
+ mChanging = false;
+ drawableChanged();
+ }
+
+ public void setWindowBackground(Drawable drawable) {
+ if (getBackground() != drawable) {
+ setBackgroundDrawable(drawable);
+ if (drawable != null) {
+ drawable.getPadding(mBackgroundPadding);
+ } else {
+ mBackgroundPadding.setEmpty();
+ }
+ drawableChanged();
+ }
+ }
+
+ public void setWindowFrame(Drawable drawable) {
+ if (getForeground() != drawable) {
+ setForeground(drawable);
+ if (drawable != null) {
+ drawable.getPadding(mFramePadding);
+ } else {
+ mFramePadding.setEmpty();
+ }
+ drawableChanged();
+ }
+ }
+
+ @Override
+ public void onWindowSystemUiVisibilityChanged(int visible) {
+ updateColorViews(null /* insets */, true /* animate */);
+ }
+
+ @Override
+ public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+ mFrameOffsets.set(insets.getSystemWindowInsets());
+ insets = updateColorViews(insets, true /* animate */);
+ insets = updateStatusGuard(insets);
+ updateNavigationGuard(insets);
+ if (getForeground() != null) {
+ drawableChanged();
+ }
+ return insets;
+ }
+
+ @Override
+ public boolean isTransitionGroup() {
+ return false;
+ }
+
+ WindowInsets updateColorViews(WindowInsets insets, boolean animate) {
+ WindowManager.LayoutParams attrs = mWindow.getAttributes();
+ int sysUiVisibility = attrs.systemUiVisibility | getWindowSystemUiVisibility();
+
+ if (!mWindow.mIsFloating && ActivityManager.isHighEndGfx()) {
+ boolean disallowAnimate = !isLaidOut();
+ disallowAnimate |= ((mLastWindowFlags ^ attrs.flags)
+ & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
+ mLastWindowFlags = attrs.flags;
+
+ if (insets != null) {
+ mLastTopInset = Math.min(insets.getStableInsetTop(),
+ insets.getSystemWindowInsetTop());
+ mLastBottomInset = Math.min(insets.getStableInsetBottom(),
+ insets.getSystemWindowInsetBottom());
+ mLastRightInset = Math.min(insets.getStableInsetRight(),
+ insets.getSystemWindowInsetRight());
+
+ // Don't animate if the presence of stable insets has changed, because that
+ // indicates that the window was either just added and received them for the
+ // first time, or the window size or position has changed.
+ boolean hasTopStableInset = insets.getStableInsetTop() != 0;
+ disallowAnimate |= (hasTopStableInset != mLastHasTopStableInset);
+ mLastHasTopStableInset = hasTopStableInset;
+
+ boolean hasBottomStableInset = insets.getStableInsetBottom() != 0;
+ disallowAnimate |= (hasBottomStableInset != mLastHasBottomStableInset);
+ mLastHasBottomStableInset = hasBottomStableInset;
+
+ boolean hasRightStableInset = insets.getStableInsetRight() != 0;
+ disallowAnimate |= (hasRightStableInset != mLastHasRightStableInset);
+ mLastHasRightStableInset = hasRightStableInset;
+ }
+
+ boolean navBarToRightEdge = mLastBottomInset == 0 && mLastRightInset > 0;
+ int navBarSize = navBarToRightEdge ? mLastRightInset : mLastBottomInset;
+ updateColorViewInt(mNavigationColorViewState, sysUiVisibility,
+ mWindow.mNavigationBarColor, navBarSize, navBarToRightEdge,
+ 0 /* rightInset */, animate && !disallowAnimate);
+
+ boolean statusBarNeedsRightInset = navBarToRightEdge
+ && mNavigationColorViewState.present;
+ int statusBarRightInset = statusBarNeedsRightInset ? mLastRightInset : 0;
+ updateColorViewInt(mStatusColorViewState, sysUiVisibility, mWindow.mStatusBarColor,
+ mLastTopInset, false /* matchVertical */, statusBarRightInset,
+ animate && !disallowAnimate);
+ }
+
+ // When we expand the window with FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, we still need
+ // to ensure that the rest of the view hierarchy doesn't notice it, unless they've
+ // explicitly asked for it.
+
+ boolean consumingNavBar =
+ (attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
+ && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0
+ && (sysUiVisibility & SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0;
+
+ int consumedRight = consumingNavBar ? mLastRightInset : 0;
+ int consumedBottom = consumingNavBar ? mLastBottomInset : 0;
+
+ if (mContentRoot != null
+ && mContentRoot.getLayoutParams() instanceof MarginLayoutParams) {
+ MarginLayoutParams lp = (MarginLayoutParams) mContentRoot.getLayoutParams();
+ if (lp.rightMargin != consumedRight || lp.bottomMargin != consumedBottom) {
+ lp.rightMargin = consumedRight;
+ lp.bottomMargin = consumedBottom;
+ mContentRoot.setLayoutParams(lp);
+
+ if (insets == null) {
+ // The insets have changed, but we're not currently in the process
+ // of dispatching them.
+ requestApplyInsets();
+ }
+ }
+ if (insets != null) {
+ insets = insets.replaceSystemWindowInsets(
+ insets.getSystemWindowInsetLeft(),
+ insets.getSystemWindowInsetTop(),
+ insets.getSystemWindowInsetRight() - consumedRight,
+ insets.getSystemWindowInsetBottom() - consumedBottom);
+ }
+ }
+
+ if (insets != null) {
+ insets = insets.consumeStableInsets();
+ }
+ return insets;
+ }
+
+ /**
+ * Update a color view
+ *
+ * @param state the color view to update.
+ * @param sysUiVis the current systemUiVisibility to apply.
+ * @param color the current color to apply.
+ * @param size the current size in the non-parent-matching dimension.
+ * @param verticalBar if true the view is attached to a vertical edge, otherwise to a
+ * horizontal edge,
+ * @param rightMargin rightMargin for the color view.
+ * @param animate if true, the change will be animated.
+ */
+ private void updateColorViewInt(final ColorViewState state, int sysUiVis, int color,
+ int size, boolean verticalBar, int rightMargin, boolean animate) {
+ state.present = size > 0 && (sysUiVis & state.systemUiHideFlag) == 0
+ && (mWindow.getAttributes().flags & state.hideWindowFlag) == 0
+ && (mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
+ boolean show = state.present
+ && (color & Color.BLACK) != 0
+ && (mWindow.getAttributes().flags & state.translucentFlag) == 0;
+
+ boolean visibilityChanged = false;
+ View view = state.view;
+
+ int resolvedHeight = verticalBar ? LayoutParams.MATCH_PARENT : size;
+ int resolvedWidth = verticalBar ? size : LayoutParams.MATCH_PARENT;
+ int resolvedGravity = verticalBar ? state.horizontalGravity : state.verticalGravity;
+
+ if (view == null) {
+ if (show) {
+ state.view = view = new View(mContext);
+ view.setBackgroundColor(color);
+ view.setTransitionName(state.transitionName);
+ view.setId(state.id);
+ visibilityChanged = true;
+ view.setVisibility(INVISIBLE);
+ state.targetVisibility = VISIBLE;
+
+ LayoutParams lp = new LayoutParams(resolvedWidth, resolvedHeight,
+ resolvedGravity);
+ lp.rightMargin = rightMargin;
+ addView(view, lp);
+ updateColorViewTranslations();
+ }
+ } else {
+ int vis = show ? VISIBLE : INVISIBLE;
+ visibilityChanged = state.targetVisibility != vis;
+ state.targetVisibility = vis;
+ LayoutParams lp = (LayoutParams) view.getLayoutParams();
+ if (lp.height != resolvedHeight || lp.width != resolvedWidth
+ || lp.gravity != resolvedGravity || lp.rightMargin != rightMargin) {
+ lp.height = resolvedHeight;
+ lp.width = resolvedWidth;
+ lp.gravity = resolvedGravity;
+ lp.rightMargin = rightMargin;
+ view.setLayoutParams(lp);
+ }
+ if (show) {
+ view.setBackgroundColor(color);
+ }
+ }
+ if (visibilityChanged) {
+ view.animate().cancel();
+ if (animate) {
+ if (show) {
+ if (view.getVisibility() != VISIBLE) {
+ view.setVisibility(VISIBLE);
+ view.setAlpha(0.0f);
+ }
+ view.animate().alpha(1.0f).setInterpolator(mShowInterpolator).
+ setDuration(mBarEnterExitDuration);
+ } else {
+ view.animate().alpha(0.0f).setInterpolator(mHideInterpolator)
+ .setDuration(mBarEnterExitDuration)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ state.view.setAlpha(1.0f);
+ state.view.setVisibility(INVISIBLE);
+ }
+ });
+ }
+ } else {
+ view.setAlpha(1.0f);
+ view.setVisibility(show ? VISIBLE : INVISIBLE);
+ }
+ }
+ }
+
+ private void updateColorViewTranslations() {
+ // Put the color views back in place when they get moved off the screen
+ // due to the the ViewRootImpl panning.
+ int rootScrollY = mRootScrollY;
+ if (mStatusColorViewState.view != null) {
+ mStatusColorViewState.view.setTranslationY(rootScrollY > 0 ? rootScrollY : 0);
+ }
+ if (mNavigationColorViewState.view != null) {
+ mNavigationColorViewState.view.setTranslationY(rootScrollY < 0 ? rootScrollY : 0);
+ }
+ }
+
+ private WindowInsets updateStatusGuard(WindowInsets insets) {
+ boolean showStatusGuard = false;
+ // Show the status guard when the non-overlay contextual action bar is showing
+ if (mPrimaryActionModeView != null) {
+ if (mPrimaryActionModeView.getLayoutParams() instanceof MarginLayoutParams) {
+ // Insets are magic!
+ final MarginLayoutParams mlp = (MarginLayoutParams)
+ mPrimaryActionModeView.getLayoutParams();
+ boolean mlpChanged = false;
+ if (mPrimaryActionModeView.isShown()) {
+ if (mTempRect == null) {
+ mTempRect = new Rect();
+ }
+ final Rect rect = mTempRect;
+
+ // If the parent doesn't consume the insets, manually
+ // apply the default system window insets.
+ mWindow.mContentParent.computeSystemWindowInsets(insets, rect);
+ final int newMargin = rect.top == 0 ? insets.getSystemWindowInsetTop() : 0;
+ if (mlp.topMargin != newMargin) {
+ mlpChanged = true;
+ mlp.topMargin = insets.getSystemWindowInsetTop();
+
+ if (mStatusGuard == null) {
+ mStatusGuard = new View(mContext);
+ mStatusGuard.setBackgroundColor(mContext.getColor(
+ R.color.input_method_navigation_guard));
+ addView(mStatusGuard, indexOfChild(mStatusColorViewState.view),
+ new LayoutParams(LayoutParams.MATCH_PARENT,
+ mlp.topMargin, Gravity.START | Gravity.TOP));
+ } else {
+ final LayoutParams lp = (LayoutParams)
+ mStatusGuard.getLayoutParams();
+ if (lp.height != mlp.topMargin) {
+ lp.height = mlp.topMargin;
+ mStatusGuard.setLayoutParams(lp);
+ }
+ }
+ }
+
+ // The action mode's theme may differ from the app, so
+ // always show the status guard above it if we have one.
+ showStatusGuard = mStatusGuard != null;
+
+ // We only need to consume the insets if the action
+ // mode is overlaid on the app content (e.g. it's
+ // sitting in a FrameLayout, see
+ // screen_simple_overlay_action_mode.xml).
+ final boolean nonOverlay = (mWindow.getLocalFeaturesPrivate()
+ & (1 << Window.FEATURE_ACTION_MODE_OVERLAY)) == 0;
+ insets = insets.consumeSystemWindowInsets(
+ false, nonOverlay && showStatusGuard /* top */, false, false);
+ } else {
+ // reset top margin
+ if (mlp.topMargin != 0) {
+ mlpChanged = true;
+ mlp.topMargin = 0;
+ }
+ }
+ if (mlpChanged) {
+ mPrimaryActionModeView.setLayoutParams(mlp);
+ }
+ }
+ }
+ if (mStatusGuard != null) {
+ mStatusGuard.setVisibility(showStatusGuard ? View.VISIBLE : View.GONE);
+ }
+ return insets;
+ }
+
+ private void updateNavigationGuard(WindowInsets insets) {
+ // IMEs lay out below the nav bar, but the content view must not (for back compat)
+ if (mWindow.getAttributes().type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
+ // prevent the content view from including the nav bar height
+ if (mWindow.mContentParent != null) {
+ if (mWindow.mContentParent.getLayoutParams() instanceof MarginLayoutParams) {
+ MarginLayoutParams mlp =
+ (MarginLayoutParams) mWindow.mContentParent.getLayoutParams();
+ mlp.bottomMargin = insets.getSystemWindowInsetBottom();
+ mWindow.mContentParent.setLayoutParams(mlp);
+ }
+ }
+ // position the navigation guard view, creating it if necessary
+ if (mNavigationGuard == null) {
+ mNavigationGuard = new View(mContext);
+ mNavigationGuard.setBackgroundColor(mContext.getColor(
+ R.color.input_method_navigation_guard));
+ addView(mNavigationGuard, indexOfChild(mNavigationColorViewState.view),
+ new LayoutParams(LayoutParams.MATCH_PARENT,
+ insets.getSystemWindowInsetBottom(),
+ Gravity.START | Gravity.BOTTOM));
+ } else {
+ LayoutParams lp = (LayoutParams) mNavigationGuard.getLayoutParams();
+ lp.height = insets.getSystemWindowInsetBottom();
+ mNavigationGuard.setLayoutParams(lp);
+ }
+ }
+ }
+
+ private void drawableChanged() {
+ if (mChanging) {
+ return;
+ }
+
+ setPadding(mFramePadding.left + mBackgroundPadding.left,
+ mFramePadding.top + mBackgroundPadding.top,
+ mFramePadding.right + mBackgroundPadding.right,
+ mFramePadding.bottom + mBackgroundPadding.bottom);
+ requestLayout();
+ invalidate();
+
+ int opacity = PixelFormat.OPAQUE;
+ if (windowHasShadow()) {
+ // If the window has a shadow, it must be translucent.
+ opacity = PixelFormat.TRANSLUCENT;
+ } else{
+ // Note: If there is no background, we will assume opaque. The
+ // common case seems to be that an application sets there to be
+ // no background so it can draw everything itself. For that,
+ // we would like to assume OPAQUE and let the app force it to
+ // the slower TRANSLUCENT mode if that is really what it wants.
+ Drawable bg = getBackground();
+ Drawable fg = getForeground();
+ if (bg != null) {
+ if (fg == null) {
+ opacity = bg.getOpacity();
+ } else if (mFramePadding.left <= 0 && mFramePadding.top <= 0
+ && mFramePadding.right <= 0 && mFramePadding.bottom <= 0) {
+ // If the frame padding is zero, then we can be opaque
+ // if either the frame -or- the background is opaque.
+ int fop = fg.getOpacity();
+ int bop = bg.getOpacity();
+ if (false)
+ Log.v(TAG, "Background opacity: " + bop + ", Frame opacity: " + fop);
+ if (fop == PixelFormat.OPAQUE || bop == PixelFormat.OPAQUE) {
+ opacity = PixelFormat.OPAQUE;
+ } else if (fop == PixelFormat.UNKNOWN) {
+ opacity = bop;
+ } else if (bop == PixelFormat.UNKNOWN) {
+ opacity = fop;
+ } else {
+ opacity = Drawable.resolveOpacity(fop, bop);
+ }
+ } else {
+ // For now we have to assume translucent if there is a
+ // frame with padding... there is no way to tell if the
+ // frame and background together will draw all pixels.
+ if (false)
+ Log.v(TAG, "Padding: " + mFramePadding);
+ opacity = PixelFormat.TRANSLUCENT;
+ }
+ }
+ if (false)
+ Log.v(TAG, "Background: " + bg + ", Frame: " + fg);
+ }
+
+ if (false)
+ Log.v(TAG, "Selected default opacity: " + opacity);
+
+ mDefaultOpacity = opacity;
+ if (mFeatureId < 0) {
+ mWindow.setDefaultWindowFormat(opacity);
+ }
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasWindowFocus) {
+ super.onWindowFocusChanged(hasWindowFocus);
+
+ // If the user is chording a menu shortcut, release the chord since
+ // this window lost focus
+ if (mWindow.hasFeature(Window.FEATURE_OPTIONS_PANEL) && !hasWindowFocus
+ && mWindow.mPanelChordingKey != 0) {
+ mWindow.closePanel(Window.FEATURE_OPTIONS_PANEL);
+ }
+
+ final Window.Callback cb = mWindow.getCallback();
+ if (cb != null && !mWindow.isDestroyed() && mFeatureId < 0) {
+ cb.onWindowFocusChanged(hasWindowFocus);
+ }
+
+ if (mPrimaryActionMode != null) {
+ mPrimaryActionMode.onWindowFocusChanged(hasWindowFocus);
+ }
+ if (mFloatingActionMode != null) {
+ mFloatingActionMode.onWindowFocusChanged(hasWindowFocus);
+ }
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ final Window.Callback cb = mWindow.getCallback();
+ if (cb != null && !mWindow.isDestroyed() && mFeatureId < 0) {
+ cb.onAttachedToWindow();
+ }
+
+ if (mFeatureId == -1) {
+ /*
+ * The main window has been attached, try to restore any panels
+ * that may have been open before. This is called in cases where
+ * an activity is being killed for configuration change and the
+ * menu was open. When the activity is recreated, the menu
+ * should be shown again.
+ */
+ mWindow.openPanelsAfterRestore();
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ final Window.Callback cb = mWindow.getCallback();
+ if (cb != null && mFeatureId < 0) {
+ cb.onDetachedFromWindow();
+ }
+
+ if (mWindow.mDecorContentParent != null) {
+ mWindow.mDecorContentParent.dismissPopups();
+ }
+
+ if (mPrimaryActionModePopup != null) {
+ removeCallbacks(mShowPrimaryActionModePopup);
+ if (mPrimaryActionModePopup.isShowing()) {
+ mPrimaryActionModePopup.dismiss();
+ }
+ mPrimaryActionModePopup = null;
+ }
+ if (mFloatingToolbar != null) {
+ mFloatingToolbar.dismiss();
+ mFloatingToolbar = null;
+ }
+
+ PhoneWindow.PanelFeatureState st = mWindow.getPanelState(Window.FEATURE_OPTIONS_PANEL, false);
+ if (st != null && st.menu != null && mFeatureId < 0) {
+ st.menu.close();
+ }
+ }
+
+ @Override
+ public void onCloseSystemDialogs(String reason) {
+ if (mFeatureId >= 0) {
+ mWindow.closeAllPanels();
+ }
+ }
+
+ public android.view.SurfaceHolder.Callback2 willYouTakeTheSurface() {
+ return mFeatureId < 0 ? mWindow.mTakeSurfaceCallback : null;
+ }
+
+ public InputQueue.Callback willYouTakeTheInputQueue() {
+ return mFeatureId < 0 ? mWindow.mTakeInputQueueCallback : null;
+ }
+
+ public void setSurfaceType(int type) {
+ mWindow.setType(type);
+ }
+
+ public void setSurfaceFormat(int format) {
+ mWindow.setFormat(format);
+ }
+
+ public void setSurfaceKeepScreenOn(boolean keepOn) {
+ if (keepOn) mWindow.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ else mWindow.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+
+ @Override
+ public void onRootViewScrollYChanged(int rootScrollY) {
+ mRootScrollY = rootScrollY;
+ updateColorViewTranslations();
+ }
+
+ private ActionMode createActionMode(
+ int type, ActionMode.Callback2 callback, View originatingView) {
+ switch (type) {
+ case ActionMode.TYPE_PRIMARY:
+ default:
+ return createStandaloneActionMode(callback);
+ case ActionMode.TYPE_FLOATING:
+ return createFloatingActionMode(originatingView, callback);
+ }
+ }
+
+ private void setHandledActionMode(ActionMode mode) {
+ if (mode.getType() == ActionMode.TYPE_PRIMARY) {
+ setHandledPrimaryActionMode(mode);
+ } else if (mode.getType() == ActionMode.TYPE_FLOATING) {
+ setHandledFloatingActionMode(mode);
+ }
+ }
+
+ private ActionMode createStandaloneActionMode(ActionMode.Callback callback) {
+ endOnGoingFadeAnimation();
+ cleanupPrimaryActionMode();
+ if (mPrimaryActionModeView == null) {
+ if (mWindow.isFloating()) {
+ // Use the action bar theme.
+ final TypedValue outValue = new TypedValue();
+ final Resources.Theme baseTheme = mContext.getTheme();
+ baseTheme.resolveAttribute(R.attr.actionBarTheme, outValue, true);
+
+ final Context actionBarContext;
+ if (outValue.resourceId != 0) {
+ final Resources.Theme actionBarTheme = mContext.getResources().newTheme();
+ actionBarTheme.setTo(baseTheme);
+ actionBarTheme.applyStyle(outValue.resourceId, true);
+
+ actionBarContext = new ContextThemeWrapper(mContext, 0);
+ actionBarContext.getTheme().setTo(actionBarTheme);
+ } else {
+ actionBarContext = mContext;
+ }
+
+ mPrimaryActionModeView = new ActionBarContextView(actionBarContext);
+ mPrimaryActionModePopup = new PopupWindow(actionBarContext, null,
+ R.attr.actionModePopupWindowStyle);
+ mPrimaryActionModePopup.setWindowLayoutType(
+ WindowManager.LayoutParams.TYPE_APPLICATION);
+ mPrimaryActionModePopup.setContentView(mPrimaryActionModeView);
+ mPrimaryActionModePopup.setWidth(MATCH_PARENT);
+
+ actionBarContext.getTheme().resolveAttribute(
+ R.attr.actionBarSize, outValue, true);
+ final int height = TypedValue.complexToDimensionPixelSize(outValue.data,
+ actionBarContext.getResources().getDisplayMetrics());
+ mPrimaryActionModeView.setContentHeight(height);
+ mPrimaryActionModePopup.setHeight(WRAP_CONTENT);
+ mShowPrimaryActionModePopup = new Runnable() {
+ public void run() {
+ mPrimaryActionModePopup.showAtLocation(
+ mPrimaryActionModeView.getApplicationWindowToken(),
+ Gravity.TOP | Gravity.FILL_HORIZONTAL, 0, 0);
+ endOnGoingFadeAnimation();
+ mFadeAnim = ObjectAnimator.ofFloat(mPrimaryActionModeView, View.ALPHA,
+ 0f, 1f);
+ mFadeAnim.addListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mPrimaryActionModeView.setVisibility(VISIBLE);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mPrimaryActionModeView.setAlpha(1f);
+ mFadeAnim = null;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+
+ }
+ });
+ mFadeAnim.start();
+ }
+ };
+ } else {
+ ViewStub stub = (ViewStub) findViewById(R.id.action_mode_bar_stub);
+ if (stub != null) {
+ mPrimaryActionModeView = (ActionBarContextView) stub.inflate();
+ }
+ }
+ }
+ if (mPrimaryActionModeView != null) {
+ mPrimaryActionModeView.killMode();
+ ActionMode mode = new StandaloneActionMode(
+ mPrimaryActionModeView.getContext(), mPrimaryActionModeView,
+ callback, mPrimaryActionModePopup == null);
+ return mode;
+ }
+ return null;
+ }
+
+ private void endOnGoingFadeAnimation() {
+ if (mFadeAnim != null) {
+ mFadeAnim.end();
+ }
+ }
+
+ private void setHandledPrimaryActionMode(ActionMode mode) {
+ endOnGoingFadeAnimation();
+ mPrimaryActionMode = mode;
+ mPrimaryActionMode.invalidate();
+ mPrimaryActionModeView.initForMode(mPrimaryActionMode);
+ if (mPrimaryActionModePopup != null) {
+ post(mShowPrimaryActionModePopup);
+ } else {
+ mFadeAnim = ObjectAnimator.ofFloat(mPrimaryActionModeView, View.ALPHA, 0f, 1f);
+ mFadeAnim.addListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mPrimaryActionModeView.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mPrimaryActionModeView.setAlpha(1f);
+ mFadeAnim = null;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+
+ }
+ });
+ mFadeAnim.start();
+ }
+ mPrimaryActionModeView.sendAccessibilityEvent(
+ AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ }
+
+ private ActionMode createFloatingActionMode(
+ View originatingView, ActionMode.Callback2 callback) {
+ if (mFloatingActionMode != null) {
+ mFloatingActionMode.finish();
+ }
+ cleanupFloatingActionModeViews();
+ final FloatingActionMode mode =
+ new FloatingActionMode(mContext, callback, originatingView);
+ mFloatingActionModeOriginatingView = originatingView;
+ mFloatingToolbarPreDrawListener =
+ new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ mode.updateViewLocationInWindow();
+ return true;
+ }
+ };
+ return mode;
+ }
+
+ private void setHandledFloatingActionMode(ActionMode mode) {
+ mFloatingActionMode = mode;
+ mFloatingToolbar = new FloatingToolbar(mContext, mWindow);
+ ((FloatingActionMode) mFloatingActionMode).setFloatingToolbar(mFloatingToolbar);
+ mFloatingActionMode.invalidate(); // Will show the floating toolbar if necessary.
+ mFloatingActionModeOriginatingView.getViewTreeObserver()
+ .addOnPreDrawListener(mFloatingToolbarPreDrawListener);
+ }
+
+ /**
+ * Informs the decor if a non client decor is attached and visible.
+ * @param attachedAndVisible true when the decor is visible.
+ * Note that this will even be called if there is no non client decor.
+ **/
+ void enableNonClientDecor(boolean attachedAndVisible) {
+ if (mHasNonClientDecor != attachedAndVisible) {
+ mHasNonClientDecor = attachedAndVisible;
+ if (getForeground() != null) {
+ drawableChanged();
+ }
+ }
+ }
+
+ /**
+ * Returns true if the window has a non client decor.
+ * @return If there is a non client decor - even if it is not visible.
+ **/
+ private boolean windowHasNonClientDecor() {
+ return mHasNonClientDecor;
+ }
+
+ /**
+ * Returns true if the Window is free floating and has a shadow (although at some times
+ * it might not be displaying it, e.g. during a resize). Note that non overlapping windows
+ * do not have a shadow since it could not be seen anyways (a small screen / tablet
+ * "tiles" the windows side by side but does not overlap them).
+ * @return Returns true when the window has a shadow created by the non client decor.
+ **/
+ private boolean windowHasShadow() {
+ return windowHasNonClientDecor() && ActivityManager.StackId
+ .hasWindowShadow(mWindow.mWorkspaceId);
+ }
+
+ void setWindow(PhoneWindow phoneWindow) {
+ mWindow = phoneWindow;
+ Context context = getContext();
+ if (context instanceof DecorContext) {
+ DecorContext decorContex = (DecorContext) context;
+ decorContex.setPhoneWindow(mWindow);
+ }
+ }
+
+ private static class ColorViewState {
+ View view = null;
+ int targetVisibility = View.INVISIBLE;
+ boolean present = false;
+
+ final int id;
+ final int systemUiHideFlag;
+ final int translucentFlag;
+ final int verticalGravity;
+ final int horizontalGravity;
+ final String transitionName;
+ final int hideWindowFlag;
+
+ ColorViewState(int systemUiHideFlag,
+ int translucentFlag, int verticalGravity, int horizontalGravity,
+ String transitionName, int id, int hideWindowFlag) {
+ this.id = id;
+ this.systemUiHideFlag = systemUiHideFlag;
+ this.translucentFlag = translucentFlag;
+ this.verticalGravity = verticalGravity;
+ this.horizontalGravity = horizontalGravity;
+ this.transitionName = transitionName;
+ this.hideWindowFlag = hideWindowFlag;
+ }
+ }
+
+ /**
+ * Clears out internal references when the action mode is destroyed.
+ */
+ private class ActionModeCallback2Wrapper extends ActionMode.Callback2 {
+ private final ActionMode.Callback mWrapped;
+
+ public ActionModeCallback2Wrapper(ActionMode.Callback wrapped) {
+ mWrapped = wrapped;
+ }
+
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ return mWrapped.onCreateActionMode(mode, menu);
+ }
+
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ requestFitSystemWindows();
+ return mWrapped.onPrepareActionMode(mode, menu);
+ }
+
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ return mWrapped.onActionItemClicked(mode, item);
+ }
+
+ public void onDestroyActionMode(ActionMode mode) {
+ mWrapped.onDestroyActionMode(mode);
+ final boolean isMncApp = mContext.getApplicationInfo().targetSdkVersion
+ >= Build.VERSION_CODES.M;
+ final boolean isPrimary;
+ final boolean isFloating;
+ if (isMncApp) {
+ isPrimary = mode == mPrimaryActionMode;
+ isFloating = mode == mFloatingActionMode;
+ if (!isPrimary && mode.getType() == ActionMode.TYPE_PRIMARY) {
+ Log.e(TAG, "Destroying unexpected ActionMode instance of TYPE_PRIMARY; "
+ + mode + " was not the current primary action mode! Expected "
+ + mPrimaryActionMode);
+ }
+ if (!isFloating && mode.getType() == ActionMode.TYPE_FLOATING) {
+ Log.e(TAG, "Destroying unexpected ActionMode instance of TYPE_FLOATING; "
+ + mode + " was not the current floating action mode! Expected "
+ + mFloatingActionMode);
+ }
+ } else {
+ isPrimary = mode.getType() == ActionMode.TYPE_PRIMARY;
+ isFloating = mode.getType() == ActionMode.TYPE_FLOATING;
+ }
+ if (isPrimary) {
+ if (mPrimaryActionModePopup != null) {
+ removeCallbacks(mShowPrimaryActionModePopup);
+ }
+ if (mPrimaryActionModeView != null) {
+ endOnGoingFadeAnimation();
+ mFadeAnim = ObjectAnimator.ofFloat(mPrimaryActionModeView, View.ALPHA,
+ 1f, 0f);
+ mFadeAnim.addListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mPrimaryActionModeView.setVisibility(GONE);
+ if (mPrimaryActionModePopup != null) {
+ mPrimaryActionModePopup.dismiss();
+ }
+ mPrimaryActionModeView.removeAllViews();
+ mFadeAnim = null;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+
+ }
+ });
+ mFadeAnim.start();
+ }
+
+ mPrimaryActionMode = null;
+ } else if (isFloating) {
+ cleanupFloatingActionModeViews();
+ mFloatingActionMode = null;
+ }
+ if (mWindow.getCallback() != null && !mWindow.isDestroyed()) {
+ try {
+ mWindow.getCallback().onActionModeFinished(mode);
+ } catch (AbstractMethodError ame) {
+ // Older apps might not implement this callback method.
+ }
+ }
+ requestFitSystemWindows();
+ }
+
+ @Override
+ public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
+ if (mWrapped instanceof ActionMode.Callback2) {
+ ((ActionMode.Callback2) mWrapped).onGetContentRect(mode, view, outRect);
+ } else {
+ super.onGetContentRect(mode, view, outRect);
+ }
+ }
+ }
+}
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 83f810fa76e5..c42714941a5b 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -18,22 +18,15 @@ package com.android.internal.policy;
import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.view.View.MeasureSpec.AT_MOST;
-import static android.view.View.MeasureSpec.EXACTLY;
-import static android.view.View.MeasureSpec.getMode;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static android.view.WindowManager.LayoutParams.*;
-import android.animation.Animator;
-import android.animation.ObjectAnimator;
import android.app.ActivityManager.StackId;
import android.app.ActivityManagerNative;
import android.app.SearchManager;
-import android.os.Build;
import android.os.UserHandle;
-import android.view.ActionMode;
import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.IRotationWatcher.Stub;
@@ -55,15 +48,9 @@ import android.view.ViewGroup;
import android.view.ViewManager;
import android.view.ViewParent;
import android.view.ViewRootImpl;
-import android.view.ViewStub;
-import android.view.ViewTreeObserver.OnPreDrawListener;
import android.view.Window;
-import android.view.WindowInsets;
import android.view.WindowManager;
import com.android.internal.R;
-import com.android.internal.view.FloatingActionMode;
-import com.android.internal.view.RootViewSurfaceTaker;
-import com.android.internal.view.StandaloneActionMode;
import com.android.internal.view.menu.ContextMenuBuilder;
import com.android.internal.view.menu.IconMenuPresenter;
import com.android.internal.view.menu.ListMenuPresenter;
@@ -72,10 +59,7 @@ import com.android.internal.view.menu.MenuDialogHelper;
import com.android.internal.view.menu.MenuPopupHelper;
import com.android.internal.view.menu.MenuPresenter;
import com.android.internal.view.menu.MenuView;
-import com.android.internal.widget.ActionBarContextView;
-import com.android.internal.widget.BackgroundFallback;
import com.android.internal.widget.DecorContentParent;
-import com.android.internal.widget.FloatingToolbar;
import com.android.internal.widget.NonClientDecorView;
import com.android.internal.widget.SwipeDismissLayout;
@@ -87,9 +71,7 @@ import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
-import android.graphics.Canvas;
import android.graphics.Color;
-import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
@@ -108,22 +90,17 @@ import android.transition.TransitionInflater;
import android.transition.TransitionManager;
import android.transition.TransitionSet;
import android.util.AndroidRuntimeException;
-import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
import android.util.SparseArray;
import android.util.TypedValue;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeProvider;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ImageView;
-import android.widget.PopupWindow;
import android.widget.ProgressBar;
import android.widget.TextView;
@@ -142,8 +119,6 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
private final static String TAG = "PhoneWindow";
- private final static boolean SWEEP_OPEN_MENU = false;
-
private final static int DEFAULT_BACKGROUND_FADE_DURATION_MS = 300;
private static final int CUSTOM_TITLE_COMPATIBLE_FEATURES = DEFAULT_FEATURES |
@@ -185,21 +160,19 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
// This is the view in which the window contents are placed. It is either
// mDecor itself, or a child of mDecor where the contents go.
- private ViewGroup mContentParent;
-
- private ViewGroup mContentRoot;
+ ViewGroup mContentParent;
Callback2 mTakeSurfaceCallback;
InputQueue.Callback mTakeInputQueueCallback;
- private boolean mIsFloating;
+ boolean mIsFloating;
private LayoutInflater mLayoutInflater;
private TextView mTitleView;
- private DecorContentParent mDecorContentParent;
+ DecorContentParent mDecorContentParent;
private ActionMenuPresenterCallback mActionMenuPresenterCallback;
private PanelMenuPresenterCallback mPanelMenuPresenterCallback;
@@ -231,13 +204,13 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
* multiple panels). Shortcuts will go to this panel. It gets set in
* {@link #preparePanel} and cleared in {@link #closePanel}.
*/
- private PanelFeatureState mPreparedPanel;
+ PanelFeatureState mPreparedPanel;
/**
* The keycode that is currently held down (as a modifier) for chording. If
* this is 0, there is no key held down.
*/
- private int mPanelChordingKey;
+ int mPanelChordingKey;
private ImageView mLeftIconView;
@@ -261,8 +234,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
private int mFrameResource = 0;
private int mTextColor = 0;
- private int mStatusBarColor = 0;
- private int mNavigationBarColor = 0;
+ int mStatusBarColor = 0;
+ int mNavigationBarColor = 0;
private boolean mForcedStatusBarColor = false;
private boolean mForcedNavigationBarColor = false;
@@ -272,9 +245,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
private boolean mAlwaysReadCloseOnTouchAttr = false;
- private ContextMenuBuilder mContextMenu;
- private MenuDialogHelper mContextMenuHelper;
- private MenuPopupHelper mContextMenuPopupHelper;
+ ContextMenuBuilder mContextMenu;
+ MenuDialogHelper mContextMenuHelper;
+ MenuPopupHelper mContextMenuPopupHelper;
private boolean mClosingActionMenu;
private int mVolumeControlStreamType = AudioManager.USE_DEFAULT_STREAM_TYPE;
@@ -312,9 +285,6 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
private long mBackgroundFadeDurationMillis = -1;
private Boolean mSharedElementsUseOverlay;
- private Rect mTempRect;
- private Rect mOutsets = new Rect();
-
private boolean mIsStartingWindow;
private int mTheme = -1;
@@ -1170,7 +1140,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
return performPanelShortcut(getPanelState(featureId, false), keyCode, event, flags);
}
- private boolean performPanelShortcut(PanelFeatureState st, int keyCode, KeyEvent event,
+ boolean performPanelShortcut(PanelFeatureState st, int keyCode, KeyEvent event,
int flags) {
if (event.isSystem() || (st == null)) {
return false;
@@ -2261,7 +2231,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
* called sometime after {@link #restorePanelState} when it is safe to add
* to the window manager.
*/
- private void openPanelsAfterRestore() {
+ void openPanelsAfterRestore() {
PanelFeatureState[] panels = mPanels;
if (panels == null) {
@@ -2332,1530 +2302,6 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
}
}
- private static final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
-
- /* package */int mDefaultOpacity = PixelFormat.OPAQUE;
-
- /** The feature ID of the panel, or -1 if this is the application's DecorView */
- private final int mFeatureId;
-
- private final Rect mDrawingBounds = new Rect();
-
- private final Rect mBackgroundPadding = new Rect();
-
- private final Rect mFramePadding = new Rect();
-
- private final Rect mFrameOffsets = new Rect();
-
- // True if a non client area decor exists.
- private boolean mHasNonClientDecor = false;
-
- private boolean mChanging;
-
- private Drawable mMenuBackground;
- private boolean mWatchingForMenu;
- private int mDownY;
-
- private ActionMode mPrimaryActionMode;
- private ActionMode mFloatingActionMode;
- private ActionBarContextView mPrimaryActionModeView;
- private PopupWindow mPrimaryActionModePopup;
- private Runnable mShowPrimaryActionModePopup;
- private OnPreDrawListener mFloatingToolbarPreDrawListener;
- private View mFloatingActionModeOriginatingView;
- private FloatingToolbar mFloatingToolbar;
- private ObjectAnimator mFadeAnim;
-
- // View added at runtime to draw under the status bar area
- private View mStatusGuard;
- // View added at runtime to draw under the navigation bar area
- private View mNavigationGuard;
-
- private final ColorViewState mStatusColorViewState = new ColorViewState(
- SYSTEM_UI_FLAG_FULLSCREEN, FLAG_TRANSLUCENT_STATUS,
- Gravity.TOP,
- Gravity.LEFT,
- STATUS_BAR_BACKGROUND_TRANSITION_NAME,
- com.android.internal.R.id.statusBarBackground,
- FLAG_FULLSCREEN);
- private final ColorViewState mNavigationColorViewState = new ColorViewState(
- SYSTEM_UI_FLAG_HIDE_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION,
- Gravity.BOTTOM,
- Gravity.RIGHT,
- NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME,
- com.android.internal.R.id.navigationBarBackground,
- 0 /* hideWindowFlag */);
-
- private final Interpolator mShowInterpolator;
- private final Interpolator mHideInterpolator;
- private final int mBarEnterExitDuration;
-
- private final BackgroundFallback mBackgroundFallback = new BackgroundFallback();
-
- private int mLastTopInset = 0;
- private int mLastBottomInset = 0;
- private int mLastRightInset = 0;
- private boolean mLastHasTopStableInset = false;
- private boolean mLastHasBottomStableInset = false;
- private boolean mLastHasRightStableInset = false;
- private int mLastWindowFlags = 0;
-
- private int mRootScrollY = 0;
-
- private PhoneWindow mWindow;
-
- private DecorView(Context context, int featureId, PhoneWindow window) {
- super(context);
- mFeatureId = featureId;
-
- mShowInterpolator = AnimationUtils.loadInterpolator(context,
- android.R.interpolator.linear_out_slow_in);
- mHideInterpolator = AnimationUtils.loadInterpolator(context,
- android.R.interpolator.fast_out_linear_in);
-
- mBarEnterExitDuration = context.getResources().getInteger(
- R.integer.dock_enter_exit_duration);
-
- setWindow(window);
- }
-
- public void setBackgroundFallback(int resId) {
- mBackgroundFallback.setDrawable(resId != 0 ? getContext().getDrawable(resId) : null);
- setWillNotDraw(getBackground() == null && !mBackgroundFallback.hasFallback());
- }
-
- @Override
- public void onDraw(Canvas c) {
- super.onDraw(c);
- mBackgroundFallback.draw(mWindow.mContentRoot, c, mWindow.mContentParent);
- }
-
- @Override
- public boolean dispatchKeyEvent(KeyEvent event) {
- final int keyCode = event.getKeyCode();
- final int action = event.getAction();
- final boolean isDown = action == KeyEvent.ACTION_DOWN;
-
- if (isDown && (event.getRepeatCount() == 0)) {
- // First handle chording of panel key: if a panel key is held
- // but not released, try to execute a shortcut in it.
- if ((mWindow.mPanelChordingKey > 0) && (mWindow.mPanelChordingKey != keyCode)) {
- boolean handled = dispatchKeyShortcutEvent(event);
- if (handled) {
- return true;
- }
- }
-
- // If a panel is open, perform a shortcut on it without the
- // chorded panel key
- if ((mWindow.mPreparedPanel != null) && mWindow.mPreparedPanel.isOpen) {
- if (mWindow.performPanelShortcut(mWindow.mPreparedPanel, keyCode, event, 0)) {
- return true;
- }
- }
- }
-
- if (!mWindow.isDestroyed()) {
- final Callback cb = mWindow.getCallback();
- final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
- : super.dispatchKeyEvent(event);
- if (handled) {
- return true;
- }
- }
-
- return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event)
- : mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event);
- }
-
- @Override
- public boolean dispatchKeyShortcutEvent(KeyEvent ev) {
- // If the panel is already prepared, then perform the shortcut using it.
- boolean handled;
- if (mWindow.mPreparedPanel != null) {
- handled = mWindow.performPanelShortcut(mWindow.mPreparedPanel, ev.getKeyCode(), ev,
- Menu.FLAG_PERFORM_NO_CLOSE);
- if (handled) {
- if (mWindow.mPreparedPanel != null) {
- mWindow.mPreparedPanel.isHandled = true;
- }
- return true;
- }
- }
-
- // Shortcut not handled by the panel. Dispatch to the view hierarchy.
- final Callback cb = mWindow.getCallback();
- handled = cb != null && !mWindow.isDestroyed() && mFeatureId < 0
- ? cb.dispatchKeyShortcutEvent(ev) : super.dispatchKeyShortcutEvent(ev);
- if (handled) {
- return true;
- }
-
- // If the panel is not prepared, then we may be trying to handle a shortcut key
- // combination such as Control+C. Temporarily prepare the panel then mark it
- // unprepared again when finished to ensure that the panel will again be prepared
- // the next time it is shown for real.
- PanelFeatureState st = mWindow.getPanelState(FEATURE_OPTIONS_PANEL, false);
- if (st != null && mWindow.mPreparedPanel == null) {
- mWindow.preparePanel(st, ev);
- handled = mWindow.performPanelShortcut(st, ev.getKeyCode(), ev,
- Menu.FLAG_PERFORM_NO_CLOSE);
- st.isPrepared = false;
- if (handled) {
- return true;
- }
- }
- return false;
- }
-
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- final Callback cb = mWindow.getCallback();
- return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
- ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
- }
-
- @Override
- public boolean dispatchTrackballEvent(MotionEvent ev) {
- final Callback cb = mWindow.getCallback();
- return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
- ? cb.dispatchTrackballEvent(ev) : super.dispatchTrackballEvent(ev);
- }
-
- @Override
- public boolean dispatchGenericMotionEvent(MotionEvent ev) {
- final Callback cb = mWindow.getCallback();
- return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
- ? cb.dispatchGenericMotionEvent(ev) : super.dispatchGenericMotionEvent(ev);
- }
-
- public boolean superDispatchKeyEvent(KeyEvent event) {
- // Give priority to closing action modes if applicable.
- if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
- final int action = event.getAction();
- // Back cancels action modes first.
- if (mPrimaryActionMode != null) {
- if (action == KeyEvent.ACTION_UP) {
- mPrimaryActionMode.finish();
- }
- return true;
- }
- }
-
- return super.dispatchKeyEvent(event);
- }
-
- public boolean superDispatchKeyShortcutEvent(KeyEvent event) {
- return super.dispatchKeyShortcutEvent(event);
- }
-
- public boolean superDispatchTouchEvent(MotionEvent event) {
- return super.dispatchTouchEvent(event);
- }
-
- public boolean superDispatchTrackballEvent(MotionEvent event) {
- return super.dispatchTrackballEvent(event);
- }
-
- public boolean superDispatchGenericMotionEvent(MotionEvent event) {
- return super.dispatchGenericMotionEvent(event);
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- return onInterceptTouchEvent(event);
- }
-
- private boolean isOutOfInnerBounds(int x, int y) {
- return x < 0 || y < 0 || x > getWidth() || y > getHeight();
- }
-
- private boolean isOutOfBounds(int x, int y) {
- return x < -5 || y < -5 || x > (getWidth() + 5)
- || y > (getHeight() + 5);
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent event) {
- int action = event.getAction();
- if (mHasNonClientDecor && mWindow.mNonClientDecorView.mVisible) {
- // Don't dispatch ACTION_DOWN to the non client decor if the window is
- // resizable and the event was (starting) outside the window.
- // Window resizing events should be handled by WindowManager.
- // TODO: Investigate how to handle the outside touch in window manager
- // without generating these events.
- // Currently we receive these because we need to enlarge the window's
- // touch region so that the monitor channel receives the events
- // in the outside touch area.
- if (action == MotionEvent.ACTION_DOWN) {
- final int x = (int) event.getX();
- final int y = (int) event.getY();
- if (isOutOfInnerBounds(x, y)) {
- return true;
- }
- }
- }
-
- if (mFeatureId >= 0) {
- if (action == MotionEvent.ACTION_DOWN) {
- int x = (int)event.getX();
- int y = (int)event.getY();
- if (isOutOfBounds(x, y)) {
- mWindow.closePanel(mFeatureId);
- return true;
- }
- }
- }
-
- if (!SWEEP_OPEN_MENU) {
- return false;
- }
-
- if (mFeatureId >= 0) {
- if (action == MotionEvent.ACTION_DOWN) {
- Log.i(TAG, "Watchiing!");
- mWatchingForMenu = true;
- mDownY = (int) event.getY();
- return false;
- }
-
- if (!mWatchingForMenu) {
- return false;
- }
-
- int y = (int)event.getY();
- if (action == MotionEvent.ACTION_MOVE) {
- if (y > (mDownY+30)) {
- Log.i(TAG, "Closing!");
- mWindow.closePanel(mFeatureId);
- mWatchingForMenu = false;
- return true;
- }
- } else if (action == MotionEvent.ACTION_UP) {
- mWatchingForMenu = false;
- }
-
- return false;
- }
-
- //Log.i(TAG, "Intercept: action=" + action + " y=" + event.getY()
- // + " (in " + getHeight() + ")");
-
- if (action == MotionEvent.ACTION_DOWN) {
- int y = (int)event.getY();
- if (y >= (getHeight()-5) && !mWindow.hasChildren()) {
- Log.i(TAG, "Watching!");
- mWatchingForMenu = true;
- }
- return false;
- }
-
- if (!mWatchingForMenu) {
- return false;
- }
-
- int y = (int)event.getY();
- if (action == MotionEvent.ACTION_MOVE) {
- if (y < (getHeight()-30)) {
- Log.i(TAG, "Opening!");
- mWindow.openPanel(FEATURE_OPTIONS_PANEL, new KeyEvent(
- KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU));
- mWatchingForMenu = false;
- return true;
- }
- } else if (action == MotionEvent.ACTION_UP) {
- mWatchingForMenu = false;
- }
-
- return false;
- }
-
- @Override
- public void sendAccessibilityEvent(int eventType) {
- if (!AccessibilityManager.getInstance(mContext).isEnabled()) {
- return;
- }
-
- // if we are showing a feature that should be announced and one child
- // make this child the event source since this is the feature itself
- // otherwise the callback will take over and announce its client
- if ((mFeatureId == FEATURE_OPTIONS_PANEL ||
- mFeatureId == FEATURE_CONTEXT_MENU ||
- mFeatureId == FEATURE_PROGRESS ||
- mFeatureId == FEATURE_INDETERMINATE_PROGRESS)
- && getChildCount() == 1) {
- getChildAt(0).sendAccessibilityEvent(eventType);
- } else {
- super.sendAccessibilityEvent(eventType);
- }
- }
-
- @Override
- public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
- final Callback cb = mWindow.getCallback();
- if (cb != null && !mWindow.isDestroyed()) {
- if (cb.dispatchPopulateAccessibilityEvent(event)) {
- return true;
- }
- }
- return super.dispatchPopulateAccessibilityEventInternal(event);
- }
-
- @Override
- protected boolean setFrame(int l, int t, int r, int b) {
- boolean changed = super.setFrame(l, t, r, b);
- if (changed) {
- final Rect drawingBounds = mDrawingBounds;
- getDrawingRect(drawingBounds);
-
- Drawable fg = getForeground();
- if (fg != null) {
- final Rect frameOffsets = mFrameOffsets;
- drawingBounds.left += frameOffsets.left;
- drawingBounds.top += frameOffsets.top;
- drawingBounds.right -= frameOffsets.right;
- drawingBounds.bottom -= frameOffsets.bottom;
- fg.setBounds(drawingBounds);
- final Rect framePadding = mFramePadding;
- drawingBounds.left += framePadding.left - frameOffsets.left;
- drawingBounds.top += framePadding.top - frameOffsets.top;
- drawingBounds.right -= framePadding.right - frameOffsets.right;
- drawingBounds.bottom -= framePadding.bottom - frameOffsets.bottom;
- }
-
- Drawable bg = getBackground();
- if (bg != null) {
- bg.setBounds(drawingBounds);
- }
-
- if (SWEEP_OPEN_MENU) {
- if (mMenuBackground == null && mFeatureId < 0
- && mWindow.getAttributes().height
- == WindowManager.LayoutParams.MATCH_PARENT) {
- mMenuBackground = getContext().getDrawable(
- R.drawable.menu_background);
- }
- if (mMenuBackground != null) {
- mMenuBackground.setBounds(drawingBounds.left,
- drawingBounds.bottom-6, drawingBounds.right,
- drawingBounds.bottom+20);
- }
- }
- }
- return changed;
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
- final boolean isPortrait = metrics.widthPixels < metrics.heightPixels;
-
- final int widthMode = getMode(widthMeasureSpec);
- final int heightMode = getMode(heightMeasureSpec);
-
- boolean fixedWidth = false;
- if (widthMode == AT_MOST) {
- final TypedValue tvw = isPortrait ? mWindow.mFixedWidthMinor
- : mWindow.mFixedWidthMajor;
- if (tvw != null && tvw.type != TypedValue.TYPE_NULL) {
- final int w;
- if (tvw.type == TypedValue.TYPE_DIMENSION) {
- w = (int) tvw.getDimension(metrics);
- } else if (tvw.type == TypedValue.TYPE_FRACTION) {
- w = (int) tvw.getFraction(metrics.widthPixels, metrics.widthPixels);
- } else {
- w = 0;
- }
-
- if (w > 0) {
- final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
- widthMeasureSpec = MeasureSpec.makeMeasureSpec(
- Math.min(w, widthSize), EXACTLY);
- fixedWidth = true;
- }
- }
- }
-
- if (heightMode == AT_MOST) {
- final TypedValue tvh = isPortrait ? mWindow.mFixedHeightMajor
- : mWindow.mFixedHeightMinor;
- if (tvh != null && tvh.type != TypedValue.TYPE_NULL) {
- final int h;
- if (tvh.type == TypedValue.TYPE_DIMENSION) {
- h = (int) tvh.getDimension(metrics);
- } else if (tvh.type == TypedValue.TYPE_FRACTION) {
- h = (int) tvh.getFraction(metrics.heightPixels, metrics.heightPixels);
- } else {
- h = 0;
- }
- if (h > 0) {
- final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
- heightMeasureSpec = MeasureSpec.makeMeasureSpec(
- Math.min(h, heightSize), EXACTLY);
- }
- }
- }
-
- getOutsets(mWindow.mOutsets);
- if (mWindow.mOutsets.top > 0 || mWindow.mOutsets.bottom > 0) {
- int mode = MeasureSpec.getMode(heightMeasureSpec);
- if (mode != MeasureSpec.UNSPECIFIED) {
- int height = MeasureSpec.getSize(heightMeasureSpec);
- heightMeasureSpec = MeasureSpec.makeMeasureSpec(
- height + mWindow.mOutsets.top + mWindow.mOutsets.bottom, mode);
- }
- }
- if (mWindow.mOutsets.left > 0 || mWindow.mOutsets.right > 0) {
- int mode = MeasureSpec.getMode(widthMeasureSpec);
- if (mode != MeasureSpec.UNSPECIFIED) {
- int width = MeasureSpec.getSize(widthMeasureSpec);
- widthMeasureSpec = MeasureSpec.makeMeasureSpec(
- width + mWindow.mOutsets.left + mWindow.mOutsets.right, mode);
- }
- }
-
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
- int width = getMeasuredWidth();
- boolean measure = false;
-
- widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, EXACTLY);
-
- if (!fixedWidth && widthMode == AT_MOST) {
- final TypedValue tv = isPortrait ? mWindow.mMinWidthMinor : mWindow.mMinWidthMajor;
- if (tv.type != TypedValue.TYPE_NULL) {
- final int min;
- if (tv.type == TypedValue.TYPE_DIMENSION) {
- min = (int)tv.getDimension(metrics);
- } else if (tv.type == TypedValue.TYPE_FRACTION) {
- min = (int)tv.getFraction(metrics.widthPixels, metrics.widthPixels);
- } else {
- min = 0;
- }
-
- if (width < min) {
- widthMeasureSpec = MeasureSpec.makeMeasureSpec(min, EXACTLY);
- measure = true;
- }
- }
- }
-
- // TODO: Support height?
-
- if (measure) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- getOutsets(mWindow.mOutsets);
- if (mWindow.mOutsets.left > 0) {
- offsetLeftAndRight(-mWindow.mOutsets.left);
- }
- if (mWindow.mOutsets.top > 0) {
- offsetTopAndBottom(-mWindow.mOutsets.top);
- }
- }
-
- @Override
- public void draw(Canvas canvas) {
- super.draw(canvas);
-
- if (mMenuBackground != null) {
- mMenuBackground.draw(canvas);
- }
- }
-
- @Override
- public boolean showContextMenuForChild(View originalView) {
- // Reuse the context menu builder
- if (mWindow.mContextMenu == null) {
- mWindow.mContextMenu = new ContextMenuBuilder(getContext());
- mWindow.mContextMenu.setCallback(mWindow.mContextMenuCallback);
- } else {
- mWindow.mContextMenu.clearAll();
- }
-
- final MenuDialogHelper helper = mWindow.mContextMenu.show(originalView,
- originalView.getWindowToken());
- if (helper != null) {
- helper.setPresenterCallback(mWindow.mContextMenuCallback);
- } else if (mWindow.mContextMenuHelper != null) {
- // No menu to show, but if we have a menu currently showing it just became blank.
- // Close it.
- mWindow.mContextMenuHelper.dismiss();
- }
- mWindow.mContextMenuHelper = helper;
- return helper != null;
- }
-
- @Override
- public boolean showContextMenuForChild(View originalView, float x, float y) {
- // Reuse the context menu builder
- if (mWindow.mContextMenu == null) {
- mWindow.mContextMenu = new ContextMenuBuilder(getContext());
- mWindow.mContextMenu.setCallback(mWindow.mContextMenuCallback);
- } else {
- mWindow.mContextMenu.clearAll();
- }
-
- final MenuPopupHelper helper = mWindow.mContextMenu.showPopup(
- getContext(), originalView, x, y);
- if (helper != null) {
- helper.setCallback(mWindow.mContextMenuCallback);
- } else if (mWindow.mContextMenuPopupHelper != null) {
- // No menu to show, but if we have a menu currently showing it just became blank.
- // Close it.
- mWindow.mContextMenuPopupHelper.dismiss();
- }
- mWindow.mContextMenuPopupHelper = helper;
- return helper != null;
- }
-
- @Override
- public ActionMode startActionModeForChild(View originalView,
- ActionMode.Callback callback) {
- return startActionModeForChild(originalView, callback, ActionMode.TYPE_PRIMARY);
- }
-
- @Override
- public ActionMode startActionModeForChild(
- View child, ActionMode.Callback callback, int type) {
- return startActionMode(child, callback, type);
- }
-
- @Override
- public ActionMode startActionMode(ActionMode.Callback callback) {
- return startActionMode(callback, ActionMode.TYPE_PRIMARY);
- }
-
- @Override
- public ActionMode startActionMode(ActionMode.Callback callback, int type) {
- return startActionMode(this, callback, type);
- }
-
- private ActionMode startActionMode(
- View originatingView, ActionMode.Callback callback, int type) {
- ActionMode.Callback2 wrappedCallback = new ActionModeCallback2Wrapper(callback);
- ActionMode mode = null;
- if (mWindow.getCallback() != null && !mWindow.isDestroyed()) {
- try {
- mode = mWindow.getCallback().onWindowStartingActionMode(wrappedCallback, type);
- } catch (AbstractMethodError ame) {
- // Older apps might not implement the typed version of this method.
- if (type == ActionMode.TYPE_PRIMARY) {
- try {
- mode = mWindow.getCallback().onWindowStartingActionMode(
- wrappedCallback);
- } catch (AbstractMethodError ame2) {
- // Older apps might not implement this callback method at all.
- }
- }
- }
- }
- if (mode != null) {
- if (mode.getType() == ActionMode.TYPE_PRIMARY) {
- cleanupPrimaryActionMode();
- mPrimaryActionMode = mode;
- } else if (mode.getType() == ActionMode.TYPE_FLOATING) {
- if (mFloatingActionMode != null) {
- mFloatingActionMode.finish();
- }
- mFloatingActionMode = mode;
- }
- } else {
- mode = createActionMode(type, wrappedCallback, originatingView);
- if (mode != null && wrappedCallback.onCreateActionMode(mode, mode.getMenu())) {
- setHandledActionMode(mode);
- } else {
- mode = null;
- }
- }
- if (mode != null && mWindow.getCallback() != null && !mWindow.isDestroyed()) {
- try {
- mWindow.getCallback().onActionModeStarted(mode);
- } catch (AbstractMethodError ame) {
- // Older apps might not implement this callback method.
- }
- }
- return mode;
- }
-
- private void cleanupPrimaryActionMode() {
- if (mPrimaryActionMode != null) {
- mPrimaryActionMode.finish();
- mPrimaryActionMode = null;
- }
- if (mPrimaryActionModeView != null) {
- mPrimaryActionModeView.killMode();
- }
- }
-
- private void cleanupFloatingActionModeViews() {
- if (mFloatingToolbar != null) {
- mFloatingToolbar.dismiss();
- mFloatingToolbar = null;
- }
- if (mFloatingActionModeOriginatingView != null) {
- if (mFloatingToolbarPreDrawListener != null) {
- mFloatingActionModeOriginatingView.getViewTreeObserver()
- .removeOnPreDrawListener(mFloatingToolbarPreDrawListener);
- mFloatingToolbarPreDrawListener = null;
- }
- mFloatingActionModeOriginatingView = null;
- }
- }
-
- public void startChanging() {
- mChanging = true;
- }
-
- public void finishChanging() {
- mChanging = false;
- drawableChanged();
- }
-
- public void setWindowBackground(Drawable drawable) {
- if (getBackground() != drawable) {
- setBackgroundDrawable(drawable);
- if (drawable != null) {
- drawable.getPadding(mBackgroundPadding);
- } else {
- mBackgroundPadding.setEmpty();
- }
- drawableChanged();
- }
- }
-
- public void setWindowFrame(Drawable drawable) {
- if (getForeground() != drawable) {
- setForeground(drawable);
- if (drawable != null) {
- drawable.getPadding(mFramePadding);
- } else {
- mFramePadding.setEmpty();
- }
- drawableChanged();
- }
- }
-
- @Override
- public void onWindowSystemUiVisibilityChanged(int visible) {
- updateColorViews(null /* insets */, true /* animate */);
- }
-
- @Override
- public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- mFrameOffsets.set(insets.getSystemWindowInsets());
- insets = updateColorViews(insets, true /* animate */);
- insets = updateStatusGuard(insets);
- updateNavigationGuard(insets);
- if (getForeground() != null) {
- drawableChanged();
- }
- return insets;
- }
-
- @Override
- public boolean isTransitionGroup() {
- return false;
- }
-
- private WindowInsets updateColorViews(WindowInsets insets, boolean animate) {
- WindowManager.LayoutParams attrs = mWindow.getAttributes();
- int sysUiVisibility = attrs.systemUiVisibility | getWindowSystemUiVisibility();
-
- if (!mWindow.mIsFloating && ActivityManager.isHighEndGfx()) {
- boolean disallowAnimate = !isLaidOut();
- disallowAnimate |= ((mLastWindowFlags ^ attrs.flags)
- & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
- mLastWindowFlags = attrs.flags;
-
- if (insets != null) {
- mLastTopInset = Math.min(insets.getStableInsetTop(),
- insets.getSystemWindowInsetTop());
- mLastBottomInset = Math.min(insets.getStableInsetBottom(),
- insets.getSystemWindowInsetBottom());
- mLastRightInset = Math.min(insets.getStableInsetRight(),
- insets.getSystemWindowInsetRight());
-
- // Don't animate if the presence of stable insets has changed, because that
- // indicates that the window was either just added and received them for the
- // first time, or the window size or position has changed.
- boolean hasTopStableInset = insets.getStableInsetTop() != 0;
- disallowAnimate |= (hasTopStableInset != mLastHasTopStableInset);
- mLastHasTopStableInset = hasTopStableInset;
-
- boolean hasBottomStableInset = insets.getStableInsetBottom() != 0;
- disallowAnimate |= (hasBottomStableInset != mLastHasBottomStableInset);
- mLastHasBottomStableInset = hasBottomStableInset;
-
- boolean hasRightStableInset = insets.getStableInsetRight() != 0;
- disallowAnimate |= (hasRightStableInset != mLastHasRightStableInset);
- mLastHasRightStableInset = hasRightStableInset;
- }
-
- boolean navBarToRightEdge = mLastBottomInset == 0 && mLastRightInset > 0;
- int navBarSize = navBarToRightEdge ? mLastRightInset : mLastBottomInset;
- updateColorViewInt(mNavigationColorViewState, sysUiVisibility,
- mWindow.mNavigationBarColor, navBarSize, navBarToRightEdge,
- 0 /* rightInset */, animate && !disallowAnimate);
-
- boolean statusBarNeedsRightInset = navBarToRightEdge
- && mNavigationColorViewState.present;
- int statusBarRightInset = statusBarNeedsRightInset ? mLastRightInset : 0;
- updateColorViewInt(mStatusColorViewState, sysUiVisibility, mWindow.mStatusBarColor,
- mLastTopInset, false /* matchVertical */, statusBarRightInset,
- animate && !disallowAnimate);
- }
-
- // When we expand the window with FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, we still need
- // to ensure that the rest of the view hierarchy doesn't notice it, unless they've
- // explicitly asked for it.
-
- boolean consumingNavBar =
- (attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
- && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0
- && (sysUiVisibility & SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0;
-
- int consumedRight = consumingNavBar ? mLastRightInset : 0;
- int consumedBottom = consumingNavBar ? mLastBottomInset : 0;
-
- if (mWindow.mContentRoot != null
- && mWindow.mContentRoot.getLayoutParams() instanceof MarginLayoutParams) {
- MarginLayoutParams lp = (MarginLayoutParams) mWindow.mContentRoot.getLayoutParams();
- if (lp.rightMargin != consumedRight || lp.bottomMargin != consumedBottom) {
- lp.rightMargin = consumedRight;
- lp.bottomMargin = consumedBottom;
- mWindow.mContentRoot.setLayoutParams(lp);
-
- if (insets == null) {
- // The insets have changed, but we're not currently in the process
- // of dispatching them.
- requestApplyInsets();
- }
- }
- if (insets != null) {
- insets = insets.replaceSystemWindowInsets(
- insets.getSystemWindowInsetLeft(),
- insets.getSystemWindowInsetTop(),
- insets.getSystemWindowInsetRight() - consumedRight,
- insets.getSystemWindowInsetBottom() - consumedBottom);
- }
- }
-
- if (insets != null) {
- insets = insets.consumeStableInsets();
- }
- return insets;
- }
-
- /**
- * Update a color view
- *
- * @param state the color view to update.
- * @param sysUiVis the current systemUiVisibility to apply.
- * @param color the current color to apply.
- * @param size the current size in the non-parent-matching dimension.
- * @param verticalBar if true the view is attached to a vertical edge, otherwise to a
- * horizontal edge,
- * @param rightMargin rightMargin for the color view.
- * @param animate if true, the change will be animated.
- */
- private void updateColorViewInt(final ColorViewState state, int sysUiVis, int color,
- int size, boolean verticalBar, int rightMargin, boolean animate) {
- state.present = size > 0 && (sysUiVis & state.systemUiHideFlag) == 0
- && (mWindow.getAttributes().flags & state.hideWindowFlag) == 0
- && (mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
- boolean show = state.present
- && (color & Color.BLACK) != 0
- && (mWindow.getAttributes().flags & state.translucentFlag) == 0;
-
- boolean visibilityChanged = false;
- View view = state.view;
-
- int resolvedHeight = verticalBar ? LayoutParams.MATCH_PARENT : size;
- int resolvedWidth = verticalBar ? size : LayoutParams.MATCH_PARENT;
- int resolvedGravity = verticalBar ? state.horizontalGravity : state.verticalGravity;
-
- if (view == null) {
- if (show) {
- state.view = view = new View(mContext);
- view.setBackgroundColor(color);
- view.setTransitionName(state.transitionName);
- view.setId(state.id);
- visibilityChanged = true;
- view.setVisibility(INVISIBLE);
- state.targetVisibility = VISIBLE;
-
- LayoutParams lp = new LayoutParams(resolvedWidth, resolvedHeight,
- resolvedGravity);
- lp.rightMargin = rightMargin;
- addView(view, lp);
- updateColorViewTranslations();
- }
- } else {
- int vis = show ? VISIBLE : INVISIBLE;
- visibilityChanged = state.targetVisibility != vis;
- state.targetVisibility = vis;
- LayoutParams lp = (LayoutParams) view.getLayoutParams();
- if (lp.height != resolvedHeight || lp.width != resolvedWidth
- || lp.gravity != resolvedGravity || lp.rightMargin != rightMargin) {
- lp.height = resolvedHeight;
- lp.width = resolvedWidth;
- lp.gravity = resolvedGravity;
- lp.rightMargin = rightMargin;
- view.setLayoutParams(lp);
- }
- if (show) {
- view.setBackgroundColor(color);
- }
- }
- if (visibilityChanged) {
- view.animate().cancel();
- if (animate) {
- if (show) {
- if (view.getVisibility() != VISIBLE) {
- view.setVisibility(VISIBLE);
- view.setAlpha(0.0f);
- }
- view.animate().alpha(1.0f).setInterpolator(mShowInterpolator).
- setDuration(mBarEnterExitDuration);
- } else {
- view.animate().alpha(0.0f).setInterpolator(mHideInterpolator)
- .setDuration(mBarEnterExitDuration)
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- state.view.setAlpha(1.0f);
- state.view.setVisibility(INVISIBLE);
- }
- });
- }
- } else {
- view.setAlpha(1.0f);
- view.setVisibility(show ? VISIBLE : INVISIBLE);
- }
- }
- }
-
- private void updateColorViewTranslations() {
- // Put the color views back in place when they get moved off the screen
- // due to the the ViewRootImpl panning.
- int rootScrollY = mRootScrollY;
- if (mStatusColorViewState.view != null) {
- mStatusColorViewState.view.setTranslationY(rootScrollY > 0 ? rootScrollY : 0);
- }
- if (mNavigationColorViewState.view != null) {
- mNavigationColorViewState.view.setTranslationY(rootScrollY < 0 ? rootScrollY : 0);
- }
- }
-
- private WindowInsets updateStatusGuard(WindowInsets insets) {
- boolean showStatusGuard = false;
- // Show the status guard when the non-overlay contextual action bar is showing
- if (mPrimaryActionModeView != null) {
- if (mPrimaryActionModeView.getLayoutParams() instanceof MarginLayoutParams) {
- // Insets are magic!
- final MarginLayoutParams mlp = (MarginLayoutParams)
- mPrimaryActionModeView.getLayoutParams();
- boolean mlpChanged = false;
- if (mPrimaryActionModeView.isShown()) {
- if (mWindow.mTempRect == null) {
- mWindow.mTempRect = new Rect();
- }
- final Rect rect = mWindow.mTempRect;
-
- // If the parent doesn't consume the insets, manually
- // apply the default system window insets.
- mWindow.mContentParent.computeSystemWindowInsets(insets, rect);
- final int newMargin = rect.top == 0 ? insets.getSystemWindowInsetTop() : 0;
- if (mlp.topMargin != newMargin) {
- mlpChanged = true;
- mlp.topMargin = insets.getSystemWindowInsetTop();
-
- if (mStatusGuard == null) {
- mStatusGuard = new View(mContext);
- mStatusGuard.setBackgroundColor(mContext.getColor(
- R.color.input_method_navigation_guard));
- addView(mStatusGuard, indexOfChild(mStatusColorViewState.view),
- new LayoutParams(LayoutParams.MATCH_PARENT,
- mlp.topMargin, Gravity.START | Gravity.TOP));
- } else {
- final LayoutParams lp = (LayoutParams)
- mStatusGuard.getLayoutParams();
- if (lp.height != mlp.topMargin) {
- lp.height = mlp.topMargin;
- mStatusGuard.setLayoutParams(lp);
- }
- }
- }
-
- // The action mode's theme may differ from the app, so
- // always show the status guard above it if we have one.
- showStatusGuard = mStatusGuard != null;
-
- // We only need to consume the insets if the action
- // mode is overlaid on the app content (e.g. it's
- // sitting in a FrameLayout, see
- // screen_simple_overlay_action_mode.xml).
- final boolean nonOverlay = (mWindow.getLocalFeatures()
- & (1 << FEATURE_ACTION_MODE_OVERLAY)) == 0;
- insets = insets.consumeSystemWindowInsets(
- false, nonOverlay && showStatusGuard /* top */, false, false);
- } else {
- // reset top margin
- if (mlp.topMargin != 0) {
- mlpChanged = true;
- mlp.topMargin = 0;
- }
- }
- if (mlpChanged) {
- mPrimaryActionModeView.setLayoutParams(mlp);
- }
- }
- }
- if (mStatusGuard != null) {
- mStatusGuard.setVisibility(showStatusGuard ? View.VISIBLE : View.GONE);
- }
- return insets;
- }
-
- private void updateNavigationGuard(WindowInsets insets) {
- // IMEs lay out below the nav bar, but the content view must not (for back compat)
- if (mWindow.getAttributes().type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
- // prevent the content view from including the nav bar height
- if (mWindow.mContentParent != null) {
- if (mWindow.mContentParent.getLayoutParams() instanceof MarginLayoutParams) {
- MarginLayoutParams mlp =
- (MarginLayoutParams) mWindow.mContentParent.getLayoutParams();
- mlp.bottomMargin = insets.getSystemWindowInsetBottom();
- mWindow.mContentParent.setLayoutParams(mlp);
- }
- }
- // position the navigation guard view, creating it if necessary
- if (mNavigationGuard == null) {
- mNavigationGuard = new View(mContext);
- mNavigationGuard.setBackgroundColor(mContext.getColor(
- R.color.input_method_navigation_guard));
- addView(mNavigationGuard, indexOfChild(mNavigationColorViewState.view),
- new LayoutParams(LayoutParams.MATCH_PARENT,
- insets.getSystemWindowInsetBottom(),
- Gravity.START | Gravity.BOTTOM));
- } else {
- LayoutParams lp = (LayoutParams) mNavigationGuard.getLayoutParams();
- lp.height = insets.getSystemWindowInsetBottom();
- mNavigationGuard.setLayoutParams(lp);
- }
- }
- }
-
- private void drawableChanged() {
- if (mChanging) {
- return;
- }
-
- setPadding(mFramePadding.left + mBackgroundPadding.left,
- mFramePadding.top + mBackgroundPadding.top,
- mFramePadding.right + mBackgroundPadding.right,
- mFramePadding.bottom + mBackgroundPadding.bottom);
- requestLayout();
- invalidate();
-
- int opacity = PixelFormat.OPAQUE;
- if (windowHasShadow()) {
- // If the window has a shadow, it must be translucent.
- opacity = PixelFormat.TRANSLUCENT;
- } else{
- // Note: If there is no background, we will assume opaque. The
- // common case seems to be that an application sets there to be
- // no background so it can draw everything itself. For that,
- // we would like to assume OPAQUE and let the app force it to
- // the slower TRANSLUCENT mode if that is really what it wants.
- Drawable bg = getBackground();
- Drawable fg = getForeground();
- if (bg != null) {
- if (fg == null) {
- opacity = bg.getOpacity();
- } else if (mFramePadding.left <= 0 && mFramePadding.top <= 0
- && mFramePadding.right <= 0 && mFramePadding.bottom <= 0) {
- // If the frame padding is zero, then we can be opaque
- // if either the frame -or- the background is opaque.
- int fop = fg.getOpacity();
- int bop = bg.getOpacity();
- if (false)
- Log.v(TAG, "Background opacity: " + bop + ", Frame opacity: " + fop);
- if (fop == PixelFormat.OPAQUE || bop == PixelFormat.OPAQUE) {
- opacity = PixelFormat.OPAQUE;
- } else if (fop == PixelFormat.UNKNOWN) {
- opacity = bop;
- } else if (bop == PixelFormat.UNKNOWN) {
- opacity = fop;
- } else {
- opacity = Drawable.resolveOpacity(fop, bop);
- }
- } else {
- // For now we have to assume translucent if there is a
- // frame with padding... there is no way to tell if the
- // frame and background together will draw all pixels.
- if (false)
- Log.v(TAG, "Padding: " + mFramePadding);
- opacity = PixelFormat.TRANSLUCENT;
- }
- }
- if (false)
- Log.v(TAG, "Background: " + bg + ", Frame: " + fg);
- }
-
- if (false)
- Log.v(TAG, "Selected default opacity: " + opacity);
-
- mDefaultOpacity = opacity;
- if (mFeatureId < 0) {
- mWindow.setDefaultWindowFormat(opacity);
- }
- }
-
- @Override
- public void onWindowFocusChanged(boolean hasWindowFocus) {
- super.onWindowFocusChanged(hasWindowFocus);
-
- // If the user is chording a menu shortcut, release the chord since
- // this window lost focus
- if (mWindow.hasFeature(FEATURE_OPTIONS_PANEL) && !hasWindowFocus
- && mWindow.mPanelChordingKey != 0) {
- mWindow.closePanel(FEATURE_OPTIONS_PANEL);
- }
-
- final Callback cb = mWindow.getCallback();
- if (cb != null && !mWindow.isDestroyed() && mFeatureId < 0) {
- cb.onWindowFocusChanged(hasWindowFocus);
- }
-
- if (mPrimaryActionMode != null) {
- mPrimaryActionMode.onWindowFocusChanged(hasWindowFocus);
- }
- if (mFloatingActionMode != null) {
- mFloatingActionMode.onWindowFocusChanged(hasWindowFocus);
- }
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- final Callback cb = mWindow.getCallback();
- if (cb != null && !mWindow.isDestroyed() && mFeatureId < 0) {
- cb.onAttachedToWindow();
- }
-
- if (mFeatureId == -1) {
- /*
- * The main window has been attached, try to restore any panels
- * that may have been open before. This is called in cases where
- * an activity is being killed for configuration change and the
- * menu was open. When the activity is recreated, the menu
- * should be shown again.
- */
- mWindow.openPanelsAfterRestore();
- }
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
-
- final Callback cb = mWindow.getCallback();
- if (cb != null && mFeatureId < 0) {
- cb.onDetachedFromWindow();
- }
-
- if (mWindow.mDecorContentParent != null) {
- mWindow.mDecorContentParent.dismissPopups();
- }
-
- if (mPrimaryActionModePopup != null) {
- removeCallbacks(mShowPrimaryActionModePopup);
- if (mPrimaryActionModePopup.isShowing()) {
- mPrimaryActionModePopup.dismiss();
- }
- mPrimaryActionModePopup = null;
- }
- if (mFloatingToolbar != null) {
- mFloatingToolbar.dismiss();
- mFloatingToolbar = null;
- }
-
- PanelFeatureState st = mWindow.getPanelState(FEATURE_OPTIONS_PANEL, false);
- if (st != null && st.menu != null && mFeatureId < 0) {
- st.menu.close();
- }
- }
-
- @Override
- public void onCloseSystemDialogs(String reason) {
- if (mFeatureId >= 0) {
- mWindow.closeAllPanels();
- }
- }
-
- public android.view.SurfaceHolder.Callback2 willYouTakeTheSurface() {
- return mFeatureId < 0 ? mWindow.mTakeSurfaceCallback : null;
- }
-
- public InputQueue.Callback willYouTakeTheInputQueue() {
- return mFeatureId < 0 ? mWindow.mTakeInputQueueCallback : null;
- }
-
- public void setSurfaceType(int type) {
- mWindow.setType(type);
- }
-
- public void setSurfaceFormat(int format) {
- mWindow.setFormat(format);
- }
-
- public void setSurfaceKeepScreenOn(boolean keepOn) {
- if (keepOn) mWindow.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- else mWindow.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- }
-
- @Override
- public void onRootViewScrollYChanged(int rootScrollY) {
- mRootScrollY = rootScrollY;
- updateColorViewTranslations();
- }
-
- private ActionMode createActionMode(
- int type, ActionMode.Callback2 callback, View originatingView) {
- switch (type) {
- case ActionMode.TYPE_PRIMARY:
- default:
- return createStandaloneActionMode(callback);
- case ActionMode.TYPE_FLOATING:
- return createFloatingActionMode(originatingView, callback);
- }
- }
-
- private void setHandledActionMode(ActionMode mode) {
- if (mode.getType() == ActionMode.TYPE_PRIMARY) {
- setHandledPrimaryActionMode(mode);
- } else if (mode.getType() == ActionMode.TYPE_FLOATING) {
- setHandledFloatingActionMode(mode);
- }
- }
-
- private ActionMode createStandaloneActionMode(ActionMode.Callback callback) {
- endOnGoingFadeAnimation();
- cleanupPrimaryActionMode();
- if (mPrimaryActionModeView == null) {
- if (mWindow.isFloating()) {
- // Use the action bar theme.
- final TypedValue outValue = new TypedValue();
- final Theme baseTheme = mContext.getTheme();
- baseTheme.resolveAttribute(R.attr.actionBarTheme, outValue, true);
-
- final Context actionBarContext;
- if (outValue.resourceId != 0) {
- final Theme actionBarTheme = mContext.getResources().newTheme();
- actionBarTheme.setTo(baseTheme);
- actionBarTheme.applyStyle(outValue.resourceId, true);
-
- actionBarContext = new ContextThemeWrapper(mContext, 0);
- actionBarContext.getTheme().setTo(actionBarTheme);
- } else {
- actionBarContext = mContext;
- }
-
- mPrimaryActionModeView = new ActionBarContextView(actionBarContext);
- mPrimaryActionModePopup = new PopupWindow(actionBarContext, null,
- R.attr.actionModePopupWindowStyle);
- mPrimaryActionModePopup.setWindowLayoutType(
- WindowManager.LayoutParams.TYPE_APPLICATION);
- mPrimaryActionModePopup.setContentView(mPrimaryActionModeView);
- mPrimaryActionModePopup.setWidth(MATCH_PARENT);
-
- actionBarContext.getTheme().resolveAttribute(
- R.attr.actionBarSize, outValue, true);
- final int height = TypedValue.complexToDimensionPixelSize(outValue.data,
- actionBarContext.getResources().getDisplayMetrics());
- mPrimaryActionModeView.setContentHeight(height);
- mPrimaryActionModePopup.setHeight(WRAP_CONTENT);
- mShowPrimaryActionModePopup = new Runnable() {
- public void run() {
- mPrimaryActionModePopup.showAtLocation(
- mPrimaryActionModeView.getApplicationWindowToken(),
- Gravity.TOP | Gravity.FILL_HORIZONTAL, 0, 0);
- endOnGoingFadeAnimation();
- mFadeAnim = ObjectAnimator.ofFloat(mPrimaryActionModeView, View.ALPHA,
- 0f, 1f);
- mFadeAnim.addListener(new Animator.AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animation) {
- mPrimaryActionModeView.setVisibility(VISIBLE);
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- mPrimaryActionModeView.setAlpha(1f);
- mFadeAnim = null;
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
-
- }
-
- @Override
- public void onAnimationRepeat(Animator animation) {
-
- }
- });
- mFadeAnim.start();
- }
- };
- } else {
- ViewStub stub = (ViewStub) findViewById(R.id.action_mode_bar_stub);
- if (stub != null) {
- mPrimaryActionModeView = (ActionBarContextView) stub.inflate();
- }
- }
- }
- if (mPrimaryActionModeView != null) {
- mPrimaryActionModeView.killMode();
- ActionMode mode = new StandaloneActionMode(
- mPrimaryActionModeView.getContext(), mPrimaryActionModeView,
- callback, mPrimaryActionModePopup == null);
- return mode;
- }
- return null;
- }
-
- private void endOnGoingFadeAnimation() {
- if (mFadeAnim != null) {
- mFadeAnim.end();
- }
- }
-
- private void setHandledPrimaryActionMode(ActionMode mode) {
- endOnGoingFadeAnimation();
- mPrimaryActionMode = mode;
- mPrimaryActionMode.invalidate();
- mPrimaryActionModeView.initForMode(mPrimaryActionMode);
- if (mPrimaryActionModePopup != null) {
- post(mShowPrimaryActionModePopup);
- } else {
- mFadeAnim = ObjectAnimator.ofFloat(mPrimaryActionModeView, View.ALPHA, 0f, 1f);
- mFadeAnim.addListener(new Animator.AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animation) {
- mPrimaryActionModeView.setVisibility(View.VISIBLE);
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- mPrimaryActionModeView.setAlpha(1f);
- mFadeAnim = null;
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
-
- }
-
- @Override
- public void onAnimationRepeat(Animator animation) {
-
- }
- });
- mFadeAnim.start();
- }
- mPrimaryActionModeView.sendAccessibilityEvent(
- AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
- }
-
- private ActionMode createFloatingActionMode(
- View originatingView, ActionMode.Callback2 callback) {
- if (mFloatingActionMode != null) {
- mFloatingActionMode.finish();
- }
- cleanupFloatingActionModeViews();
- final FloatingActionMode mode =
- new FloatingActionMode(mContext, callback, originatingView);
- mFloatingActionModeOriginatingView = originatingView;
- mFloatingToolbarPreDrawListener =
- new OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- mode.updateViewLocationInWindow();
- return true;
- }
- };
- return mode;
- }
-
- private void setHandledFloatingActionMode(ActionMode mode) {
- mFloatingActionMode = mode;
- mFloatingToolbar = new FloatingToolbar(mContext, mWindow);
- ((FloatingActionMode) mFloatingActionMode).setFloatingToolbar(mFloatingToolbar);
- mFloatingActionMode.invalidate(); // Will show the floating toolbar if necessary.
- mFloatingActionModeOriginatingView.getViewTreeObserver()
- .addOnPreDrawListener(mFloatingToolbarPreDrawListener);
- }
-
- /**
- * Informs the decor if a non client decor is attached and visible.
- * @param attachedAndVisible true when the decor is visible.
- * Note that this will even be called if there is no non client decor.
- **/
- void enableNonClientDecor(boolean attachedAndVisible) {
- if (mHasNonClientDecor != attachedAndVisible) {
- mHasNonClientDecor = attachedAndVisible;
- if (getForeground() != null) {
- drawableChanged();
- }
- }
- }
-
- /**
- * Returns true if the window has a non client decor.
- * @return If there is a non client decor - even if it is not visible.
- **/
- private boolean windowHasNonClientDecor() {
- return mHasNonClientDecor;
- }
-
- /**
- * Returns true if the Window is free floating and has a shadow (although at some times
- * it might not be displaying it, e.g. during a resize). Note that non overlapping windows
- * do not have a shadow since it could not be seen anyways (a small screen / tablet
- * "tiles" the windows side by side but does not overlap them).
- * @return Returns true when the window has a shadow created by the non client decor.
- **/
- private boolean windowHasShadow() {
- return windowHasNonClientDecor() && StackId.hasWindowShadow(mWindow.mWorkspaceId);
- }
-
- void setWindow(PhoneWindow phoneWindow) {
- mWindow = phoneWindow;
- Context context = getContext();
- if (context instanceof DecorContext) {
- DecorContext decorContex = (DecorContext) context;
- decorContex.setPhoneWindow(mWindow);
- }
- }
-
- /**
- * Clears out internal references when the action mode is destroyed.
- */
- private class ActionModeCallback2Wrapper extends ActionMode.Callback2 {
- private final ActionMode.Callback mWrapped;
-
- public ActionModeCallback2Wrapper(ActionMode.Callback wrapped) {
- mWrapped = wrapped;
- }
-
- public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- return mWrapped.onCreateActionMode(mode, menu);
- }
-
- public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
- requestFitSystemWindows();
- return mWrapped.onPrepareActionMode(mode, menu);
- }
-
- public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
- return mWrapped.onActionItemClicked(mode, item);
- }
-
- public void onDestroyActionMode(ActionMode mode) {
- mWrapped.onDestroyActionMode(mode);
- final boolean isMncApp = mContext.getApplicationInfo().targetSdkVersion
- >= Build.VERSION_CODES.M;
- final boolean isPrimary;
- final boolean isFloating;
- if (isMncApp) {
- isPrimary = mode == mPrimaryActionMode;
- isFloating = mode == mFloatingActionMode;
- if (!isPrimary && mode.getType() == ActionMode.TYPE_PRIMARY) {
- Log.e(TAG, "Destroying unexpected ActionMode instance of TYPE_PRIMARY; "
- + mode + " was not the current primary action mode! Expected "
- + mPrimaryActionMode);
- }
- if (!isFloating && mode.getType() == ActionMode.TYPE_FLOATING) {
- Log.e(TAG, "Destroying unexpected ActionMode instance of TYPE_FLOATING; "
- + mode + " was not the current floating action mode! Expected "
- + mFloatingActionMode);
- }
- } else {
- isPrimary = mode.getType() == ActionMode.TYPE_PRIMARY;
- isFloating = mode.getType() == ActionMode.TYPE_FLOATING;
- }
- if (isPrimary) {
- if (mPrimaryActionModePopup != null) {
- removeCallbacks(mShowPrimaryActionModePopup);
- }
- if (mPrimaryActionModeView != null) {
- endOnGoingFadeAnimation();
- mFadeAnim = ObjectAnimator.ofFloat(mPrimaryActionModeView, View.ALPHA,
- 1f, 0f);
- mFadeAnim.addListener(new Animator.AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animation) {
-
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- mPrimaryActionModeView.setVisibility(GONE);
- if (mPrimaryActionModePopup != null) {
- mPrimaryActionModePopup.dismiss();
- }
- mPrimaryActionModeView.removeAllViews();
- mFadeAnim = null;
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
-
- }
-
- @Override
- public void onAnimationRepeat(Animator animation) {
-
- }
- });
- mFadeAnim.start();
- }
-
- mPrimaryActionMode = null;
- } else if (isFloating) {
- cleanupFloatingActionModeViews();
- mFloatingActionMode = null;
- }
- if (mWindow.getCallback() != null && !mWindow.isDestroyed()) {
- try {
- mWindow.getCallback().onActionModeFinished(mode);
- } catch (AbstractMethodError ame) {
- // Older apps might not implement this callback method.
- }
- }
- requestFitSystemWindows();
- }
-
- @Override
- public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
- if (mWrapped instanceof ActionMode.Callback2) {
- ((ActionMode.Callback2) mWrapped).onGetContentRect(mode, view, outRect);
- } else {
- super.onGetContentRect(mode, view, outRect);
- }
- }
- }
- }
-
protected DecorView generateDecor(int featureId) {
// System process doesn't have application context and in that case we need to directly use
// the context we have. Otherwise we want the application context, so we don't cling to the
@@ -4147,7 +2593,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
} else {
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
- mContentRoot = (ViewGroup) in;
+ decor.mContentRoot = (ViewGroup) in;
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
@@ -4450,7 +2896,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
* isn't in our features, this throws an exception).
* @return The panel state.
*/
- private PanelFeatureState getPanelState(int featureId, boolean required) {
+ PanelFeatureState getPanelState(int featureId, boolean required) {
return getPanelState(featureId, required, null);
}
@@ -4914,7 +3360,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
int curAlpha = 255;
}
- private static final class PanelFeatureState {
+ static final class PanelFeatureState {
/** Feature ID for this panel. */
int featureId;
@@ -5316,30 +3762,12 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
}
}
- private static class ColorViewState {
- View view = null;
- int targetVisibility = View.INVISIBLE;
- boolean present = false;
-
- final int id;
- final int systemUiHideFlag;
- final int translucentFlag;
- final int verticalGravity;
- final int horizontalGravity;
- final String transitionName;
- final int hideWindowFlag;
+ int getLocalFeaturesPrivate() {
+ return super.getLocalFeatures();
+ }
- ColorViewState(int systemUiHideFlag,
- int translucentFlag, int verticalGravity, int horizontalGravity,
- String transitionName, int id, int hideWindowFlag) {
- this.id = id;
- this.systemUiHideFlag = systemUiHideFlag;
- this.translucentFlag = translucentFlag;
- this.verticalGravity = verticalGravity;
- this.horizontalGravity = horizontalGravity;
- this.transitionName = transitionName;
- this.hideWindowFlag = hideWindowFlag;
- }
+ protected void setDefaultWindowFormat(int format) {
+ super.setDefaultWindowFormat(format);
}
void sendCloseSystemWindows() {
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index 703a9bd2fcd5..a805b6d1d151 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -1006,6 +1006,7 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) {
// is disposed.
int dupFd = dup(blob.fd());
if (dupFd < 0) {
+ ALOGE("Error allocating dup fd. Error:%d", errno);
blob.release();
SkSafeUnref(ctable);
doThrowRE(env, "Could not allocate dup blob fd.");
diff --git a/core/jni/android_util_Log.cpp b/core/jni/android_util_Log.cpp
index c89f293c05a2..2d23cda5ff15 100644
--- a/core/jni/android_util_Log.cpp
+++ b/core/jni/android_util_Log.cpp
@@ -42,9 +42,7 @@ struct levels_t {
static levels_t levels;
static jboolean isLoggable(const char* tag, jint level) {
- return __android_log_is_loggable(level, tag,
- ANDROID_LOG_INFO |
- ANDROID_LOGGABLE_FLAG_NOT_WITHIN_SIGNAL);
+ return __android_log_is_loggable(level, tag, ANDROID_LOG_INFO);
}
static jboolean android_util_Log_isLoggable(JNIEnv* env, jobject clazz, jstring tag, jint level)
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index f900301c61d5..6f6d1b0b43df 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -420,7 +420,7 @@
<string name="permdesc_useFingerprint" msgid="9165097460730684114">"指紋ハードウェアを認証に使用することをアプリに許可します"</string>
<string name="fingerprint_acquired_partial" msgid="735082772341716043">"指紋を一部しか検出できませんでした。もう一度お試しください。"</string>
<string name="fingerprint_acquired_insufficient" msgid="4596546021310923214">"指紋を処理できませんでした。もう一度お試しください。"</string>
- <string name="fingerprint_acquired_imager_dirty" msgid="1087209702421076105">"指紋センサーに汚れがあります。汚れを落としてもう一度お試しください。"</string>
+ <string name="fingerprint_acquired_imager_dirty" msgid="1087209702421076105">"指紋認証センサーに汚れがあります。汚れを落としてもう一度お試しください。"</string>
<string name="fingerprint_acquired_too_fast" msgid="6470642383109155969">"指の動きが速すぎました。もう一度お試しください。"</string>
<string name="fingerprint_acquired_too_slow" msgid="59250885689661653">"指の動きが遅すぎました。もう一度お試しください。"</string>
<string-array name="fingerprint_acquired_vendor">
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 50cf30262f69..90fc22b8b3b8 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3242,6 +3242,14 @@ i
</p>
-->
<attr name="canRequestFilterKeyEvents" format="boolean" />
+ <!-- Attribute whether the accessibility service wants to be able to control
+ display magnification.
+ <p>
+ Required to allow setting the {@link android.accessibilityservice
+ #AccessibilityServiceInfo#FLAG_CAN_CONTROL_MAGNIFICATION} flag.
+ </p>
+ -->
+ <attr name="canControlMagnification" format="boolean" />
<!-- Short description of the accessibility serivce purpose or behavior.-->
<attr name="description" />
</declare-styleable>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 54e43c8e3e7c..b6b2e204b9cf 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2682,6 +2682,7 @@
<public type="attr" name="forceDeviceEncrypted" />
<public type="attr" name="encryptionAware" />
<public type="attr" name="preferenceFragmentStyle" />
+ <public type="attr" name="canControlMagnification" />
<public type="style" name="Theme.Material.DayNight" />
<public type="style" name="Theme.Material.DayNight.DarkActionBar" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index faa76f2fe9ce..00c0fe8b26e1 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -613,6 +613,12 @@
<string name="capability_desc_canRequestFilterKeyEvents">Includes personal data such as credit
card numbers and passwords.</string>
+ <!-- Title for the capability of an accessibility service to control display magnification. -->
+ <string name="capability_title_canControlMagnification">Control display magnification</string>
+ <!-- Description for the capability of an accessibility service to control display magnification. -->
+ <string name="capability_desc_canControlMagnification">Control the display\'s zoom level and
+ positioning.</string>
+
<!-- Permissions -->
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6820c259a51a..e8f6b462d447 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -564,6 +564,8 @@
<java-symbol type="string" name="capability_desc_canRequestFilterKeyEvents" />
<java-symbol type="string" name="capability_title_canRequestTouchExploration" />
<java-symbol type="string" name="capability_title_canRetrieveWindowContent" />
+ <java-symbol type="string" name="capability_desc_canControlMagnification" />
+ <java-symbol type="string" name="capability_title_canControlMagnification" />
<java-symbol type="string" name="cfTemplateForwarded" />
<java-symbol type="string" name="cfTemplateForwardedTime" />
<java-symbol type="string" name="cfTemplateNotForwarded" />
diff --git a/docs/html/guide/topics/manifest/uses-feature-element.jd b/docs/html/guide/topics/manifest/uses-feature-element.jd
index e22dc4a4bda2..21e3057aaf04 100644
--- a/docs/html/guide/topics/manifest/uses-feature-element.jd
+++ b/docs/html/guide/topics/manifest/uses-feature-element.jd
@@ -563,8 +563,8 @@ is sensitive to delays or lag in sound input or output.</td>
<td rowspan="6">Camera</td>
<td><code>android.hardware.camera</code></td>
<td>The application uses the device's back-facing (main) camera.</td>
- <td>Importantly, devices with only a front-facing camera will not list this
- feature, so the <code>android.hardware.camera.any</code> feature should be
+ <td>Devices with only a front-facing camera do not list this feature, so the
+ <code>android.hardware.camera.any</code> feature should be
used instead if a camera facing any direction is acceptable for the
application.</td>
</tr>
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 64f269886c70..a50c945ca4f6 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -1240,30 +1240,54 @@ public abstract class Drawable {
*/
public static abstract class ConstantState {
/**
- * Create a new drawable without supplying resources the caller
- * is running in. Note that using this means the density-dependent
- * drawables (like bitmaps) will not be able to update their target
- * density correctly. One should use {@link #newDrawable(Resources)}
- * instead to provide a resource.
+ * Creates a new Drawable instance from its constant state.
+ * <p>
+ * <strong>Note:</strong> Using this method means density-dependent
+ * properties, such as pixel dimensions or bitmap images, will not be
+ * updated to match the density of the target display. To ensure
+ * correct scaling, use {@link #newDrawable(Resources)} instead to
+ * provide an appropriate Resources object.
+ *
+ * @return a new drawable object based on this constant state
+ * @see {@link #newDrawable(Resources)}
*/
+ @NonNull
public abstract Drawable newDrawable();
/**
- * Create a new Drawable instance from its constant state. This
- * must be implemented for drawables that change based on the target
- * density of their caller (that is depending on whether it is
- * in compatibility mode).
+ * Creates a new Drawable instance from its constant state using the
+ * specified resources. This method should be implemented for drawables
+ * that have density-dependent properties.
+ * <p>
+ * The default implementation for this method calls through to
+ * {@link #newDrawable()}.
+ *
+ * @param res the resources of the context in which the drawable will
+ * be displayed
+ * @return a new drawable object based on this constant state
*/
- public Drawable newDrawable(Resources res) {
+ @NonNull
+ public Drawable newDrawable(@Nullable Resources res) {
return newDrawable();
}
/**
- * Create a new Drawable instance from its constant state. This must be
- * implemented for drawables that can have a theme applied.
+ * Creates a new Drawable instance from its constant state using the
+ * specified resources and theme. This method should be implemented for
+ * drawables that have theme-dependent properties.
+ * <p>
+ * The default implementation for this method calls through to
+ * {@link #newDrawable(Resources)}.
+ *
+ * @param res the resources of the context in which the drawable will
+ * be displayed
+ * @param theme the theme of the context in which the drawable will be
+ * displayed
+ * @return a new drawable object based on this constant state
*/
- public Drawable newDrawable(Resources res, Theme theme) {
- return newDrawable(null);
+ @NonNull
+ public Drawable newDrawable(@Nullable Resources res, @Nullable Theme theme) {
+ return newDrawable(res);
}
/**
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 4acad67f716a..8565372f3b53 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -206,7 +206,9 @@ LOCAL_MODULE := hwui_unit_tests
LOCAL_MODULE_TAGS := tests
LOCAL_SHARED_LIBRARIES := $(hwui_shared_libraries)
LOCAL_STATIC_LIBRARIES := libhwui_static_null_gpu
-LOCAL_CFLAGS := $(hwui_cflags)
+LOCAL_CFLAGS := \
+ $(hwui_cflags) \
+ -DHWUI_NULL_GPU
LOCAL_SRC_FILES += \
unit_tests/CanvasStateTests.cpp \
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index 1aa291f23a1e..d2d3285b71f8 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -35,11 +35,11 @@ OffscreenBuffer* BakedOpRenderer::startTemporaryLayer(uint32_t width, uint32_t h
LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer...");
OffscreenBuffer* buffer = mRenderState.layerPool().get(mRenderState, width, height);
- startRepaintLayer(buffer);
+ startRepaintLayer(buffer, Rect(width, height));
return buffer;
}
-void BakedOpRenderer::startRepaintLayer(OffscreenBuffer* offscreenBuffer) {
+void BakedOpRenderer::startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) {
LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer...");
mRenderTarget.offscreenBuffer = offscreenBuffer;
@@ -55,12 +55,10 @@ void BakedOpRenderer::startRepaintLayer(OffscreenBuffer* offscreenBuffer) {
LOG_ALWAYS_FATAL_IF(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE,
"framebuffer incomplete!");
- // Clear the FBO
- mRenderState.scissor().setEnabled(false);
- glClear(GL_COLOR_BUFFER_BIT);
-
// Change the viewport & ortho projection
setViewport(offscreenBuffer->viewportWidth, offscreenBuffer->viewportHeight);
+
+ clearColorBuffer(repaintRect);
}
void BakedOpRenderer::endLayer() {
@@ -74,16 +72,13 @@ void BakedOpRenderer::endLayer() {
mRenderTarget.frameBufferId = -1;
}
-void BakedOpRenderer::startFrame(uint32_t width, uint32_t height) {
+void BakedOpRenderer::startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) {
mRenderState.bindFramebuffer(0);
setViewport(width, height);
mCaches.clearGarbage();
if (!mOpaque) {
- // TODO: partial invalidate!
- mRenderState.scissor().setEnabled(false);
- glClear(GL_COLOR_BUFFER_BIT);
- mHasDrawn = true;
+ clearColorBuffer(repaintRect);
}
}
@@ -113,6 +108,20 @@ void BakedOpRenderer::setViewport(uint32_t width, uint32_t height) {
mRenderState.blend().syncEnabled();
}
+void BakedOpRenderer::clearColorBuffer(const Rect& rect) {
+ if (Rect(mRenderTarget.viewportWidth, mRenderTarget.viewportHeight).contains(rect)) {
+ // Full viewport is being cleared - disable scissor
+ mRenderState.scissor().setEnabled(false);
+ } else {
+ // Requested rect is subset of viewport - scissor to it to avoid over-clearing
+ mRenderState.scissor().setEnabled(true);
+ mRenderState.scissor().set(rect.left, mRenderTarget.viewportHeight - rect.bottom,
+ rect.getWidth(), rect.getHeight());
+ }
+ glClear(GL_COLOR_BUFFER_BIT);
+ if (!mRenderTarget.frameBufferId) mHasDrawn = true;
+}
+
Texture* BakedOpRenderer::getTexture(const SkBitmap* bitmap) {
Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap);
if (!texture) {
@@ -136,7 +145,7 @@ void BakedOpRenderer::renderGlop(const BakedOpState& state, const Glop& glop) {
mRenderTarget.offscreenBuffer->region.orSelf(dirty);
}
mRenderState.render(glop, mRenderTarget.orthoMatrix);
- mHasDrawn = true;
+ if (!mRenderTarget.frameBufferId) mHasDrawn = true;
}
////////////////////////////////////////////////////////////////////////////////
@@ -217,7 +226,7 @@ static void renderShadow(BakedOpRenderer& renderer, const BakedOpState& state, f
paint.setAntiAlias(true); // want to use AlphaVertex
// The caller has made sure casterAlpha > 0.
- uint8_t ambientShadowAlpha = 128u; //TODO: mAmbientShadowAlpha;
+ uint8_t ambientShadowAlpha = renderer.getLightInfo().ambientShadowAlpha;
if (CC_UNLIKELY(Properties::overrideAmbientShadowStrength >= 0)) {
ambientShadowAlpha = Properties::overrideAmbientShadowStrength;
}
@@ -227,7 +236,7 @@ static void renderShadow(BakedOpRenderer& renderer, const BakedOpState& state, f
paint, VertexBufferRenderFlags::ShadowInterp);
}
- uint8_t spotShadowAlpha = 128u; //TODO: mSpotShadowAlpha;
+ uint8_t spotShadowAlpha = renderer.getLightInfo().spotShadowAlpha;
if (CC_UNLIKELY(Properties::overrideSpotShadowStrength >= 0)) {
spotShadowAlpha = Properties::overrideSpotShadowStrength;
}
@@ -240,12 +249,10 @@ static void renderShadow(BakedOpRenderer& renderer, const BakedOpState& state, f
void BakedOpDispatcher::onShadowOp(BakedOpRenderer& renderer, const ShadowOp& op, const BakedOpState& state) {
TessellationCache::vertexBuffer_pair_t buffers;
- Vector3 lightCenter = { 300, 300, 300 }; // TODO!
- float lightRadius = 150; // TODO!
-
renderer.caches().tessellationCache.getShadowBuffers(&state.computedState.transform,
op.localClipRect, op.casterAlpha >= 1.0f, op.casterPath,
- &op.shadowMatrixXY, &op.shadowMatrixZ, lightCenter, lightRadius,
+ &op.shadowMatrixXY, &op.shadowMatrixZ,
+ op.lightCenter, renderer.getLightInfo().lightRadius,
buffers);
renderShadow(renderer, state, op.casterAlpha, buffers.first, buffers.second);
diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h
index d6d9cb139328..29f9a6f81242 100644
--- a/libs/hwui/BakedOpRenderer.h
+++ b/libs/hwui/BakedOpRenderer.h
@@ -39,27 +39,39 @@ class RenderState;
*/
class BakedOpRenderer {
public:
- BakedOpRenderer(Caches& caches, RenderState& renderState, bool opaque)
+ /**
+ * Position agnostic shadow lighting info. Used with all shadow ops in scene.
+ */
+ struct LightInfo {
+ float lightRadius = 0;
+ uint8_t ambientShadowAlpha = 0;
+ uint8_t spotShadowAlpha = 0;
+ };
+
+ BakedOpRenderer(Caches& caches, RenderState& renderState, bool opaque, const LightInfo& lightInfo)
: mRenderState(renderState)
, mCaches(caches)
- , mOpaque(opaque) {
+ , mOpaque(opaque)
+ , mLightInfo(lightInfo) {
}
RenderState& renderState() { return mRenderState; }
Caches& caches() { return mCaches; }
- void startFrame(uint32_t width, uint32_t height);
+ void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect);
void endFrame();
OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height);
- void startRepaintLayer(OffscreenBuffer* offscreenBuffer);
+ void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect);
void endLayer();
Texture* getTexture(const SkBitmap* bitmap);
+ const LightInfo& getLightInfo() { return mLightInfo; }
void renderGlop(const BakedOpState& state, const Glop& glop);
bool didDraw() { return mHasDrawn; }
private:
void setViewport(uint32_t width, uint32_t height);
+ void clearColorBuffer(const Rect& clearRect);
RenderState& mRenderState;
Caches& mCaches;
@@ -75,6 +87,8 @@ private:
uint32_t viewportHeight = 0;
Matrix4 orthoMatrix;
} mRenderTarget;
+
+ const LightInfo mLightInfo;
};
/**
diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp
index a327614a50ef..94a11f131229 100644
--- a/libs/hwui/Caches.cpp
+++ b/libs/hwui/Caches.cpp
@@ -79,7 +79,6 @@ bool Caches::init() {
}
void Caches::initExtensions() {
- mExtensions.load();
if (mExtensions.hasDebugMarker()) {
eventMark = glInsertEventMarkerEXT;
diff --git a/libs/hwui/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp
index 03b1706faa49..39b7ecb9a914 100644
--- a/libs/hwui/DeviceInfo.cpp
+++ b/libs/hwui/DeviceInfo.cpp
@@ -40,7 +40,6 @@ void DeviceInfo::initialize() {
}
void DeviceInfo::load() {
- mExtensions.load();
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize);
}
diff --git a/libs/hwui/Extensions.cpp b/libs/hwui/Extensions.cpp
index e257715acaeb..02caaa49e99c 100644
--- a/libs/hwui/Extensions.cpp
+++ b/libs/hwui/Extensions.cpp
@@ -35,7 +35,7 @@ namespace uirenderer {
#endif
-void Extensions::load() {
+Extensions::Extensions() {
auto extensions = StringUtils::split((const char*) glGetString(GL_EXTENSIONS));
mHasNPot = extensions.has("GL_OES_texture_npot");
mHasFramebufferFetch = extensions.has("GL_NV_shader_framebuffer_fetch");
diff --git a/libs/hwui/Extensions.h b/libs/hwui/Extensions.h
index 8ccfabdd2450..67cc747015e0 100644
--- a/libs/hwui/Extensions.h
+++ b/libs/hwui/Extensions.h
@@ -31,7 +31,7 @@ namespace uirenderer {
class Extensions {
public:
- void load();
+ Extensions();
inline bool hasNPot() const { return mHasNPot; }
inline bool hasFramebufferFetch() const { return mHasFramebufferFetch; }
diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp
index 80efaed44d13..b04f16fe4788 100644
--- a/libs/hwui/OpReorderer.cpp
+++ b/libs/hwui/OpReorderer.cpp
@@ -18,6 +18,7 @@
#include "LayerUpdateQueue.h"
#include "RenderNode.h"
+#include "renderstate/OffscreenBufferPool.h"
#include "utils/FatVector.h"
#include "utils/PaintUtils.h"
@@ -33,8 +34,8 @@ class BatchBase {
public:
BatchBase(batchid_t batchId, BakedOpState* op, bool merging)
- : mBatchId(batchId)
- , mMerging(merging) {
+ : mBatchId(batchId)
+ , mMerging(merging) {
mBounds = op->computedState.clippedBounds;
mOps.push_back(op);
}
@@ -207,9 +208,10 @@ private:
};
OpReorderer::LayerReorderer::LayerReorderer(uint32_t width, uint32_t height,
- const BeginLayerOp* beginLayerOp, RenderNode* renderNode)
+ const Rect& repaintRect, const BeginLayerOp* beginLayerOp, RenderNode* renderNode)
: width(width)
, height(height)
+ , repaintRect(repaintRect)
, offscreenBuffer(renderNode ? renderNode->getLayer() : nullptr)
, beginLayerOp(beginLayerOp)
, renderNode(renderNode) {}
@@ -309,15 +311,19 @@ void OpReorderer::LayerReorderer::dump() const {
OpReorderer::OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip,
uint32_t viewportWidth, uint32_t viewportHeight,
- const std::vector< sp<RenderNode> >& nodes)
+ const std::vector< sp<RenderNode> >& nodes, const Vector3& lightCenter)
: mCanvasState(*this) {
ATRACE_NAME("prepare drawing commands");
- mLayerReorderers.emplace_back(viewportWidth, viewportHeight);
- mLayerStack.push_back(0);
+ mLayerReorderers.reserve(layers.entries().size());
+ mLayerStack.reserve(layers.entries().size());
+
+ // Prepare to defer Fbo0
+ mLayerReorderers.emplace_back(viewportWidth, viewportHeight, Rect(clip));
+ mLayerStack.push_back(0);
mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
clip.fLeft, clip.fTop, clip.fRight, clip.fBottom,
- Vector3());
+ lightCenter);
// Render all layers to be updated, in order. Defer in reverse order, so that they'll be
// updated in the order they're passed in (mLayerReorderers are issued to Renderer in reverse)
@@ -325,7 +331,8 @@ OpReorderer::OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip,
RenderNode* layerNode = layers.entries()[i].renderNode;
const Rect& layerDamage = layers.entries()[i].damage;
- saveForLayer(layerNode->getWidth(), layerNode->getHeight(), nullptr, layerNode);
+ saveForLayer(layerNode->getWidth(), layerNode->getHeight(),
+ layerDamage, nullptr, layerNode);
mCanvasState.writableSnapshot()->setClip(
layerDamage.left, layerDamage.top, layerDamage.right, layerDamage.bottom);
@@ -345,14 +352,17 @@ OpReorderer::OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip,
}
}
-OpReorderer::OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList)
+OpReorderer::OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList,
+ const Vector3& lightCenter)
: mCanvasState(*this) {
ATRACE_NAME("prepare drawing commands");
- mLayerReorderers.emplace_back(viewportWidth, viewportHeight);
+ // Prepare to defer Fbo0
+ mLayerReorderers.emplace_back(viewportWidth, viewportHeight,
+ Rect(viewportWidth, viewportHeight));
mLayerStack.push_back(0);
-
mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
- 0, 0, viewportWidth, viewportHeight, Vector3());
+ 0, 0, viewportWidth, viewportHeight, lightCenter);
+
deferImpl(displayList);
}
@@ -508,7 +518,8 @@ void OpReorderer::deferShadow(const RenderNodeOp& casterNodeOp) {
}
ShadowOp* shadowOp = new (mAllocator) ShadowOp(casterNodeOp, casterAlpha, casterPath,
- mCanvasState.getLocalClipBounds());
+ mCanvasState.getLocalClipBounds(),
+ mCanvasState.currentSnapshot()->getRelativeLightCenter());
BakedOpState* bakedOpState = BakedOpState::tryShadowOpConstruct(
mAllocator, *mCanvasState.currentSnapshot(), shadowOp);
if (CC_LIKELY(bakedOpState)) {
@@ -589,17 +600,36 @@ void OpReorderer::onSimpleRectsOp(const SimpleRectsOp& op) {
currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, OpBatchType::Vertices);
}
-void OpReorderer::saveForLayer(uint32_t layerWidth, uint32_t layerHeight,
+void OpReorderer::saveForLayer(uint32_t layerWidth, uint32_t layerHeight, const Rect& repaintRect,
const BeginLayerOp* beginLayerOp, RenderNode* renderNode) {
+ auto previous = mCanvasState.currentSnapshot();
mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
mCanvasState.writableSnapshot()->transform->loadIdentity();
mCanvasState.writableSnapshot()->initializeViewport(layerWidth, layerHeight);
mCanvasState.writableSnapshot()->roundRectClipState = nullptr;
+ Vector3 lightCenter = previous->getRelativeLightCenter();
+ if (renderNode) {
+ Matrix4& inverse = renderNode->getLayer()->inverseTransformInWindow;
+ inverse.mapPoint3d(lightCenter);
+ } else {
+ // Combine all transforms used to present saveLayer content:
+ // parent content transform * canvas transform * bounds offset
+ Matrix4 contentTransform(*previous->transform);
+ contentTransform.multiply(beginLayerOp->localMatrix);
+ contentTransform.translate(beginLayerOp->unmappedBounds.left, beginLayerOp->unmappedBounds.top);
+
+ // inverse the total transform, to map light center into layer-relative space
+ Matrix4 inverse;
+ inverse.loadInverse(contentTransform);
+ inverse.mapPoint3d(lightCenter);
+ }
+ mCanvasState.writableSnapshot()->setRelativeLightCenter(lightCenter);
+
// create a new layer, and push its index on the stack
mLayerStack.push_back(mLayerReorderers.size());
- mLayerReorderers.emplace_back(layerWidth, layerHeight, beginLayerOp, renderNode);
+ mLayerReorderers.emplace_back(layerWidth, layerHeight, repaintRect, beginLayerOp, renderNode);
}
void OpReorderer::restoreForLayer() {
@@ -612,7 +642,7 @@ void OpReorderer::restoreForLayer() {
void OpReorderer::onBeginLayerOp(const BeginLayerOp& op) {
const uint32_t layerWidth = (uint32_t) op.unmappedBounds.getWidth();
const uint32_t layerHeight = (uint32_t) op.unmappedBounds.getHeight();
- saveForLayer(layerWidth, layerHeight, &op, nullptr);
+ saveForLayer(layerWidth, layerHeight, Rect(layerWidth, layerHeight), &op, nullptr);
}
void OpReorderer::onEndLayerOp(const EndLayerOp& /* ignored */) {
diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/OpReorderer.h
index 2c30f0dc30de..09d5cbcf7559 100644
--- a/libs/hwui/OpReorderer.h
+++ b/libs/hwui/OpReorderer.h
@@ -67,13 +67,13 @@ class OpReorderer : public CanvasStateClient {
class LayerReorderer {
public:
// Create LayerReorderer for Fbo0
- LayerReorderer(uint32_t width, uint32_t height)
- : LayerReorderer(width, height, nullptr, nullptr) {};
+ LayerReorderer(uint32_t width, uint32_t height, const Rect& repaintRect)
+ : LayerReorderer(width, height, repaintRect, nullptr, nullptr) {};
// Create LayerReorderer for an offscreen layer, where beginLayerOp is present for a
// saveLayer, renderNode is present for a HW layer.
LayerReorderer(uint32_t width, uint32_t height,
- const BeginLayerOp* beginLayerOp, RenderNode* renderNode);
+ const Rect& repaintRect, const BeginLayerOp* beginLayerOp, RenderNode* renderNode);
// iterate back toward target to see if anything drawn since should overlap the new op
// if no target, merging ops still iterate to find similar batch to insert after
@@ -101,6 +101,7 @@ class OpReorderer : public CanvasStateClient {
const uint32_t width;
const uint32_t height;
+ const Rect repaintRect;
OffscreenBuffer* offscreenBuffer;
const BeginLayerOp* beginLayerOp;
const RenderNode* renderNode;
@@ -116,14 +117,15 @@ class OpReorderer : public CanvasStateClient {
// Maps batch ids to the most recent *non-merging* batch of that id
OpBatch* mBatchLookup[OpBatchType::Count] = { nullptr };
-
};
+
public:
OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip,
uint32_t viewportWidth, uint32_t viewportHeight,
- const std::vector< sp<RenderNode> >& nodes);
+ const std::vector< sp<RenderNode> >& nodes, const Vector3& lightCenter);
- OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList);
+ OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList,
+ const Vector3& lightCenter);
virtual ~OpReorderer() {}
@@ -153,7 +155,7 @@ public:
LayerReorderer& layer = mLayerReorderers[i];
if (layer.renderNode) {
// cached HW layer - can't skip layer if empty
- renderer.startRepaintLayer(layer.offscreenBuffer);
+ renderer.startRepaintLayer(layer.offscreenBuffer, layer.repaintRect);
layer.replayBakedOpsImpl((void*)&renderer, receivers);
renderer.endLayer();
} else if (!layer.empty()) { // save layer - skip entire layer if empty
@@ -164,7 +166,7 @@ public:
}
const LayerReorderer& fbo0 = mLayerReorderers[0];
- renderer.startFrame(fbo0.width, fbo0.height);
+ renderer.startFrame(fbo0.width, fbo0.height, fbo0.repaintRect);
fbo0.replayBakedOpsImpl((void*)&renderer, receivers);
renderer.endFrame();
}
@@ -188,7 +190,7 @@ private:
Positive
};
void saveForLayer(uint32_t layerWidth, uint32_t layerHeight,
- const BeginLayerOp* beginLayerOp, RenderNode* renderNode);
+ const Rect& repaintRect, const BeginLayerOp* beginLayerOp, RenderNode* renderNode);
void restoreForLayer();
LayerReorderer& currentLayer() { return mLayerReorderers[mLayerStack.back()]; }
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
index bb7a0a7ce446..ef0536761a11 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -17,10 +17,11 @@
#ifndef ANDROID_HWUI_RECORDED_OP_H
#define ANDROID_HWUI_RECORDED_OP_H
-#include "utils/LinearAllocator.h"
-#include "Rect.h"
#include "Matrix.h"
+#include "Rect.h"
#include "RenderNode.h"
+#include "utils/LinearAllocator.h"
+#include "Vector.h"
#include "SkXfermode.h"
@@ -116,15 +117,17 @@ struct RectOp : RecordedOp {
* Uses invalid/empty bounds and matrix since ShadowOp bounds aren't known at defer time,
* and are resolved dynamically, and transform isn't needed.
*
- * State construction handles these properties specially.
+ * State construction handles these properties specially, ignoring matrix/bounds.
*/
struct ShadowOp : RecordedOp {
- ShadowOp(const RenderNodeOp& casterOp, float casterAlpha, const SkPath* casterPath, const Rect& clipRect)
+ ShadowOp(const RenderNodeOp& casterOp, float casterAlpha, const SkPath* casterPath,
+ const Rect& clipRect, const Vector3& lightCenter)
: RecordedOp(RecordedOpId::ShadowOp, Rect(), Matrix4::identity(), clipRect, nullptr)
, shadowMatrixXY(casterOp.localMatrix)
, shadowMatrixZ(casterOp.localMatrix)
, casterAlpha(casterAlpha)
- , casterPath(casterPath) {
+ , casterPath(casterPath)
+ , lightCenter(lightCenter) {
const RenderNode& node = *casterOp.renderNode;
node.applyViewPropertyTransforms(shadowMatrixXY, false);
node.applyViewPropertyTransforms(shadowMatrixZ, true);
@@ -133,6 +136,7 @@ struct ShadowOp : RecordedOp {
Matrix4 shadowMatrixZ;
const float casterAlpha;
const SkPath* casterPath;
+ const Vector3 lightCenter;
};
struct SimpleRectsOp : RecordedOp { // Filled, no AA (TODO: better name?)
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index e9885552369d..6ab253c2491f 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -156,7 +156,7 @@ int RecordingCanvas::saveLayer(float left, float top, float right, float bottom,
snapshot.flags |= Snapshot::kFlagFboTarget | Snapshot::kFlagIsFboLayer;
snapshot.initializeViewport(untransformedBounds.getWidth(), untransformedBounds.getHeight());
- snapshot.resetTransform(-untransformedBounds.left, -untransformedBounds.top, 0.0f);
+ snapshot.transform->loadTranslate(-untransformedBounds.left, -untransformedBounds.top, 0.0f);
Rect clip = layerBounds;
clip.translate(-untransformedBounds.left, -untransformedBounds.top);
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index e177f9a86a2c..2713f46ab33b 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -326,15 +326,11 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) {
return;
}
- if (transformUpdateNeeded) {
+ if (transformUpdateNeeded && mLayer) {
// update the transform in window of the layer to reset its origin wrt light source position
Matrix4 windowTransform;
info.damageAccumulator->computeCurrentTransform(&windowTransform);
-#if HWUI_NEW_OPS
- // TODO: update layer transform (perhaps as part of enqueueLayerWithDamage)
-#else
mLayer->setWindowTransform(windowTransform);
-#endif
}
#if HWUI_NEW_OPS
diff --git a/libs/hwui/Snapshot.cpp b/libs/hwui/Snapshot.cpp
index 0a58f4b42e4c..2f535bb1cb08 100644
--- a/libs/hwui/Snapshot.cpp
+++ b/libs/hwui/Snapshot.cpp
@@ -130,6 +130,9 @@ void Snapshot::resetClip(float left, float top, float right, float bottom) {
///////////////////////////////////////////////////////////////////////////////
void Snapshot::resetTransform(float x, float y, float z) {
+#if HWUI_NEW_OPS
+ LOG_ALWAYS_FATAL("not supported - light center managed differently");
+#else
// before resetting, map current light pos with inverse of current transform
Vector3 center = mRelativeLightCenter;
mat4 inverse;
@@ -139,6 +142,7 @@ void Snapshot::resetTransform(float x, float y, float z) {
transform = &mTransformRoot;
transform->loadTranslate(x, y, z);
+#endif
}
void Snapshot::buildScreenSpaceTransform(Matrix4* outTransform) const {
diff --git a/libs/hwui/microbench/OpReordererBench.cpp b/libs/hwui/microbench/OpReordererBench.cpp
index 7b8d0e542a2f..b24858e73525 100644
--- a/libs/hwui/microbench/OpReordererBench.cpp
+++ b/libs/hwui/microbench/OpReordererBench.cpp
@@ -48,7 +48,7 @@ BENCHMARK_NO_ARG(BM_OpReorderer_defer);
void BM_OpReorderer_defer::Run(int iters) {
StartBenchmarkTiming();
for (int i = 0; i < iters; i++) {
- OpReorderer reorderer(200, 200, *sReorderingDisplayList);
+ OpReorderer reorderer(200, 200, *sReorderingDisplayList, (Vector3) { 100, 100, 100 });
MicroBench::DoNotOptimize(&reorderer);
}
StopBenchmarkTiming();
@@ -59,11 +59,13 @@ void BM_OpReorderer_deferAndRender::Run(int iters) {
TestUtils::runOnRenderThread([this, iters](renderthread::RenderThread& thread) {
RenderState& renderState = thread.renderState();
Caches& caches = Caches::getInstance();
+ BakedOpRenderer::LightInfo lightInfo = { 50.0f, 128, 128 };
+
StartBenchmarkTiming();
for (int i = 0; i < iters; i++) {
- OpReorderer reorderer(200, 200, *sReorderingDisplayList);
+ OpReorderer reorderer(200, 200, *sReorderingDisplayList, (Vector3) { 100, 100, 100 });
- BakedOpRenderer renderer(caches, renderState, true);
+ BakedOpRenderer renderer(caches, renderState, true, lightInfo);
reorderer.replayBakedOps<BakedOpDispatcher>(renderer);
MicroBench::DoNotOptimize(&renderer);
}
diff --git a/libs/hwui/renderstate/OffscreenBufferPool.h b/libs/hwui/renderstate/OffscreenBufferPool.h
index f0fd82d0444f..fac6c35179d5 100644
--- a/libs/hwui/renderstate/OffscreenBufferPool.h
+++ b/libs/hwui/renderstate/OffscreenBufferPool.h
@@ -34,6 +34,11 @@ class RenderState;
* Lightweight alternative to Layer. Owns the persistent state of an offscreen render target, and
* encompasses enough information to draw it back on screen (minus paint properties, which are held
* by LayerOp).
+ *
+ * Has two distinct sizes - viewportWidth/viewportHeight describe content area,
+ * texture.width/.height are actual allocated texture size. Texture will tend to be larger than the
+ * viewport bounds, since textures are always allocated with width / height as a multiple of 64, for
+ * the purpose of improving reuse.
*/
class OffscreenBuffer {
public:
@@ -44,11 +49,17 @@ public:
// must be called prior to rendering, to construct/update vertex buffer
void updateMeshFromRegion();
+ // Set by RenderNode for HW layers, TODO for clipped saveLayers
+ void setWindowTransform(const Matrix4& transform) {
+ inverseTransformInWindow.loadInverse(transform);
+ }
+
static uint32_t computeIdealDimension(uint32_t dimension);
uint32_t getSizeInBytes() { return texture.width * texture.height * 4; }
RenderState& renderState;
+
uint32_t viewportWidth;
uint32_t viewportHeight;
Texture texture;
@@ -56,6 +67,10 @@ public:
// Portion of layer that has been drawn to. Used to minimize drawing area when
// drawing back to screen / parent FBO.
Region region;
+
+ Matrix4 inverseTransformInWindow;
+
+ // vbo / size of mesh
GLsizei elementCount = 0;
GLuint vbo = 0;
};
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index f094b2d0c289..89cadea775f3 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -31,7 +31,6 @@
#include "utils/TimeUtils.h"
#if HWUI_NEW_OPS
-#include "BakedOpRenderer.h"
#include "OpReorderer.h"
#endif
@@ -150,13 +149,23 @@ bool CanvasContext::pauseSurface(ANativeWindow* window) {
// TODO: don't pass viewport size, it's automatic via EGL
void CanvasContext::setup(int width, int height, float lightRadius,
uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) {
+#if HWUI_NEW_OPS
+ mLightInfo.lightRadius = lightRadius;
+ mLightInfo.ambientShadowAlpha = ambientShadowAlpha;
+ mLightInfo.spotShadowAlpha = spotShadowAlpha;
+#else
if (!mCanvas) return;
mCanvas->initLight(lightRadius, ambientShadowAlpha, spotShadowAlpha);
+#endif
}
void CanvasContext::setLightCenter(const Vector3& lightCenter) {
+#if HWUI_NEW_OPS
+ mLightCenter = lightCenter;
+#else
if (!mCanvas) return;
mCanvas->setLightCenter(lightCenter);
+#endif
}
void CanvasContext::setOpaque(bool opaque) {
@@ -340,9 +349,11 @@ void CanvasContext::draw() {
mEglManager.damageFrame(frame, dirty);
#if HWUI_NEW_OPS
- OpReorderer reorderer(mLayerUpdateQueue, dirty, frame.width(), frame.height(), mRenderNodes);
+ OpReorderer reorderer(mLayerUpdateQueue, dirty, frame.width(), frame.height(),
+ mRenderNodes, mLightCenter);
mLayerUpdateQueue.clear();
- BakedOpRenderer renderer(Caches::getInstance(), mRenderThread.renderState(), mOpaque);
+ BakedOpRenderer renderer(Caches::getInstance(), mRenderThread.renderState(),
+ mOpaque, mLightInfo);
// TODO: profiler().draw(mCanvas);
reorderer.replayBakedOps<BakedOpDispatcher>(renderer);
@@ -576,8 +587,12 @@ void CanvasContext::buildLayer(RenderNode* node) {
// purposes when the frame is actually drawn
node->setPropertyFieldsDirty(RenderNode::GENERIC);
+#if HWUI_NEW_OPS
+ LOG_ALWAYS_FATAL("unsupported");
+#else
mCanvas->markLayersAsBuildLayers();
mCanvas->flushLayerUpdates();
+#endif
node->incStrong(nullptr);
mPrefetechedLayers.insert(node);
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index d656014fdbcb..c3cfc940281e 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -27,6 +27,10 @@
#include "renderthread/RenderTask.h"
#include "renderthread/RenderThread.h"
+#if HWUI_NEW_OPS
+#include "BakedOpRenderer.h"
+#endif
+
#include <cutils/compiler.h>
#include <EGL/egl.h>
#include <SkBitmap.h>
@@ -165,6 +169,11 @@ private:
bool mOpaque;
OpenGLRenderer* mCanvas = nullptr;
+#if HWUI_NEW_OPS
+ BakedOpRenderer::LightInfo mLightInfo;
+ Vector3 mLightCenter = { 0, 0, 0 };
+#endif
+
bool mHaveNewSurface = false;
DamageAccumulator mDamageAccumulator;
LayerUpdateQueue mLayerUpdateQueue;
diff --git a/libs/hwui/unit_tests/OpReordererTests.cpp b/libs/hwui/unit_tests/OpReordererTests.cpp
index 07080a2f3730..a8c9bba32e64 100644
--- a/libs/hwui/unit_tests/OpReordererTests.cpp
+++ b/libs/hwui/unit_tests/OpReordererTests.cpp
@@ -29,6 +29,14 @@ namespace android {
namespace uirenderer {
LayerUpdateQueue sEmptyLayerUpdateQueue;
+Vector3 sLightCenter = {100, 100, 100};
+
+static std::vector<sp<RenderNode>> createSyncedNodeList(sp<RenderNode>& node) {
+ TestUtils::syncHierarchyPropertiesAndDisplayList(node);
+ std::vector<sp<RenderNode>> vec;
+ vec.emplace_back(node);
+ return vec;
+}
/**
* Virtual class implemented by each test to redirect static operation / state transitions to
@@ -48,13 +56,13 @@ public:
ADD_FAILURE() << "Layer creation not expected in this test";
return nullptr;
}
- virtual void startRepaintLayer(OffscreenBuffer*) {
+ virtual void startRepaintLayer(OffscreenBuffer*, const Rect& repaintRect) {
ADD_FAILURE() << "Layer repaint not expected in this test";
}
virtual void endLayer() {
ADD_FAILURE() << "Layer updates not expected in this test";
}
- virtual void startFrame(uint32_t width, uint32_t height) {}
+ virtual void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) {}
virtual void endFrame() {}
// define virtual defaults for direct
@@ -87,7 +95,7 @@ class FailRenderer : public TestRendererBase {};
TEST(OpReorderer, simple) {
class SimpleTestRenderer : public TestRendererBase {
public:
- void startFrame(uint32_t width, uint32_t height) override {
+ void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
EXPECT_EQ(0, mIndex++);
EXPECT_EQ(100u, width);
EXPECT_EQ(200u, height);
@@ -108,7 +116,7 @@ TEST(OpReorderer, simple) {
canvas.drawRect(0, 0, 100, 200, SkPaint());
canvas.drawBitmap(bitmap, 10, 10, nullptr);
});
- OpReorderer reorderer(100, 200, *dl);
+ OpReorderer reorderer(100, 200, *dl, sLightCenter);
SimpleTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -122,7 +130,7 @@ TEST(OpReorderer, simpleRejection) {
canvas.drawRect(0, 0, 400, 400, SkPaint());
canvas.restore();
});
- OpReorderer reorderer(200, 200, *dl);
+ OpReorderer reorderer(200, 200, *dl, sLightCenter);
FailRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -154,7 +162,7 @@ TEST(OpReorderer, simpleBatching) {
canvas.restore();
});
- OpReorderer reorderer(200, 200, *dl);
+ OpReorderer reorderer(200, 200, *dl, sLightCenter);
SimpleBatchingTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -198,13 +206,8 @@ TEST(OpReorderer, renderNode) {
canvas.restore();
});
- TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
-
- std::vector< sp<RenderNode> > nodes;
- nodes.push_back(parent.get());
-
- OpReorderer reorderer(sEmptyLayerUpdateQueue,
- SkRect::MakeWH(200, 200), 200, 200, nodes);
+ OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ createSyncedNodeList(parent), sLightCenter);
RenderNodeTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -225,13 +228,10 @@ TEST(OpReorderer, clipped) {
SkBitmap bitmap = TestUtils::createSkBitmap(200, 200);
canvas.drawBitmap(bitmap, 0, 0, nullptr);
});
- TestUtils::syncHierarchyPropertiesAndDisplayList(node);
- std::vector< sp<RenderNode> > nodes;
- nodes.push_back(node.get());
OpReorderer reorderer(sEmptyLayerUpdateQueue,
SkRect::MakeLTRB(10, 20, 30, 40), // clip to small area, should see in receiver
- 200, 200, nodes);
+ 200, 200, createSyncedNodeList(node), sLightCenter);
ClippedTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -273,7 +273,7 @@ TEST(OpReorderer, saveLayerSimple) {
canvas.restore();
});
- OpReorderer reorderer(200, 200, *dl);
+ OpReorderer reorderer(200, 200, *dl, sLightCenter);
SaveLayerSimpleTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -305,7 +305,7 @@ TEST(OpReorderer, saveLayerNested) {
int index = mIndex++;
EXPECT_TRUE(index == 2 || index == 6);
}
- void startFrame(uint32_t width, uint32_t height) override {
+ void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
EXPECT_EQ(7, mIndex++);
}
void endFrame() override {
@@ -344,7 +344,7 @@ TEST(OpReorderer, saveLayerNested) {
canvas.restore();
});
- OpReorderer reorderer(800, 800, *dl);
+ OpReorderer reorderer(800, 800, *dl, sLightCenter);
SaveLayerNestedTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -363,19 +363,21 @@ TEST(OpReorderer, saveLayerContentRejection) {
canvas.restore();
canvas.restore();
});
- OpReorderer reorderer(200, 200, *dl);
+ OpReorderer reorderer(200, 200, *dl, sLightCenter);
FailRenderer renderer;
// should see no ops, even within the layer, since the layer should be rejected
reorderer.replayBakedOps<TestDispatcher>(renderer);
}
-TEST(OpReorderer, hwLayerSimple) {
+RENDERTHREAD_TEST(OpReorderer, hwLayerSimple) {
class HwLayerSimpleTestRenderer : public TestRendererBase {
public:
- void startRepaintLayer(OffscreenBuffer* offscreenBuffer) override {
+ void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override {
EXPECT_EQ(0, mIndex++);
- EXPECT_EQ(offscreenBuffer, (OffscreenBuffer*) 0x0124);
+ EXPECT_EQ(100u, offscreenBuffer->viewportWidth);
+ EXPECT_EQ(100u, offscreenBuffer->viewportHeight);
+ EXPECT_EQ(Rect(25, 25, 75, 75), repaintRect);
}
void onRectOp(const RectOp& op, const BakedOpState& state) override {
EXPECT_EQ(1, mIndex++);
@@ -389,7 +391,7 @@ TEST(OpReorderer, hwLayerSimple) {
void endLayer() override {
EXPECT_EQ(2, mIndex++);
}
- void startFrame(uint32_t width, uint32_t height) override {
+ void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
EXPECT_EQ(3, mIndex++);
}
void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
@@ -405,29 +407,29 @@ TEST(OpReorderer, hwLayerSimple) {
paint.setColor(SK_ColorWHITE);
canvas.drawRect(0, 0, 100, 100, paint);
}, TestUtils::getHwLayerSetupCallback());
- OffscreenBuffer** bufferHandle = node->getLayerHandle();
- *bufferHandle = (OffscreenBuffer*) 0x0124;
+ OffscreenBuffer** layerHandle = node->getLayerHandle();
- TestUtils::syncHierarchyPropertiesAndDisplayList(node);
+ // create RenderNode's layer here in same way prepareTree would
+ OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 100, 100);
+ *layerHandle = &layer;
- std::vector< sp<RenderNode> > nodes;
- nodes.push_back(node.get());
+ auto syncedNodeList = createSyncedNodeList(node);
// only enqueue partial damage
- LayerUpdateQueue layerUpdateQueue;
+ LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
layerUpdateQueue.enqueueLayerWithDamage(node.get(), Rect(25, 25, 75, 75));
- OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes);
-
+ OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ syncedNodeList, sLightCenter);
HwLayerSimpleTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(6, renderer.getIndex());
// clean up layer pointer, so we can safely destruct RenderNode
- *bufferHandle = nullptr;
+ *layerHandle = nullptr;
}
-TEST(OpReorderer, hwLayerComplex) {
+RENDERTHREAD_TEST(OpReorderer, hwLayerComplex) {
/* parentLayer { greyRect, saveLayer { childLayer { whiteRect } } } will play back as:
* - startRepaintLayer(child), rect(grey), endLayer
* - startTemporaryLayer, drawLayer(child), endLayer
@@ -440,14 +442,16 @@ TEST(OpReorderer, hwLayerComplex) {
EXPECT_EQ(3, mIndex++); // savelayer first
return (OffscreenBuffer*)0xabcd;
}
- void startRepaintLayer(OffscreenBuffer* offscreenBuffer) override {
+ void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override {
int index = mIndex++;
if (index == 0) {
// starting inner layer
- EXPECT_EQ((OffscreenBuffer*)0x4567, offscreenBuffer);
+ EXPECT_EQ(100u, offscreenBuffer->viewportWidth);
+ EXPECT_EQ(100u, offscreenBuffer->viewportHeight);
} else if (index == 6) {
// starting outer layer
- EXPECT_EQ((OffscreenBuffer*)0x0123, offscreenBuffer);
+ EXPECT_EQ(200u, offscreenBuffer->viewportWidth);
+ EXPECT_EQ(200u, offscreenBuffer->viewportHeight);
} else { ADD_FAILURE(); }
}
void onRectOp(const RectOp& op, const BakedOpState& state) override {
@@ -464,17 +468,20 @@ TEST(OpReorderer, hwLayerComplex) {
int index = mIndex++;
EXPECT_TRUE(index == 2 || index == 5 || index == 9);
}
- void startFrame(uint32_t width, uint32_t height) override {
+ void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
EXPECT_EQ(10, mIndex++);
}
void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+ OffscreenBuffer* layer = *op.layerHandle;
int index = mIndex++;
if (index == 4) {
- EXPECT_EQ((OffscreenBuffer*)0x4567, *op.layerHandle);
+ EXPECT_EQ(100u, layer->viewportWidth);
+ EXPECT_EQ(100u, layer->viewportHeight);
} else if (index == 8) {
EXPECT_EQ((OffscreenBuffer*)0xabcd, *op.layerHandle);
} else if (index == 11) {
- EXPECT_EQ((OffscreenBuffer*)0x0123, *op.layerHandle);
+ EXPECT_EQ(200u, layer->viewportWidth);
+ EXPECT_EQ(200u, layer->viewportHeight);
} else { ADD_FAILURE(); }
}
void endFrame() override {
@@ -488,7 +495,8 @@ TEST(OpReorderer, hwLayerComplex) {
paint.setColor(SK_ColorWHITE);
canvas.drawRect(0, 0, 100, 100, paint);
}, TestUtils::getHwLayerSetupCallback());
- *(child->getLayerHandle()) = (OffscreenBuffer*) 0x4567;
+ OffscreenBuffer childLayer(renderThread.renderState(), Caches::getInstance(), 100, 100);
+ *(child->getLayerHandle()) = &childLayer;
RenderNode* childPtr = child.get();
auto parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200,
@@ -501,18 +509,17 @@ TEST(OpReorderer, hwLayerComplex) {
canvas.drawRenderNode(childPtr);
canvas.restore();
}, TestUtils::getHwLayerSetupCallback());
- *(parent->getLayerHandle()) = (OffscreenBuffer*) 0x0123;
-
- TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
+ OffscreenBuffer parentLayer(renderThread.renderState(), Caches::getInstance(), 200, 200);
+ *(parent->getLayerHandle()) = &parentLayer;
- std::vector< sp<RenderNode> > nodes;
- nodes.push_back(parent.get());
+ auto syncedList = createSyncedNodeList(parent);
- LayerUpdateQueue layerUpdateQueue;
+ LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
layerUpdateQueue.enqueueLayerWithDamage(child.get(), Rect(100, 100));
layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(200, 200));
- OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes);
+ OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ syncedList, sLightCenter);
HwLayerComplexTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -561,58 +568,184 @@ TEST(OpReorderer, zReorder) {
drawOrderedRect(&canvas, 8);
drawOrderedNode(&canvas, 9, -10.0f); // in reorder=false at this point, so played inorder
});
- TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
-
- std::vector< sp<RenderNode> > nodes;
- nodes.push_back(parent.get());
- OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100, nodes);
+ OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100,
+ createSyncedNodeList(parent), sLightCenter);
ZReorderTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(10, renderer.getIndex());
};
+// creates a 100x100 shadow casting node with provided translationZ
+static sp<RenderNode> createWhiteRectShadowCaster(float translationZ) {
+ return TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+ [](RecordingCanvas& canvas) {
+ SkPaint paint;
+ paint.setColor(SK_ColorWHITE);
+ canvas.drawRect(0, 0, 100, 100, paint);
+ }, [translationZ] (RenderProperties& properties) {
+ properties.setTranslationZ(translationZ);
+ properties.mutableOutline().setRoundRect(0, 0, 100, 100, 0.0f, 1.0f);
+ return RenderNode::GENERIC | RenderNode::TRANSLATION_Z;
+ });
+}
+
TEST(OpReorderer, shadow) {
class ShadowTestRenderer : public TestRendererBase {
public:
void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
EXPECT_EQ(0, mIndex++);
+ EXPECT_FLOAT_EQ(1.0f, op.casterAlpha);
+ EXPECT_TRUE(op.casterPath->isRect(nullptr));
+ EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), op.shadowMatrixXY);
+
+ Matrix4 expectedZ;
+ expectedZ.loadTranslate(0, 0, 5);
+ EXPECT_MATRIX_APPROX_EQ(expectedZ, op.shadowMatrixZ);
}
void onRectOp(const RectOp& op, const BakedOpState& state) override {
EXPECT_EQ(1, mIndex++);
}
};
- sp<RenderNode> caster = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
- [](RecordingCanvas& canvas) {
- SkPaint paint;
- paint.setColor(SK_ColorWHITE);
- canvas.drawRect(0, 0, 100, 100, paint);
- }, [] (RenderProperties& properties) {
- properties.setTranslationZ(5.0f);
- properties.mutableOutline().setRoundRect(0, 0, 100, 100, 5, 1.0f);
- return RenderNode::GENERIC | RenderNode::TRANSLATION_Z;
+ sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200,
+ [] (RecordingCanvas& canvas) {
+ canvas.insertReorderBarrier(true);
+ canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
});
+
+ OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ createSyncedNodeList(parent), sLightCenter);
+
+ ShadowTestRenderer renderer;
+ reorderer.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(2, renderer.getIndex());
+}
+
+TEST(OpReorderer, shadowSaveLayer) {
+ class ShadowSaveLayerTestRenderer : public TestRendererBase {
+ public:
+ OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override {
+ EXPECT_EQ(0, mIndex++);
+ return nullptr;
+ }
+ void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(1, mIndex++);
+ EXPECT_FLOAT_EQ(50, op.lightCenter.x);
+ EXPECT_FLOAT_EQ(40, op.lightCenter.y);
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(2, mIndex++);
+ }
+ void endLayer() override {
+ EXPECT_EQ(3, mIndex++);
+ }
+ void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(4, mIndex++);
+ }
+ };
+
sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200,
- [&caster] (RecordingCanvas& canvas) {
+ [] (RecordingCanvas& canvas) {
+ // save/restore outside of reorderBarrier, so they don't get moved out of place
+ canvas.translate(20, 10);
+ int count = canvas.saveLayerAlpha(30, 50, 130, 150, 128, SkCanvas::kClipToLayer_SaveFlag);
canvas.insertReorderBarrier(true);
- canvas.drawRenderNode(caster.get());
+ canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
+ canvas.insertReorderBarrier(false);
+ canvas.restoreToCount(count);
});
- TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
+ OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ createSyncedNodeList(parent), (Vector3) { 100, 100, 100 });
- std::vector< sp<RenderNode> > nodes;
- nodes.push_back(parent.get());
+ ShadowSaveLayerTestRenderer renderer;
+ reorderer.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(5, renderer.getIndex());
+}
- OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes);
+RENDERTHREAD_TEST(OpReorderer, shadowHwLayer) {
+ class ShadowHwLayerTestRenderer : public TestRendererBase {
+ public:
+ void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override {
+ EXPECT_EQ(0, mIndex++);
+ }
+ void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(1, mIndex++);
+ EXPECT_FLOAT_EQ(50, op.lightCenter.x);
+ EXPECT_FLOAT_EQ(40, op.lightCenter.y);
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(2, mIndex++);
+ }
+ void endLayer() override {
+ EXPECT_EQ(3, mIndex++);
+ }
+ void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(4, mIndex++);
+ }
+ };
- ShadowTestRenderer renderer;
+ sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(50, 60, 150, 160,
+ [] (RecordingCanvas& canvas) {
+ canvas.insertReorderBarrier(true);
+ canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+ canvas.translate(20, 10);
+ canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
+ canvas.restore();
+ }, TestUtils::getHwLayerSetupCallback());
+ OffscreenBuffer** layerHandle = parent->getLayerHandle();
+
+ // create RenderNode's layer here in same way prepareTree would, setting windowTransform
+ OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 100, 100);
+ Matrix4 windowTransform;
+ windowTransform.loadTranslate(50, 60, 0); // total transform of layer's origin
+ layer.setWindowTransform(windowTransform);
+ *layerHandle = &layer;
+
+ auto syncedList = createSyncedNodeList(parent);
+ LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
+ layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(100, 100));
+ OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ syncedList, (Vector3) { 100, 100, 100 });
+
+ ShadowHwLayerTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(2, renderer.getIndex());
+ EXPECT_EQ(5, renderer.getIndex());
+
+ // clean up layer pointer, so we can safely destruct RenderNode
+ *layerHandle = nullptr;
}
-static void testProperty(
- TestUtils::PropSetupCallback propSetupCallback,
+TEST(OpReorderer, shadowLayering) {
+ class ShadowLayeringTestRenderer : public TestRendererBase {
+ public:
+ void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
+ int index = mIndex++;
+ EXPECT_TRUE(index == 0 || index == 1);
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ int index = mIndex++;
+ EXPECT_TRUE(index == 2 || index == 3);
+ }
+ };
+ sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200,
+ [] (RecordingCanvas& canvas) {
+ canvas.insertReorderBarrier(true);
+ canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
+ canvas.drawRenderNode(createWhiteRectShadowCaster(5.0001f).get());
+ });
+
+ OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ createSyncedNodeList(parent), sLightCenter);
+
+ ShadowLayeringTestRenderer renderer;
+ reorderer.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(4, renderer.getIndex());
+}
+
+
+static void testProperty(TestUtils::PropSetupCallback propSetupCallback,
std::function<void(const RectOp&, const BakedOpState&)> opValidateCallback) {
class PropertyTestRenderer : public TestRendererBase {
public:
@@ -630,13 +763,9 @@ static void testProperty(
paint.setColor(SK_ColorWHITE);
canvas.drawRect(0, 0, 100, 100, paint);
}, propSetupCallback);
- TestUtils::syncHierarchyPropertiesAndDisplayList(node);
-
- std::vector< sp<RenderNode> > nodes;
- nodes.push_back(node.get());
- OpReorderer reorderer(sEmptyLayerUpdateQueue,
- SkRect::MakeWH(100, 100), 200, 200, nodes);
+ OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 200, 200,
+ createSyncedNodeList(node), sLightCenter);
PropertyTestRenderer renderer(opValidateCallback);
reorderer.replayBakedOps<TestDispatcher>(renderer);
diff --git a/libs/hwui/unit_tests/TestUtils.h b/libs/hwui/unit_tests/TestUtils.h
index efa28ae90262..38bafd52cac8 100644
--- a/libs/hwui/unit_tests/TestUtils.h
+++ b/libs/hwui/unit_tests/TestUtils.h
@@ -45,6 +45,20 @@ namespace uirenderer {
&& MathUtils::areEqual(a.right, b.right) \
&& MathUtils::areEqual(a.bottom, b.bottom));
+/**
+ * Like gtest's TEST, but runs on the RenderThread, and 'renderThread' is passed, in top level scope
+ * (for e.g. accessing its RenderState)
+ */
+#define RENDERTHREAD_TEST(test_case_name, test_name) \
+ class test_case_name##_##test_name##_RenderThreadTest { \
+ public: \
+ static void doTheThing(renderthread::RenderThread& renderThread); \
+ }; \
+ TEST(test_case_name, test_name) { \
+ TestUtils::runOnRenderThread(test_case_name##_##test_name##_RenderThreadTest::doTheThing); \
+ }; \
+ void test_case_name##_##test_name##_RenderThreadTest::doTheThing(renderthread::RenderThread& renderThread)
+
class TestUtils {
public:
class SignalingDtor {
diff --git a/media/java/android/media/tv/TvContentRating.java b/media/java/android/media/tv/TvContentRating.java
index 043b80e7d117..6197c707c752 100644
--- a/media/java/android/media/tv/TvContentRating.java
+++ b/media/java/android/media/tv/TvContentRating.java
@@ -931,9 +931,7 @@ public final class TvContentRating {
*
* @param rating The {@link TvContentRating} to check.
* @return {@code true} if this object contains {@code rating}, {@code false} otherwise.
- * @hide
*/
- @SystemApi
public final boolean contains(@NonNull TvContentRating rating) {
Preconditions.checkNotNull(rating);
if (!rating.getMainRating().equals(mRating)) {
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java
index 6f1a89baf525..369ab7dc59ea 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java
@@ -32,6 +32,8 @@ import android.provider.DocumentsContract.Document;
import android.test.MoreAsserts;
import android.test.ServiceTestCase;
import android.test.mock.MockContentResolver;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
import com.android.documentsui.model.DocumentInfo;
@@ -52,6 +54,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+@MediumTest
public class CopyTest extends ServiceTestCase<CopyService> {
public CopyTest() {
@@ -89,9 +92,6 @@ public class CopyTest extends ServiceTestCase<CopyService> {
super.tearDown();
}
- /**
- * Test copying a single file.
- */
public void testCopyFile() throws Exception {
String srcPath = "/test0.txt";
Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain",
@@ -131,9 +131,6 @@ public class CopyTest extends ServiceTestCase<CopyService> {
MoreAsserts.assertEquals("Moved file contents differ", testContent.getBytes(), dstContent);
}
- /**
- * Test copying multiple files.
- */
public void testCopyMultipleFiles() throws Exception {
String testContent[] = {
"The five boxing wizards jump quickly",
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
index 906051640c6a..ba91c83b95e8 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
@@ -31,11 +31,13 @@ import android.support.test.uiautomator.Configurator;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.Until;
import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
import android.util.Log;
import android.view.MotionEvent;
import com.android.documentsui.model.RootInfo;
+@LargeTest
public class FilesActivityUiTest extends InstrumentationTestCase {
private static final int TIMEOUT = 5000;
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DirectoryFragmentModelTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DirectoryFragmentModelTest.java
index 746e2117dc02..b250e5d344c4 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DirectoryFragmentModelTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DirectoryFragmentModelTest.java
@@ -25,6 +25,7 @@ import android.provider.DocumentsContract.Document;
import android.support.v7.widget.RecyclerView;
import android.test.AndroidTestCase;
import android.test.mock.MockContentResolver;
+import android.test.suitebuilder.annotation.SmallTest;
import android.view.ViewGroup;
import com.android.documentsui.DirectoryResult;
@@ -34,6 +35,7 @@ import com.android.documentsui.model.DocumentInfo;
import java.util.List;
+@SmallTest
public class DirectoryFragmentModelTest extends AndroidTestCase {
private static final int ITEM_COUNT = 5;
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
index d1ce56457a87..b3d45aee48b1 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
@@ -18,6 +18,7 @@ package com.android.documentsui.dirlist;
import android.support.v7.widget.RecyclerView;
import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
import android.util.SparseBooleanArray;
import android.view.View;
import android.view.ViewGroup;
@@ -32,6 +33,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
+@SmallTest
public class MultiSelectManagerTest extends AndroidTestCase {
private static final List<String> items;
@@ -163,7 +165,6 @@ public class MultiSelectManagerTest extends AndroidTestCase {
assertRangeSelection(14, 17);
}
-
public void testSingleTapUp_ShiftReversesSelectionDirection() {
longPress(7);
shiftTap(17);
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java
index c4b6ce5c7f87..c856b2290ea1 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java
@@ -20,11 +20,13 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.support.v7.widget.RecyclerView.OnScrollListener;
import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
import android.util.SparseBooleanArray;
import android.view.View;
import com.android.documentsui.dirlist.MultiSelectManager.GridModel;
+@SmallTest
public class MultiSelectManager_GridModelTest extends AndroidTestCase {
private static final int VIEW_PADDING_PX = 5;
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_SelectionTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_SelectionTest.java
index 64da750b1306..72fc10814e72 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_SelectionTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_SelectionTest.java
@@ -17,10 +17,11 @@
package com.android.documentsui.dirlist;
import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
import com.android.documentsui.dirlist.MultiSelectManager.Selection;
-
+@SmallTest
public class MultiSelectManager_SelectionTest extends AndroidTestCase{
private Selection selection;
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
index 0d4265a6417c..1c96906a1d79 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
@@ -18,7 +18,7 @@ package com.android.mtp;
import android.content.ContentResolver;
import android.database.Cursor;
-import android.database.MatrixCursor;
+import android.database.sqlite.SQLiteException;
import android.mtp.MtpObjectInfo;
import android.net.Uri;
import android.os.Bundle;
@@ -26,6 +26,7 @@ import android.os.Process;
import android.provider.DocumentsContract;
import android.util.Log;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Arrays;
import java.util.Date;
@@ -44,12 +45,14 @@ class DocumentLoader {
private final MtpManager mMtpManager;
private final ContentResolver mResolver;
+ private final MtpDatabase mDatabase;
private final TaskList mTaskList = new TaskList();
private boolean mHasBackgroundThread = false;
- DocumentLoader(MtpManager mtpManager, ContentResolver resolver) {
+ DocumentLoader(MtpManager mtpManager, ContentResolver resolver, MtpDatabase database) {
mMtpManager = mtpManager;
mResolver = resolver;
+ mDatabase = database;
}
private static MtpObjectInfo[] loadDocuments(MtpManager manager, int deviceId, int[] handles)
@@ -65,13 +68,17 @@ class DocumentLoader {
throws IOException {
LoaderTask task = mTaskList.findTask(parent);
if (task == null) {
+ if (parent.mDocumentId == null) {
+ throw new FileNotFoundException("Parent not found.");
+ }
+
int parentHandle = parent.mObjectHandle;
// Need to pass the special value MtpManager.OBJECT_HANDLE_ROOT_CHILDREN to
// getObjectHandles if we would like to obtain children under the root.
if (parentHandle == CursorHelper.DUMMY_HANDLE_FOR_ROOT) {
parentHandle = MtpManager.OBJECT_HANDLE_ROOT_CHILDREN;
}
- task = new LoaderTask(parent, mMtpManager.getObjectHandles(
+ task = new LoaderTask(mDatabase, parent, mMtpManager.getObjectHandles(
parent.mDeviceId, parent.mStorageId, parentHandle));
task.fillDocuments(loadDocuments(
mMtpManager,
@@ -83,11 +90,10 @@ class DocumentLoader {
}
mTaskList.addFirst(task);
- if (!task.completed() && !mHasBackgroundThread) {
+ if (task.getState() == LoaderTask.STATE_LOADING && !mHasBackgroundThread) {
mHasBackgroundThread = true;
new BackgroundLoaderThread().start();
}
-
return task.createCursor(mResolver, columnNames);
}
@@ -120,26 +126,20 @@ class DocumentLoader {
deviceId = task.mIdentifier.mDeviceId;
handles = task.getUnloadedObjectHandles(NUM_LOADING_ENTRIES);
}
- MtpObjectInfo[] objectInfos;
+
try {
- objectInfos = loadDocuments(mMtpManager, deviceId, handles);
- } catch (IOException exception) {
- objectInfos = null;
- Log.d(MtpDocumentsProvider.TAG, exception.getMessage());
- }
- synchronized (DocumentLoader.this) {
- if (objectInfos != null) {
- task.fillDocuments(objectInfos);
- final boolean shouldNotify =
- task.mLastNotified.getTime() <
- new Date().getTime() - NOTIFY_PERIOD_MS ||
- task.completed();
- if (shouldNotify) {
- task.notify(mResolver);
- }
- } else {
- mTaskList.remove(task);
+ final MtpObjectInfo[] objectInfos =
+ loadDocuments(mMtpManager, deviceId, handles);
+ task.fillDocuments(objectInfos);
+ final boolean shouldNotify =
+ task.mLastNotified.getTime() <
+ new Date().getTime() - NOTIFY_PERIOD_MS ||
+ task.getState() != LoaderTask.STATE_LOADING;
+ if (shouldNotify) {
+ task.notify(mResolver);
}
+ } catch (IOException exception) {
+ task.setError(exception);
}
}
}
@@ -156,7 +156,7 @@ class DocumentLoader {
LoaderTask findRunningTask() {
for (int i = 0; i < size(); i++) {
- if (!get(i).completed())
+ if (get(i).getState() == LoaderTask.STATE_LOADING)
return get(i);
}
return null;
@@ -165,7 +165,7 @@ class DocumentLoader {
void clearCompletedTasks() {
int i = 0;
while (i < size()) {
- if (get(i).completed()) {
+ if (get(i).getState() == LoaderTask.STATE_COMPLETED) {
remove(i);
} else {
i++;
@@ -186,36 +186,51 @@ class DocumentLoader {
}
private static class LoaderTask {
+ static final int STATE_LOADING = 0;
+ static final int STATE_COMPLETED = 1;
+ static final int STATE_ERROR = 2;
+
+ final MtpDatabase mDatabase;
final Identifier mIdentifier;
final int[] mObjectHandles;
- final MtpObjectInfo[] mObjectInfos;
Date mLastNotified;
int mNumLoaded;
+ Exception mError;
- LoaderTask(Identifier identifier, int[] objectHandles) {
+ LoaderTask(MtpDatabase database, Identifier identifier, int[] objectHandles) {
+ mDatabase = database;
mIdentifier = identifier;
mObjectHandles = objectHandles;
- mObjectInfos = new MtpObjectInfo[mObjectHandles.length];
mNumLoaded = 0;
mLastNotified = new Date();
}
- Cursor createCursor(ContentResolver resolver, String[] columnNames) {
- final MatrixCursor cursor = new MatrixCursor(columnNames);
- final Identifier rootIdentifier = new Identifier(
- mIdentifier.mDeviceId, mIdentifier.mStorageId);
- for (int i = 0; i < mNumLoaded; i++) {
- CursorHelper.addToCursor(mObjectInfos[i], rootIdentifier, cursor.newRow());
- }
+ Cursor createCursor(ContentResolver resolver, String[] columnNames) throws IOException {
final Bundle extras = new Bundle();
- extras.putBoolean(DocumentsContract.EXTRA_LOADING, !completed());
+ switch (getState()) {
+ case STATE_LOADING:
+ extras.putBoolean(DocumentsContract.EXTRA_LOADING, true);
+ break;
+ case STATE_ERROR:
+ throw new IOException(mError);
+ }
+
+ final Cursor cursor = mDatabase.queryChildDocuments(
+ columnNames, mIdentifier.mDocumentId, /* use old ID format */ true);
cursor.setNotificationUri(resolver, createUri());
cursor.respond(extras);
+
return cursor;
}
- boolean completed() {
- return mNumLoaded == mObjectInfos.length;
+ int getState() {
+ if (mError != null) {
+ return STATE_ERROR;
+ } else if (mNumLoaded == mObjectHandles.length) {
+ return STATE_COMPLETED;
+ } else {
+ return STATE_LOADING;
+ }
}
int[] getUnloadedObjectHandles(int count) {
@@ -230,9 +245,32 @@ class DocumentLoader {
mLastNotified = new Date();
}
- void fillDocuments(MtpObjectInfo[] objectInfos) {
- for (int i = 0; i < objectInfos.length; i++) {
- mObjectInfos[mNumLoaded++] = objectInfos[i];
+ void fillDocuments(MtpObjectInfo[] objectInfoList) {
+ if (objectInfoList.length == 0 || getState() != STATE_LOADING) {
+ return;
+ }
+ if (mNumLoaded == 0) {
+ mDatabase.startAddingChildDocuments(mIdentifier.mDocumentId);
+ }
+ try {
+ mDatabase.putChildDocuments(
+ mIdentifier.mDeviceId, mIdentifier.mDocumentId, objectInfoList);
+ mNumLoaded += objectInfoList.length;
+ } catch (SQLiteException exp) {
+ mError = exp;
+ mNumLoaded = 0;
+ }
+ if (getState() != STATE_LOADING) {
+ mDatabase.stopAddingChildDocuments(mIdentifier.mDocumentId);
+ }
+ }
+
+ void setError(Exception message) {
+ final int lastState = getState();
+ mError = message;
+ mNumLoaded = 0;
+ if (lastState == STATE_LOADING) {
+ mDatabase.stopAddingChildDocuments(mIdentifier.mDocumentId);
}
}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java b/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java
index ae29f526e3ec..4238721e1238 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java
@@ -23,6 +23,7 @@ class Identifier {
final int mDeviceId;
final int mStorageId;
final int mObjectHandle;
+ final String mDocumentId;
static Identifier createFromRootId(String rootId) {
final String[] components = rootId.split("_");
@@ -45,9 +46,14 @@ class Identifier {
}
Identifier(int deviceId, int storageId, int objectHandle) {
+ this(deviceId, storageId, objectHandle, null);
+ }
+
+ Identifier(int deviceId, int storageId, int objectHandle, String documentId) {
mDeviceId = deviceId;
mStorageId = storageId;
mObjectHandle = objectHandle;
+ mDocumentId = documentId;
}
// TODO: Make the ID persistent.
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
index 3151ccb3c9cd..3dc69ccbf1ee 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
@@ -23,6 +23,7 @@ import android.content.Context;
import android.content.res.Resources;
import android.database.Cursor;
import android.mtp.MtpObjectInfo;
+import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root;
@@ -79,8 +80,8 @@ class MtpDatabase {
private final Map<String, Integer> mMappingMode = new HashMap<>();
@VisibleForTesting
- MtpDatabase(Context context) {
- mDatabase = new MtpDatabaseInternal(context);
+ MtpDatabase(Context context, int flags) {
+ mDatabase = new MtpDatabaseInternal(context, flags);
}
/**
@@ -111,7 +112,29 @@ class MtpDatabase {
*/
@VisibleForTesting
Cursor queryChildDocuments(String[] columnNames, String parentDocumentId) {
- return mDatabase.queryChildDocuments(columnNames, parentDocumentId);
+ return queryChildDocuments(columnNames, parentDocumentId, false);
+ }
+
+ @VisibleForTesting
+ Cursor queryChildDocuments(String[] columnNames, String parentDocumentId, boolean useOldId) {
+ final String[] newColumnNames = new String[columnNames.length];
+
+ // TODO: Temporary replace document ID with old format.
+ for (int i = 0; i < columnNames.length; i++) {
+ if (useOldId && DocumentsContract.Document.COLUMN_DOCUMENT_ID.equals(columnNames[i])) {
+ newColumnNames[i] = COLUMN_DEVICE_ID + " || '_' || " + COLUMN_STORAGE_ID +
+ " || '_' || IFNULL(" + COLUMN_OBJECT_HANDLE + ",0) AS " +
+ DocumentsContract.Document.COLUMN_DOCUMENT_ID;
+ } else {
+ newColumnNames[i] = columnNames[i];
+ }
+ }
+
+ return mDatabase.queryChildDocuments(newColumnNames, parentDocumentId);
+ }
+
+ Identifier createIdentifier(String parentDocumentId) {
+ return mDatabase.createIdentifier(parentDocumentId);
}
/**
@@ -193,9 +216,13 @@ class MtpDatabase {
int i = 0;
for (final MtpRoot root : roots) {
// Use the same value for the root ID and the corresponding document ID.
- values.put(
- Root.COLUMN_ROOT_ID,
- valuesList[i++].getAsString(Document.COLUMN_DOCUMENT_ID));
+ final String documentId = valuesList[i++].getAsString(Document.COLUMN_DOCUMENT_ID);
+ // If it fails to insert/update documents, the document ID will be set with -1.
+ // In this case we don't insert/update root extra information neither.
+ if (documentId == null) {
+ continue;
+ }
+ values.put(Root.COLUMN_ROOT_ID, documentId);
values.put(
Root.COLUMN_FLAGS,
Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE);
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
index 977b12ed7366..97c1d293172c 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
@@ -26,6 +26,9 @@ class MtpDatabaseConstants {
static final int DATABASE_VERSION = 1;
static final String DATABASE_NAME = null;
+ static final int FLAG_DATABASE_IN_MEMORY = 1;
+ static final int FLAG_DATABASE_IN_FILE = 0;
+
/**
* Table representing documents including root documents.
*/
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java
index 7328f054b16d..9c5d6b6ad3ec 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java
@@ -23,6 +23,7 @@ import android.content.Context;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.provider.DocumentsContract.Document;
@@ -35,8 +36,11 @@ import java.util.Objects;
*/
class MtpDatabaseInternal {
private static class OpenHelper extends SQLiteOpenHelper {
- public OpenHelper(Context context) {
- super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ public OpenHelper(Context context, int flags) {
+ super(context,
+ flags == FLAG_DATABASE_IN_MEMORY ? null : DATABASE_NAME,
+ null,
+ DATABASE_VERSION);
}
@Override
@@ -54,8 +58,8 @@ class MtpDatabaseInternal {
private final SQLiteDatabase mDatabase;
- MtpDatabaseInternal(Context context) {
- final OpenHelper helper = new OpenHelper(context);
+ MtpDatabaseInternal(Context context, int flags) {
+ final OpenHelper helper = new OpenHelper(context, flags);
mDatabase = helper.getWritableDatabase();
}
@@ -122,6 +126,64 @@ class MtpDatabaseInternal {
}
/**
+ * Gets identifier from document ID.
+ * @param documentId Document ID.
+ * @return Identifier.
+ */
+ Identifier createIdentifier(String documentId) {
+ // Currently documentId is old format.
+ final Identifier oldIdentifier = Identifier.createFromDocumentId(documentId);
+ final String selection;
+ final String[] args;
+ if (oldIdentifier.mObjectHandle == CursorHelper.DUMMY_HANDLE_FOR_ROOT) {
+ selection = COLUMN_DEVICE_ID + "= ? AND " +
+ COLUMN_ROW_STATE + " IN (?, ?) AND " +
+ COLUMN_STORAGE_ID + "= ? AND " +
+ COLUMN_PARENT_DOCUMENT_ID + " IS NULL";
+ args = strings(
+ oldIdentifier.mDeviceId,
+ ROW_STATE_VALID,
+ ROW_STATE_INVALIDATED,
+ oldIdentifier.mStorageId);
+ } else {
+ selection = COLUMN_DEVICE_ID + "= ? AND " +
+ COLUMN_ROW_STATE + " IN (?, ?) AND " +
+ COLUMN_STORAGE_ID + "= ? AND " +
+ COLUMN_OBJECT_HANDLE + " = ?";
+ args = strings(
+ oldIdentifier.mDeviceId,
+ ROW_STATE_VALID,
+ ROW_STATE_INVALIDATED,
+ oldIdentifier.mStorageId,
+ oldIdentifier.mObjectHandle);
+ }
+
+ final Cursor cursor = mDatabase.query(
+ TABLE_DOCUMENTS,
+ strings(Document.COLUMN_DOCUMENT_ID),
+ selection,
+ args,
+ null,
+ null,
+ null,
+ "1");
+ try {
+ if (cursor.getCount() == 0) {
+ return oldIdentifier;
+ } else {
+ cursor.moveToNext();
+ return new Identifier(
+ oldIdentifier.mDeviceId,
+ oldIdentifier.mStorageId,
+ oldIdentifier.mObjectHandle,
+ cursor.getString(0));
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+
+ /**
* Starts adding new documents.
* The methods decides mapping mode depends on if all documents under the given parent have MTP
* identifier or not. If all the documents have MTP identifier, it uses the identifier to find
@@ -164,7 +226,7 @@ class MtpDatabaseInternal {
* {@link #stopAddingDocuments(String, String, String)} turns the pending rows into 'valid'
* rows. If the methods adds rows to database, it updates valueList with correct document ID.
*
- * @param valuesList Values that are stored in the database.
+ * @param valuesList Values for documents to be stored in the database.
* @param selection SQL where closure to select rows that shares the same parent.
* @param arg Argument for selection SQL.
* @param heuristic Whether the mapping mode is heuristic.
@@ -191,23 +253,32 @@ class MtpDatabaseInternal {
null,
null,
"1");
- final long rowId;
- if (candidateCursor.getCount() == 0) {
- rowId = mDatabase.insert(TABLE_DOCUMENTS, null, values);
- added = true;
- } else if (!heuristic) {
- candidateCursor.moveToNext();
- final String documentId = candidateCursor.getString(0);
- rowId = mDatabase.update(
- TABLE_DOCUMENTS, values, SELECTION_DOCUMENT_ID, strings(documentId));
- } else {
- values.put(COLUMN_ROW_STATE, ROW_STATE_PENDING);
- rowId = mDatabase.insert(TABLE_DOCUMENTS, null, values);
+ try {
+ final long rowId;
+ if (candidateCursor.getCount() == 0) {
+ rowId = mDatabase.insert(TABLE_DOCUMENTS, null, values);
+ if (rowId == -1) {
+ throw new SQLiteException("Failed to put a document into database.");
+ }
+ added = true;
+ } else if (!heuristic) {
+ candidateCursor.moveToNext();
+ final String documentId = candidateCursor.getString(0);
+ rowId = mDatabase.update(
+ TABLE_DOCUMENTS,
+ values,
+ SELECTION_DOCUMENT_ID,
+ strings(documentId));
+ } else {
+ values.put(COLUMN_ROW_STATE, ROW_STATE_PENDING);
+ rowId = mDatabase.insert(TABLE_DOCUMENTS, null, values);
+ }
+ // Document ID is a primary integer key of the table. So the returned row
+ // IDs should be same with the document ID.
+ values.put(Document.COLUMN_DOCUMENT_ID, rowId);
+ } finally {
+ candidateCursor.close();
}
- // Document ID is a primary integer key of the table. So the returned row
- // IDs should be same with the document ID.
- values.put(Document.COLUMN_DOCUMENT_ID, rowId);
- candidateCursor.close();
}
mDatabase.setTransactionSuccessful();
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
index 0931445b1035..f0f816173586 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
@@ -78,7 +78,7 @@ public class MtpDocumentsProvider extends DocumentsProvider {
mMtpManager = new MtpManager(getContext());
mResolver = getContext().getContentResolver();
mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
- mDatabase = new MtpDatabase(getContext());
+ mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE);
mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase);
return true;
}
@@ -155,7 +155,7 @@ public class MtpDocumentsProvider extends DocumentsProvider {
if (projection == null) {
projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION;
}
- final Identifier parentIdentifier = Identifier.createFromDocumentId(parentDocumentId);
+ final Identifier parentIdentifier = mDatabase.createIdentifier(parentDocumentId);
try {
return getDocumentLoader(parentIdentifier).queryChildDocuments(
projection, parentIdentifier);
@@ -255,7 +255,7 @@ public class MtpDocumentsProvider extends DocumentsProvider {
void openDevice(int deviceId) throws IOException {
mMtpManager.openDevice(deviceId);
- mDeviceToolkits.put(deviceId, new DeviceToolkit(mMtpManager, mResolver));
+ mDeviceToolkits.put(deviceId, new DeviceToolkit(mMtpManager, mResolver, mDatabase));
mRootScanner.scanNow();
}
@@ -318,9 +318,9 @@ public class MtpDocumentsProvider extends DocumentsProvider {
public final PipeManager mPipeManager;
public final DocumentLoader mDocumentLoader;
- public DeviceToolkit(MtpManager manager, ContentResolver resolver) {
+ public DeviceToolkit(MtpManager manager, ContentResolver resolver, MtpDatabase database) {
mPipeManager = new PipeManager();
- mDocumentLoader = new DocumentLoader(manager, resolver);
+ mDocumentLoader = new DocumentLoader(manager, resolver, database);
}
}
}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
index 415f89e4acab..d9ed4ab5dc4a 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
@@ -2,6 +2,7 @@ package com.android.mtp;
import android.content.ContentResolver;
import android.content.res.Resources;
+import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.os.Process;
import android.provider.DocumentsContract;
@@ -93,16 +94,17 @@ final class RootScanner {
}
boolean changed = false;
for (int deviceId : deviceIds) {
+ mDatabase.startAddingRootDocuments(deviceId);
try {
- mDatabase.startAddingRootDocuments(deviceId);
changed = mDatabase.putRootDocuments(
deviceId, mResources, mManager.getRoots(deviceId)) || changed;
- changed = mDatabase.stopAddingRootDocuments(deviceId) || changed;
- } catch (IOException exp) {
+ } catch (IOException|SQLiteException exp) {
// The error may happen on the device. We would like to continue getting
// roots for other devices.
Log.e(MtpDocumentsProvider.TAG, exp.getMessage());
continue;
+ } finally {
+ changed = mDatabase.stopAddingRootDocuments(deviceId) || changed;
}
}
if (changed) {
diff --git a/packages/MtpDocumentsProvider/tests/AndroidManifest.xml b/packages/MtpDocumentsProvider/tests/AndroidManifest.xml
index 28ad3f4e53c0..e1307e90ac64 100644
--- a/packages/MtpDocumentsProvider/tests/AndroidManifest.xml
+++ b/packages/MtpDocumentsProvider/tests/AndroidManifest.xml
@@ -18,7 +18,4 @@
<instrumentation android:name="com.android.mtp.TestResultInstrumentation"
android:targetPackage="com.android.mtp"
android:label="Tests for MtpDocumentsProvider with the UI for output." />
- <instrumentation android:name="android.test.InstrumentationTestRunner"
- android:targetPackage="com.android.mtp"
- android:label="Tests for MtpDocumentsProvider." />
</manifest>
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
index a012d7fd49bc..a80eb51b43a0 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
@@ -22,26 +22,37 @@ import android.mtp.MtpObjectInfo;
import android.net.Uri;
import android.provider.DocumentsContract;
import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.MediumTest;
import java.io.IOException;
-import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
-@SmallTest
+@MediumTest
public class DocumentLoaderTest extends AndroidTestCase {
+ private MtpDatabase mDatabase;
private BlockableTestMtpManager mManager;
private TestContentResolver mResolver;
private DocumentLoader mLoader;
- final private Identifier mParentIdentifier = new Identifier(0, 0, 0);
+ final private Identifier mParentIdentifier = new Identifier(0, 0, 0, "1");
@Override
public void setUp() {
+ mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
+ mDatabase.startAddingRootDocuments(0);
+ mDatabase.putRootDocuments(0, new TestResources(), new MtpRoot[] {
+ new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "")
+ });
+ mDatabase.stopAddingRootDocuments(0);
mManager = new BlockableTestMtpManager(getContext());
mResolver = new TestContentResolver();
- mLoader = new DocumentLoader(mManager, mResolver);
+ mLoader = new DocumentLoader(mManager, mResolver, mDatabase);
+ }
+
+ @Override
+ public void tearDown() {
+ mDatabase.close();
}
public void testBasic() throws Exception {
@@ -88,6 +99,7 @@ public class DocumentLoaderTest extends AndroidTestCase {
childDocuments[i] = objectHandle;
manager.setObjectInfo(0, new MtpObjectInfo.Builder()
.setObjectHandle(objectHandle)
+ .setName(Integer.toString(i))
.build());
}
manager.setObjectHandles(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, childDocuments);
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
index ce4cf14d822c..25dd1c83766e 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
@@ -41,18 +41,29 @@ public class MtpDatabaseTest extends AndroidTestCase {
};
private final TestResources resources = new TestResources();
+ MtpDatabase mDatabase;
+
+ @Override
+ public void setUp() {
+ mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
+ }
+
+ @Override
+ public void tearDown() {
+ mDatabase.close();
+ mDatabase = null;
+ }
public void testPutRootDocuments() throws Exception {
- final MtpDatabase database = new MtpDatabase(getContext());
- database.startAddingRootDocuments(0);
- database.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.startAddingRootDocuments(0);
+ mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 1, "Device", "Storage", 1000, 2000, ""),
new MtpRoot(0, 2, "Device", "Storage", 2000, 4000, ""),
new MtpRoot(0, 3, "Device", "/@#%&<>Storage", 3000, 6000,"")
});
{
- final Cursor cursor = database.queryRootDocuments(COLUMN_NAMES);
+ final Cursor cursor = mDatabase.queryRootDocuments(COLUMN_NAMES);
assertEquals(3, cursor.getCount());
cursor.moveToNext();
@@ -80,7 +91,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
}
{
- final Cursor cursor = database.queryRoots(new String [] {
+ final Cursor cursor = mDatabase.queryRoots(new String [] {
Root.COLUMN_ROOT_ID,
Root.COLUMN_FLAGS,
Root.COLUMN_ICON,
@@ -136,15 +147,14 @@ public class MtpDatabaseTest extends AndroidTestCase {
}
public void testPutChildDocuments() throws Exception {
- final MtpDatabase database = new MtpDatabase(getContext());
- database.startAddingChildDocuments("parentId");
- database.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
+ mDatabase.startAddingChildDocuments("parentId");
+ mDatabase.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
createDocument(101, "image.jpg", MtpConstants.FORMAT_EXIF_JPEG, 2 * 1024 * 1024),
createDocument(102, "music.mp3", MtpConstants.FORMAT_MP3, 3 * 1024 * 1024)
});
- final Cursor cursor = database.queryChildDocuments(COLUMN_NAMES, "parentId");
+ final Cursor cursor = mDatabase.queryChildDocuments(COLUMN_NAMES, "parentId");
assertEquals(3, cursor.getCount());
cursor.moveToNext();
@@ -202,7 +212,6 @@ public class MtpDatabaseTest extends AndroidTestCase {
}
public void testRestoreIdForRootDocuments() throws Exception {
- final MtpDatabase database = new MtpDatabase(getContext());
final String[] columns = new String[] {
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
MtpDatabaseConstants.COLUMN_STORAGE_ID,
@@ -212,14 +221,15 @@ public class MtpDatabaseTest extends AndroidTestCase {
Root.COLUMN_ROOT_ID,
Root.COLUMN_AVAILABLE_BYTES
};
- database.startAddingRootDocuments(0);
- database.putRootDocuments(0, resources, new MtpRoot[] {
+
+ mDatabase.startAddingRootDocuments(0);
+ mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage A", 1000, 0, ""),
new MtpRoot(0, 101, "Device", "Storage B", 1001, 0, "")
});
{
- final Cursor cursor = database.queryRootDocuments(columns);
+ final Cursor cursor = mDatabase.queryRootDocuments(columns);
assertEquals(2, cursor.getCount());
cursor.moveToNext();
assertEquals("documentId", 1, cursor.getInt(0));
@@ -233,7 +243,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
}
{
- final Cursor cursor = database.queryRoots(rootColumns);
+ final Cursor cursor = mDatabase.queryRoots(rootColumns);
assertEquals(2, cursor.getCount());
cursor.moveToNext();
assertEquals("rootId", 1, cursor.getInt(0));
@@ -244,10 +254,10 @@ public class MtpDatabaseTest extends AndroidTestCase {
cursor.close();
}
- database.clearMapping();
+ mDatabase.clearMapping();
{
- final Cursor cursor = database.queryRootDocuments(columns);
+ final Cursor cursor = mDatabase.queryRootDocuments(columns);
assertEquals(2, cursor.getCount());
cursor.moveToNext();
assertEquals("documentId", 1, cursor.getInt(0));
@@ -261,7 +271,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
}
{
- final Cursor cursor = database.queryRoots(rootColumns);
+ final Cursor cursor = mDatabase.queryRoots(rootColumns);
assertEquals(2, cursor.getCount());
cursor.moveToNext();
assertEquals("rootId", 1, cursor.getInt(0));
@@ -272,14 +282,14 @@ public class MtpDatabaseTest extends AndroidTestCase {
cursor.close();
}
- database.startAddingRootDocuments(0);
- database.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.startAddingRootDocuments(0);
+ mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 200, "Device", "Storage A", 2000, 0, ""),
new MtpRoot(0, 202, "Device", "Storage C", 2002, 0, "")
});
{
- final Cursor cursor = database.queryRootDocuments(columns);
+ final Cursor cursor = mDatabase.queryRootDocuments(columns);
assertEquals(3, cursor.getCount());
cursor.moveToNext();
assertEquals("documentId", 1, cursor.getInt(0));
@@ -297,7 +307,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
}
{
- final Cursor cursor = database.queryRoots(rootColumns);
+ final Cursor cursor = mDatabase.queryRoots(rootColumns);
assertEquals(3, cursor.getCount());
cursor.moveToNext();
assertEquals("rootId", 1, cursor.getInt(0));
@@ -311,10 +321,10 @@ public class MtpDatabaseTest extends AndroidTestCase {
cursor.close();
}
- database.stopAddingRootDocuments(0);
+ mDatabase.stopAddingRootDocuments(0);
{
- final Cursor cursor = database.queryRootDocuments(columns);
+ final Cursor cursor = mDatabase.queryRootDocuments(columns);
assertEquals(2, cursor.getCount());
cursor.moveToNext();
assertEquals("documentId", 1, cursor.getInt(0));
@@ -328,7 +338,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
}
{
- final Cursor cursor = database.queryRoots(rootColumns);
+ final Cursor cursor = mDatabase.queryRoots(rootColumns);
assertEquals(2, cursor.getCount());
cursor.moveToNext();
assertEquals("rootId", 1, cursor.getInt(0));
@@ -341,22 +351,21 @@ public class MtpDatabaseTest extends AndroidTestCase {
}
public void testRestoreIdForChildDocuments() throws Exception {
- final MtpDatabase database = new MtpDatabase(getContext());
final String[] columns = new String[] {
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
MtpDatabaseConstants.COLUMN_OBJECT_HANDLE,
DocumentsContract.Document.COLUMN_DISPLAY_NAME
};
- database.startAddingChildDocuments("parentId");
- database.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
+ mDatabase.startAddingChildDocuments("parentId");
+ mDatabase.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
createDocument(101, "image.jpg", MtpConstants.FORMAT_EXIF_JPEG, 2 * 1024 * 1024),
createDocument(102, "music.mp3", MtpConstants.FORMAT_MP3, 3 * 1024 * 1024)
});
- database.clearMapping();
+ mDatabase.clearMapping();
{
- final Cursor cursor = database.queryChildDocuments(columns, "parentId");
+ final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId");
assertEquals(3, cursor.getCount());
cursor.moveToNext();
@@ -377,14 +386,14 @@ public class MtpDatabaseTest extends AndroidTestCase {
cursor.close();
}
- database.startAddingChildDocuments("parentId");
- database.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
+ mDatabase.startAddingChildDocuments("parentId");
+ mDatabase.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
createDocument(203, "video.mp4", MtpConstants.FORMAT_MP4_CONTAINER, 1024),
});
{
- final Cursor cursor = database.queryChildDocuments(columns, "parentId");
+ final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId");
assertEquals(4, cursor.getCount());
cursor.moveToPosition(3);
@@ -395,10 +404,10 @@ public class MtpDatabaseTest extends AndroidTestCase {
cursor.close();
}
- database.stopAddingChildDocuments("parentId");
+ mDatabase.stopAddingChildDocuments("parentId");
{
- final Cursor cursor = database.queryChildDocuments(columns, "parentId");
+ final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId");
assertEquals(2, cursor.getCount());
cursor.moveToNext();
@@ -415,7 +424,6 @@ public class MtpDatabaseTest extends AndroidTestCase {
}
public void testRestoreIdForDifferentDevices() throws Exception {
- final MtpDatabase database = new MtpDatabase(getContext());
final String[] columns = new String[] {
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
MtpDatabaseConstants.COLUMN_STORAGE_ID,
@@ -425,17 +433,17 @@ public class MtpDatabaseTest extends AndroidTestCase {
Root.COLUMN_ROOT_ID,
Root.COLUMN_AVAILABLE_BYTES
};
- database.startAddingRootDocuments(0);
- database.startAddingRootDocuments(1);
- database.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.startAddingRootDocuments(0);
+ mDatabase.startAddingRootDocuments(1);
+ mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage", 0, 0, "")
});
- database.putRootDocuments(1, resources, new MtpRoot[] {
+ mDatabase.putRootDocuments(1, resources, new MtpRoot[] {
new MtpRoot(1, 100, "Device", "Storage", 0, 0, "")
});
{
- final Cursor cursor = database.queryRootDocuments(columns);
+ final Cursor cursor = mDatabase.queryRootDocuments(columns);
assertEquals(2, cursor.getCount());
cursor.moveToNext();
assertEquals("documentId", 1, cursor.getInt(0));
@@ -449,7 +457,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
}
{
- final Cursor cursor = database.queryRoots(rootColumns);
+ final Cursor cursor = mDatabase.queryRoots(rootColumns);
assertEquals(2, cursor.getCount());
cursor.moveToNext();
assertEquals("rootId", 1, cursor.getInt(0));
@@ -460,21 +468,21 @@ public class MtpDatabaseTest extends AndroidTestCase {
cursor.close();
}
- database.clearMapping();
+ mDatabase.clearMapping();
- database.startAddingRootDocuments(0);
- database.startAddingRootDocuments(1);
- database.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.startAddingRootDocuments(0);
+ mDatabase.startAddingRootDocuments(1);
+ mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 200, "Device", "Storage", 2000, 0, "")
});
- database.putRootDocuments(1, resources, new MtpRoot[] {
+ mDatabase.putRootDocuments(1, resources, new MtpRoot[] {
new MtpRoot(1, 300, "Device", "Storage", 3000, 0, "")
});
- database.stopAddingRootDocuments(0);
- database.stopAddingRootDocuments(1);
+ mDatabase.stopAddingRootDocuments(0);
+ mDatabase.stopAddingRootDocuments(1);
{
- final Cursor cursor = database.queryRootDocuments(columns);
+ final Cursor cursor = mDatabase.queryRootDocuments(columns);
assertEquals(2, cursor.getCount());
cursor.moveToNext();
assertEquals("documentId", 1, cursor.getInt(0));
@@ -488,7 +496,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
}
{
- final Cursor cursor = database.queryRoots(rootColumns);
+ final Cursor cursor = mDatabase.queryRoots(rootColumns);
assertEquals(2, cursor.getCount());
cursor.moveToNext();
assertEquals("rootId", 1, cursor.getInt(0));
@@ -501,34 +509,33 @@ public class MtpDatabaseTest extends AndroidTestCase {
}
public void testRestoreIdForDifferentParents() throws Exception {
- final MtpDatabase database = new MtpDatabase(getContext());
final String[] columns = new String[] {
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
MtpDatabaseConstants.COLUMN_OBJECT_HANDLE
};
- database.startAddingChildDocuments("parentId1");
- database.startAddingChildDocuments("parentId2");
- database.putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
+ mDatabase.startAddingChildDocuments("parentId1");
+ mDatabase.startAddingChildDocuments("parentId2");
+ mDatabase.putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
});
- database.putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
+ mDatabase.putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
createDocument(101, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
});
- database.clearMapping();
+ mDatabase.clearMapping();
- database.startAddingChildDocuments("parentId1");
- database.startAddingChildDocuments("parentId2");
- database.putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
+ mDatabase.startAddingChildDocuments("parentId1");
+ mDatabase.startAddingChildDocuments("parentId2");
+ mDatabase.putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
});
- database.putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
+ mDatabase.putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
createDocument(201, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
});
- database.stopAddingChildDocuments("parentId1");
+ mDatabase.stopAddingChildDocuments("parentId1");
{
- final Cursor cursor = database.queryChildDocuments(columns, "parentId1");
+ final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId1");
assertEquals(1, cursor.getCount());
cursor.moveToNext();
assertEquals("documentId", 1, cursor.getInt(0));
@@ -536,7 +543,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
cursor.close();
}
{
- final Cursor cursor = database.queryChildDocuments(columns, "parentId2");
+ final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId2");
assertEquals(1, cursor.getCount());
cursor.moveToNext();
assertEquals("documentId", 2, cursor.getInt(0));
@@ -546,7 +553,6 @@ public class MtpDatabaseTest extends AndroidTestCase {
}
public void testClearMtpIdentifierBeforeResolveRootDocuments() {
- final MtpDatabase database = new MtpDatabase(getContext());
final String[] columns = new String[] {
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
MtpDatabaseConstants.COLUMN_STORAGE_ID,
@@ -557,26 +563,26 @@ public class MtpDatabaseTest extends AndroidTestCase {
Root.COLUMN_AVAILABLE_BYTES
};
- database.startAddingRootDocuments(0);
- database.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.startAddingRootDocuments(0);
+ mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage", 0, 0, ""),
});
- database.clearMapping();
+ mDatabase.clearMapping();
- database.startAddingRootDocuments(0);
- database.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.startAddingRootDocuments(0);
+ mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 200, "Device", "Storage", 2000, 0, ""),
});
- database.clearMapping();
+ mDatabase.clearMapping();
- database.startAddingRootDocuments(0);
- database.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.startAddingRootDocuments(0);
+ mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 300, "Device", "Storage", 3000, 0, ""),
});
- database.stopAddingRootDocuments(0);
+ mDatabase.stopAddingRootDocuments(0);
{
- final Cursor cursor = database.queryRootDocuments(columns);
+ final Cursor cursor = mDatabase.queryRootDocuments(columns);
assertEquals(1, cursor.getCount());
cursor.moveToNext();
assertEquals("documentId", 1, cursor.getInt(0));
@@ -585,7 +591,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
cursor.close();
}
{
- final Cursor cursor = database.queryRoots(rootColumns);
+ final Cursor cursor = mDatabase.queryRoots(rootColumns);
assertEquals(1, cursor.getCount());
cursor.moveToNext();
assertEquals("rootId", 1, cursor.getInt(0));
@@ -595,7 +601,6 @@ public class MtpDatabaseTest extends AndroidTestCase {
}
public void testPutSameNameRootsAfterClearing() throws Exception {
- final MtpDatabase database = new MtpDatabase(getContext());
final String[] columns = new String[] {
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
MtpDatabaseConstants.COLUMN_STORAGE_ID,
@@ -606,21 +611,21 @@ public class MtpDatabaseTest extends AndroidTestCase {
Root.COLUMN_AVAILABLE_BYTES
};
- database.startAddingRootDocuments(0);
- database.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.startAddingRootDocuments(0);
+ mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage", 0, 0, ""),
});
- database.clearMapping();
+ mDatabase.clearMapping();
- database.startAddingRootDocuments(0);
- database.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.startAddingRootDocuments(0);
+ mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 200, "Device", "Storage", 2000, 0, ""),
new MtpRoot(0, 201, "Device", "Storage", 2001, 0, ""),
});
- database.stopAddingRootDocuments(0);
+ mDatabase.stopAddingRootDocuments(0);
{
- final Cursor cursor = database.queryRootDocuments(columns);
+ final Cursor cursor = mDatabase.queryRootDocuments(columns);
assertEquals(2, cursor.getCount());
cursor.moveToNext();
assertEquals("documentId", 2, cursor.getInt(0));
@@ -633,7 +638,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
cursor.close();
}
{
- final Cursor cursor = database.queryRoots(rootColumns);
+ final Cursor cursor = mDatabase.queryRoots(rootColumns);
assertEquals(2, cursor.getCount());
cursor.moveToNext();
assertEquals("rootId", 2, cursor.getInt(0));
@@ -646,27 +651,26 @@ public class MtpDatabaseTest extends AndroidTestCase {
}
public void testReplaceExistingRoots() {
- // The client code should be able to replace exisitng rows with new information.
- final MtpDatabase database = new MtpDatabase(getContext());
+ // The client code should be able to replace existing rows with new information.
// Add one.
- database.startAddingRootDocuments(0);
- database.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.startAddingRootDocuments(0);
+ mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
});
- database.stopAddingRootDocuments(0);
+ mDatabase.stopAddingRootDocuments(0);
// Replace it.
- database.startAddingRootDocuments(0);
- database.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.startAddingRootDocuments(0);
+ mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""),
});
- database.stopAddingRootDocuments(0);
+ mDatabase.stopAddingRootDocuments(0);
{
final String[] columns = new String[] {
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
MtpDatabaseConstants.COLUMN_STORAGE_ID,
DocumentsContract.Document.COLUMN_DISPLAY_NAME
};
- final Cursor cursor = database.queryRootDocuments(columns);
+ final Cursor cursor = mDatabase.queryRootDocuments(columns);
assertEquals(1, cursor.getCount());
cursor.moveToNext();
assertEquals("documentId", 1, cursor.getInt(0));
@@ -679,7 +683,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
Root.COLUMN_ROOT_ID,
Root.COLUMN_AVAILABLE_BYTES
};
- final Cursor cursor = database.queryRoots(columns);
+ final Cursor cursor = mDatabase.queryRoots(columns);
assertEquals(1, cursor.getCount());
cursor.moveToNext();
assertEquals("rootId", 1, cursor.getInt(0));
@@ -690,20 +694,19 @@ public class MtpDatabaseTest extends AndroidTestCase {
public void _testFailToReplaceExisitingUnmappedRoots() {
// The client code should not be able to replace rows before resolving 'unmapped' rows.
- final MtpDatabase database = new MtpDatabase(getContext());
// Add one.
- database.startAddingRootDocuments(0);
- database.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.startAddingRootDocuments(0);
+ mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
});
- database.clearMapping();
+ mDatabase.clearMapping();
// Add one.
- database.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""),
});
// Add one more before resolving unmapped documents.
try {
- database.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""),
});
fail();
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
index bc7f28c59f33..82e08cd26927 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
@@ -43,7 +43,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase {
mResolver = new TestContentResolver();
mMtpManager = new TestMtpManager(getContext());
mProvider = new MtpDocumentsProvider();
- mDatabase = new MtpDatabase(getContext());
+ mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
mProvider.onCreateForTesting(mResources, mMtpManager, mResolver, mDatabase);
}
@@ -200,6 +200,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase {
mProvider.openDevice(0);
mMtpManager.setObjectInfo(0, new MtpObjectInfo.Builder()
.setObjectHandle(2)
+ .setStorageId(1)
.setFormat(MtpConstants.FORMAT_EXIF_JPEG)
.setName("image.jpg")
.setDateModified(1422716400000L)
@@ -210,6 +211,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase {
assertEquals(1, cursor.getCount());
cursor.moveToNext();
+
assertEquals("0_1_2", cursor.getString(0));
assertEquals("image/jpeg", cursor.getString(1));
assertEquals("image.jpg", cursor.getString(2));
@@ -227,6 +229,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase {
mProvider.openDevice(0);
mMtpManager.setObjectInfo(0, new MtpObjectInfo.Builder()
.setObjectHandle(2)
+ .setStorageId(1)
.setFormat(MtpConstants.FORMAT_ASSOCIATION)
.setName("directory")
.setDateModified(1422716400000L)
@@ -277,6 +280,12 @@ public class MtpDocumentsProviderTest extends AndroidTestCase {
mProvider.openDevice(0);
mMtpManager.setObjectHandles(0, 0, -1, new int[] { 1 });
+ mDatabase.startAddingRootDocuments(0);
+ mDatabase.putRootDocuments(0, mResources, new MtpRoot[] {
+ new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "")
+ });
+ mDatabase.stopAddingRootDocuments(0);
+
mMtpManager.setObjectInfo(0, new MtpObjectInfo.Builder()
.setObjectHandle(1)
.setFormat(MtpConstants.FORMAT_EXIF_JPEG)
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java
index 53018cc8d810..7c947f5be147 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java
@@ -19,15 +19,14 @@ package com.android.mtp;
import android.mtp.MtpObjectInfo;
import android.os.ParcelFileDescriptor;
import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.MediumTest;
import java.io.IOException;
-import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
-@SmallTest
+@MediumTest
public class PipeManagerTest extends AndroidTestCase {
private static final byte[] HELLO_BYTES = new byte[] { 'h', 'e', 'l', 'l', 'o' };
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java
index 3d92cc22c146..3833799bacc4 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java
@@ -96,7 +96,7 @@ public class TestMtpManager extends MtpManager {
if (mRoots.containsKey(deviceId)) {
return mRoots.get(deviceId);
} else {
- throw new IOException("getRoots error");
+ throw new IOException("getRoots error: " + Integer.toString(deviceId));
}
}
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResultInstrumentation.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResultInstrumentation.java
index a24337534e5b..0fb0f34eec65 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResultInstrumentation.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResultInstrumentation.java
@@ -12,8 +12,20 @@ public class TestResultInstrumentation extends InstrumentationTestRunner impleme
@Override
public void onCreate(Bundle arguments) {
+ if (arguments == null) {
+ arguments = new Bundle();
+ }
+ final boolean includeRealDeviceTest =
+ Boolean.parseBoolean(arguments.getString("realDeviceTest", "false"));
+ if (!includeRealDeviceTest) {
+ arguments.putString("notAnnotation", "com.android.mtp.RealDeviceTest");
+ }
super.onCreate(arguments);
- addTestListener(this);
+ if (includeRealDeviceTest) {
+ // Show the test result by using activity because we need to disconnect USB cable
+ // from adb host while testing with real MTP device.
+ addTestListener(this);
+ }
}
@Override
diff --git a/packages/PrintSpooler/res/drawable/ic_add.xml b/packages/PrintSpooler/res/drawable/ic_add.xml
new file mode 100644
index 000000000000..1442b1b61f46
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable/ic_add.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+ 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"
+ android:fillColor="#FFFFFF"/>
+</vector> \ No newline at end of file
diff --git a/packages/PrintSpooler/res/drawable/ic_search.xml b/packages/PrintSpooler/res/drawable/ic_search.xml
deleted file mode 100644
index 991fa38b2da4..000000000000
--- a/packages/PrintSpooler/res/drawable/ic_search.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
-
- 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.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
- android:autoMirrored="true">
-
- <item
- android:state_checked="true">
- <bitmap
- android:src="@*android:drawable/ic_menu_search"
- android:tint="?android:attr/colorControlActivated">
- </bitmap>
- </item>
-
- <item
- android:state_pressed="true">
- <bitmap
- android:src="@*android:drawable/ic_menu_search"
- android:tint="?android:attr/colorControlActivated">
- </bitmap>
- </item>
-
- <item>
- <bitmap
- android:src="@*android:drawable/ic_menu_search"
- android:tint="?android:attr/colorControlNormal">
- </bitmap>
- </item>
-
-</selector>
diff --git a/packages/PrintSpooler/res/layout/printer_dropdown_prompt.xml b/packages/PrintSpooler/res/layout/printer_dropdown_prompt.xml
new file mode 100644
index 000000000000..11fef2d21fde
--- /dev/null
+++ b/packages/PrintSpooler/res/layout/printer_dropdown_prompt.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+ 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.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textIsSelectable="false"
+ android:textColor="?android:attr/textColorPrimary"
+ android:paddingStart="20dip"
+ android:paddingEnd="8dip"
+ android:minHeight="56dip"
+ android:orientation="horizontal"
+ android:text="@string/destination_default_text"
+ android:gravity="start|center_vertical" />
diff --git a/packages/PrintSpooler/res/menu/select_printer_activity.xml b/packages/PrintSpooler/res/menu/select_printer_activity.xml
index 8da57694007b..15cc13939cf0 100644
--- a/packages/PrintSpooler/res/menu/select_printer_activity.xml
+++ b/packages/PrintSpooler/res/menu/select_printer_activity.xml
@@ -19,7 +19,7 @@
<item
android:id="@+id/action_search"
android:title="@string/search"
- android:icon="@*android:drawable/ic_search"
+ android:icon="@*android:drawable/ic_search_api_material"
android:actionViewClass="android.widget.SearchView"
android:showAsAction="ifRoom|collapseActionView"
android:alphabeticShortcut="f"
@@ -29,7 +29,7 @@
<item
android:id="@+id/action_add_printer"
android:title="@string/print_add_printer"
- android:icon="@*android:drawable/create_contact"
+ android:icon="@drawable/ic_add"
android:showAsAction="ifRoom"
android:alphabeticShortcut="a">
</item>
diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml
index 70abdf4920bb..6d8178844fba 100644
--- a/packages/PrintSpooler/res/values/strings.xml
+++ b/packages/PrintSpooler/res/values/strings.xml
@@ -49,6 +49,9 @@
<!-- Label of the page selection widget. [CHAR LIMIT=20] -->
<string name="label_pages">Pages</string>
+ <!-- Label of the destination widget. [CHAR LIMIT=20] -->
+ <string name="destination_default_text">Select a printer</string>
+
<!-- Template for the all pages option in the page selection widget. [CHAR LIMIT=20] -->
<string name="template_all_pages">All <xliff:g id="page_count" example="100">%1$s</xliff:g></string>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index f409fd43d085..e7588359478a 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -64,6 +64,7 @@ import android.text.TextWatcher;
import android.util.ArrayMap;
import android.util.Log;
import android.view.KeyEvent;
+import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
@@ -125,6 +126,8 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
private static final String FRAGMENT_TAG = "FRAGMENT_TAG";
+ private static final String HAS_PRINTED_PREF = "has_printed";
+
private static final int ORIENTATION_PORTRAIT = 0;
private static final int ORIENTATION_LANDSCAPE = 1;
@@ -187,6 +190,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
private Spinner mDestinationSpinner;
private DestinationAdapter mDestinationSpinnerAdapter;
+ private boolean mShowDestinationPrompt;
private Spinner mMediaSizeSpinner;
private ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter;
@@ -1093,6 +1097,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
updateOptionsUi();
addCurrentPrinterToHistory();
+ setUserPrinted();
PageRange[] selectedPages = computeSelectedPages();
if (!Arrays.equals(mSelectedPages, selectedPages)) {
@@ -1195,6 +1200,29 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
// Print button
mPrintButton = (ImageView) findViewById(R.id.print_button);
mPrintButton.setOnClickListener(clickListener);
+
+ // Special prompt instead of destination spinner for the first time the user printed
+ if (!hasUserEverPrinted()) {
+ mShowDestinationPrompt = true;
+
+ mSummaryCopies.setEnabled(false);
+ mSummaryPaperSize.setEnabled(false);
+
+ mDestinationSpinner.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ mShowDestinationPrompt = false;
+ mSummaryCopies.setEnabled(true);
+ mSummaryPaperSize.setEnabled(true);
+ updateOptionsUi();
+
+ mDestinationSpinner.setOnTouchListener(null);
+ mDestinationSpinnerAdapter.notifyDataSetChanged();
+
+ return false;
+ }
+ });
+ }
}
/**
@@ -1332,6 +1360,22 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
&& printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE;
}
+ /**
+ * Disable all options UI elements, beside the {@link #mDestinationSpinner}
+ */
+ private void disableOptionsUi() {
+ mCopiesEditText.setEnabled(false);
+ mCopiesEditText.setFocusable(false);
+ mMediaSizeSpinner.setEnabled(false);
+ mColorModeSpinner.setEnabled(false);
+ mDuplexModeSpinner.setEnabled(false);
+ mOrientationSpinner.setEnabled(false);
+ mRangeOptionsSpinner.setEnabled(false);
+ mPageRangeEditText.setEnabled(false);
+ mPrintButton.setVisibility(View.GONE);
+ mMoreOptionsButton.setEnabled(false);
+ }
+
void updateOptionsUi() {
// Always update the summary.
updateSummary();
@@ -1346,32 +1390,14 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
if (mState != STATE_PRINTER_UNAVAILABLE) {
mDestinationSpinner.setEnabled(false);
}
- mCopiesEditText.setEnabled(false);
- mCopiesEditText.setFocusable(false);
- mMediaSizeSpinner.setEnabled(false);
- mColorModeSpinner.setEnabled(false);
- mDuplexModeSpinner.setEnabled(false);
- mOrientationSpinner.setEnabled(false);
- mRangeOptionsSpinner.setEnabled(false);
- mPageRangeEditText.setEnabled(false);
- mPrintButton.setVisibility(View.GONE);
- mMoreOptionsButton.setEnabled(false);
+ disableOptionsUi();
return;
}
// If no current printer, or it has no capabilities, or it is not
// available, we disable all print options except the destination.
if (mCurrentPrinter == null || !canPrint(mCurrentPrinter)) {
- mCopiesEditText.setEnabled(false);
- mCopiesEditText.setFocusable(false);
- mMediaSizeSpinner.setEnabled(false);
- mColorModeSpinner.setEnabled(false);
- mDuplexModeSpinner.setEnabled(false);
- mOrientationSpinner.setEnabled(false);
- mRangeOptionsSpinner.setEnabled(false);
- mPageRangeEditText.setEnabled(false);
- mPrintButton.setVisibility(View.GONE);
- mMoreOptionsButton.setEnabled(false);
+ disableOptionsUi();
return;
}
@@ -1679,6 +1705,10 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
mCopiesEditText.setText(MIN_COPIES_STRING);
mCopiesEditText.requestFocus();
}
+
+ if (mShowDestinationPrompt) {
+ disableOptionsUi();
+ }
}
private void updateSummary() {
@@ -1980,6 +2010,32 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
}
}
+
+ /**
+ * Check if the user has ever printed a document
+ *
+ * @return true iff the user has ever printed a document
+ */
+ private boolean hasUserEverPrinted() {
+ SharedPreferences preferences = getSharedPreferences(HAS_PRINTED_PREF, MODE_PRIVATE);
+
+ return preferences.getBoolean(HAS_PRINTED_PREF, false);
+ }
+
+ /**
+ * Remember that the user printed a document
+ */
+ private void setUserPrinted() {
+ SharedPreferences preferences = getSharedPreferences(HAS_PRINTED_PREF, MODE_PRIVATE);
+
+ if (!preferences.getBoolean(HAS_PRINTED_PREF, false)) {
+ SharedPreferences.Editor edit = preferences.edit();
+
+ edit.putBoolean(HAS_PRINTED_PREF, true);
+ edit.apply();
+ }
+ }
+
private final class DestinationAdapter extends BaseAdapter
implements PrinterRegistry.OnPrintersChangeListener {
private final List<PrinterHolder> mPrinterHolders = new ArrayList<>();
@@ -1988,6 +2044,11 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
private boolean mHistoricalPrintersLoaded;
+ /**
+ * Has the {@link #mDestinationSpinner} ever used a view from printer_dropdown_prompt
+ */
+ private boolean hadPromptView;
+
public DestinationAdapter() {
mHistoricalPrintersLoaded = mPrinterRegistry.areHistoricalPrintersLoaded();
if (mHistoricalPrintersLoaded) {
@@ -2098,9 +2159,20 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
@Override
public View getView(int position, View convertView, ViewGroup parent) {
- if (convertView == null) {
- convertView = getLayoutInflater().inflate(
- R.layout.printer_dropdown_item, parent, false);
+ if (mShowDestinationPrompt) {
+ if (convertView == null) {
+ convertView = getLayoutInflater().inflate(
+ R.layout.printer_dropdown_prompt, parent, false);
+ hadPromptView = true;
+ }
+
+ return convertView;
+ } else {
+ // We don't know if we got an recyled printer_dropdown_prompt, hence do not use it
+ if (hadPromptView || convertView == null) {
+ convertView = getLayoutInflater().inflate(
+ R.layout.printer_dropdown_item, parent, false);
+ }
}
CharSequence title = null;
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 9e48849226a3..a37196e48891 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -348,4 +348,7 @@
<!-- Header for items under the work user [CHAR LIMIT=30] -->
<string name="category_work">Work</string>
+ <!-- Full package name of OEM preferred device feedback reporter. Leave this blank, overlaid in Settings/TvSettings [DO NOT TRANSLATE] -->
+ <string name="oem_preferred_feedback_reporter" translatable="false" />
+
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java
new file mode 100644
index 000000000000..ff1c8665b3d2
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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 com.android.settingslib;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Build;
+import android.text.TextUtils;
+import android.text.format.DateFormat;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class DeviceInfoUtils {
+ private static final String TAG = "DeviceInfoUtils";
+
+ private static final String FILENAME_PROC_VERSION = "/proc/version";
+ private static final String FILENAME_MSV = "/sys/board_properties/soc/msv";
+
+ /**
+ * Reads a line from the specified file.
+ * @param filename the file to read from
+ * @return the first line, if any.
+ * @throws IOException if the file couldn't be read
+ */
+ private static String readLine(String filename) throws IOException {
+ BufferedReader reader = new BufferedReader(new FileReader(filename), 256);
+ try {
+ return reader.readLine();
+ } finally {
+ reader.close();
+ }
+ }
+
+ public static String getFormattedKernelVersion() {
+ try {
+ return formatKernelVersion(readLine(FILENAME_PROC_VERSION));
+ } catch (IOException e) {
+ Log.e(TAG, "IO Exception when getting kernel version for Device Info screen",
+ e);
+
+ return "Unavailable";
+ }
+ }
+
+ public static String formatKernelVersion(String rawKernelVersion) {
+ // Example (see tests for more):
+ // Linux version 3.0.31-g6fb96c9 (android-build@xxx.xxx.xxx.xxx.com) \
+ // (gcc version 4.6.x-xxx 20120106 (prerelease) (GCC) ) #1 SMP PREEMPT \
+ // Thu Jun 28 11:02:39 PDT 2012
+
+ final String PROC_VERSION_REGEX =
+ "Linux version (\\S+) " + /* group 1: "3.0.31-g6fb96c9" */
+ "\\((\\S+?)\\) " + /* group 2: "x@y.com" (kernel builder) */
+ "(?:\\(gcc.+? \\)) " + /* ignore: GCC version information */
+ "(#\\d+) " + /* group 3: "#1" */
+ "(?:.*?)?" + /* ignore: optional SMP, PREEMPT, and any CONFIG_FLAGS */
+ "((Sun|Mon|Tue|Wed|Thu|Fri|Sat).+)"; /* group 4: "Thu Jun 28 11:02:39 PDT 2012" */
+
+ Matcher m = Pattern.compile(PROC_VERSION_REGEX).matcher(rawKernelVersion);
+ if (!m.matches()) {
+ Log.e(TAG, "Regex did not match on /proc/version: " + rawKernelVersion);
+ return "Unavailable";
+ } else if (m.groupCount() < 4) {
+ Log.e(TAG, "Regex match on /proc/version only returned " + m.groupCount()
+ + " groups");
+ return "Unavailable";
+ }
+ return m.group(1) + "\n" + // 3.0.31-g6fb96c9
+ m.group(2) + " " + m.group(3) + "\n" + // x@y.com #1
+ m.group(4); // Thu Jun 28 11:02:39 PDT 2012
+ }
+
+ /**
+ * Returns " (ENGINEERING)" if the msv file has a zero value, else returns "".
+ * @return a string to append to the model number description.
+ */
+ public static String getMsvSuffix() {
+ // Production devices should have a non-zero value. If we can't read it, assume it's a
+ // production device so that we don't accidentally show that it's an ENGINEERING device.
+ try {
+ String msv = readLine(FILENAME_MSV);
+ // Parse as a hex number. If it evaluates to a zero, then it's an engineering build.
+ if (Long.parseLong(msv, 16) == 0) {
+ return " (ENGINEERING)";
+ }
+ } catch (IOException|NumberFormatException e) {
+ // Fail quietly, as the file may not exist on some devices, or may be unreadable
+ }
+ return "";
+ }
+
+ public static String getFeedbackReporterPackage(Context context) {
+ final String feedbackReporter =
+ context.getResources().getString(R.string.oem_preferred_feedback_reporter);
+ if (TextUtils.isEmpty(feedbackReporter)) {
+ // Reporter not configured. Return.
+ return feedbackReporter;
+ }
+ // Additional checks to ensure the reporter is on system image, and reporter is
+ // configured to listen to the intent. Otherwise, dont show the "send feedback" option.
+ final Intent intent = new Intent(Intent.ACTION_BUG_REPORT);
+
+ PackageManager pm = context.getPackageManager();
+ List<ResolveInfo> resolvedPackages =
+ pm.queryIntentActivities(intent, PackageManager.GET_RESOLVED_FILTER);
+ for (ResolveInfo info : resolvedPackages) {
+ if (info.activityInfo != null) {
+ if (!TextUtils.isEmpty(info.activityInfo.packageName)) {
+ try {
+ ApplicationInfo ai =
+ pm.getApplicationInfo(info.activityInfo.packageName, 0);
+ if ((ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ // Package is on the system image
+ if (TextUtils.equals(
+ info.activityInfo.packageName, feedbackReporter)) {
+ return feedbackReporter;
+ }
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // No need to do anything here.
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ public static String getSecurityPatch() {
+ String patch = Build.VERSION.SECURITY_PATCH;
+ if (!"".equals(patch)) {
+ try {
+ SimpleDateFormat template = new SimpleDateFormat("yyyy-MM-dd");
+ Date patchDate = template.parse(patch);
+ String format = DateFormat.getBestDateTimePattern(Locale.getDefault(), "dMMMMyyyy");
+ patch = DateFormat.format(format, patchDate).toString();
+ } catch (ParseException e) {
+ // broken parse; fall through and use the raw string
+ }
+ return patch;
+ } else {
+ return null;
+ }
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ExitRecentsWindowFirstAnimationFrameEvent.java b/packages/SystemUI/src/com/android/systemui/recents/ExitRecentsWindowFirstAnimationFrameEvent.java
new file mode 100644
index 000000000000..8ae8c53a9197
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/ExitRecentsWindowFirstAnimationFrameEvent.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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 com.android.systemui.recents;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * Event sent when the exit animation is started.
+ *
+ * This is sent so parts of UI can synchronize on this event and adjust their appearance. An example
+ * of that is hiding the tasks when the launched application window becomes visible.
+ */
+public class ExitRecentsWindowFirstAnimationFrameEvent extends EventBus.Event {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 3ae8827b3fc6..1e46e6f54245 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -37,6 +37,7 @@ import android.view.KeyEvent;
import android.view.View;
import android.view.ViewStub;
import android.view.ViewTreeObserver;
+import android.view.WindowManager;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
import com.android.systemui.recents.events.EventBus;
@@ -352,6 +353,8 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
mEmptyViewStub = (ViewStub) findViewById(R.id.empty_view_stub);
mScrimViews = new SystemBarScrimViews(this);
+ getWindow().getAttributes().privateFlags |=
+ WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
// Create the home intent runnable
Intent homeIntent = new Intent(Intent.ACTION_MAIN, null);
@@ -648,6 +651,11 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
}
+ public final void onBusEvent(ExitRecentsWindowFirstAnimationFrameEvent event) {
+ mRecentsView.setStackViewVisibility(View.INVISIBLE);
+ mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
+ }
+
public final void onBusEvent(CancelEnterRecentsWindowAnimationEvent event) {
RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
int launchToTaskId = launchState.launchedToTaskId;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 50aa2f746f23..b491f943734d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -68,8 +68,8 @@ import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
* An implementation of the Recents component for the current user. For secondary users, this can
* be called remotely from the system user.
*/
-public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub
- implements ActivityOptions.OnAnimationFinishedListener {
+public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub implements
+ ActivityOptions.OnAnimationFinishedListener {
private final static String TAG = "RecentsImpl";
private final static boolean DEBUG = false;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
index 85b8fcfb4a7f..b2f716d2add5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
@@ -27,12 +27,12 @@ import android.os.Handler;
import android.os.IRemoteCallback;
import android.os.RemoteException;
import android.util.Log;
-import android.util.SparseArray;
import android.view.AppTransitionAnimationSpec;
import android.view.IAppTransitionAnimationSpecsFuture;
import android.view.WindowManagerGlobal;
import com.android.internal.annotations.GuardedBy;
import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.ExitRecentsWindowFirstAnimationFrameEvent;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
@@ -106,6 +106,7 @@ public class RecentsTransitionHelper {
// If we are launching into another task, cancel the previous task's
// window transition
EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
+ EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
if (lockToTask) {
// Request screen pinning after the animation runs
@@ -116,7 +117,12 @@ public class RecentsTransitionHelper {
} else {
// This is only the case if the task is not on screen (scrolled offscreen for example)
transitionFuture = null;
- animStartedListener = null;
+ animStartedListener = new ActivityOptions.OnAnimationStartedListener() {
+ @Override
+ public void onAnimationStarted() {
+ EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
+ }
+ };
}
if (taskView == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 3f0000e645d5..069279fdafe3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -62,6 +62,7 @@ import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
@@ -175,6 +176,7 @@ public abstract class BaseStatusBar extends SystemUI implements
protected boolean mDeviceInteractive;
protected boolean mVisible;
+ protected ArraySet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new ArraySet<>();
// mScreenOnFromKeyguard && mVisible.
private boolean mVisibleToUser;
@@ -1664,7 +1666,10 @@ public abstract class BaseStatusBar extends SystemUI implements
return;
}
- final PendingIntent intent = sbn.getNotification().contentIntent;
+ Notification notification = sbn.getNotification();
+ final PendingIntent intent = notification.contentIntent != null
+ ? notification.contentIntent
+ : notification.fullScreenIntent;
final String notificationKey = sbn.getKey();
// Mark notification for one frame.
@@ -1746,8 +1751,8 @@ public abstract class BaseStatusBar extends SystemUI implements
}
public void register(ExpandableNotificationRow row, StatusBarNotification sbn) {
- final PendingIntent contentIntent = sbn.getNotification().contentIntent;
- if (contentIntent != null) {
+ Notification notification = sbn.getNotification();
+ if (notification.contentIntent != null || notification.fullScreenIntent != null) {
row.setOnClickListener(this);
} else {
row.setOnClickListener(null);
@@ -2013,6 +2018,8 @@ public abstract class BaseStatusBar extends SystemUI implements
Entry entry = mNotificationData.get(key);
if (entry == null) {
return;
+ } else if (mHeadsUpEntriesToRemoveOnSwitch.contains(entry)) {
+ mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
}
Notification n = notification.getNotification();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
index bbef1c031ff5..fbe97300aa3d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -84,10 +84,10 @@ public class NotificationGroupManager {
// the close future. See b/23676310 for reference.
return;
}
- if (notif.isGroupSummary()) {
- group.summary = null;
- } else {
+ if (notif.isGroupChild()) {
group.children.remove(removed);
+ } else {
+ group.summary = null;
}
if (group.children.isEmpty()) {
if (group.summary == null) {
@@ -107,17 +107,17 @@ public class NotificationGroupManager {
group = new NotificationGroup();
mGroupMap.put(groupKey, group);
}
- if (notif.isGroupSummary()) {
+ if (notif.isGroupChild()) {
+ group.children.add(added);
+ if (group.summary != null && group.children.size() == 1 && !group.expanded) {
+ group.summary.row.updateNotificationHeader();
+ }
+ } else {
group.summary = added;
group.expanded = added.row.areChildrenExpanded();
if (!group.children.isEmpty()) {
mListener.onGroupCreatedFromChildren(group);
}
- } else {
- group.children.add(added);
- if (group.summary != null && group.children.size() == 1 && !group.expanded) {
- group.summary.row.updateNotificationHeader();
- }
}
}
@@ -169,7 +169,7 @@ public class NotificationGroupManager {
* @return whether a given notification is a summary in a group which has children
*/
public boolean isSummaryOfGroup(StatusBarNotification sbn) {
- if (sbn.getNotification().isGroupChild()) {
+ if (!sbn.getNotification().isGroupSummary()) {
return false;
}
NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 3e5251587820..9ab2a2c28793 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -617,7 +617,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
};
private HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>> mTmpChildOrderMap
= new HashMap<>();
- private HashSet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new HashSet<>();
private RankingMap mLatestRankingMap;
private boolean mNoAnimationOnNextBarModeChange;
private FalsingManager mFalsingManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index dc9f5e863379..5cfd17463886 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -458,7 +458,10 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
mReleaseOnExpandFinish = false;
} else {
for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) {
- removeHeadsUpEntry(entry);
+ if (isHeadsUp(entry.key)) {
+ // Maybe the heads-up was removed already
+ removeHeadsUpEntry(entry);
+ }
}
}
mEntriesToRemoveAfterExpand.clear();
@@ -596,6 +599,9 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
postTime = Math.max(postTime, currentTime);
}
removeAutoRemovalCallbacks();
+ if (mEntriesToRemoveAfterExpand.contains(entry)) {
+ mEntriesToRemoveAfterExpand.remove(entry);
+ }
if (!hasFullScreenIntent(entry) && !mRemoteInputActive) {
long finishTime = postTime + mHeadsUpNotificationDecay;
long removeDelay = Math.max(finishTime - currentTime, mMinimumDisplayTime);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index b52687a78298..5f6cbf9f0025 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -21,7 +21,6 @@ import android.os.PowerManager;
import android.util.Pools.SimplePool;
import android.util.Slog;
import android.view.Choreographer;
-import android.view.Display;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.InputFilter;
@@ -103,7 +102,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
private TouchExplorer mTouchExplorer;
- private ScreenMagnifier mScreenMagnifier;
+ private MagnificationGestureHandler mMagnificationGestureHandler;
private AutoclickController mAutoclickController;
@@ -363,14 +362,13 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
}
if ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0) {
- mScreenMagnifier = new ScreenMagnifier(mContext, mUserId,
- Display.DEFAULT_DISPLAY, mAms);
- addFirstEventHandler(mScreenMagnifier);
+ mMagnificationGestureHandler = new MagnificationGestureHandler(mContext, mAms);
+ addFirstEventHandler(mMagnificationGestureHandler);
}
if ((mEnabledFeatures & FLAG_FEATURE_FILTER_KEY_EVENTS) != 0) {
- mKeyboardInterceptor = new KeyboardInterceptor(mAms);
- addFirstEventHandler(mKeyboardInterceptor);
+ mKeyboardInterceptor = new KeyboardInterceptor(mAms);
+ addFirstEventHandler(mKeyboardInterceptor);
}
}
@@ -398,9 +396,9 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
mTouchExplorer.onDestroy();
mTouchExplorer = null;
}
- if (mScreenMagnifier != null) {
- mScreenMagnifier.onDestroy();
- mScreenMagnifier = null;
+ if (mMagnificationGestureHandler != null) {
+ mMagnificationGestureHandler.onDestroy();
+ mMagnificationGestureHandler = null;
}
if (mKeyboardInterceptor != null) {
mKeyboardInterceptor.onDestroy();
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 535a8efc496b..9f1dc0a96559 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -23,6 +23,7 @@ import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.annotation.NonNull;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.app.StatusBarManager;
@@ -90,6 +91,7 @@ import android.view.accessibility.IAccessibilityManagerClient;
import com.android.internal.R;
import com.android.internal.content.PackageMonitor;
+import com.android.internal.os.SomeArgs;
import com.android.internal.statusbar.IStatusBarService;
import com.android.server.LocalServices;
@@ -181,6 +183,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
private MagnificationController mMagnificationController;
+ private boolean mUnregisterMagnificationOnReset;
+
private InteractionBridge mInteractionBridge;
private AlertDialog mEnableTouchExplorationDialog;
@@ -762,6 +766,28 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
/**
+ * Called by the MagnificationController when the state of display
+ * magnification changes.
+ *
+ * @param region the new magnified region, may be empty if
+ * magnification is not enabled (e.g. scale is 1)
+ * @param scale the new scale
+ * @param centerX the new screen-relative center X coordinate
+ * @param centerY the new screen-relative center Y coordinate
+ */
+ void notifyMagnificationChanged(@NonNull Region region,
+ float scale, float centerX, float centerY) {
+ synchronized (mLock) {
+ notifyMagnificationChangedLocked(region, scale, centerX, centerY);
+
+ if (mUnregisterMagnificationOnReset && scale == 1.0f) {
+ mUnregisterMagnificationOnReset = false;
+ mMagnificationController.unregister();
+ }
+ }
+ }
+
+ /**
* Gets a point within the accessibility focused node where we can send down
* and up events to perform a click.
*
@@ -942,6 +968,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
}
+ private void notifyMagnificationChangedLocked(@NonNull Region region,
+ float scale, float centerX, float centerY) {
+ final UserState state = getCurrentUserStateLocked();
+ for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
+ final Service service = state.mBoundServices.get(i);
+ service.notifyMagnificationChanged(region, scale, centerX, centerY);
+ }
+ }
+
/**
* Removes an AccessibilityInteractionConnection.
*
@@ -1363,6 +1398,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
}
+ /**
+ * Called when any property of the user state has changed.
+ *
+ * @param userState the new user state
+ */
private void onUserStateChangedLocked(UserState userState) {
// TODO: Remove this hack
mInitialized = true;
@@ -1374,6 +1414,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
updateTouchExplorationLocked(userState);
updateEnhancedWebAccessibilityLocked(userState);
updateDisplayColorAdjustmentSettingsLocked(userState);
+ updateMagnificationLocked(userState);
scheduleUpdateInputFilter(userState);
scheduleUpdateClientsIfNeededLocked(userState);
}
@@ -1663,6 +1704,44 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
DisplayAdjustmentUtils.applyAdjustments(mContext, userState.mUserId);
}
+ private void updateMagnificationLocked(UserState userState) {
+ final int userId = userState.mUserId;
+ if (userId == mCurrentUserId && mMagnificationController != null) {
+ if (userHasMagnificationServicesLocked(userState)) {
+ mMagnificationController.setUserId(userState.mUserId);
+ } else {
+ // If the user no longer has any magnification-controlling
+ // services and is not using magnification gestures, then
+ // reset the state to normal.
+ if (!userState.mIsDisplayMagnificationEnabled
+ && mMagnificationController.resetIfNeeded(true)) {
+ // Animations are still running, so wait until we receive a
+ // callback verifying that we've reset magnification.
+ mUnregisterMagnificationOnReset = true;
+ } else {
+ mUnregisterMagnificationOnReset = false;
+ mMagnificationController.unregister();
+ mMagnificationController = null;
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns whether the specified user has any services that are capable of
+ * controlling magnification.
+ */
+ private boolean userHasMagnificationServicesLocked(UserState userState) {
+ final List<Service> services = userState.mBoundServices;
+ for (int i = 0, count = services.size(); i < count; i++) {
+ final Service service = services.get(i);
+ if (mSecurityPolicy.canControlMagnification(service)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private MagnificationSpec getCompatibleMagnificationSpecLocked(int windowId) {
IBinder windowToken = mGlobalWindowTokens.get(windowId);
if (windowToken == null) {
@@ -1938,10 +2017,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
MagnificationController getMagnificationController() {
- if (mMagnificationController == null) {
- mMagnificationController = new MagnificationController(mContext, this);
+ synchronized (mLock) {
+ if (mMagnificationController == null) {
+ mMagnificationController = new MagnificationController(mContext, this);
+ mMagnificationController.register();
+ mMagnificationController.setUserId(mCurrentUserId);
+ }
+ return mMagnificationController;
}
- return mMagnificationController;
}
/**
@@ -2625,6 +2708,149 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
@Override
+ public float getMagnificationScale() {
+ synchronized (mLock) {
+ // We treat calls from a profile as if made by its parent as profiles
+ // share the accessibility state of the parent. The call below
+ // performs the current profile parent resolution.
+ final int resolvedUserId = mSecurityPolicy
+ .resolveCallingUserIdEnforcingPermissionsLocked(
+ UserHandle.USER_CURRENT);
+ if (resolvedUserId != mCurrentUserId) {
+ return 1.0f;
+ }
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return getMagnificationController().getScale();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public Region getMagnifiedRegion() {
+ synchronized (mLock) {
+ // We treat calls from a profile as if made by its parent as profiles
+ // share the accessibility state of the parent. The call below
+ // performs the current profile parent resolution.
+ final int resolvedUserId = mSecurityPolicy
+ .resolveCallingUserIdEnforcingPermissionsLocked(
+ UserHandle.USER_CURRENT);
+ if (resolvedUserId != mCurrentUserId) {
+ return Region.obtain();
+ }
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ final Region region = Region.obtain();
+ getMagnificationController().getMagnifiedRegion(region);
+ return region;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public float getMagnificationCenterX() {
+ synchronized (mLock) {
+ // We treat calls from a profile as if made by its parent as profiles
+ // share the accessibility state of the parent. The call below
+ // performs the current profile parent resolution.
+ final int resolvedUserId = mSecurityPolicy
+ .resolveCallingUserIdEnforcingPermissionsLocked(
+ UserHandle.USER_CURRENT);
+ if (resolvedUserId != mCurrentUserId) {
+ return 0.0f;
+ }
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return getMagnificationController().getCenterX();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public float getMagnificationCenterY() {
+ synchronized (mLock) {
+ // We treat calls from a profile as if made by its parent as profiles
+ // share the accessibility state of the parent. The call below
+ // performs the current profile parent resolution.
+ final int resolvedUserId = mSecurityPolicy
+ .resolveCallingUserIdEnforcingPermissionsLocked(
+ UserHandle.USER_CURRENT);
+ if (resolvedUserId != mCurrentUserId) {
+ return 0.0f;
+ }
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return getMagnificationController().getCenterY();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public boolean resetMagnification(boolean animate) {
+ synchronized (mLock) {
+ // We treat calls from a profile as if made by its parent as profiles
+ // share the accessibility state of the parent. The call below
+ // performs the current profile parent resolution.
+ final int resolvedUserId = mSecurityPolicy
+ .resolveCallingUserIdEnforcingPermissionsLocked(
+ UserHandle.USER_CURRENT);
+ if (resolvedUserId != mCurrentUserId) {
+ return false;
+ }
+ final boolean permissionGranted = mSecurityPolicy.canControlMagnification(this);
+ if (!permissionGranted) {
+ return false;
+ }
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return getMagnificationController().reset(animate);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public boolean setMagnificationScaleAndCenter(float scale, float centerX, float centerY,
+ boolean animate) {
+ synchronized (mLock) {
+ // We treat calls from a profile as if made by its parent as profiles
+ // share the accessibility state of the parent. The call below
+ // performs the current profile parent resolution.
+ final int resolvedUserId = mSecurityPolicy
+ .resolveCallingUserIdEnforcingPermissionsLocked(
+ UserHandle.USER_CURRENT);
+ if (resolvedUserId != mCurrentUserId) {
+ return false;
+ }
+ final boolean permissionGranted = mSecurityPolicy.canControlMagnification(this);
+ if (!permissionGranted) {
+ return false;
+ }
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return getMagnificationController().setScaleAndCenter(
+ scale, centerX, centerY, animate);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void setMagnificationCallbackEnabled(boolean enabled) {
+ mInvocationHandler.setMagnificationCallbackEnabled(enabled);
+ }
+
+ @Override
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
mSecurityPolicy.enforceCallingPermission(Manifest.permission.DUMP, FUNCTION_DUMP);
synchronized (mLock) {
@@ -2819,6 +3045,30 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
InvocationHandler.MSG_CLEAR_ACCESSIBILITY_CACHE);
}
+ public void notifyMagnificationChanged(@NonNull Region region,
+ float scale, float centerX, float centerY) {
+ mInvocationHandler.notifyMagnificationChanged(region, scale, centerX, centerY);
+ }
+
+ /**
+ * Called by the invocation handler to notify the service that the
+ * state of magnification has changed.
+ */
+ private void notifyMagnificationChangedInternal(@NonNull Region region,
+ float scale, float centerX, float centerY) {
+ final IAccessibilityServiceClient listener;
+ synchronized (mLock) {
+ listener = mServiceInterface;
+ }
+ if (listener != null) {
+ try {
+ listener.onMagnificationChanged(region, scale, centerX, centerY);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error sending magnification changes to " + mService, re);
+ }
+ }
+ }
+
private void notifyGestureInternal(int gestureId) {
final IAccessibilityServiceClient listener;
synchronized (mLock) {
@@ -2959,6 +3209,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
public static final int MSG_CLEAR_ACCESSIBILITY_CACHE = 3;
public static final int MSG_ON_KEY_EVENT_TIMEOUT = 4;
+ private static final int MSG_ON_MAGNIFICATION_CHANGED = 5;
+
+ private boolean mIsMagnificationCallbackEnabled = false;
+
public InvocationHandler(Looper looper) {
super(looper, null, true);
}
@@ -2987,11 +3241,41 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
setOnKeyEventResult(false, eventState.sequence);
} break;
+ case MSG_ON_MAGNIFICATION_CHANGED: {
+ final SomeArgs args = (SomeArgs) message.obj;
+ final Region region = (Region) args.arg1;
+ final float scale = (float) args.arg2;
+ final float centerX = (float) args.arg3;
+ final float centerY = (float) args.arg4;
+ notifyMagnificationChangedInternal(region, scale, centerX, centerY);
+ } break;
+
default: {
throw new IllegalArgumentException("Unknown message: " + type);
}
}
}
+
+ public void notifyMagnificationChanged(@NonNull Region region, float scale,
+ float centerX, float centerY) {
+ if (!mIsMagnificationCallbackEnabled) {
+ // Callback is disabled, don't bother packing args.
+ return;
+ }
+
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = region;
+ args.arg2 = scale;
+ args.arg3 = centerX;
+ args.arg4 = centerY;
+
+ final Message msg = obtainMessage(MSG_ON_MAGNIFICATION_CHANGED, args);
+ msg.sendToTarget();
+ }
+
+ public void setMagnificationCallbackEnabled(boolean enabled) {
+ mIsMagnificationCallbackEnabled = enabled;
+ }
}
private final class KeyEventDispatcher {
@@ -3660,6 +3944,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
& AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT) != 0;
}
+ public boolean canControlMagnification(Service service) {
+ return (service.mAccessibilityServiceInfo.getCapabilities()
+ & AccessibilityServiceInfo.CAPABILITY_CAN_CONTROL_MAGNIFICATION) != 0;
+ }
+
private int resolveProfileParentLocked(int userId) {
if (userId != mCurrentUserId) {
final long identity = Binder.clearCallingIdentity();
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
index 781d13453be4..a093d9225deb 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
@@ -17,20 +17,36 @@
package com.android.server.accessibility;
import com.android.internal.R;
+import com.android.internal.os.SomeArgs;
import com.android.server.LocalServices;
import android.animation.ObjectAnimator;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.graphics.Rect;
import android.graphics.Region;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.MathUtils;
import android.util.Property;
import android.util.Slog;
import android.view.MagnificationSpec;
+import android.view.View;
import android.view.WindowManagerInternal;
import android.view.animation.DecelerateInterpolator;
+import java.util.Locale;
+
/**
* This class is used to control and query the state of display magnification
* from the accessibility manager and related classes. It is responsible for
@@ -38,37 +54,71 @@ import android.view.animation.DecelerateInterpolator;
* communication between the accessibility manager and window manager.
*/
class MagnificationController {
- private static final String LOG_TAG = ScreenMagnifier.class.getSimpleName();
+ private static final String LOG_TAG = "MagnificationController";
private static final boolean DEBUG_SET_MAGNIFICATION_SPEC = false;
- private static final boolean DEBUG_MAGNIFICATION_CONTROLLER = false;
- private static final String PROPERTY_NAME_MAGNIFICATION_SPEC = "magnificationSpec";
+ private static final int DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE = 1;
+
+ private static final float DEFAULT_MAGNIFICATION_SCALE = 2.0f;
+
+ private static final float MIN_SCALE = 1.0f;
+ private static final float MAX_SCALE = 5.0f;
+
+ /**
+ * The minimum scaling factor that can be persisted to secure settings.
+ * This must be > 1.0 to ensure that magnification is actually set to an
+ * enabled state when the scaling factor is restored from settings.
+ */
+ private static final float MIN_PERSISTED_SCALE = 2.0f;
- private final MagnificationSpec mSentMagnificationSpec = MagnificationSpec.obtain();
+ private final Object mLock = new Object();
+
+ /**
+ * The current magnification spec. If an animation is running, this
+ * reflects the end state.
+ */
private final MagnificationSpec mCurrentMagnificationSpec = MagnificationSpec.obtain();
- private final Region mMagnifiedBounds = new Region();
+ private final Region mMagnifiedRegion = Region.obtain();
+ private final Region mAvailableRegion = Region.obtain();
+ private final Rect mMagnifiedBounds = new Rect();
+
private final Rect mTempRect = new Rect();
+ private final Rect mTempRect1 = new Rect();
private final AccessibilityManagerService mAms;
- private final WindowManagerInternal mWindowManager;
- private final ValueAnimator mTransformationAnimator;
+ private final ContentResolver mContentResolver;
+
+ private final ScreenStateObserver mScreenStateObserver;
+ private final WindowStateObserver mWindowStateObserver;
+
+ private final SpecAnimationBridge mSpecAnimationBridge;
+
+ private int mUserId;
public MagnificationController(Context context, AccessibilityManagerService ams) {
mAms = ams;
- mWindowManager = LocalServices.getService(WindowManagerInternal.class);
+ mContentResolver = context.getContentResolver();
+ mScreenStateObserver = new ScreenStateObserver(context, this);
+ mWindowStateObserver = new WindowStateObserver(context, this);
+ mSpecAnimationBridge = new SpecAnimationBridge(context);
+ }
- final Property<MagnificationController, MagnificationSpec> property =
- Property.of(MagnificationController.class, MagnificationSpec.class,
- PROPERTY_NAME_MAGNIFICATION_SPEC);
- final MagnificationSpecEvaluator evaluator = new MagnificationSpecEvaluator();
- final long animationDuration = context.getResources().getInteger(
- R.integer.config_longAnimTime);
- mTransformationAnimator = ObjectAnimator.ofObject(this, property, evaluator,
- mSentMagnificationSpec, mCurrentMagnificationSpec);
- mTransformationAnimator.setDuration(animationDuration);
- mTransformationAnimator.setInterpolator(new DecelerateInterpolator(2.5f));
+ /**
+ * Registers magnification-related observers.
+ */
+ public void register() {
+ mScreenStateObserver.register();
+ mWindowStateObserver.register();
+ }
+
+ /**
+ * Unregisters magnification-related observers.
+ */
+ public void unregister() {
+ mScreenStateObserver.unregister();
+ mWindowStateObserver.unregister();
}
/**
@@ -80,26 +130,33 @@ class MagnificationController {
}
/**
- * Sets the magnified region.
+ * Sets the magnified and available regions.
*
- * @param region the region to set
- * @param updateSpec {@code true} to update the scale and center based on
- * the region bounds, {@code false} to leave them as-is
+ * @param magnified the magnified region
+ * @param available the region available for magnification
+ * @param updateSpec {@code true} to update the scale and center based on
+ * the region bounds, {@code false} to leave them as-is
*/
- public void setMagnifiedRegion(Region region, boolean updateSpec) {
- mMagnifiedBounds.set(region);
-
- if (updateSpec) {
- final Rect magnifiedFrame = mTempRect;
- region.getBounds(magnifiedFrame);
- final float scale = mSentMagnificationSpec.scale;
- final float offsetX = mSentMagnificationSpec.offsetX;
- final float offsetY = mSentMagnificationSpec.offsetY;
- final float centerX = (-offsetX + magnifiedFrame.width() / 2) / scale;
- final float centerY = (-offsetY + magnifiedFrame.height() / 2) / scale;
- setScaleAndMagnifiedRegionCenter(scale, centerX, centerY, false);
- } else {
- mAms.onMagnificationStateChanged();
+ public void setMagnifiedRegion(Region magnified, Region available, boolean updateSpec) {
+ synchronized (mLock) {
+ mMagnifiedRegion.set(magnified);
+ mMagnifiedRegion.getBounds(mMagnifiedBounds);
+ mAvailableRegion.set(available);
+
+ final MagnificationSpec sentSpec = mSpecAnimationBridge.mSentMagnificationSpec;
+ final float scale = sentSpec.scale;
+ final float offsetX = sentSpec.offsetX;
+ final float offsetY = sentSpec.offsetY;
+
+ // Compute the new center and update spec as needed.
+ final float centerX = (mMagnifiedBounds.width() / 2.0f - offsetX) / scale;
+ final float centerY = (mMagnifiedBounds.height() / 2.0f - offsetY) / scale;
+ if (updateSpec) {
+ setScaleAndCenter(scale, centerX, centerY, false);
+ } else {
+ mAms.onMagnificationStateChanged();
+ mAms.notifyMagnificationChanged(mMagnifiedRegion, scale, centerX, centerY);
+ }
}
}
@@ -113,18 +170,51 @@ class MagnificationController {
* magnified region, or {@code false} otherwise
*/
public boolean magnifiedRegionContains(float x, float y) {
- return mMagnifiedBounds.contains((int) x, (int) y);
+ synchronized (mLock) {
+ return mMagnifiedRegion.contains((int) x, (int) y);
+ }
}
/**
- * Populates the specified rect with the bounds of the magnified
- * region.
+ * Returns whether the region available for magnification contains the
+ * specified screen-relative coordinates.
+ *
+ * @param x the screen-relative X coordinate to check
+ * @param y the screen-relative Y coordinate to check
+ * @return {@code true} if the coordinate is contained within the
+ * region available for magnification, or {@code false} otherwise
+ */
+ private boolean availableRegionContains(float x, float y) {
+ synchronized (mLock) {
+ return mAvailableRegion.contains((int) x, (int) y);
+ }
+ }
+
+ /**
+ * Populates the specified rect with the screen-relative bounds of the
+ * magnified region. If magnification is not enabled, the returned
+ * bounds will be empty.
*
* @param outBounds rect to populate with the bounds of the magnified
* region
*/
- public void getMagnifiedBounds(Rect outBounds) {
- mMagnifiedBounds.getBounds(outBounds);
+ public void getMagnifiedBounds(@NonNull Rect outBounds) {
+ synchronized (mLock) {
+ outBounds.set(mMagnifiedBounds);
+ }
+ }
+
+ /**
+ * Populates the specified region with the screen-relative magnified
+ * region. If magnification is not enabled, then the returned region
+ * will be empty.
+ *
+ * @param outRegion the region to populate
+ */
+ public void getMagnifiedRegion(@NonNull Region outRegion) {
+ synchronized (mLock) {
+ outRegion.set(mMagnifiedRegion);
+ }
}
/**
@@ -147,6 +237,19 @@ class MagnificationController {
return mCurrentMagnificationSpec.offsetX;
}
+
+ /**
+ * Returns the screen-relative X coordinate of the center of the
+ * magnification viewport.
+ *
+ * @return the X coordinate
+ */
+ public float getCenterX() {
+ synchronized (mLock) {
+ return (mMagnifiedBounds.width() / 2.0f - getOffsetX()) / getScale();
+ }
+ }
+
/**
* Returns the Y offset of the magnification viewport. If an animation
* is in progress, this reflects the end state of the animation.
@@ -158,6 +261,18 @@ class MagnificationController {
}
/**
+ * Returns the screen-relative Y coordinate of the center of the
+ * magnification viewport.
+ *
+ * @return the Y coordinate
+ */
+ public float getCenterY() {
+ synchronized (mLock) {
+ return (mMagnifiedBounds.height() / 2.0f - getOffsetY()) / getScale();
+ }
+ }
+
+ /**
* Returns the scale currently used by the window manager. If an
* animation is in progress, this reflects the current state of the
* animation.
@@ -165,7 +280,7 @@ class MagnificationController {
* @return the scale currently used by the window manager
*/
public float getSentScale() {
- return mSentMagnificationSpec.scale;
+ return mSpecAnimationBridge.mSentMagnificationSpec.scale;
}
/**
@@ -176,7 +291,7 @@ class MagnificationController {
* @return the X offset currently used by the window manager
*/
public float getSentOffsetX() {
- return mSentMagnificationSpec.offsetX;
+ return mSpecAnimationBridge.mSentMagnificationSpec.offsetX;
}
/**
@@ -187,7 +302,7 @@ class MagnificationController {
* @return the Y offset currently used by the window manager
*/
public float getSentOffsetY() {
- return mSentMagnificationSpec.offsetY;
+ return mSpecAnimationBridge.mSentMagnificationSpec.offsetY;
}
/**
@@ -196,21 +311,24 @@ class MagnificationController {
*
* @param animate {@code true} to animate the transition, {@code false}
* to transition immediately
+ * @return {@code true} if the magnification spec changed, {@code false} if
+ * the spec did not change
*/
- public void reset(boolean animate) {
- if (mTransformationAnimator.isRunning()) {
- mTransformationAnimator.cancel();
+ public boolean reset(boolean animate) {
+ synchronized (mLock) {
+ return resetLocked(animate);
}
- mCurrentMagnificationSpec.clear();
- if (animate) {
- animateMagnificationSpec(mSentMagnificationSpec,
- mCurrentMagnificationSpec);
- } else {
- setMagnificationSpec(mCurrentMagnificationSpec);
+ }
+
+ private boolean resetLocked(boolean animate) {
+ final MagnificationSpec spec = mCurrentMagnificationSpec;
+ final boolean changed = !spec.isNop();
+ if (changed) {
+ spec.clear();
}
- final Rect bounds = mTempRect;
- bounds.setEmpty();
- mAms.onMagnificationStateChanged();
+
+ mSpecAnimationBridge.updateSentSpec(spec, animate);
+ return changed;
}
/**
@@ -219,23 +337,32 @@ class MagnificationController {
* transition is immediate.
*
* @param scale the target scale, must be >= 1
+ * @param pivotX the screen-relative X coordinate around which to scale
+ * @param pivotY the screen-relative Y coordinate around which to scale
* @param animate {@code true} to animate the transition, {@code false}
* to transition immediately
+ * @return {@code true} if the magnification spec changed, {@code false} if
+ * the spec did not change
*/
- public void setScale(float scale, float pivotX, float pivotY, boolean animate) {
- final Rect magnifiedFrame = mTempRect;
- mMagnifiedBounds.getBounds(magnifiedFrame);
- final MagnificationSpec spec = mCurrentMagnificationSpec;
- final float oldScale = spec.scale;
- final float oldCenterX = (-spec.offsetX + magnifiedFrame.width() / 2) / oldScale;
- final float oldCenterY = (-spec.offsetY + magnifiedFrame.height() / 2) / oldScale;
- final float normPivotX = (-spec.offsetX + pivotX) / oldScale;
- final float normPivotY = (-spec.offsetY + pivotY) / oldScale;
- final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale);
- final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale);
- final float centerX = normPivotX + offsetX;
- final float centerY = normPivotY + offsetY;
- setScaleAndMagnifiedRegionCenter(scale, centerX, centerY, animate);
+ public boolean setScale(float scale, float pivotX, float pivotY, boolean animate) {
+ synchronized (mLock) {
+ // Constrain scale immediately for use in the pivot calculations.
+ scale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
+
+ final Rect viewport = mTempRect;
+ mMagnifiedRegion.getBounds(viewport);
+ final MagnificationSpec spec = mCurrentMagnificationSpec;
+ final float oldScale = spec.scale;
+ final float oldCenterX = (viewport.width() / 2.0f - spec.offsetX) / oldScale;
+ final float oldCenterY = (viewport.height() / 2.0f - spec.offsetY) / oldScale;
+ final float normPivotX = (pivotX - spec.offsetX) / oldScale;
+ final float normPivotY = (pivotY - spec.offsetY) / oldScale;
+ final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale);
+ final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale);
+ final float centerX = normPivotX + offsetX;
+ final float centerY = normPivotY + offsetY;
+ return setScaleAndCenterLocked(scale, centerX, centerY, animate);
+ }
}
/**
@@ -248,10 +375,13 @@ class MagnificationController {
* center
* @param animate {@code true} to animate the transition, {@code false}
* to transition immediately
+ * @return {@code true} if the magnification spec changed, {@code false} if
+ * the spec did not change
*/
- public void setMagnifiedRegionCenter(float centerX, float centerY, boolean animate) {
- setScaleAndMagnifiedRegionCenter(mCurrentMagnificationSpec.scale, centerX, centerY,
- animate);
+ public boolean setCenter(float centerX, float centerY, boolean animate) {
+ synchronized (mLock) {
+ return setScaleAndCenterLocked(Float.NaN, centerX, centerY, animate);
+ }
}
/**
@@ -259,35 +389,27 @@ class MagnificationController {
* animating the transition. If animation is disabled, the transition
* is immediate.
*
- * @param scale the target scale, must be >= 1
+ * @param scale the target scale, or {@link Float#NaN} to leave unchanged
* @param centerX the screen-relative X coordinate around which to
- * center and scale
+ * center and scale, or {@link Float#NaN} to leave unchanged
* @param centerY the screen-relative Y coordinate around which to
- * center and scale
+ * center and scale, or {@link Float#NaN} to leave unchanged
* @param animate {@code true} to animate the transition, {@code false}
* to transition immediately
+ * @return {@code true} if the magnification spec changed, {@code false} if
+ * the spec did not change
*/
- public void setScaleAndMagnifiedRegionCenter(float scale, float centerX, float centerY,
- boolean animate) {
- if (Float.compare(mCurrentMagnificationSpec.scale, scale) == 0
- && Float.compare(mCurrentMagnificationSpec.offsetX, centerX) == 0
- && Float.compare(mCurrentMagnificationSpec.offsetY, centerY) == 0) {
- return;
- }
- if (mTransformationAnimator.isRunning()) {
- mTransformationAnimator.cancel();
+ public boolean setScaleAndCenter(float scale, float centerX, float centerY, boolean animate) {
+ synchronized (mLock) {
+ return setScaleAndCenterLocked(scale, centerX, centerY, animate);
}
- if (DEBUG_MAGNIFICATION_CONTROLLER) {
- Slog.i(LOG_TAG, "scale: " + scale + " offsetX: " + centerX + " offsetY: " + centerY);
- }
- updateMagnificationSpec(scale, centerX, centerY);
- if (animate) {
- animateMagnificationSpec(mSentMagnificationSpec,
- mCurrentMagnificationSpec);
- } else {
- setMagnificationSpec(mCurrentMagnificationSpec);
- }
- mAms.onMagnificationStateChanged();
+ }
+
+ private boolean setScaleAndCenterLocked(
+ float scale, float centerX, float centerY, boolean animate) {
+ final boolean changed = updateMagnificationSpecLocked(scale, centerX, centerY);
+ mSpecAnimationBridge.updateSentSpec(mCurrentMagnificationSpec, animate);
+ return changed;
}
/**
@@ -297,75 +419,504 @@ class MagnificationController {
* @param offsetY the amount in pixels to offset the Y center
*/
public void offsetMagnifiedRegionCenter(float offsetX, float offsetY) {
- final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX;
- mCurrentMagnificationSpec.offsetX = Math.min(Math.max(nonNormOffsetX,
- getMinOffsetX()), 0);
- final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY;
- mCurrentMagnificationSpec.offsetY = Math.min(Math.max(nonNormOffsetY,
- getMinOffsetY()), 0);
- setMagnificationSpec(mCurrentMagnificationSpec);
- }
-
- private void updateMagnificationSpec(float scale, float magnifiedCenterX,
- float magnifiedCenterY) {
- final Rect magnifiedFrame = mTempRect;
- mMagnifiedBounds.getBounds(magnifiedFrame);
- mCurrentMagnificationSpec.scale = scale;
- final int viewportWidth = magnifiedFrame.width();
- final float nonNormOffsetX = viewportWidth / 2 - magnifiedCenterX * scale;
- mCurrentMagnificationSpec.offsetX = Math.min(Math.max(nonNormOffsetX,
- getMinOffsetX()), 0);
- final int viewportHeight = magnifiedFrame.height();
- final float nonNormOffsetY = viewportHeight / 2 - magnifiedCenterY * scale;
- mCurrentMagnificationSpec.offsetY = Math.min(Math.max(nonNormOffsetY,
- getMinOffsetY()), 0);
- }
-
- private float getMinOffsetX() {
- final Rect magnifiedFrame = mTempRect;
- mMagnifiedBounds.getBounds(magnifiedFrame);
- final float viewportWidth = magnifiedFrame.width();
+ synchronized (mLock) {
+ final MagnificationSpec currSpec = mCurrentMagnificationSpec;
+ final float nonNormOffsetX = currSpec.offsetX - offsetX;
+ currSpec.offsetX = MathUtils.constrain(nonNormOffsetX, getMinOffsetXLocked(), 0);
+ final float nonNormOffsetY = currSpec.offsetY - offsetY;
+ currSpec.offsetY = MathUtils.constrain(nonNormOffsetY, getMinOffsetYLocked(), 0);
+ mSpecAnimationBridge.updateSentSpec(currSpec, false);
+ }
+ }
+
+ /**
+ * Persists the current magnification scale to the current user's settings.
+ */
+ public void persistScale() {
+ final float scale = mCurrentMagnificationSpec.scale;
+ final int userId = mUserId;
+
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ Settings.Secure.putFloatForUser(mContentResolver,
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale, userId);
+ return null;
+ }
+ }.execute();
+ }
+
+ /**
+ * Retrieves a previously persisted magnification scale from the current
+ * user's settings.
+ *
+ * @return the previously persisted magnification scale, or the default
+ * scale if none is available
+ */
+ public float getPersistedScale() {
+ return Settings.Secure.getFloatForUser(mContentResolver,
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
+ DEFAULT_MAGNIFICATION_SCALE, mUserId);
+ }
+
+ /**
+ * Updates the current magnification spec.
+ *
+ * @param scale the magnification scale
+ * @param centerX the unscaled, screen-relative X coordinate of the center
+ * of the viewport, or {@link Float#NaN} to leave unchanged
+ * @param centerY the unscaled, screen-relative Y coordinate of the center
+ * of the viewport, or {@link Float#NaN} to leave unchanged
+ * @return {@code true} if the magnification spec changed or {@code false}
+ * otherwise
+ */
+ private boolean updateMagnificationSpecLocked(float scale, float centerX, float centerY) {
+ if (!availableRegionContains(centerX, centerY)) {
+ return false;
+ }
+
+ boolean changed = false;
+
+ final MagnificationSpec currSpec = mCurrentMagnificationSpec;
+
+ // Handle scale.
+ if (Float.isNaN(scale)) {
+ scale = getScale();
+ }
+
+ final float normScale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
+ if (Float.compare(currSpec.scale, normScale) != 0) {
+ currSpec.scale = normScale;
+ changed = true;
+ }
+
+ // Handle X offset.
+ if (Float.isNaN(centerX)) {
+ centerX = getCenterX();
+ }
+
+ final float nonNormOffsetX = mMagnifiedBounds.width() / 2.0f - centerX * scale;
+ final float offsetX = MathUtils.constrain(nonNormOffsetX, getMinOffsetXLocked(), 0);
+ if (Float.compare(currSpec.offsetX, offsetX) != 0) {
+ currSpec.offsetX = offsetX;
+ changed = true;
+ }
+
+ // Handle Y offset.
+ if (Float.isNaN(centerY)) {
+ centerY = getCenterY();
+ }
+
+ final float nonNormOffsetY = mMagnifiedBounds.height() / 2.0f - centerY * scale;
+ final float offsetY = MathUtils.constrain(nonNormOffsetY, getMinOffsetYLocked(), 0);
+ if (Float.compare(currSpec.offsetY, offsetY) != 0) {
+ currSpec.offsetY = offsetY;
+ changed = true;
+ }
+
+ return changed;
+ }
+
+ private float getMinOffsetXLocked() {
+ final float viewportWidth = mMagnifiedBounds.width();
return viewportWidth - viewportWidth * mCurrentMagnificationSpec.scale;
}
- private float getMinOffsetY() {
- final Rect magnifiedFrame = mTempRect;
- mMagnifiedBounds.getBounds(magnifiedFrame);
- final float viewportHeight = magnifiedFrame.height();
+ private float getMinOffsetYLocked() {
+ final float viewportHeight = mMagnifiedBounds.height();
return viewportHeight - viewportHeight * mCurrentMagnificationSpec.scale;
}
- private void animateMagnificationSpec(MagnificationSpec fromSpec,
- MagnificationSpec toSpec) {
- mTransformationAnimator.setObjectValues(fromSpec, toSpec);
- mTransformationAnimator.start();
+ /**
+ * Sets the currently active user ID.
+ *
+ * @param userId the currently active user ID
+ */
+ public void setUserId(int userId) {
+ if (mUserId != userId) {
+ mUserId = userId;
+
+ synchronized (mLock) {
+ if (isMagnifying()) {
+ reset(false);
+ }
+ }
+ }
+ }
+
+ private boolean isScreenMagnificationAutoUpdateEnabled() {
+ return (Settings.Secure.getInt(mContentResolver,
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE,
+ DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE) == 1);
}
- public void setMagnificationSpec(MagnificationSpec spec) {
- if (DEBUG_SET_MAGNIFICATION_SPEC) {
- Slog.i(LOG_TAG, "Sending: " + spec);
+ /**
+ * Resets magnification if magnification and auto-update are both enabled.
+ *
+ * @param animate whether the animate the transition
+ * @return {@code true} if magnification was reset to the disabled state,
+ * {@code false} if magnification is still active
+ */
+ boolean resetIfNeeded(boolean animate) {
+ synchronized (mLock) {
+ if (isMagnifying() && isScreenMagnificationAutoUpdateEnabled()) {
+ reset(animate);
+ return true;
+ }
+ return false;
+ }
+ }
+
+ private void getMagnifiedFrameInContentCoordsLocked(Rect outFrame) {
+ final float scale = getSentScale();
+ final float offsetX = getSentOffsetX();
+ final float offsetY = getSentOffsetY();
+ getMagnifiedBounds(outFrame);
+ outFrame.offset((int) -offsetX, (int) -offsetY);
+ outFrame.scale(1.0f / scale);
+ }
+
+ private void requestRectangleOnScreen(int left, int top, int right, int bottom) {
+ synchronized (mLock) {
+ final Rect magnifiedFrame = mTempRect;
+ getMagnifiedBounds(magnifiedFrame);
+ if (!magnifiedFrame.intersects(left, top, right, bottom)) {
+ return;
+ }
+
+ final Rect magnifFrameInScreenCoords = mTempRect1;
+ getMagnifiedFrameInContentCoordsLocked(magnifFrameInScreenCoords);
+
+ final float scrollX;
+ final float scrollY;
+ if (right - left > magnifFrameInScreenCoords.width()) {
+ final int direction = TextUtils
+ .getLayoutDirectionFromLocale(Locale.getDefault());
+ if (direction == View.LAYOUT_DIRECTION_LTR) {
+ scrollX = left - magnifFrameInScreenCoords.left;
+ } else {
+ scrollX = right - magnifFrameInScreenCoords.right;
+ }
+ } else if (left < magnifFrameInScreenCoords.left) {
+ scrollX = left - magnifFrameInScreenCoords.left;
+ } else if (right > magnifFrameInScreenCoords.right) {
+ scrollX = right - magnifFrameInScreenCoords.right;
+ } else {
+ scrollX = 0;
+ }
+
+ if (bottom - top > magnifFrameInScreenCoords.height()) {
+ scrollY = top - magnifFrameInScreenCoords.top;
+ } else if (top < magnifFrameInScreenCoords.top) {
+ scrollY = top - magnifFrameInScreenCoords.top;
+ } else if (bottom > magnifFrameInScreenCoords.bottom) {
+ scrollY = bottom - magnifFrameInScreenCoords.bottom;
+ } else {
+ scrollY = 0;
+ }
+
+ final float scale = getScale();
+ offsetMagnifiedRegionCenter(scrollX * scale, scrollY * scale);
+ }
+ }
+
+ /**
+ * Class responsible for animating spec on the main thread and sending spec
+ * updates to the window manager.
+ */
+ private static class SpecAnimationBridge {
+ private static final int ACTION_UPDATE_SPEC = 1;
+
+ private final Handler mHandler;
+ private final WindowManagerInternal mWindowManager;
+
+ /**
+ * The magnification spec that was sent to the window manager. This should
+ * only be accessed and modified on the main (e.g. animation) thread.
+ */
+ private final MagnificationSpec mSentMagnificationSpec = MagnificationSpec.obtain();
+
+ /**
+ * The animator that updates the sent spec. This should only be accessed
+ * and modified on the main (e.g. animation) thread.
+ */
+ private final ValueAnimator mTransformationAnimator;
+
+ private final long mMainThreadId;
+
+ private SpecAnimationBridge(Context context) {
+ final Looper mainLooper = context.getMainLooper();
+ mMainThreadId = mainLooper.getThread().getId();
+
+ mHandler = new UpdateHandler(context);
+ mWindowManager = LocalServices.getService(WindowManagerInternal.class);
+
+ final MagnificationSpecProperty property = new MagnificationSpecProperty();
+ final MagnificationSpecEvaluator evaluator = new MagnificationSpecEvaluator();
+ final long animationDuration = context.getResources().getInteger(
+ R.integer.config_longAnimTime);
+ mTransformationAnimator = ObjectAnimator.ofObject(this, property, evaluator,
+ mSentMagnificationSpec);
+ mTransformationAnimator.setDuration(animationDuration);
+ mTransformationAnimator.setInterpolator(new DecelerateInterpolator(2.5f));
+ }
+
+ public void updateSentSpec(MagnificationSpec spec, boolean animate) {
+ if (Thread.currentThread().getId() == mMainThreadId) {
+ // Already on the main thread, don't bother proxying.
+ updateSentSpecInternal(spec, animate);
+ } else {
+ mHandler.obtainMessage(ACTION_UPDATE_SPEC,
+ animate ? 1 : 0, 0, spec).sendToTarget();
+ }
+ }
+
+ /**
+ * Updates the sent spec.
+ */
+ private void updateSentSpecInternal(MagnificationSpec spec, boolean animate) {
+ if (mTransformationAnimator.isRunning()) {
+ mTransformationAnimator.cancel();
+ }
+
+ // If the current and sent specs don't match, update the sent spec.
+ final boolean changed = !mSentMagnificationSpec.equals(spec);
+ if (changed) {
+ if (animate) {
+ animateMagnificationSpec(spec);
+ } else {
+ setMagnificationSpec(spec);
+ }
+ }
+ }
+
+ private void animateMagnificationSpec(MagnificationSpec toSpec) {
+ mTransformationAnimator.setObjectValues(mSentMagnificationSpec, toSpec);
+ mTransformationAnimator.start();
+ }
+
+ private void setMagnificationSpec(MagnificationSpec spec) {
+ if (DEBUG_SET_MAGNIFICATION_SPEC) {
+ Slog.i(LOG_TAG, "Sending: " + spec);
+ }
+
+ mSentMagnificationSpec.setTo(spec);
+ mWindowManager.setMagnificationSpec(spec);
+ }
+
+ private class UpdateHandler extends Handler {
+ public UpdateHandler(Context context) {
+ super(context.getMainLooper());
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case ACTION_UPDATE_SPEC:
+ final boolean animate = msg.arg1 == 1;
+ final MagnificationSpec spec = (MagnificationSpec) msg.obj;
+ updateSentSpecInternal(spec, animate);
+ break;
+ }
+ }
+ }
+
+ private static class MagnificationSpecProperty
+ extends Property<SpecAnimationBridge, MagnificationSpec> {
+ public MagnificationSpecProperty() {
+ super(MagnificationSpec.class, "spec");
+ }
+
+ @Override
+ public MagnificationSpec get(SpecAnimationBridge object) {
+ return object.mSentMagnificationSpec;
+ }
+
+ @Override
+ public void set(SpecAnimationBridge object, MagnificationSpec value) {
+ object.setMagnificationSpec(value);
+ }
+ }
+
+ private static class MagnificationSpecEvaluator
+ implements TypeEvaluator<MagnificationSpec> {
+ private final MagnificationSpec mTempSpec = MagnificationSpec.obtain();
+
+ @Override
+ public MagnificationSpec evaluate(float fraction, MagnificationSpec fromSpec,
+ MagnificationSpec toSpec) {
+ final MagnificationSpec result = mTempSpec;
+ result.scale = fromSpec.scale + (toSpec.scale - fromSpec.scale) * fraction;
+ result.offsetX = fromSpec.offsetX + (toSpec.offsetX - fromSpec.offsetX) * fraction;
+ result.offsetY = fromSpec.offsetY + (toSpec.offsetY - fromSpec.offsetY) * fraction;
+ return result;
+ }
}
- mSentMagnificationSpec.scale = spec.scale;
- mSentMagnificationSpec.offsetX = spec.offsetX;
- mSentMagnificationSpec.offsetY = spec.offsetY;
- mWindowManager.setMagnificationSpec(MagnificationSpec.obtain(spec));
}
- public MagnificationSpec getMagnificationSpec() {
- return mSentMagnificationSpec;
+ private static class ScreenStateObserver extends BroadcastReceiver {
+ private static final int MESSAGE_ON_SCREEN_STATE_CHANGE = 1;
+
+ private final Context mContext;
+ private final MagnificationController mController;
+ private final Handler mHandler;
+
+ public ScreenStateObserver(Context context, MagnificationController controller) {
+ mContext = context;
+ mController = controller;
+ mHandler = new StateChangeHandler(context);
+ }
+
+ public void register() {
+ mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_SCREEN_OFF));
+ }
+
+ public void unregister() {
+ mContext.unregisterReceiver(this);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mHandler.obtainMessage(MESSAGE_ON_SCREEN_STATE_CHANGE,
+ intent.getAction()).sendToTarget();
+ }
+
+ private void handleOnScreenStateChange() {
+ mController.resetIfNeeded(false);
+ }
+
+ private class StateChangeHandler extends Handler {
+ public StateChangeHandler(Context context) {
+ super(context.getMainLooper());
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MESSAGE_ON_SCREEN_STATE_CHANGE:
+ handleOnScreenStateChange();
+ break;
+ }
+ }
+ }
}
- private static class MagnificationSpecEvaluator implements TypeEvaluator<MagnificationSpec> {
- private final MagnificationSpec mTempTransformationSpec = MagnificationSpec.obtain();
+ /**
+ * This class handles the screen magnification when accessibility is enabled.
+ */
+ private static class WindowStateObserver
+ implements WindowManagerInternal.MagnificationCallbacks {
+ private static final int MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED = 1;
+ private static final int MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED = 2;
+ private static final int MESSAGE_ON_USER_CONTEXT_CHANGED = 3;
+ private static final int MESSAGE_ON_ROTATION_CHANGED = 4;
+
+ private final Rect mTempRect = new Rect();
+ private final Rect mTempRect1 = new Rect();
+
+ private final MagnificationController mController;
+ private final WindowManagerInternal mWindowManager;
+ private final Handler mHandler;
+
+ private boolean mSpecIsDirty;
+
+ public WindowStateObserver(Context context, MagnificationController controller) {
+ mController = controller;
+ mWindowManager = LocalServices.getService(WindowManagerInternal.class);
+ mHandler = new CallbackHandler(context);
+ }
+
+ public void register() {
+ mWindowManager.setMagnificationCallbacks(this);
+ }
+
+ public void unregister() {
+ mWindowManager.setMagnificationCallbacks(null);
+ }
@Override
- public MagnificationSpec evaluate(float fraction, MagnificationSpec fromSpec,
- MagnificationSpec toSpec) {
- final MagnificationSpec result = mTempTransformationSpec;
- result.scale = fromSpec.scale + (toSpec.scale - fromSpec.scale) * fraction;
- result.offsetX = fromSpec.offsetX + (toSpec.offsetX - fromSpec.offsetX) * fraction;
- result.offsetY = fromSpec.offsetY + (toSpec.offsetY - fromSpec.offsetY) * fraction;
- return result;
+ public void onMagnifiedBoundsChanged(Region magnified, Region available) {
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = Region.obtain(magnified);
+ args.arg2 = Region.obtain(available);
+ mHandler.obtainMessage(MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED, args).sendToTarget();
+ }
+
+ private void handleOnMagnifiedBoundsChanged(Region magnified, Region available) {
+ mController.setMagnifiedRegion(magnified, available, mSpecIsDirty);
+ mSpecIsDirty = false;
+ }
+
+ @Override
+ public void onRectangleOnScreenRequested(int left, int top, int right, int bottom) {
+ final SomeArgs args = SomeArgs.obtain();
+ args.argi1 = left;
+ args.argi2 = top;
+ args.argi3 = right;
+ args.argi4 = bottom;
+ mHandler.obtainMessage(MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED, args).sendToTarget();
+ }
+
+ private void handleOnRectangleOnScreenRequested(int left, int top, int right, int bottom) {
+ mController.requestRectangleOnScreen(left, top, right, bottom);
+ }
+
+ @Override
+ public void onRotationChanged(int rotation) {
+ mHandler.obtainMessage(MESSAGE_ON_ROTATION_CHANGED, rotation, 0).sendToTarget();
+ }
+
+ private void handleOnRotationChanged() {
+ // If there was a rotation and magnification is still enabled,
+ // we'll need to rewrite the spec to reflect the new screen
+ // configuration. Conveniently, we'll receive a callback from
+ // the window manager with updated bounds for the magnified
+ // region.
+ mSpecIsDirty = !mController.resetIfNeeded(true);
+ }
+
+ @Override
+ public void onUserContextChanged() {
+ mHandler.sendEmptyMessage(MESSAGE_ON_USER_CONTEXT_CHANGED);
+ }
+
+ private void handleOnUserContextChanged() {
+ mController.resetIfNeeded(true);
+ }
+
+ private class CallbackHandler extends Handler {
+ public CallbackHandler(Context context) {
+ super(context.getMainLooper());
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED: {
+ final SomeArgs args = (SomeArgs) message.obj;
+ final Region magnifiedBounds = (Region) args.arg1;
+ final Region availableBounds = (Region) args.arg2;
+ handleOnMagnifiedBoundsChanged(magnifiedBounds, availableBounds);
+ magnifiedBounds.recycle();
+ availableBounds.recycle();
+ } break;
+ case MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED: {
+ final SomeArgs args = (SomeArgs) message.obj;
+ final int left = args.argi1;
+ final int top = args.argi2;
+ final int right = args.argi3;
+ final int bottom = args.argi4;
+ handleOnRectangleOnScreenRequested(left, top, right, bottom);
+ args.recycle();
+ } break;
+ case MESSAGE_ON_USER_CONTEXT_CHANGED: {
+ handleOnUserContextChanged();
+ } break;
+ case MESSAGE_ON_ROTATION_CHANGED: {
+ handleOnRotationChanged();
+ } break;
+ }
+ }
}
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
index 8feb167f4c99..51c8ab572350 100644
--- a/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,18 +16,10 @@
package com.android.server.accessibility;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.os.AsyncTask;
-import android.os.Binder;
import android.os.Handler;
import android.os.Message;
-import android.provider.Settings;
-import android.text.TextUtils;
+import android.util.MathUtils;
import android.util.Slog;
import android.util.TypedValue;
import android.view.GestureDetector;
@@ -39,18 +31,12 @@ import android.view.MotionEvent.PointerCoords;
import android.view.MotionEvent.PointerProperties;
import android.view.ScaleGestureDetector;
import android.view.ScaleGestureDetector.OnScaleGestureListener;
-import android.view.View;
import android.view.ViewConfiguration;
-import android.view.WindowManagerInternal;
import android.view.accessibility.AccessibilityEvent;
-import com.android.internal.os.SomeArgs;
-import com.android.server.LocalServices;
-
-import java.util.Locale;
-
/**
- * This class handles the screen magnification when accessibility is enabled.
+ * This class handles magnification in response to touch events.
+ *
* The behavior is as follows:
*
* 1. Triple tap toggles permanent screen magnification which is magnifying
@@ -88,10 +74,8 @@ import java.util.Locale;
*
* 6. The magnification scale will be persisted in settings and in the cloud.
*/
-public final class ScreenMagnifier implements WindowManagerInternal.MagnificationCallbacks,
- EventStreamTransformation {
-
- private static final String LOG_TAG = ScreenMagnifier.class.getSimpleName();
+class MagnificationGestureHandler implements EventStreamTransformation {
+ private static final String LOG_TAG = "MagnificationEventHandler";
private static final boolean DEBUG_STATE_TRANSITIONS = false;
private static final boolean DEBUG_DETECTING = false;
@@ -103,40 +87,19 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio
private static final int STATE_VIEWPORT_DRAGGING = 3;
private static final int STATE_MAGNIFIED_INTERACTION = 4;
- private static final float DEFAULT_MAGNIFICATION_SCALE = 2.0f;
-
- private static final int MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED = 1;
- private static final int MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED = 2;
- private static final int MESSAGE_ON_USER_CONTEXT_CHANGED = 3;
- private static final int MESSAGE_ON_ROTATION_CHANGED = 4;
+ private static final float MIN_SCALE = 2.0f;
+ private static final float MAX_SCALE = 5.0f;
- private static final int DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE = 1;
-
- private static final int MY_PID = android.os.Process.myPid();
-
- private final Rect mTempRect = new Rect();
- private final Rect mTempRect1 = new Rect();
-
- private final Context mContext;
- private final WindowManagerInternal mWindowManager;
private final MagnificationController mMagnificationController;
- private final ScreenStateObserver mScreenStateObserver;
-
private final DetectingStateHandler mDetectingStateHandler;
- private final MagnifiedContentInteractonStateHandler mMagnifiedContentInteractonStateHandler;
+ private final MagnifiedContentInteractionStateHandler mMagnifiedContentInteractionStateHandler;
private final StateViewportDraggingHandler mStateViewportDraggingHandler;
- private final int mUserId;
-
- private final int mTapTimeSlop = ViewConfiguration.getJumpTapTimeout();
- private final int mMultiTapTimeSlop;
- private final int mTapDistanceSlop;
- private final int mMultiTapDistanceSlop;
-
private EventStreamTransformation mNext;
private int mCurrentState;
private int mPreviousState;
+
private boolean mTranslationEnabledBeforePan;
private PointerCoords[] mTempPointerCoords;
@@ -144,189 +107,44 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio
private long mDelegatingStateDownTime;
- private boolean mUpdateMagnificationSpecOnNextBoundsChange;
-
- private final Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message message) {
- switch (message.what) {
- case MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED: {
- Region bounds = (Region) message.obj;
- handleOnMagnifiedBoundsChanged(bounds);
- bounds.recycle();
- } break;
- case MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED: {
- SomeArgs args = (SomeArgs) message.obj;
- final int left = args.argi1;
- final int top = args.argi2;
- final int right = args.argi3;
- final int bottom = args.argi4;
- handleOnRectangleOnScreenRequested(left, top, right, bottom);
- args.recycle();
- } break;
- case MESSAGE_ON_USER_CONTEXT_CHANGED: {
- handleOnUserContextChanged();
- } break;
- case MESSAGE_ON_ROTATION_CHANGED: {
- final int rotation = message.arg1;
- handleOnRotationChanged(rotation);
- } break;
- }
- }
- };
-
- public ScreenMagnifier(Context context, int userId, int displayId,
- AccessibilityManagerService service) {
- mContext = context;
- mUserId = userId;
- mWindowManager = LocalServices.getService(WindowManagerInternal.class);
-
- mMultiTapTimeSlop = ViewConfiguration.getDoubleTapTimeout()
- + mContext.getResources().getInteger(
- com.android.internal.R.integer.config_screen_magnification_multi_tap_adjustment);
- mTapDistanceSlop = ViewConfiguration.get(context).getScaledTouchSlop();
- mMultiTapDistanceSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
-
- mDetectingStateHandler = new DetectingStateHandler();
+ public MagnificationGestureHandler(Context context, AccessibilityManagerService ams) {
+ mMagnificationController = ams.getMagnificationController();
+ mDetectingStateHandler = new DetectingStateHandler(context);
mStateViewportDraggingHandler = new StateViewportDraggingHandler();
- mMagnifiedContentInteractonStateHandler = new MagnifiedContentInteractonStateHandler(
- context);
-
- mMagnificationController = service.getMagnificationController();
- mScreenStateObserver = new ScreenStateObserver(context, mMagnificationController);
-
- mWindowManager.setMagnificationCallbacks(this);
+ mMagnifiedContentInteractionStateHandler =
+ new MagnifiedContentInteractionStateHandler(context);
transitionToState(STATE_DETECTING);
}
@Override
- public void onMagnifedBoundsChanged(Region bounds) {
- Region newBounds = Region.obtain(bounds);
- mHandler.obtainMessage(MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED, newBounds).sendToTarget();
- if (MY_PID != Binder.getCallingPid()) {
- bounds.recycle();
- }
- }
-
- private void handleOnMagnifiedBoundsChanged(Region bounds) {
- // If there was a rotation we have to update the center of the magnified
- // region since the old offset X/Y may be out of its acceptable range for
- // the new display width and height.
- mMagnificationController.setMagnifiedRegion(
- bounds, mUpdateMagnificationSpecOnNextBoundsChange);
- mUpdateMagnificationSpecOnNextBoundsChange = false;
- }
-
- @Override
- public void onRectangleOnScreenRequested(int left, int top, int right, int bottom) {
- SomeArgs args = SomeArgs.obtain();
- args.argi1 = left;
- args.argi2 = top;
- args.argi3 = right;
- args.argi4 = bottom;
- mHandler.obtainMessage(MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED, args).sendToTarget();
- }
-
- private void handleOnRectangleOnScreenRequested(int left, int top, int right, int bottom) {
- Rect magnifiedFrame = mTempRect;
- mMagnificationController.getMagnifiedBounds(magnifiedFrame);
- if (!magnifiedFrame.intersects(left, top, right, bottom)) {
- return;
- }
- Rect magnifFrameInScreenCoords = mTempRect1;
- getMagnifiedFrameInContentCoords(magnifFrameInScreenCoords);
- final float scrollX;
- final float scrollY;
- if (right - left > magnifFrameInScreenCoords.width()) {
- final int direction = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault());
- if (direction == View.LAYOUT_DIRECTION_LTR) {
- scrollX = left - magnifFrameInScreenCoords.left;
- } else {
- scrollX = right - magnifFrameInScreenCoords.right;
- }
- } else if (left < magnifFrameInScreenCoords.left) {
- scrollX = left - magnifFrameInScreenCoords.left;
- } else if (right > magnifFrameInScreenCoords.right) {
- scrollX = right - magnifFrameInScreenCoords.right;
- } else {
- scrollX = 0;
- }
- if (bottom - top > magnifFrameInScreenCoords.height()) {
- scrollY = top - magnifFrameInScreenCoords.top;
- } else if (top < magnifFrameInScreenCoords.top) {
- scrollY = top - magnifFrameInScreenCoords.top;
- } else if (bottom > magnifFrameInScreenCoords.bottom) {
- scrollY = bottom - magnifFrameInScreenCoords.bottom;
- } else {
- scrollY = 0;
- }
- final float scale = mMagnificationController.getScale();
- mMagnificationController.offsetMagnifiedRegionCenter(scrollX * scale, scrollY * scale);
- }
-
- @Override
- public void onRotationChanged(int rotation) {
- mHandler.obtainMessage(MESSAGE_ON_ROTATION_CHANGED, rotation, 0).sendToTarget();
- }
-
- private void handleOnRotationChanged(int rotation) {
- resetMagnificationIfNeeded();
- if (mMagnificationController.isMagnifying()) {
- mUpdateMagnificationSpecOnNextBoundsChange = true;
- }
- }
-
- @Override
- public void onUserContextChanged() {
- mHandler.sendEmptyMessage(MESSAGE_ON_USER_CONTEXT_CHANGED);
- }
-
- private void handleOnUserContextChanged() {
- resetMagnificationIfNeeded();
- }
-
- private void getMagnifiedFrameInContentCoords(Rect rect) {
- final float scale = mMagnificationController.getSentScale();
- final float offsetX = mMagnificationController.getSentOffsetX();
- final float offsetY = mMagnificationController.getSentOffsetY();
- mMagnificationController.getMagnifiedBounds(rect);
- rect.offset((int) -offsetX, (int) -offsetY);
- rect.scale(1.0f / scale);
- }
-
- private void resetMagnificationIfNeeded() {
- if (mMagnificationController.isMagnifying()
- && isScreenMagnificationAutoUpdateEnabled(mContext)) {
- mMagnificationController.reset(true);
- }
- }
-
- @Override
- public void onMotionEvent(MotionEvent event, MotionEvent rawEvent,
- int policyFlags) {
+ public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
if (!event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) {
if (mNext != null) {
mNext.onMotionEvent(event, rawEvent, policyFlags);
}
return;
}
- mMagnifiedContentInteractonStateHandler.onMotionEvent(event);
+ mMagnifiedContentInteractionStateHandler.onMotionEvent(event, rawEvent, policyFlags);
switch (mCurrentState) {
case STATE_DELEGATING: {
handleMotionEventStateDelegating(event, rawEvent, policyFlags);
- } break;
+ }
+ break;
case STATE_DETECTING: {
mDetectingStateHandler.onMotionEvent(event, rawEvent, policyFlags);
- } break;
+ }
+ break;
case STATE_VIEWPORT_DRAGGING: {
- mStateViewportDraggingHandler.onMotionEvent(event, policyFlags);
- } break;
+ mStateViewportDraggingHandler.onMotionEvent(event, rawEvent, policyFlags);
+ }
+ break;
case STATE_MAGNIFIED_INTERACTION: {
// mMagnifiedContentInteractonStateHandler handles events only
// if this is the current state since it uses ScaleGestureDetecotr
// and a GestureDetector which need well formed event stream.
- } break;
+ }
+ break;
default: {
throw new IllegalStateException("Unknown state: " + mCurrentState);
}
@@ -336,7 +154,7 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio
@Override
public void onKeyEvent(KeyEvent event, int policyFlags) {
if (mNext != null) {
- mNext.onKeyEvent(event, policyFlags);
+ mNext.onKeyEvent(event, policyFlags);
}
}
@@ -366,15 +184,13 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio
@Override
public void onDestroy() {
clear();
- mScreenStateObserver.destroy();
- mWindowManager.setMagnificationCallbacks(null);
}
private void clear() {
mCurrentState = STATE_DETECTING;
mDetectingStateHandler.clear();
mStateViewportDraggingHandler.clear();
- mMagnifiedContentInteractonStateHandler.clear();
+ mMagnifiedContentInteractionStateHandler.clear();
}
private void handleMotionEventStateDelegating(MotionEvent event,
@@ -382,12 +198,14 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
mDelegatingStateDownTime = event.getDownTime();
- } break;
+ }
+ break;
case MotionEvent.ACTION_UP: {
if (mDetectingStateHandler.mDelayedEventQueue == null) {
transitionToState(STATE_DETECTING);
}
- } break;
+ }
+ break;
}
if (mNext != null) {
// If the event is within the magnified portion of the screen we have
@@ -402,7 +220,8 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio
final float scaledOffsetY = mMagnificationController.getOffsetY();
final int pointerCount = event.getPointerCount();
PointerCoords[] coords = getTempPointerCoordsWithMinSize(pointerCount);
- PointerProperties[] properties = getTempPointerPropertiesWithMinSize(pointerCount);
+ PointerProperties[] properties = getTempPointerPropertiesWithMinSize(
+ pointerCount);
for (int i = 0; i < pointerCount; i++) {
event.getPointerCoords(i, coords[i]);
coords[i].x = (coords[i].x - scaledOffsetX) / scale;
@@ -441,12 +260,14 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio
}
private PointerProperties[] getTempPointerPropertiesWithMinSize(int size) {
- final int oldSize = (mTempPointerProperties != null) ? mTempPointerProperties.length : 0;
+ final int oldSize = (mTempPointerProperties != null) ? mTempPointerProperties.length
+ : 0;
if (oldSize < size) {
PointerProperties[] oldTempPointerProperties = mTempPointerProperties;
mTempPointerProperties = new PointerProperties[size];
if (oldTempPointerProperties != null) {
- System.arraycopy(oldTempPointerProperties, 0, mTempPointerProperties, 0, oldSize);
+ System.arraycopy(oldTempPointerProperties, 0, mTempPointerProperties, 0,
+ oldSize);
}
}
for (int i = oldSize; i < size; i++) {
@@ -460,16 +281,20 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio
switch (state) {
case STATE_DELEGATING: {
Slog.i(LOG_TAG, "mCurrentState: STATE_DELEGATING");
- } break;
+ }
+ break;
case STATE_DETECTING: {
Slog.i(LOG_TAG, "mCurrentState: STATE_DETECTING");
- } break;
+ }
+ break;
case STATE_VIEWPORT_DRAGGING: {
Slog.i(LOG_TAG, "mCurrentState: STATE_VIEWPORT_DRAGGING");
- } break;
+ }
+ break;
case STATE_MAGNIFIED_INTERACTION: {
Slog.i(LOG_TAG, "mCurrentState: STATE_MAGNIFIED_INTERACTION");
- } break;
+ }
+ break;
default: {
throw new IllegalArgumentException("Unknown state: " + state);
}
@@ -479,20 +304,30 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio
mCurrentState = state;
}
- private final class MagnifiedContentInteractonStateHandler
- extends SimpleOnGestureListener implements OnScaleGestureListener {
- private static final float MIN_SCALE = 1.3f;
- private static final float MAX_SCALE = 5.0f;
+ private interface MotionEventHandler {
+
+ void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags);
+
+ void clear();
+ }
+
+ /**
+ * This class determines if the user is performing a scale or pan gesture.
+ */
+ private final class MagnifiedContentInteractionStateHandler extends SimpleOnGestureListener
+ implements OnScaleGestureListener, MotionEventHandler {
private final ScaleGestureDetector mScaleGestureDetector;
+
private final GestureDetector mGestureDetector;
private final float mScalingThreshold;
private float mInitialScaleFactor = -1;
+
private boolean mScaling;
- public MagnifiedContentInteractonStateHandler(Context context) {
+ public MagnifiedContentInteractionStateHandler(Context context) {
final TypedValue scaleValue = new TypedValue();
context.getResources().getValue(
com.android.internal.R.dimen.config_screen_magnification_scaling_threshold,
@@ -503,7 +338,8 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio
mGestureDetector = new GestureDetector(context, this);
}
- public void onMotionEvent(MotionEvent event) {
+ @Override
+ public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
mScaleGestureDetector.onTouchEvent(event);
mGestureDetector.onTouchEvent(event);
if (mCurrentState != STATE_MAGNIFIED_INTERACTION) {
@@ -511,11 +347,7 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio
}
if (event.getActionMasked() == MotionEvent.ACTION_UP) {
clear();
- final float scale = Math.min(Math.max(mMagnificationController.getScale(),
- MIN_SCALE), MAX_SCALE);
- if (scale != getPersistedScale()) {
- persistScale(scale);
- }
+ mMagnificationController.persistScale();
if (mPreviousState == STATE_VIEWPORT_DRAGGING) {
transitionToState(STATE_VIEWPORT_DRAGGING);
} else {
@@ -552,14 +384,29 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio
}
return false;
}
- final float newScale = mMagnificationController.getScale()
- * detector.getScaleFactor();
- final float normalizedNewScale = Math.min(Math.max(newScale, MIN_SCALE), MAX_SCALE);
- if (DEBUG_SCALING) {
- Slog.i(LOG_TAG, "normalizedNewScale: " + normalizedNewScale);
+
+ final float initialScale = mMagnificationController.getScale();
+ final float targetScale = initialScale * detector.getScaleFactor();
+
+ // Don't allow a gesture to move the user further outside the
+ // desired bounds for gesture-controlled scaling.
+ final float scale;
+ if (targetScale > MAX_SCALE && targetScale > initialScale) {
+ // The target scale is too big and getting bigger.
+ scale = MAX_SCALE;
+ } else if (targetScale < MIN_SCALE && targetScale < initialScale) {
+ // The target scale is too small and getting smaller.
+ scale = MIN_SCALE;
+ } else {
+ // The target scale may be outside our bounds, but at least
+ // it's moving in the right direction. This avoids a "jump" if
+ // we're at odds with some other service's desired bounds.
+ scale = targetScale;
}
- mMagnificationController.setScale(normalizedNewScale, detector.getFocusX(),
- detector.getFocusY(), false);
+
+ final float pivotX = detector.getFocusX();
+ final float pivotY = detector.getFocusY();
+ mMagnificationController.setScale(scale, pivotX, pivotY, false);
return true;
}
@@ -573,16 +420,24 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio
clear();
}
- private void clear() {
+ @Override
+ public void clear() {
mInitialScaleFactor = -1;
mScaling = false;
}
}
- private final class StateViewportDraggingHandler {
+ /**
+ * This class handles motion events when the event dispatcher has
+ * determined that the user is performing a single-finger drag of the
+ * magnification viewport.
+ */
+ private final class StateViewportDraggingHandler implements MotionEventHandler {
+
private boolean mLastMoveOutsideMagnifiedRegion;
- private void onMotionEvent(MotionEvent event, int policyFlags) {
+ @Override
+ public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
final int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN: {
@@ -591,7 +446,8 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio
case MotionEvent.ACTION_POINTER_DOWN: {
clear();
transitionToState(STATE_MAGNIFIED_INTERACTION);
- } break;
+ }
+ break;
case MotionEvent.ACTION_MOVE: {
if (event.getPointerCount() != 1) {
throw new IllegalStateException("Should have one pointer down.");
@@ -601,35 +457,43 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio
if (mMagnificationController.magnifiedRegionContains(eventX, eventY)) {
if (mLastMoveOutsideMagnifiedRegion) {
mLastMoveOutsideMagnifiedRegion = false;
- mMagnificationController.setMagnifiedRegionCenter(eventX,
+ mMagnificationController.setCenter(eventX,
eventY, true);
} else {
- mMagnificationController.setMagnifiedRegionCenter(eventX,
+ mMagnificationController.setCenter(eventX,
eventY, false);
}
} else {
mLastMoveOutsideMagnifiedRegion = true;
}
- } break;
+ }
+ break;
case MotionEvent.ACTION_UP: {
if (!mTranslationEnabledBeforePan) {
mMagnificationController.reset(true);
}
clear();
transitionToState(STATE_DETECTING);
- } break;
+ }
+ break;
case MotionEvent.ACTION_POINTER_UP: {
- throw new IllegalArgumentException("Unexpected event type: ACTION_POINTER_UP");
+ throw new IllegalArgumentException(
+ "Unexpected event type: ACTION_POINTER_UP");
}
}
}
+ @Override
public void clear() {
mLastMoveOutsideMagnifiedRegion = false;
}
}
- private final class DetectingStateHandler {
+ /**
+ * This class handles motion events when the event dispatch has not yet
+ * determined what the user is doing. It watches for various tap events.
+ */
+ private final class DetectingStateHandler implements MotionEventHandler {
private static final int MESSAGE_ON_ACTION_TAP_AND_HOLD = 1;
@@ -637,12 +501,30 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio
private static final int ACTION_TAP_COUNT = 3;
+ private final int mTapTimeSlop = ViewConfiguration.getJumpTapTimeout();
+
+ private final int mMultiTapTimeSlop;
+
+ private final int mTapDistanceSlop;
+
+ private final int mMultiTapDistanceSlop;
+
private MotionEventInfo mDelayedEventQueue;
private MotionEvent mLastDownEvent;
+
private MotionEvent mLastTapUpEvent;
+
private int mTapCount;
+ public DetectingStateHandler(Context context) {
+ mMultiTapTimeSlop = ViewConfiguration.getDoubleTapTimeout()
+ + context.getResources().getInteger(
+ com.android.internal.R.integer.config_screen_magnification_multi_tap_adjustment);
+ mTapDistanceSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+ mMultiTapDistanceSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
+ }
+
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message message) {
@@ -652,12 +534,14 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio
MotionEvent event = (MotionEvent) message.obj;
final int policyFlags = message.arg1;
onActionTapAndHold(event, policyFlags);
- } break;
+ }
+ break;
case MESSAGE_TRANSITION_TO_DELEGATING_STATE: {
transitionToState(STATE_DELEGATING);
sendDelayedMotionEvents();
clear();
- } break;
+ }
+ break;
default: {
throw new IllegalArgumentException("Unknown message type: " + type);
}
@@ -665,6 +549,7 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio
}
};
+ @Override
public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
cacheDelayedMotionEvent(event, rawEvent, policyFlags);
final int action = event.getActionMasked();
@@ -678,7 +563,7 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio
}
if (mTapCount == ACTION_TAP_COUNT - 1 && mLastDownEvent != null
&& GestureUtils.isMultiTap(mLastDownEvent, event,
- mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) {
+ mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) {
Message message = mHandler.obtainMessage(MESSAGE_ON_ACTION_TAP_AND_HOLD,
policyFlags, 0, event);
mHandler.sendMessageDelayed(message,
@@ -690,7 +575,8 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio
}
clearLastDownEvent();
mLastDownEvent = MotionEvent.obtain(event);
- } break;
+ }
+ break;
case MotionEvent.ACTION_POINTER_DOWN: {
if (mMagnificationController.isMagnifying()) {
transitionToState(STATE_MAGNIFIED_INTERACTION);
@@ -698,7 +584,8 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio
} else {
transitionToDelegatingStateAndClear();
}
- } break;
+ }
+ break;
case MotionEvent.ACTION_MOVE: {
if (mLastDownEvent != null && mTapCount < ACTION_TAP_COUNT - 1) {
final double distance = GestureUtils.computeDistance(mLastDownEvent,
@@ -707,7 +594,8 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio
transitionToDelegatingStateAndClear();
}
}
- } break;
+ }
+ break;
case MotionEvent.ACTION_UP: {
if (mLastDownEvent == null) {
return;
@@ -715,8 +603,8 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio
mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD);
if (!mMagnificationController.magnifiedRegionContains(
event.getX(), event.getY())) {
- transitionToDelegatingStateAndClear();
- return;
+ transitionToDelegatingStateAndClear();
+ return;
}
if (!GestureUtils.isTap(mLastDownEvent, event, mTapTimeSlop,
mTapDistanceSlop, 0)) {
@@ -739,13 +627,16 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio
}
clearLastTapUpEvent();
mLastTapUpEvent = MotionEvent.obtain(event);
- } break;
+ }
+ break;
case MotionEvent.ACTION_POINTER_UP: {
/* do nothing */
- } break;
+ }
+ break;
}
}
+ @Override
public void clear() {
mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD);
mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
@@ -792,7 +683,7 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio
while (mDelayedEventQueue != null) {
MotionEventInfo info = mDelayedEventQueue;
mDelayedEventQueue = info.mNext;
- ScreenMagnifier.this.onMotionEvent(info.mEvent, info.mRawEvent,
+ MagnificationGestureHandler.this.onMotionEvent(info.mEvent, info.mRawEvent,
info.mPolicyFlags);
info.recycle();
}
@@ -816,9 +707,11 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio
if (DEBUG_DETECTING) {
Slog.i(LOG_TAG, "onActionTap()");
}
+
if (!mMagnificationController.isMagnifying()) {
- mMagnificationController.setScaleAndMagnifiedRegionCenter(getPersistedScale(),
- up.getX(), up.getY(), true);
+ final float targetScale = mMagnificationController.getPersistedScale();
+ final float scale = MathUtils.constrain(targetScale, MIN_SCALE, MAX_SCALE);
+ mMagnificationController.setScaleAndCenter(scale, up.getX(), up.getY(), true);
} else {
mMagnificationController.reset(true);
}
@@ -828,35 +721,16 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio
if (DEBUG_DETECTING) {
Slog.i(LOG_TAG, "onActionTapAndHold()");
}
+
clear();
mTranslationEnabledBeforePan = mMagnificationController.isMagnifying();
- mMagnificationController.setScaleAndMagnifiedRegionCenter(getPersistedScale(),
- down.getX(), down.getY(), true);
- transitionToState(STATE_VIEWPORT_DRAGGING);
- }
- }
- private void persistScale(final float scale) {
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- Settings.Secure.putFloatForUser(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale, mUserId);
- return null;
- }
- }.execute();
- }
+ final float targetScale = mMagnificationController.getPersistedScale();
+ final float scale = MathUtils.constrain(targetScale, MIN_SCALE, MAX_SCALE);
+ mMagnificationController.setScaleAndCenter(scale, down.getX(), down.getY(), true);
- private float getPersistedScale() {
- return Settings.Secure.getFloatForUser(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
- DEFAULT_MAGNIFICATION_SCALE, mUserId);
- }
-
- private static boolean isScreenMagnificationAutoUpdateEnabled(Context context) {
- return (Settings.Secure.getInt(context.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE,
- DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE) == 1);
+ transitionToState(STATE_VIEWPORT_DRAGGING);
+ }
}
private static final class MotionEventInfo {
@@ -864,14 +738,19 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio
private static final int MAX_POOL_SIZE = 10;
private static final Object sLock = new Object();
+
private static MotionEventInfo sPool;
+
private static int sPoolSize;
private MotionEventInfo mNext;
+
private boolean mInPool;
public MotionEvent mEvent;
+
public MotionEvent mRawEvent;
+
public int mPolicyFlags;
public static MotionEventInfo obtain(MotionEvent event, MotionEvent rawEvent,
@@ -922,47 +801,4 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio
mPolicyFlags = 0;
}
}
-
- private final class ScreenStateObserver extends BroadcastReceiver {
- private static final int MESSAGE_ON_SCREEN_STATE_CHANGE = 1;
-
- private final Context mContext;
- private final MagnificationController mMagnificationController;
-
- private final Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message message) {
- switch (message.what) {
- case MESSAGE_ON_SCREEN_STATE_CHANGE: {
- String action = (String) message.obj;
- handleOnScreenStateChange(action);
- } break;
- }
- }
- };
-
- public ScreenStateObserver(Context context,
- MagnificationController magnificationController) {
- mContext = context;
- mMagnificationController = magnificationController;
- mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_SCREEN_OFF));
- }
-
- public void destroy() {
- mContext.unregisterReceiver(this);
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- mHandler.obtainMessage(MESSAGE_ON_SCREEN_STATE_CHANGE,
- intent.getAction()).sendToTarget();
- }
-
- private void handleOnScreenStateChange(String action) {
- if (mMagnificationController.isMagnifying()
- && isScreenMagnificationAutoUpdateEnabled(mContext)) {
- mMagnificationController.reset(false);
- }
- }
- }
}
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index a5ddc12a2afc..f9d9950089b6 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -115,6 +115,7 @@ class AlarmManagerService extends SystemService {
final LocalLog mLog = new LocalLog(TAG);
AppOpsManager mAppOps;
+ DeviceIdleController.LocalService mLocalDeviceIdleController;
final Object mLock = new Object();
@@ -897,6 +898,8 @@ class AlarmManagerService extends SystemService {
if (phase == PHASE_SYSTEM_SERVICES_READY) {
mConstants.start(getContext().getContentResolver());
mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
+ mLocalDeviceIdleController
+ = LocalServices.getService(DeviceIdleController.LocalService.class);
}
}
@@ -2468,10 +2471,9 @@ class AlarmManagerService extends SystemService {
private class AlarmHandler extends Handler {
public static final int ALARM_EVENT = 1;
- public static final int MINUTE_CHANGE_EVENT = 2;
- public static final int DATE_CHANGE_EVENT = 3;
- public static final int SEND_NEXT_ALARM_CLOCK_CHANGED = 4;
- public static final int LISTENER_TIMEOUT = 5;
+ public static final int SEND_NEXT_ALARM_CLOCK_CHANGED = 2;
+ public static final int LISTENER_TIMEOUT = 3;
+ public static final int REPORT_ALARMS_ACTIVE = 4;
public AlarmHandler() {
}
@@ -2511,6 +2513,12 @@ class AlarmManagerService extends SystemService {
mDeliveryTracker.alarmTimedOut((IBinder) msg.obj);
break;
+ case REPORT_ALARMS_ACTIVE:
+ if (mLocalDeviceIdleController != null) {
+ mLocalDeviceIdleController.setAlarmsActive(msg.arg1 != 0);
+ }
+ break;
+
default:
// nope, just ignore it
break;
@@ -2740,6 +2748,7 @@ class AlarmManagerService extends SystemService {
}
mBroadcastRefCount--;
if (mBroadcastRefCount == 0) {
+ mHandler.obtainMessage(AlarmHandler.REPORT_ALARMS_ACTIVE, 0).sendToTarget();
mWakeLock.release();
if (mInFlight.size() > 0) {
mLog.w("Finished all dispatches with " + mInFlight.size()
@@ -2873,6 +2882,7 @@ class AlarmManagerService extends SystemService {
alarm.type, alarm.statsTag, (alarm.operation == null) ? alarm.uid : -1,
true);
mWakeLock.acquire();
+ mHandler.obtainMessage(AlarmHandler.REPORT_ALARMS_ACTIVE, 1).sendToTarget();
}
final InFlight inflight = new InFlight(AlarmManagerService.this,
alarm.operation, alarm.listener, alarm.workSource, alarm.uid,
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 927b995e5cb0..485e26b7752a 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -48,6 +48,7 @@ import android.os.Bundle;
import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
+import android.os.IBinder;
import android.os.IDeviceIdleController;
import android.os.Looper;
import android.os.Message;
@@ -196,6 +197,12 @@ public class DeviceIdleController extends SystemService
private long mNextIdleDelay;
private long mNextLightAlarmTime;
+ private int mActiveIdleOpCount;
+ private IBinder mDownloadServiceActive;
+ private boolean mSyncActive;
+ private boolean mJobsActive;
+ private boolean mAlarmsActive;
+
public final AtomicFile mConfigFile;
/**
@@ -282,16 +289,22 @@ public class DeviceIdleController extends SystemService
}
} else if (ACTION_STEP_LIGHT_IDLE_STATE.equals(intent.getAction())) {
synchronized (DeviceIdleController.this) {
- stepLightIdleStateLocked();
+ stepLightIdleStateLocked("s:alarm");
}
} else if (ACTION_STEP_IDLE_STATE.equals(intent.getAction())) {
synchronized (DeviceIdleController.this) {
- stepIdleStateLocked();
+ stepIdleStateLocked("s:alarm");
}
}
}
};
+ private final BroadcastReceiver mIdleStartedDoneReceiver = new BroadcastReceiver() {
+ @Override public void onReceive(Context context, Intent intent) {
+ decActiveIdleOps();
+ }
+ };
+
private final DisplayManager.DisplayListener mDisplayListener
= new DisplayManager.DisplayListener() {
@Override public void onDisplayAdded(int displayId) {
@@ -733,7 +746,7 @@ public class DeviceIdleController extends SystemService
// If we are currently sensing, it is time to move to locating.
synchronized (this) {
mNotMoving = true;
- stepIdleStateLocked();
+ stepIdleStateLocked("s:stationary");
}
} else if (mState == STATE_LOCATING) {
// If we are currently locating, note that we are not moving and step
@@ -741,7 +754,7 @@ public class DeviceIdleController extends SystemService
synchronized (this) {
mNotMoving = true;
if (mLocated) {
- stepIdleStateLocked();
+ stepIdleStateLocked("s:stationary");
}
}
}
@@ -804,11 +817,18 @@ public class DeviceIdleController extends SystemService
} catch (RemoteException e) {
}
if (fullChanged) {
- getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
+ incActiveIdleOps();
+ getContext().sendOrderedBroadcastAsUser(mIdleIntent, UserHandle.ALL,
+ null, mIdleStartedDoneReceiver, null, 0, null, null);
}
if (lightChanged) {
- getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL);
+ incActiveIdleOps();
+ getContext().sendOrderedBroadcastAsUser(mLightIdleIntent, UserHandle.ALL,
+ null, mIdleStartedDoneReceiver, null, 0, null, null);
}
+ // Always start with one active op for the message being sent here.
+ // Now we we done!
+ decActiveIdleOps();
EventLogTags.writeDeviceIdleOffComplete();
} break;
case MSG_REPORT_ACTIVE: {
@@ -913,11 +933,23 @@ public class DeviceIdleController extends SystemService
}
@Override public void exitIdle(String reason) {
- getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
+ getContext().enforceCallingOrSelfPermission(Manifest.permission.DEVICE_POWER,
null);
exitIdleInternal(reason);
}
+ @Override public void downloadServiceActive(IBinder token) {
+ getContext().enforceCallingOrSelfPermission(
+ "android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS", null);
+ DeviceIdleController.this.downloadServiceActive(token);
+ }
+
+ @Override public void downloadServiceInactive() {
+ getContext().enforceCallingOrSelfPermission(
+ "android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS", null);
+ DeviceIdleController.this.downloadServiceInactive();
+ }
+
@Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
DeviceIdleController.this.dump(fd, pw, args);
}
@@ -937,6 +969,19 @@ public class DeviceIdleController extends SystemService
public void setNetworkPolicyTempWhitelistCallback(Runnable callback) {
setNetworkPolicyTempWhitelistCallbackInternal(callback);
}
+
+ public void setSyncActive(boolean active) {
+ DeviceIdleController.this.setSyncActive(active);
+ }
+
+ public void setJobsActive(boolean active) {
+ DeviceIdleController.this.setJobsActive(active);
+ }
+
+ // Up-call from alarm manager.
+ public void setAlarmsActive(boolean active) {
+ DeviceIdleController.this.setAlarmsActive(active);
+ }
}
public DeviceIdleController(Context context) {
@@ -1439,7 +1484,7 @@ public class DeviceIdleController extends SystemService
}
}
- void stepLightIdleStateLocked() {
+ void stepLightIdleStateLocked(String reason) {
if (mLightState == LIGHT_STATE_OVERRIDE) {
// If we are already in full device idle mode, then
// there is nothing left to do for light mode.
@@ -1455,22 +1500,23 @@ public class DeviceIdleController extends SystemService
scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_TIMEOUT);
if (DEBUG) Slog.d(TAG, "Moved to LIGHT_STATE_IDLE.");
mLightState = LIGHT_STATE_IDLE;
- EventLogTags.writeDeviceIdleLight(mLightState, "step");
+ EventLogTags.writeDeviceIdleLight(mLightState, reason);
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON_LIGHT);
break;
case LIGHT_STATE_IDLE:
// We have been idling long enough, now it is time to do some work.
+ mActiveIdleOpCount = 1;
scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_PENDING_TIMEOUT);
if (DEBUG) Slog.d(TAG,
"Moved from LIGHT_STATE_IDLE to LIGHT_STATE_IDLE_MAINTENANCE.");
mLightState = LIGHT_STATE_IDLE_MAINTENANCE;
- EventLogTags.writeDeviceIdleLight(mLightState, "step");
+ EventLogTags.writeDeviceIdleLight(mLightState, reason);
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
break;
}
}
- void stepIdleStateLocked() {
+ void stepIdleStateLocked(String reason) {
if (DEBUG) Slog.d(TAG, "stepIdleStateLocked: mState=" + mState);
EventLogTags.writeDeviceIdleStep();
@@ -1494,12 +1540,12 @@ public class DeviceIdleController extends SystemService
mNextIdleDelay = mConstants.IDLE_TIMEOUT;
mState = STATE_IDLE_PENDING;
if (DEBUG) Slog.d(TAG, "Moved from STATE_INACTIVE to STATE_IDLE_PENDING.");
- EventLogTags.writeDeviceIdle(mState, "step");
+ EventLogTags.writeDeviceIdle(mState, reason);
break;
case STATE_IDLE_PENDING:
mState = STATE_SENSING;
if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE_PENDING to STATE_SENSING.");
- EventLogTags.writeDeviceIdle(mState, "step");
+ EventLogTags.writeDeviceIdle(mState, reason);
scheduleAlarmLocked(mConstants.SENSING_TIMEOUT, false);
cancelLocatingLocked();
mAnyMotionDetector.checkForAnyMotion();
@@ -1511,7 +1557,7 @@ public class DeviceIdleController extends SystemService
case STATE_SENSING:
mState = STATE_LOCATING;
if (DEBUG) Slog.d(TAG, "Moved from STATE_SENSING to STATE_LOCATING.");
- EventLogTags.writeDeviceIdle(mState, "step");
+ EventLogTags.writeDeviceIdle(mState, reason);
scheduleAlarmLocked(mConstants.LOCATING_TIMEOUT, false);
if (mLocationManager != null
&& mLocationManager.getProvider(LocationManager.NETWORK_PROVIDER) != null) {
@@ -1553,23 +1599,101 @@ public class DeviceIdleController extends SystemService
mLightState = LIGHT_STATE_OVERRIDE;
cancelLightAlarmLocked();
}
- EventLogTags.writeDeviceIdle(mState, "step");
+ EventLogTags.writeDeviceIdle(mState, reason);
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON);
break;
case STATE_IDLE:
// We have been idling long enough, now it is time to do some work.
+ mActiveIdleOpCount = 1;
scheduleAlarmLocked(mNextIdlePendingDelay, false);
if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE to STATE_IDLE_MAINTENANCE. " +
"Next alarm in " + mNextIdlePendingDelay + " ms.");
mNextIdlePendingDelay = Math.min(mConstants.MAX_IDLE_PENDING_TIMEOUT,
(long)(mNextIdlePendingDelay * mConstants.IDLE_PENDING_FACTOR));
mState = STATE_IDLE_MAINTENANCE;
- EventLogTags.writeDeviceIdle(mState, "step");
+ EventLogTags.writeDeviceIdle(mState, reason);
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
break;
}
}
+ void incActiveIdleOps() {
+ synchronized (this) {
+ mActiveIdleOpCount++;
+ }
+ }
+
+ void decActiveIdleOps() {
+ synchronized (this) {
+ mActiveIdleOpCount--;
+ if (mActiveIdleOpCount <= 0) {
+ exitMaintenanceEarlyIfNeededLocked();
+ }
+ }
+ }
+
+ void downloadServiceActive(IBinder token) {
+ synchronized (this) {
+ mDownloadServiceActive = token;
+ try {
+ token.linkToDeath(new IBinder.DeathRecipient() {
+ @Override public void binderDied() {
+ downloadServiceInactive();
+ }
+ }, 0);
+ } catch (RemoteException e) {
+ mDownloadServiceActive = null;
+ }
+ }
+ }
+
+ void downloadServiceInactive() {
+ synchronized (this) {
+ mDownloadServiceActive = null;
+ exitMaintenanceEarlyIfNeededLocked();
+ }
+ }
+
+ void setSyncActive(boolean active) {
+ synchronized (this) {
+ mSyncActive = active;
+ if (!active) {
+ exitMaintenanceEarlyIfNeededLocked();
+ }
+ }
+ }
+
+ void setJobsActive(boolean active) {
+ synchronized (this) {
+ mJobsActive = active;
+ if (!active) {
+ exitMaintenanceEarlyIfNeededLocked();
+ }
+ }
+ }
+
+ void setAlarmsActive(boolean active) {
+ synchronized (this) {
+ mAlarmsActive = active;
+ if (!active) {
+ exitMaintenanceEarlyIfNeededLocked();
+ }
+ }
+ }
+
+ void exitMaintenanceEarlyIfNeededLocked() {
+ if (mState == STATE_IDLE_MAINTENANCE || mLightState == LIGHT_STATE_IDLE_MAINTENANCE) {
+ if (mActiveIdleOpCount <= 0 && mDownloadServiceActive == null
+ && !mSyncActive && !mJobsActive && !mAlarmsActive) {
+ if (mState == STATE_IDLE_MAINTENANCE) {
+ stepIdleStateLocked("s:early");
+ } else {
+ stepLightIdleStateLocked("s:early");
+ }
+ }
+ }
+ }
+
void motionLocked() {
if (DEBUG) Slog.d(TAG, "motionLocked()");
// The motion sensor will have been disabled at this point
@@ -1612,7 +1736,7 @@ public class DeviceIdleController extends SystemService
}
mLocated = true;
if (mNotMoving) {
- stepIdleStateLocked();
+ stepIdleStateLocked("s:location");
}
}
@@ -1628,7 +1752,7 @@ public class DeviceIdleController extends SystemService
}
mLocated = true;
if (mNotMoving) {
- stepIdleStateLocked();
+ stepIdleStateLocked("s:gps");
}
}
@@ -1933,7 +2057,7 @@ public class DeviceIdleController extends SystemService
long token = Binder.clearCallingIdentity();
try {
exitForceIdleLocked();
- stepIdleStateLocked();
+ stepIdleStateLocked("s:shell");
pw.print("Stepped to: ");
pw.println(stateToString(mState));
} finally {
@@ -1947,7 +2071,7 @@ public class DeviceIdleController extends SystemService
long token = Binder.clearCallingIdentity();
try {
exitForceIdleLocked();
- stepLightIdleStateLocked();
+ stepLightIdleStateLocked("s:shell");
pw.print("Stepped to: "); pw.println(lightStateToString(mLightState));
} finally {
Binder.restoreCallingIdentity(token);
@@ -1967,7 +2091,7 @@ public class DeviceIdleController extends SystemService
becomeInactiveIfAppropriateLocked();
int curState = mState;
while (curState != STATE_IDLE) {
- stepIdleStateLocked();
+ stepIdleStateLocked("s:shell");
if (curState == mState) {
pw.print("Unable to go idle; stopped at ");
pw.println(stateToString(mState));
@@ -2226,6 +2350,9 @@ public class DeviceIdleController extends SystemService
pw.println(lightStateToString(mLightState));
pw.print(" mInactiveTimeout="); TimeUtils.formatDuration(mInactiveTimeout, pw);
pw.println();
+ if (mActiveIdleOpCount != 0) {
+ pw.print(" mActiveIdleOpCount="); pw.println(mActiveIdleOpCount);
+ }
if (mNextAlarmTime != 0) {
pw.print(" mNextAlarmTime=");
TimeUtils.formatDuration(mNextAlarmTime, SystemClock.elapsedRealtime(), pw);
@@ -2246,6 +2373,18 @@ public class DeviceIdleController extends SystemService
TimeUtils.formatDuration(mNextLightAlarmTime, SystemClock.elapsedRealtime(), pw);
pw.println();
}
+ if (mSyncActive) {
+ pw.print(" mSyncActive="); pw.println(mSyncActive);
+ }
+ if (mJobsActive) {
+ pw.print(" mJobsActive="); pw.println(mJobsActive);
+ }
+ if (mAlarmsActive) {
+ pw.print(" mAlarmsActive="); pw.println(mAlarmsActive);
+ }
+ if (mDownloadServiceActive != null) {
+ pw.print(" mDownloadServiceActive="); pw.println(mDownloadServiceActive);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/IntentResolver.java b/services/core/java/com/android/server/IntentResolver.java
index 3359060d3fe7..43d10c74478c 100644
--- a/services/core/java/com/android/server/IntentResolver.java
+++ b/services/core/java/com/android/server/IntentResolver.java
@@ -226,7 +226,7 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> {
final int N = a.length;
boolean printedHeader = false;
F filter;
- if (collapseDuplicates) {
+ if (collapseDuplicates && !printFilter) {
found.clear();
for (int i=0; i<N && (filter=a[i]) != null; i++) {
if (packageName != null && !isPackageForFilter(packageName, filter)) {
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index 37dd884c8dc7..f89155d93d23 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -56,6 +56,7 @@ import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
@@ -2768,6 +2769,12 @@ class MountService extends IMountService.Stub
}
@Override
+ public ParcelFileDescriptor mountAppFuse(String name) throws RemoteException {
+ // TODO: Invoke vold to mount app fuse.
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public int mkdirs(String callingPkg, String appPath) {
final int userId = UserHandle.getUserId(Binder.getCallingUid());
final UserEnvironment userEnv = new UserEnvironment(userId);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 566065c02852..c1413ff4abd9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3337,15 +3337,6 @@ public final class ActivityManagerService extends ActivityManagerNative
if ("1".equals(SystemProperties.get("debug.checkjni"))) {
debugFlags |= Zygote.DEBUG_ENABLE_CHECKJNI;
}
- String jitDebugProperty = SystemProperties.get("debug.usejit");
- if ("true".equals(jitDebugProperty)) {
- debugFlags |= Zygote.DEBUG_ENABLE_JIT;
- } else if (!"false".equals(jitDebugProperty)) {
- // If we didn't force disable by setting false, defer to the dalvik vm options.
- if ("true".equals(SystemProperties.get("dalvik.vm.usejit"))) {
- debugFlags |= Zygote.DEBUG_ENABLE_JIT;
- }
- }
String genDebugInfoProperty = SystemProperties.get("debug.generate-debug-info");
if ("true".equals(genDebugInfoProperty)) {
debugFlags |= Zygote.DEBUG_GENERATE_DEBUG_INFO;
@@ -18894,8 +18885,6 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
}
- Process.setSwappiness(app.pid,
- app.curSchedGroup <= Process.THREAD_GROUP_BG_NONINTERACTIVE);
}
}
if (app.repForegroundActivities != app.foregroundActivities) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index d1e7e85b6d51..13c14176e670 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -73,7 +73,7 @@ class ActivityManagerShellCommand extends ShellCommand {
String opt;
while ((opt = getNextOption()) != null) {
if (opt.equals("--user")) {
- userId = parseUserArg(getNextArgRequired());
+ userId = UserHandle.parseUserArg(getNextArgRequired());
} else {
pw.println("Error: Unknown option: " + opt);
return -1;
@@ -89,7 +89,7 @@ class ActivityManagerShellCommand extends ShellCommand {
String opt;
while ((opt=getNextOption()) != null) {
if (opt.equals("--user")) {
- userId = parseUserArg(getNextArgRequired());
+ userId = UserHandle.parseUserArg(getNextArgRequired());
} else {
pw.println("Error: Unknown option: " + opt);
return -1;
@@ -141,22 +141,6 @@ class ActivityManagerShellCommand extends ShellCommand {
return 0;
}
- int parseUserArg(String arg) {
- int userId;
- if ("all".equals(arg)) {
- userId = UserHandle.USER_ALL;
- } else if ("current".equals(arg) || "cur".equals(arg)) {
- userId = UserHandle.USER_CURRENT;
- } else {
- try {
- userId = Integer.parseInt(arg);
- } catch (NumberFormatException e) {
- throw new IllegalArgumentException("Bad user number: " + arg);
- }
- }
- return userId;
- }
-
@Override
public void onHelp() {
PrintWriter pw = getOutPrintWriter();
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 9896ec5e49a7..e28d198cf821 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1418,19 +1418,9 @@ final class ActivityStack {
if (top == null) {
return;
}
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
- "ensureActivitiesVisible behind " + top
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "ensureActivitiesVisible behind " + top
+ " configChanges=0x" + Integer.toHexString(configChanges));
-
- if (mTranslucentActivityWaiting != top) {
- mUndrawnActivitiesBelowTopTranslucent.clear();
- if (mTranslucentActivityWaiting != null) {
- // Call the callback with a timeout indication.
- notifyActivityDrawnLocked(null);
- mTranslucentActivityWaiting = null;
- }
- mHandler.removeMessages(TRANSLUCENT_TIMEOUT_MSG);
- }
+ checkTranslucentActivityWaiting(top);
// If the top activity is not fullscreen, then we need to
// make sure any activities under it are now visible.
@@ -1458,7 +1448,6 @@ final class ActivityStack {
if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
"Make visible? " + r + " finishing=" + r.finishing
+ " state=" + r.state);
-
// First: if this is not the current activity being started, make
// sure it matches the current configuration.
if (r != starting) {
@@ -1466,143 +1455,28 @@ final class ActivityStack {
}
if (r.app == null || r.app.thread == null) {
- // We need to make sure the app is running if it's the top, or it is
- // just made visible from invisible.
- // If the app is already visible, it must have died while it was visible.
- // In this case, we'll show the dead window but will not restart the app.
- // Otherwise we could end up thrashing.
- if (r == top || !r.visible) {
- // This activity needs to be visible, but isn't even running...
- // get it started and resume if no other stack in this stack is resumed.
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
- "Start and freeze screen for " + r);
- if (r != starting) {
- r.startFreezingScreenLocked(r.app, configChanges);
- }
- if (!r.visible || r.mLaunchTaskBehind) {
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
- "Starting and making visible: " + r);
- setVisible(r, true);
- }
- if (r != starting) {
- mStackSupervisor.startSpecificActivityLocked(
- r, noStackActivityResumed, false);
- if (activityNdx >= activities.size()) {
- // Record may be removed if its process needs to restart.
- activityNdx = activities.size() - 1;
- } else {
- noStackActivityResumed = false;
- }
+ if (makeVisibleAndRestartIfNeeded(starting, configChanges, top,
+ noStackActivityResumed, r)) {
+ if (activityNdx >= activities.size()) {
+ // Record may be removed if its process needs to restart.
+ activityNdx = activities.size() - 1;
+ } else {
+ noStackActivityResumed = false;
}
}
} else if (r.visible) {
- // If this activity is already visible, then there is nothing
- // else to do here.
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
- "Skipping: already visible at " + r);
- r.stopFreezingScreenLocked(false);
- try {
- if (r.returningOptions != null) {
- r.app.thread.scheduleOnNewActivityOptions(r.appToken,
- r.returningOptions);
- }
- } catch(RemoteException e) {
- }
- if (r.state == ActivityState.RESUMED) {
+ if (alreadyVisible(r)) {
noStackActivityResumed = false;
}
} else {
- // This activity is not currently visible, but is running.
- // Tell it to become visible.
- r.visible = true;
- if (r.state != ActivityState.RESUMED && r != starting) {
- // If this activity is paused, tell it
- // to now show its window.
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
- "Making visible and scheduling visibility: " + r);
- try {
- if (mTranslucentActivityWaiting != null) {
- r.updateOptionsLocked(r.returningOptions);
- mUndrawnActivitiesBelowTopTranslucent.add(r);
- }
- setVisible(r, true);
- r.sleeping = false;
- r.app.pendingUiClean = true;
- r.app.thread.scheduleWindowVisibility(r.appToken, true);
- r.stopFreezingScreenLocked(false);
- } catch (Exception e) {
- // Just skip on any failure; we'll make it
- // visible when it next restarts.
- Slog.w(TAG, "Exception thrown making visibile: "
- + r.intent.getComponent(), e);
- }
- }
+ becomeVisible(starting, r);
}
-
// Aggregate current change flags.
configChanges |= r.configChangeFlags;
-
- if (r.fullscreen) {
- // At this point, nothing else needs to be shown in this task.
- behindFullscreenActivity = true;
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Fullscreen: at " + r
- + " stackInvisible=" + stackInvisible
- + " behindFullscreenActivity=" + behindFullscreenActivity);
- } else if (!isHomeStack() && r.frontOfTask && task.isOverHomeStack()) {
- behindFullscreenActivity = true;
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Showing home: at " + r
- + " stackInvisible=" + stackInvisible
- + " behindFullscreenActivity=" + behindFullscreenActivity);
- }
+ behindFullscreenActivity = updateBehindFullscreen(stackInvisible,
+ behindFullscreenActivity, task, r);
} else {
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
- "Make invisible? " + r + " finishing=" + r.finishing
- + " state=" + r.state + " stackInvisible=" + stackInvisible
- + " behindFullscreenActivity=" + behindFullscreenActivity);
- // Now for any activities that aren't visible to the user, make
- // sure they no longer are keeping the screen frozen.
- if (r.visible) {
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Making invisible: " + r);
- try {
- setVisible(r, false);
- switch (r.state) {
- case STOPPING:
- case STOPPED:
- if (r.app != null && r.app.thread != null) {
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
- "Scheduling invisibility: " + r);
- r.app.thread.scheduleWindowVisibility(r.appToken, false);
- }
- break;
-
- case INITIALIZING:
- case RESUMED:
- case PAUSING:
- case PAUSED:
- // This case created for transitioning activities from
- // translucent to opaque {@link Activity#convertToOpaque}.
- if (getVisibleBehindActivity() == r) {
- releaseBackgroundResources(r);
- } else {
- if (!mStackSupervisor.mStoppingActivities.contains(r)) {
- mStackSupervisor.mStoppingActivities.add(r);
- }
- mStackSupervisor.scheduleIdleLocked();
- }
- break;
-
- default:
- break;
- }
- } catch (Exception e) {
- // Just skip on any failure; we'll make it
- // visible when it next restarts.
- Slog.w(TAG, "Exception thrown making hidden: "
- + r.intent.getComponent(), e);
- }
- } else {
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Already invisible: " + r);
- }
+ becomeInvisible(stackInvisible, behindFullscreenActivity, r);
}
}
if (mStackId == FREEFORM_WORKSPACE_STACK_ID) {
@@ -1620,6 +1494,147 @@ final class ActivityStack {
}
}
+ private void checkTranslucentActivityWaiting(ActivityRecord top) {
+ if (mTranslucentActivityWaiting != top) {
+ mUndrawnActivitiesBelowTopTranslucent.clear();
+ if (mTranslucentActivityWaiting != null) {
+ // Call the callback with a timeout indication.
+ notifyActivityDrawnLocked(null);
+ mTranslucentActivityWaiting = null;
+ }
+ mHandler.removeMessages(TRANSLUCENT_TIMEOUT_MSG);
+ }
+ }
+
+ private boolean makeVisibleAndRestartIfNeeded(ActivityRecord starting, int configChanges,
+ ActivityRecord top, boolean noStackActivityResumed, ActivityRecord r) {
+ // We need to make sure the app is running if it's the top, or it is just made visible from
+ // invisible. If the app is already visible, it must have died while it was visible. In this
+ // case, we'll show the dead window but will not restart the app. Otherwise we could end up
+ // thrashing.
+ if (r == top || !r.visible) {
+ // This activity needs to be visible, but isn't even running...
+ // get it started and resume if no other stack in this stack is resumed.
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Start and freeze screen for " + r);
+ if (r != starting) {
+ r.startFreezingScreenLocked(r.app, configChanges);
+ }
+ if (!r.visible || r.mLaunchTaskBehind) {
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Starting and making visible: " + r);
+ setVisible(r, true);
+ }
+ if (r != starting) {
+ mStackSupervisor.startSpecificActivityLocked(r, noStackActivityResumed, false);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void becomeInvisible(boolean stackInvisible, boolean behindFullscreenActivity,
+ ActivityRecord r) {
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Make invisible? " + r + " finishing="
+ + r.finishing + " state=" + r.state + " stackInvisible=" + stackInvisible
+ + " behindFullscreenActivity=" + behindFullscreenActivity);
+ if (!r.visible) {
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Already invisible: " + r);
+ return;
+ }
+ // Now for any activities that aren't visible to the user, make sure they no longer are
+ // keeping the screen frozen.
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Making invisible: " + r);
+ try {
+ setVisible(r, false);
+ switch (r.state) {
+ case STOPPING:
+ case STOPPED:
+ if (r.app != null && r.app.thread != null) {
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
+ "Scheduling invisibility: " + r);
+ r.app.thread.scheduleWindowVisibility(r.appToken, false);
+ }
+ break;
+
+ case INITIALIZING:
+ case RESUMED:
+ case PAUSING:
+ case PAUSED:
+ // This case created for transitioning activities from
+ // translucent to opaque {@link Activity#convertToOpaque}.
+ if (getVisibleBehindActivity() == r) {
+ releaseBackgroundResources(r);
+ } else {
+ if (!mStackSupervisor.mStoppingActivities.contains(r)) {
+ mStackSupervisor.mStoppingActivities.add(r);
+ }
+ mStackSupervisor.scheduleIdleLocked();
+ }
+ break;
+
+ default:
+ break;
+ }
+ } catch (Exception e) {
+ // Just skip on any failure; we'll make it visible when it next restarts.
+ Slog.w(TAG, "Exception thrown making hidden: " + r.intent.getComponent(), e);
+ }
+ }
+
+ private boolean updateBehindFullscreen(boolean stackInvisible, boolean behindFullscreenActivity,
+ TaskRecord task, ActivityRecord r) {
+ if (r.fullscreen) {
+ // At this point, nothing else needs to be shown in this task.
+ behindFullscreenActivity = true;
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Fullscreen: at " + r
+ + " stackInvisible=" + stackInvisible
+ + " behindFullscreenActivity=" + behindFullscreenActivity);
+ } else if (!isHomeStack() && r.frontOfTask && task.isOverHomeStack()) {
+ behindFullscreenActivity = true;
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Showing home: at " + r
+ + " stackInvisible=" + stackInvisible
+ + " behindFullscreenActivity=" + behindFullscreenActivity);
+ }
+ return behindFullscreenActivity;
+ }
+
+ private void becomeVisible(ActivityRecord starting, ActivityRecord r) {
+ // This activity is not currently visible, but is running. Tell it to become visible.
+ r.visible = true;
+ if (r.state != ActivityState.RESUMED && r != starting) {
+ // If this activity is paused, tell it to now show its window.
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
+ "Making visible and scheduling visibility: " + r);
+ try {
+ if (mTranslucentActivityWaiting != null) {
+ r.updateOptionsLocked(r.returningOptions);
+ mUndrawnActivitiesBelowTopTranslucent.add(r);
+ }
+ setVisible(r, true);
+ r.sleeping = false;
+ r.app.pendingUiClean = true;
+ r.app.thread.scheduleWindowVisibility(r.appToken, true);
+ r.stopFreezingScreenLocked(false);
+ } catch (Exception e) {
+ // Just skip on any failure; we'll make it
+ // visible when it next restarts.
+ Slog.w(TAG, "Exception thrown making visibile: " + r.intent.getComponent(), e);
+ }
+ }
+ }
+
+ private boolean alreadyVisible(ActivityRecord r) {
+ // If this activity is already visible, then there is nothing else to do here.
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Skipping: already visible at " + r);
+ r.stopFreezingScreenLocked(false);
+ try {
+ if (r.returningOptions != null) {
+ r.app.thread.scheduleOnNewActivityOptions(r.appToken, r.returningOptions);
+ }
+ } catch(RemoteException e) {
+ }
+ return r.state == ActivityState.RESUMED;
+ }
+
void convertActivityToTranslucent(ActivityRecord r) {
mTranslucentActivityWaiting = r;
mUndrawnActivitiesBelowTopTranslucent.clear();
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 13d3ee1d18bc..0ec4b18ff28a 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -3354,11 +3354,10 @@ public final class ActivityStackSupervisor implements DisplayListener {
// preserve the old window until the new one is drawn. This prevents having a gap
// between the removal and addition, in which no window is visible. We also want the
// entrance of the new window to be properly animated.
- mWindowManager.setReplacingWindow(topActivity.appToken, true /* animate */);
+ mWindowManager.setReplacingWindow(topActivity.appToken);
}
- final ActivityStack stack =
- moveTaskToStackUncheckedLocked(task, stackId, toTop, forceFocus,
- "moveTaskToStack:" + reason);
+ final ActivityStack stack = moveTaskToStackUncheckedLocked(
+ task, stackId, toTop, forceFocus, "moveTaskToStack:" + reason);
// Make sure the task has the appropriate bounds/size for the stack it is in.
if (stackId == FULLSCREEN_WORKSPACE_STACK_ID && task.mBounds != null) {
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 82e0eafe6bea..e2a0f8241479 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -84,6 +84,8 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IBatteryStats;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.DeviceIdleController;
+import com.android.server.LocalServices;
import com.android.server.accounts.AccountManagerService;
import com.android.server.content.SyncStorageEngine.AuthorityInfo;
import com.android.server.content.SyncStorageEngine.EndPoint;
@@ -207,6 +209,7 @@ public class SyncManager {
volatile private boolean mDataConnectionIsConnected = false;
volatile private boolean mStorageIsLow = false;
volatile private boolean mDeviceIsIdle = false;
+ volatile private boolean mReportedSyncActive = false;
private final NotificationManager mNotificationMgr;
private AlarmManager mAlarmService = null;
@@ -267,6 +270,12 @@ public class SyncManager {
SyncStorageEngine.EndPoint.USER_ALL_PROVIDER_ALL_ACCOUNTS_ALL,
null /* any sync */);
} else {
+ if (mLocalDeviceIdleController != null) {
+ if (!mReportedSyncActive) {
+ mReportedSyncActive = true;
+ mLocalDeviceIdleController.setSyncActive(true);
+ }
+ }
sendCheckAlarmsMessage();
}
}
@@ -292,6 +301,7 @@ public class SyncManager {
};
private final PowerManager mPowerManager;
+ DeviceIdleController.LocalService mLocalDeviceIdleController;
// Use this as a random offset to seed all periodic syncs.
private int mSyncRandomOffsetMillis;
@@ -1470,6 +1480,7 @@ public class SyncManager {
}
pw.print("memory low: "); pw.println(mStorageIsLow);
pw.print("device idle: "); pw.println(mDeviceIsIdle);
+ pw.print("reported active: "); pw.println(mReportedSyncActive);
final AccountAndUser[] accounts = AccountManagerService.getSingleton().getAllAccounts();
@@ -2544,6 +2555,7 @@ public class SyncManager {
if (isLoggable) {
Log.v(TAG, "maybeStartNextSync: no data connection, skipping");
}
+ setSyncActive(false);
return Long.MAX_VALUE;
}
@@ -2551,6 +2563,7 @@ public class SyncManager {
if (isLoggable) {
Log.v(TAG, "maybeStartNextSync: memory low, skipping");
}
+ setSyncActive(false);
return Long.MAX_VALUE;
}
@@ -2558,6 +2571,7 @@ public class SyncManager {
if (isLoggable) {
Log.v(TAG, "maybeStartNextSync: device idle, skipping");
}
+ setSyncActive(false);
return Long.MAX_VALUE;
}
@@ -2567,6 +2581,7 @@ public class SyncManager {
if (isLoggable) {
Log.v(TAG, "maybeStartNextSync: accounts not known, skipping");
}
+ setSyncActive(false);
return Long.MAX_VALUE;
}
@@ -2772,9 +2787,25 @@ public class SyncManager {
dispatchSyncOperation(candidate);
}
+ setSyncActive(mActiveSyncContexts.size() > 0);
+
return nextReadyToRunTime;
}
+ void setSyncActive(boolean active) {
+ if (mLocalDeviceIdleController == null) {
+ mLocalDeviceIdleController
+ = LocalServices.getService(DeviceIdleController.LocalService.class);
+ }
+ if (mLocalDeviceIdleController != null) {
+ if (mReportedSyncActive != active) {
+ mReportedSyncActive = active;
+ mLocalDeviceIdleController.setSyncActive(active);
+ }
+ }
+
+ }
+
private boolean isSyncNotUsingNetworkH(ActiveSyncContext activeSyncContext) {
final long bytesTransferredCurrent =
getTotalBytesTransferredByUid(activeSyncContext.mSyncAdapterUid);
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 2b535b9699f7..759199ce58e6 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -51,6 +51,8 @@ import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.app.IBatteryStats;
+import com.android.server.DeviceIdleController;
+import com.android.server.LocalServices;
import com.android.server.job.controllers.AppIdleController;
import com.android.server.job.controllers.BatteryController;
import com.android.server.job.controllers.ConnectivityController;
@@ -127,6 +129,7 @@ public class JobSchedulerService extends com.android.server.SystemService
IBatteryStats mBatteryStats;
PowerManager mPowerManager;
+ DeviceIdleController.LocalService mLocalDeviceIdleController;
/**
* Set to true once we are allowed to run third party apps.
@@ -139,6 +142,11 @@ public class JobSchedulerService extends com.android.server.SystemService
boolean mDeviceIdleMode;
/**
+ * What we last reported to DeviceIdleController about wheter we are active.
+ */
+ boolean mReportedActive;
+
+ /**
* Cleans up outstanding jobs when a package is removed. Even if it's being replaced later we
* still clean up. On reinstall the package will have a new uid.
*/
@@ -268,6 +276,7 @@ public class JobSchedulerService extends com.android.server.SystemService
mPendingJobs.remove(cancelled);
// Cancel if running.
stopJobOnServiceContextLocked(cancelled);
+ reportActive();
}
}
@@ -299,12 +308,39 @@ public class JobSchedulerService extends com.android.server.SystemService
}
} else {
// When coming out of idle, allow thing to start back up.
+ if (rocking) {
+ if (mLocalDeviceIdleController != null) {
+ if (!mReportedActive) {
+ mReportedActive = true;
+ mLocalDeviceIdleController.setJobsActive(true);
+ }
+ }
+ }
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
}
}
}
}
+ void reportActive() {
+ boolean active = false;
+ if (mPendingJobs.size() <= 0) {
+ for (int i=0; i<mActiveServices.size(); i++) {
+ JobServiceContext jsc = mActiveServices.get(i);
+ if (!jsc.isAvailable()) {
+ active = true;
+ break;
+ }
+ }
+ }
+ if (mLocalDeviceIdleController != null) {
+ if (mReportedActive != active) {
+ mReportedActive = active;
+ mLocalDeviceIdleController.setJobsActive(active);
+ }
+ }
+ }
+
/**
* Initializes the system service.
* <p>
@@ -354,6 +390,8 @@ public class JobSchedulerService extends com.android.server.SystemService
mReadyToRock = true;
mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
BatteryStats.SERVICE_NAME));
+ mLocalDeviceIdleController
+ = LocalServices.getService(DeviceIdleController.LocalService.class);
// Create the "runners".
for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
mActiveServices.add(
@@ -623,6 +661,7 @@ public class JobSchedulerService extends com.android.server.SystemService
stopJobOnServiceContextLocked(job);
}
}
+ reportActive();
if (DEBUG) {
final int queuedJobs = mPendingJobs.size();
if (queuedJobs == 0) {
@@ -685,6 +724,7 @@ public class JobSchedulerService extends com.android.server.SystemService
Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Not running anything.");
}
}
+ reportActive();
if (DEBUG) {
Slog.d(TAG, "idle=" + idleCount + " connectivity=" +
connectivityCount + " charging=" + chargingCount + " tot=" +
@@ -766,6 +806,7 @@ public class JobSchedulerService extends com.android.server.SystemService
it.remove();
}
}
+ reportActive();
}
}
}
@@ -948,6 +989,7 @@ public class JobSchedulerService extends com.android.server.SystemService
pw.println();
pw.print("mReadyToRock="); pw.println(mReadyToRock);
pw.print("mDeviceIdleMode="); pw.println(mDeviceIdleMode);
+ pw.print("mReportedActive="); pw.println(mReportedActive);
}
pw.println();
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index c1525144765b..02a620468afc 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1414,18 +1414,18 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
- packageName, extras, null, null, firstUsers);
+ packageName, extras, 0, null, null, firstUsers);
final boolean update = res.removedInfo.removedPackage != null;
if (update) {
extras.putBoolean(Intent.EXTRA_REPLACING, true);
}
sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
- packageName, extras, null, null, updateUsers);
+ packageName, extras, 0, null, null, updateUsers);
if (update) {
sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
- packageName, extras, null, null, updateUsers);
+ packageName, extras, 0, null, null, updateUsers);
sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED,
- null, null, packageName, null, updateUsers);
+ null, null, 0, packageName, null, updateUsers);
// treat asec-hosted packages like removable media on upgrade
if (res.pkg.isForwardLocked() || isExternal(res.pkg)) {
@@ -1956,8 +1956,7 @@ public class PackageManagerService extends IPackageManager.Stub {
mUserAppDataDir = new File(dataDir, "user");
mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
- sUserManager = new UserManagerService(context, this,
- mInstallLock, mPackages);
+ sUserManager = new UserManagerService(context, this, mPackages);
// Propagate permission configuration in to package manager.
ArrayMap<String, SystemConfig.PermissionEntry> permConfig
@@ -9371,8 +9370,8 @@ public class PackageManagerService extends IPackageManager.Stub {
}
};
- final void sendPackageBroadcast(final String action, final String pkg,
- final Bundle extras, final String targetPkg, final IIntentReceiver finishedReceiver,
+ final void sendPackageBroadcast(final String action, final String pkg, final Bundle extras,
+ final int flags, final String targetPkg, final IIntentReceiver finishedReceiver,
final int[] userIds) {
mHandler.post(new Runnable() {
@Override
@@ -9402,7 +9401,7 @@ public class PackageManagerService extends IPackageManager.Stub {
intent.putExtra(Intent.EXTRA_UID, uid);
}
intent.putExtra(Intent.EXTRA_USER_HANDLE, id);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | flags);
if (DEBUG_BROADCASTS) {
RuntimeException here = new RuntimeException("here");
here.fillInStackTrace();
@@ -9594,7 +9593,7 @@ public class PackageManagerService extends IPackageManager.Stub {
extras.putInt(Intent.EXTRA_UID, UserHandle.getUid(userId, pkgSetting.appId));
sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
- packageName, extras, null, null, new int[] {userId});
+ packageName, extras, 0, null, null, new int[] {userId});
try {
IActivityManager am = ActivityManagerNative.getDefault();
final boolean isSystem =
@@ -12904,11 +12903,11 @@ public class PackageManagerService extends IPackageManager.Stub {
extras.putBoolean(Intent.EXTRA_REPLACING, true);
sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
- extras, null, null, null);
+ extras, 0, null, null, null);
sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName,
- extras, null, null, null);
+ extras, 0, null, null, null);
sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null,
- null, packageName, null, null);
+ null, 0, packageName, null, null);
}
}
// Force a gc here.
@@ -12943,14 +12942,14 @@ public class PackageManagerService extends IPackageManager.Stub {
extras.putBoolean(Intent.EXTRA_REMOVED_FOR_ALL_USERS, removedForAllUsers);
if (removedPackage != null) {
sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage,
- extras, null, null, removedUsers);
+ extras, 0, null, null, removedUsers);
if (fullRemove && !replacing) {
sendPackageBroadcast(Intent.ACTION_PACKAGE_FULLY_REMOVED, removedPackage,
- extras, null, null, removedUsers);
+ extras, 0, null, null, removedUsers);
}
}
if (removedAppId >= 0) {
- sendPackageBroadcast(Intent.ACTION_UID_REMOVED, null, extras, null, null,
+ sendPackageBroadcast(Intent.ACTION_UID_REMOVED, null, extras, 0, null, null,
removedUsers);
}
}
@@ -14650,7 +14649,12 @@ public class PackageManagerService extends IPackageManager.Stub {
extras.putStringArray(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, nameList);
extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, killFlag);
extras.putInt(Intent.EXTRA_UID, packageUid);
- sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED, packageName, extras, null, null,
+ // If this is not reporting a change of the overall package, then only send it
+ // to registered receivers. We don't want to launch a swath of apps for every
+ // little component state change.
+ final int flags = !componentNames.contains(packageName)
+ ? Intent.FLAG_RECEIVER_REGISTERED_ONLY : 0;
+ sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED, packageName, extras, flags, null, null,
new int[] {UserHandle.getUserId(packageUid)});
}
@@ -14838,20 +14842,23 @@ public class PackageManagerService extends IPackageManager.Stub {
static class DumpState {
public static final int DUMP_LIBS = 1 << 0;
public static final int DUMP_FEATURES = 1 << 1;
- public static final int DUMP_RESOLVERS = 1 << 2;
- public static final int DUMP_PERMISSIONS = 1 << 3;
- public static final int DUMP_PACKAGES = 1 << 4;
- public static final int DUMP_SHARED_USERS = 1 << 5;
- public static final int DUMP_MESSAGES = 1 << 6;
- public static final int DUMP_PROVIDERS = 1 << 7;
- public static final int DUMP_VERIFIERS = 1 << 8;
- public static final int DUMP_PREFERRED = 1 << 9;
- public static final int DUMP_PREFERRED_XML = 1 << 10;
- public static final int DUMP_KEYSETS = 1 << 11;
- public static final int DUMP_VERSION = 1 << 12;
- public static final int DUMP_INSTALLS = 1 << 13;
- public static final int DUMP_INTENT_FILTER_VERIFIERS = 1 << 14;
- public static final int DUMP_DOMAIN_PREFERRED = 1 << 15;
+ public static final int DUMP_ACTIVITY_RESOLVERS = 1 << 2;
+ public static final int DUMP_SERVICE_RESOLVERS = 1 << 3;
+ public static final int DUMP_RECEIVER_RESOLVERS = 1 << 4;
+ public static final int DUMP_CONTENT_RESOLVERS = 1 << 5;
+ public static final int DUMP_PERMISSIONS = 1 << 6;
+ public static final int DUMP_PACKAGES = 1 << 7;
+ public static final int DUMP_SHARED_USERS = 1 << 8;
+ public static final int DUMP_MESSAGES = 1 << 9;
+ public static final int DUMP_PROVIDERS = 1 << 10;
+ public static final int DUMP_VERIFIERS = 1 << 11;
+ public static final int DUMP_PREFERRED = 1 << 12;
+ public static final int DUMP_PREFERRED_XML = 1 << 13;
+ public static final int DUMP_KEYSETS = 1 << 14;
+ public static final int DUMP_VERSION = 1 << 15;
+ public static final int DUMP_INSTALLS = 1 << 16;
+ public static final int DUMP_INTENT_FILTER_VERIFIERS = 1 << 17;
+ public static final int DUMP_DOMAIN_PREFERRED = 1 << 18;
public static final int OPTION_SHOW_FILTERS = 1 << 0;
@@ -14950,9 +14957,9 @@ public class PackageManagerService extends IPackageManager.Stub {
pw.println(" -h: print this help");
pw.println(" cmd may be one of:");
pw.println(" l[ibraries]: list known shared libraries");
- pw.println(" f[ibraries]: list device features");
+ pw.println(" f[eatures]: list device features");
pw.println(" k[eysets]: print known keysets");
- pw.println(" r[esolvers]: dump intent resolvers");
+ pw.println(" r[esolvers] [activity|service|receiver|content]: dump intent resolvers");
pw.println(" perm[issions]: dump permissions");
pw.println(" permission [name ...]: dump declaration and use of given permission");
pw.println(" pref[erred]: print preferred package settings");
@@ -15019,7 +15026,29 @@ public class PackageManagerService extends IPackageManager.Stub {
} else if ("f".equals(cmd) || "features".equals(cmd)) {
dumpState.setDump(DumpState.DUMP_FEATURES);
} else if ("r".equals(cmd) || "resolvers".equals(cmd)) {
- dumpState.setDump(DumpState.DUMP_RESOLVERS);
+ if (opti >= args.length) {
+ dumpState.setDump(DumpState.DUMP_ACTIVITY_RESOLVERS
+ | DumpState.DUMP_SERVICE_RESOLVERS
+ | DumpState.DUMP_RECEIVER_RESOLVERS
+ | DumpState.DUMP_CONTENT_RESOLVERS);
+ } else {
+ while (opti < args.length) {
+ String name = args[opti];
+ if ("a".equals(name) || "activity".equals(name)) {
+ dumpState.setDump(DumpState.DUMP_ACTIVITY_RESOLVERS);
+ } else if ("s".equals(name) || "service".equals(name)) {
+ dumpState.setDump(DumpState.DUMP_SERVICE_RESOLVERS);
+ } else if ("r".equals(name) || "receiver".equals(name)) {
+ dumpState.setDump(DumpState.DUMP_RECEIVER_RESOLVERS);
+ } else if ("c".equals(name) || "content".equals(name)) {
+ dumpState.setDump(DumpState.DUMP_CONTENT_RESOLVERS);
+ } else {
+ pw.println("Error: unknown resolver table type: " + name);
+ return;
+ }
+ opti++;
+ }
+ }
} else if ("perm".equals(cmd) || "permissions".equals(cmd)) {
dumpState.setDump(DumpState.DUMP_PERMISSIONS);
} else if ("permission".equals(cmd)) {
@@ -15186,22 +15215,28 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
- if (!checkin && dumpState.isDumping(DumpState.DUMP_RESOLVERS)) {
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_ACTIVITY_RESOLVERS)) {
if (mActivities.dump(pw, dumpState.getTitlePrinted() ? "\nActivity Resolver Table:"
: "Activity Resolver Table:", " ", packageName,
dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS), true)) {
dumpState.setTitlePrinted(true);
}
+ }
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_RECEIVER_RESOLVERS)) {
if (mReceivers.dump(pw, dumpState.getTitlePrinted() ? "\nReceiver Resolver Table:"
: "Receiver Resolver Table:", " ", packageName,
dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS), true)) {
dumpState.setTitlePrinted(true);
}
+ }
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_SERVICE_RESOLVERS)) {
if (mServices.dump(pw, dumpState.getTitlePrinted() ? "\nService Resolver Table:"
: "Service Resolver Table:", " ", packageName,
dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS), true)) {
dumpState.setTitlePrinted(true);
}
+ }
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_CONTENT_RESOLVERS)) {
if (mProviders.dump(pw, dumpState.getTitlePrinted() ? "\nProvider Resolver Table:"
: "Provider Resolver Table:", " ", packageName,
dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS), true)) {
@@ -15648,7 +15683,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE
: Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE;
- sendPackageBroadcast(action, null, extras, null, finishedReceiver, null);
+ sendPackageBroadcast(action, null, extras, 0, null, finishedReceiver, null);
}
}
@@ -16353,23 +16388,26 @@ public class PackageManagerService extends IPackageManager.Stub {
}
/** Called by UserManagerService */
- void cleanUpUserLILPw(UserManagerService userManager, int userHandle) {
- mDirtyUsers.remove(userHandle);
- mSettings.removeUserLPw(userHandle);
- mPendingBroadcasts.remove(userHandle);
- if (mInstaller != null) {
- // Technically, we shouldn't be doing this with the package lock
- // held. However, this is very rare, and there is already so much
- // other disk I/O going on, that we'll let it slide for now.
- final StorageManager storage = mContext.getSystemService(StorageManager.class);
- for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
- final String volumeUuid = vol.getFsUuid();
- if (DEBUG_INSTALL) Slog.d(TAG, "Removing user data on volume " + volumeUuid);
- mInstaller.removeUserDataDirs(volumeUuid, userHandle);
+ void cleanUpUser(UserManagerService userManager, int userHandle) {
+ synchronized (mPackages) {
+ mDirtyUsers.remove(userHandle);
+ mUserNeedsBadging.delete(userHandle);
+ mSettings.removeUserLPw(userHandle);
+ mPendingBroadcasts.remove(userHandle);
+ }
+ synchronized (mInstallLock) {
+ if (mInstaller != null) {
+ final StorageManager storage = mContext.getSystemService(StorageManager.class);
+ for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
+ final String volumeUuid = vol.getFsUuid();
+ if (DEBUG_INSTALL) Slog.d(TAG, "Removing user data on volume " + volumeUuid);
+ mInstaller.removeUserDataDirs(volumeUuid, userHandle);
+ }
+ }
+ synchronized (mPackages) {
+ removeUnusedPackagesLILPw(userManager, userHandle);
}
}
- mUserNeedsBadging.delete(userHandle);
- removeUnusedPackagesLILPw(userManager, userHandle);
}
/**
@@ -16419,12 +16457,18 @@ public class PackageManagerService extends IPackageManager.Stub {
}
/** Called by UserManagerService */
- void createNewUserLILPw(int userHandle) {
+ void createNewUser(int userHandle) {
if (mInstaller != null) {
- mInstaller.createUserConfig(userHandle);
- mSettings.createNewUserLILPw(this, mInstaller, userHandle);
- applyFactoryDefaultBrowserLPw(userHandle);
- primeDomainVerificationsLPw(userHandle);
+ synchronized (mInstallLock) {
+ synchronized (mPackages) {
+ mInstaller.createUserConfig(userHandle);
+ mSettings.createNewUserLILPw(this, mInstaller, userHandle);
+ }
+ }
+ synchronized (mPackages) {
+ applyFactoryDefaultBrowserLPw(userHandle);
+ primeDomainVerificationsLPw(userHandle);
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index d7176fd0776b..2cedc9ccd7c1 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -35,6 +35,7 @@ import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageInstaller.SessionParams;
+import android.content.pm.ResolveInfo;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.net.Uri;
@@ -46,6 +47,7 @@ import android.os.ShellCommand;
import android.os.UserHandle;
import android.text.TextUtils;
+import android.util.PrintWriterPrinter;
import com.android.internal.util.SizedInputStream;
import libcore.io.IoUtils;
@@ -56,6 +58,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
+import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -68,6 +71,7 @@ class PackageManagerShellCommand extends ShellCommand {
final IPackageManager mInterface;
final private WeakHashMap<String, Resources> mResourceCache =
new WeakHashMap<String, Resources>();
+ int mTargetUser;
PackageManagerShellCommand(PackageManagerService service) {
mInterface = service;
@@ -97,6 +101,12 @@ class PackageManagerShellCommand extends ShellCommand {
return runList();
case "uninstall":
return runUninstall();
+ case "query-intent-activities":
+ return runQueryIntentActivities();
+ case "query-intent-services":
+ return runQueryIntentServices();
+ case "query-intent-receivers":
+ return runQueryIntentReceivers();
default:
return handleDefaultCommands(cmd);
}
@@ -340,7 +350,7 @@ class PackageManagerShellCommand extends ShellCommand {
listThirdParty = true;
break;
case "--user":
- userId = Integer.parseInt(getNextArg());
+ userId = UserHandle.parseUserArg(getNextArgRequired());
break;
default:
pw.println("Error: Unknown option: " + opt);
@@ -487,7 +497,7 @@ class PackageManagerShellCommand extends ShellCommand {
flags |= PackageManager.DELETE_KEEP_DATA;
break;
case "--user":
- userId = Integer.parseInt(getNextArg());
+ userId = UserHandle.parseUserArg(getNextArgRequired());
break;
default:
pw.println("Error: Unknown option: " + opt);
@@ -538,6 +548,104 @@ class PackageManagerShellCommand extends ShellCommand {
}
}
+ private Intent parseIntentAndUser() throws URISyntaxException {
+ mTargetUser = UserHandle.USER_CURRENT;
+ Intent intent = Intent.parseCommandArgs(this, new Intent.CommandOptionHandler() {
+ @Override
+ public boolean handleOption(String opt, ShellCommand cmd) {
+ if ("--user".equals(opt)) {
+ mTargetUser = UserHandle.parseUserArg(cmd.getNextArgRequired());
+ return true;
+ }
+ return false;
+ }
+ });
+ mTargetUser = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+ Binder.getCallingUid(), mTargetUser, false, false, null, null);
+ return intent;
+ }
+
+ private int runQueryIntentActivities() {
+ Intent intent;
+ try {
+ intent = parseIntentAndUser();
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ try {
+ List<ResolveInfo> result = mInterface.queryIntentActivities(intent, null, 0,
+ mTargetUser);
+ PrintWriter pw = getOutPrintWriter();
+ if (result == null || result.size() <= 0) {
+ pw.println("No activities found");
+ } else {
+ pw.print(result.size()); pw.println(" activities found:");
+ PrintWriterPrinter pr = new PrintWriterPrinter(pw);
+ for (int i=0; i<result.size(); i++) {
+ pw.print(" Activity #"); pw.print(i); pw.println(":");
+ result.get(i).dump(pr, " ");
+ }
+ }
+ } catch (RemoteException e) {
+ throw new RuntimeException("Failed calling service", e);
+ }
+ return 0;
+ }
+
+ private int runQueryIntentServices() {
+ Intent intent;
+ try {
+ intent = parseIntentAndUser();
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ try {
+ List<ResolveInfo> result = mInterface.queryIntentServices(intent, null, 0,
+ mTargetUser);
+ PrintWriter pw = getOutPrintWriter();
+ if (result == null || result.size() <= 0) {
+ pw.println("No services found");
+ } else {
+ pw.print(result.size()); pw.println(" services found:");
+ PrintWriterPrinter pr = new PrintWriterPrinter(pw);
+ for (int i=0; i<result.size(); i++) {
+ pw.print(" Service #"); pw.print(i); pw.println(":");
+ result.get(i).dump(pr, " ");
+ }
+ }
+ } catch (RemoteException e) {
+ throw new RuntimeException("Failed calling service", e);
+ }
+ return 0;
+ }
+
+ private int runQueryIntentReceivers() {
+ Intent intent;
+ try {
+ intent = parseIntentAndUser();
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ try {
+ List<ResolveInfo> result = mInterface.queryIntentReceivers(intent, null, 0,
+ mTargetUser);
+ PrintWriter pw = getOutPrintWriter();
+ if (result == null || result.size() <= 0) {
+ pw.println("No receivers found");
+ } else {
+ pw.print(result.size()); pw.println(" receivers found:");
+ PrintWriterPrinter pr = new PrintWriterPrinter(pw);
+ for (int i=0; i<result.size(); i++) {
+ pw.print(" Receiver #"); pw.print(i); pw.println(":");
+ result.get(i).dump(pr, " ");
+ }
+ }
+ } catch (RemoteException e) {
+ throw new RuntimeException("Failed calling service", e);
+ }
+ return 0;
+ }
+
private static class InstallParams {
SessionParams sessionParams;
String installerPackageName;
@@ -598,7 +706,7 @@ class PackageManagerShellCommand extends ShellCommand {
sessionParams.abiOverride = checkAbiArgument(getNextArg());
break;
case "--user":
- params.userId = Integer.parseInt(getNextArg());
+ params.userId = UserHandle.parseUserArg(getNextArgRequired());
break;
case "--install-location":
sessionParams.installLocation = Integer.parseInt(getNextArg());
@@ -908,7 +1016,14 @@ class PackageManagerShellCommand extends ShellCommand {
pw.println(" -s: short summary");
pw.println(" -d: only list dangerous permissions");
pw.println(" -u: list only the permissions users will see");
- pw.println("");
+ pw.println(" query-intent-activities [--user USER_ID] INTENT");
+ pw.println(" Prints all activities that can handle the given Intent.");
+ pw.println(" query-intent-services [--user USER_ID] INTENT");
+ pw.println(" Prints all services that can handle the given Intent.");
+ pw.println(" query-intent-receivers [--user USER_ID] INTENT");
+ pw.println(" Prints all broadcast receivers that can handle the given Intent.");
+ pw.println();
+ Intent.printIntentArgsHelp(pw , "");
}
private static class LocalIntentReceiver {
diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
index 5d8b1d281d54..903d12bcc217 100644
--- a/services/core/java/com/android/server/pm/SELinuxMMAC.java
+++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java
@@ -103,6 +103,9 @@ public final class SELinuxMMAC {
// Append privapp to existing seinfo label
private static final String PRIVILEGED_APP_STR = ":privapp";
+ // Append autoplay to existing seinfo label
+ private static final String AUTOPLAY_APP_STR = ":autoplayapp";
+
/**
* Load the mac_permissions.xml file containing all seinfo assignments used to
* label apps. The loaded mac_permissions.xml file is determined by the
@@ -316,6 +319,9 @@ public final class SELinuxMMAC {
}
}
+ if (pkg.applicationInfo.isAutoPlayApp())
+ pkg.applicationInfo.seinfo += AUTOPLAY_APP_STR;
+
if (pkg.applicationInfo.isPrivilegedApp())
pkg.applicationInfo.seinfo += PRIVILEGED_APP_STR;
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index de1473943203..1d299d74b2db 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3861,7 +3861,7 @@ final class Settings {
if (pkgSetting.getNotLaunched(userId)) {
if (pkgSetting.installerPackageName != null) {
yucky.sendPackageBroadcast(Intent.ACTION_PACKAGE_FIRST_LAUNCH,
- pkgSetting.name, null,
+ pkgSetting.name, null, 0,
pkgSetting.installerPackageName, null, new int[] {userId});
}
pkgSetting.setNotLaunched(false, userId);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 3a1d2de0d37c..ab0b182762e1 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -97,8 +97,7 @@ import libcore.io.IoUtils;
*
* Method naming convention:
* <ul>
- * <li> Methods suffixed with "LILP" should be called within {@link #mInstallLock} and
- * {@link #mPackagesLock} locks obtained in the respective order.
+ * <li> Methods suffixed with "LP" should be called within the {@link #mPackagesLock} lock.
* <li> Methods suffixed with "LR" should be called within the {@link #mRestrictionsLock} lock.
* <li> Methods suffixed with "LU" should be called within the {@link #mUsersLock} lock.
* </ul>
@@ -164,7 +163,6 @@ public class UserManagerService extends IUserManager.Stub {
private final Context mContext;
private final PackageManagerService mPm;
- private final Object mInstallLock;
private final Object mPackagesLock;
// Short-term lock for internal state, when interaction/sync with PM is not required
private final Object mUsersLock = new Object();
@@ -215,6 +213,7 @@ public class UserManagerService extends IUserManager.Stub {
@GuardedBy("mRestrictionsLock")
private final SparseArray<Bundle> mAppliedUserRestrictions = new SparseArray<>();
+ @GuardedBy("mGuestRestrictions")
private final Bundle mGuestRestrictions = new Bundle();
/**
@@ -226,6 +225,7 @@ public class UserManagerService extends IUserManager.Stub {
@GuardedBy("mUsersLock")
private int[] mUserIds;
+ @GuardedBy("mPackagesLock")
private int mNextSerialNumber;
private int mUserVersion = 0;
@@ -245,11 +245,9 @@ public class UserManagerService extends IUserManager.Stub {
}
}
- /**
- * Available for testing purposes.
- */
- UserManagerService(File dataDir, File baseUserPath) {
- this(null, null, new Object(), new Object(), dataDir, baseUserPath);
+ @VisibleForTesting
+ UserManagerService(File dataDir) {
+ this(null, null, new Object(), dataDir);
}
/**
@@ -257,68 +255,53 @@ public class UserManagerService extends IUserManager.Stub {
* associated with the package manager, and the given lock is the
* package manager's own lock.
*/
- UserManagerService(Context context, PackageManagerService pm,
- Object installLock, Object packagesLock) {
- this(context, pm, installLock, packagesLock,
- Environment.getDataDirectory(),
- new File(Environment.getDataDirectory(), "user"));
+ UserManagerService(Context context, PackageManagerService pm, Object packagesLock) {
+ this(context, pm, packagesLock, Environment.getDataDirectory());
}
- /**
- * Available for testing purposes.
- */
private UserManagerService(Context context, PackageManagerService pm,
- Object installLock, Object packagesLock,
- File dataDir, File baseUserPath) {
+ Object packagesLock, File dataDir) {
mContext = context;
mPm = pm;
- mInstallLock = installLock;
mPackagesLock = packagesLock;
mHandler = new MainHandler();
- synchronized (mInstallLock) {
- synchronized (mPackagesLock) {
- mUsersDir = new File(dataDir, USER_INFO_DIR);
- mUsersDir.mkdirs();
- // Make zeroth user directory, for services to migrate their files to that location
- File userZeroDir = new File(mUsersDir, String.valueOf(UserHandle.USER_SYSTEM));
- userZeroDir.mkdirs();
- FileUtils.setPermissions(mUsersDir.toString(),
- FileUtils.S_IRWXU|FileUtils.S_IRWXG
- |FileUtils.S_IROTH|FileUtils.S_IXOTH,
- -1, -1);
- mUserListFile = new File(mUsersDir, USER_LIST_FILENAME);
- initDefaultGuestRestrictions();
- readUserListLILP();
- sInstance = this;
- }
+ synchronized (mPackagesLock) {
+ mUsersDir = new File(dataDir, USER_INFO_DIR);
+ mUsersDir.mkdirs();
+ // Make zeroth user directory, for services to migrate their files to that location
+ File userZeroDir = new File(mUsersDir, String.valueOf(UserHandle.USER_SYSTEM));
+ userZeroDir.mkdirs();
+ FileUtils.setPermissions(mUsersDir.toString(),
+ FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH,
+ -1, -1);
+ mUserListFile = new File(mUsersDir, USER_LIST_FILENAME);
+ initDefaultGuestRestrictions();
+ readUserListLP();
+ sInstance = this;
}
mLocalService = new LocalService();
LocalServices.addService(UserManagerInternal.class, mLocalService);
}
void systemReady() {
- synchronized (mInstallLock) {
- synchronized (mPackagesLock) {
- synchronized (mUsersLock) {
- // Prune out any partially created/partially removed users.
- ArrayList<UserInfo> partials = new ArrayList<UserInfo>();
- final int userSize = mUsers.size();
- for (int i = 0; i < userSize; i++) {
- UserInfo ui = mUsers.valueAt(i);
- if ((ui.partial || ui.guestToRemove) && i != 0) {
- partials.add(ui);
- }
- }
- final int partialsSize = partials.size();
- for (int i = 0; i < partialsSize; i++) {
- UserInfo ui = partials.get(i);
- Slog.w(LOG_TAG, "Removing partially created user " + ui.id
- + " (name=" + ui.name + ")");
- removeUserStateLILP(ui.id);
- }
+ // Prune out any partially created/partially removed users.
+ ArrayList<UserInfo> partials = new ArrayList<>();
+ synchronized (mUsersLock) {
+ final int userSize = mUsers.size();
+ for (int i = 0; i < userSize; i++) {
+ UserInfo ui = mUsers.valueAt(i);
+ if ((ui.partial || ui.guestToRemove) && i != 0) {
+ partials.add(ui);
}
}
}
+ final int partialsSize = partials.size();
+ for (int i = 0; i < partialsSize; i++) {
+ UserInfo ui = partials.get(i);
+ Slog.w(LOG_TAG, "Removing partially created user " + ui.id
+ + " (name=" + ui.name + ")");
+ removeUserState(ui.id);
+ }
onUserForeground(UserHandle.USER_SYSTEM);
mAppOpsService = IAppOpsService.Stub.asInterface(
ServiceManager.getService(Context.APP_OPS_SERVICE));
@@ -627,17 +610,22 @@ public class UserManagerService extends IUserManager.Stub {
public void makeInitialized(int userId) {
checkManageUsersPermission("makeInitialized");
- synchronized (mPackagesLock) {
- UserInfo info = getUserInfoNoChecks(userId);
+ boolean scheduleWriteUser = false;
+ UserInfo info;
+ synchronized (mUsersLock) {
+ info = mUsers.get(userId);
if (info == null || info.partial) {
Slog.w(LOG_TAG, "makeInitialized: unknown user #" + userId);
- // TODO Check if we should return here instead of a null check below
+ return;
}
- if (info != null && (info.flags&UserInfo.FLAG_INITIALIZED) == 0) {
+ if ((info.flags & UserInfo.FLAG_INITIALIZED) == 0) {
info.flags |= UserInfo.FLAG_INITIALIZED;
- scheduleWriteUser(info);
+ scheduleWriteUser = true;
}
}
+ if (scheduleWriteUser) {
+ scheduleWriteUser(info);
+ }
}
/**
@@ -645,17 +633,18 @@ public class UserManagerService extends IUserManager.Stub {
* restrictions.
*/
private void initDefaultGuestRestrictions() {
- if (mGuestRestrictions.isEmpty()) {
- mGuestRestrictions.putBoolean(UserManager.DISALLOW_OUTGOING_CALLS, true);
- mGuestRestrictions.putBoolean(UserManager.DISALLOW_SMS, true);
+ synchronized (mGuestRestrictions) {
+ if (mGuestRestrictions.isEmpty()) {
+ mGuestRestrictions.putBoolean(UserManager.DISALLOW_OUTGOING_CALLS, true);
+ mGuestRestrictions.putBoolean(UserManager.DISALLOW_SMS, true);
+ }
}
}
@Override
public Bundle getDefaultGuestRestrictions() {
checkManageUsersPermission("getDefaultGuestRestrictions");
- // TODO Switch to mGuestRestrictions for locking
- synchronized (mPackagesLock) {
+ synchronized (mGuestRestrictions) {
return new Bundle(mGuestRestrictions);
}
}
@@ -663,12 +652,12 @@ public class UserManagerService extends IUserManager.Stub {
@Override
public void setDefaultGuestRestrictions(Bundle restrictions) {
checkManageUsersPermission("setDefaultGuestRestrictions");
- synchronized (mInstallLock) {
- synchronized (mPackagesLock) {
- mGuestRestrictions.clear();
- mGuestRestrictions.putAll(restrictions);
- writeUserListLILP();
- }
+ synchronized (mGuestRestrictions) {
+ mGuestRestrictions.clear();
+ mGuestRestrictions.putAll(restrictions);
+ }
+ synchronized (mPackagesLock) {
+ writeUserListLP();
}
}
@@ -775,7 +764,7 @@ public class UserManagerService extends IUserManager.Stub {
Preconditions.checkState(mCachedEffectiveUserRestrictions.get(userId)
!= newRestrictions);
mBaseUserRestrictions.put(userId, newRestrictions);
- scheduleWriteUser(mUsers.get(userId));
+ scheduleWriteUser(getUserInfoNoChecks(userId));
}
final Bundle effective = computeEffectiveUserRestrictionsLR(userId);
@@ -996,9 +985,9 @@ public class UserManagerService extends IUserManager.Stub {
}
}
- private void readUserListLILP() {
+ private void readUserListLP() {
if (!mUserListFile.exists()) {
- fallbackToSingleUserLILP();
+ fallbackToSingleUserLP();
return;
}
FileInputStream fis = null;
@@ -1015,7 +1004,7 @@ public class UserManagerService extends IUserManager.Stub {
if (type != XmlPullParser.START_TAG) {
Slog.e(LOG_TAG, "Unable to read user list");
- fallbackToSingleUserLILP();
+ fallbackToSingleUserLP();
return;
}
@@ -1036,7 +1025,7 @@ public class UserManagerService extends IUserManager.Stub {
final String name = parser.getName();
if (name.equals(TAG_USER)) {
String id = parser.getAttributeValue(null, ATTR_ID);
- UserInfo user = readUserLILP(Integer.parseInt(id));
+ UserInfo user = readUserLP(Integer.parseInt(id));
if (user != null) {
synchronized (mUsersLock) {
@@ -1051,8 +1040,10 @@ public class UserManagerService extends IUserManager.Stub {
&& type != XmlPullParser.END_TAG) {
if (type == XmlPullParser.START_TAG) {
if (parser.getName().equals(TAG_RESTRICTIONS)) {
- UserRestrictionsUtils
- .readRestrictions(parser, mGuestRestrictions);
+ synchronized (mGuestRestrictions) {
+ UserRestrictionsUtils
+ .readRestrictions(parser, mGuestRestrictions);
+ }
}
break;
}
@@ -1061,25 +1052,18 @@ public class UserManagerService extends IUserManager.Stub {
}
}
updateUserIds();
- upgradeIfNecessaryLILP();
- } catch (IOException ioe) {
- fallbackToSingleUserLILP();
- } catch (XmlPullParserException pe) {
- fallbackToSingleUserLILP();
+ upgradeIfNecessaryLP();
+ } catch (IOException | XmlPullParserException e) {
+ fallbackToSingleUserLP();
} finally {
- if (fis != null) {
- try {
- fis.close();
- } catch (IOException e) {
- }
- }
+ IoUtils.closeQuietly(fis);
}
}
/**
* Upgrade steps between versions, either for fixing bugs or changing the data format.
*/
- private void upgradeIfNecessaryLILP() {
+ private void upgradeIfNecessaryLP() {
int userVersion = mUserVersion;
if (userVersion < 1) {
// Assign a proper name for the owner, if not initialized correctly before
@@ -1132,11 +1116,11 @@ public class UserManagerService extends IUserManager.Stub {
+ USER_VERSION);
} else {
mUserVersion = userVersion;
- writeUserListLILP();
+ writeUserListLP();
}
}
- private void fallbackToSingleUserLILP() {
+ private void fallbackToSingleUserLP() {
int flags = UserInfo.FLAG_INITIALIZED;
// In split system user mode, the admin and primary flags are assigned to the first human
// user.
@@ -1161,7 +1145,7 @@ public class UserManagerService extends IUserManager.Stub {
updateUserIds();
initDefaultGuestRestrictions();
- writeUserListLILP();
+ writeUserListLP();
writeUserLP(system);
}
@@ -1247,8 +1231,7 @@ public class UserManagerService extends IUserManager.Stub {
* <user id="2"></user>
* </users>
*/
- private void writeUserListLILP() {
- // TODO Investigate removing a dependency on mInstallLock
+ private void writeUserListLP() {
FileOutputStream fos = null;
AtomicFile userListFile = new AtomicFile(mUserListFile);
try {
@@ -1266,8 +1249,10 @@ public class UserManagerService extends IUserManager.Stub {
serializer.attribute(null, ATTR_USER_VERSION, Integer.toString(mUserVersion));
serializer.startTag(null, TAG_GUEST_RESTRICTIONS);
- UserRestrictionsUtils
- .writeRestrictions(serializer, mGuestRestrictions, TAG_RESTRICTIONS);
+ synchronized (mGuestRestrictions) {
+ UserRestrictionsUtils
+ .writeRestrictions(serializer, mGuestRestrictions, TAG_RESTRICTIONS);
+ }
serializer.endTag(null, TAG_GUEST_RESTRICTIONS);
int[] userIdsToWrite;
synchronized (mUsersLock) {
@@ -1293,7 +1278,7 @@ public class UserManagerService extends IUserManager.Stub {
}
}
- private UserInfo readUserLILP(int id) {
+ private UserInfo readUserLP(int id) {
int flags = 0;
int serialNumber = id;
String name = null;
@@ -1468,8 +1453,7 @@ public class UserManagerService extends IUserManager.Stub {
}
private UserInfo createUserInternal(String name, int flags, int parentId) {
- if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(
- UserManager.DISALLOW_ADD_USER, false)) {
+ if (hasUserRestriction(UserManager.DISALLOW_ADD_USER, UserHandle.getCallingUserId())) {
Log.w(LOG_TAG, "Cannot add user. DISALLOW_ADD_USER is enabled.");
return null;
}
@@ -1480,120 +1464,114 @@ public class UserManagerService extends IUserManager.Stub {
final boolean isManagedProfile = (flags & UserInfo.FLAG_MANAGED_PROFILE) != 0;
final boolean isRestricted = (flags & UserInfo.FLAG_RESTRICTED) != 0;
final long ident = Binder.clearCallingIdentity();
- UserInfo userInfo = null;
+ UserInfo userInfo;
final int userId;
try {
- synchronized (mInstallLock) {
- synchronized (mPackagesLock) {
- UserInfo parent = null;
- if (parentId != UserHandle.USER_NULL) {
- synchronized (mUsersLock) {
- parent = getUserInfoLU(parentId);
- }
- if (parent == null) return null;
- }
- if (isManagedProfile && !canAddMoreManagedProfiles(parentId, false)) {
- Log.e(LOG_TAG, "Cannot add more managed profiles for user " + parentId);
- return null;
- }
- if (!isGuest && !isManagedProfile && isUserLimitReached()) {
- // If we're not adding a guest user or a managed profile and the limit has
- // been reached, cannot add a user.
- return null;
+ synchronized (mPackagesLock) {
+ UserInfo parent = null;
+ if (parentId != UserHandle.USER_NULL) {
+ synchronized (mUsersLock) {
+ parent = getUserInfoLU(parentId);
}
- // If we're adding a guest and there already exists one, bail.
- if (isGuest && findCurrentGuestUser() != null) {
+ if (parent == null) return null;
+ }
+ if (isManagedProfile && !canAddMoreManagedProfiles(parentId, false)) {
+ Log.e(LOG_TAG, "Cannot add more managed profiles for user " + parentId);
+ return null;
+ }
+ if (!isGuest && !isManagedProfile && isUserLimitReached()) {
+ // If we're not adding a guest user or a managed profile and the limit has
+ // been reached, cannot add a user.
+ return null;
+ }
+ // If we're adding a guest and there already exists one, bail.
+ if (isGuest && findCurrentGuestUser() != null) {
+ return null;
+ }
+ // In legacy mode, restricted profile's parent can only be the owner user
+ if (isRestricted && !UserManager.isSplitSystemUser()
+ && (parentId != UserHandle.USER_SYSTEM)) {
+ Log.w(LOG_TAG, "Cannot add restricted profile - parent user must be owner");
+ return null;
+ }
+ if (isRestricted && UserManager.isSplitSystemUser()) {
+ if (parent == null) {
+ Log.w(LOG_TAG, "Cannot add restricted profile - parent user must be "
+ + "specified");
return null;
}
- // In legacy mode, restricted profile's parent can only be the owner user
- if (isRestricted && !UserManager.isSplitSystemUser()
- && (parentId != UserHandle.USER_SYSTEM)) {
- Log.w(LOG_TAG, "Cannot add restricted profile - parent user must be owner");
+ if (!parent.canHaveProfile()) {
+ Log.w(LOG_TAG, "Cannot add restricted profile - profiles cannot be "
+ + "created for the specified parent user id " + parentId);
return null;
}
- if (isRestricted && UserManager.isSplitSystemUser()) {
- if (parent == null) {
- Log.w(LOG_TAG, "Cannot add restricted profile - parent user must be "
- + "specified");
- return null;
- }
- if (!parent.canHaveProfile()) {
- Log.w(LOG_TAG, "Cannot add restricted profile - profiles cannot be "
- + "created for the specified parent user id " + parentId);
- return null;
- }
- }
- // In split system user mode, we assign the first human user the primary flag.
- // And if there is no device owner, we also assign the admin flag to primary
- // user.
- if (UserManager.isSplitSystemUser()
- && !isGuest && !isManagedProfile && getPrimaryUser() == null) {
- flags |= UserInfo.FLAG_PRIMARY;
- DevicePolicyManager devicePolicyManager = (DevicePolicyManager)
- mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
- if (devicePolicyManager == null
- || devicePolicyManager.getDeviceOwner() == null) {
- flags |= UserInfo.FLAG_ADMIN;
- }
+ }
+ // In split system user mode, we assign the first human user the primary flag.
+ // And if there is no device owner, we also assign the admin flag to primary user.
+ if (UserManager.isSplitSystemUser()
+ && !isGuest && !isManagedProfile && getPrimaryUser() == null) {
+ flags |= UserInfo.FLAG_PRIMARY;
+ DevicePolicyManager devicePolicyManager = (DevicePolicyManager)
+ mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ if (devicePolicyManager == null
+ || devicePolicyManager.getDeviceOwner() == null) {
+ flags |= UserInfo.FLAG_ADMIN;
}
- userId = getNextAvailableId();
- userInfo = new UserInfo(userId, name, null, flags);
- userInfo.serialNumber = mNextSerialNumber++;
- long now = System.currentTimeMillis();
- userInfo.creationTime = (now > EPOCH_PLUS_30_YEARS) ? now : 0;
- userInfo.partial = true;
- Environment.getUserSystemDirectory(userInfo.id).mkdirs();
+ }
+ userId = getNextAvailableId();
+ userInfo = new UserInfo(userId, name, null, flags);
+ userInfo.serialNumber = mNextSerialNumber++;
+ long now = System.currentTimeMillis();
+ userInfo.creationTime = (now > EPOCH_PLUS_30_YEARS) ? now : 0;
+ userInfo.partial = true;
+ Environment.getUserSystemDirectory(userInfo.id).mkdirs();
+ synchronized (mUsersLock) {
mUsers.put(userId, userInfo);
- writeUserListLILP();
- if (parent != null) {
- if (isManagedProfile) {
- if (parent.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
- parent.profileGroupId = parent.id;
- scheduleWriteUser(parent);
- }
- userInfo.profileGroupId = parent.profileGroupId;
- } else if (isRestricted) {
- if (!parent.canHaveProfile()) {
- Log.w(LOG_TAG, "Cannot add restricted profile - parent user must be owner");
- }
- if (parent.restrictedProfileParentId == UserInfo.NO_PROFILE_GROUP_ID) {
- parent.restrictedProfileParentId = parent.id;
- scheduleWriteUser(parent);
- }
- userInfo.restrictedProfileParentId = parent.restrictedProfileParentId;
+ }
+ writeUserListLP();
+ if (parent != null) {
+ if (isManagedProfile) {
+ if (parent.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
+ parent.profileGroupId = parent.id;
+ writeUserLP(parent);
}
- }
-
- final StorageManager storage = mContext.getSystemService(StorageManager.class);
- storage.createUserKey(userId, userInfo.serialNumber);
- for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
- final String volumeUuid = vol.getFsUuid();
- try {
- final File userDir = Environment.getDataUserDirectory(volumeUuid,
- userId);
- storage.prepareUserStorage(volumeUuid, userId, userInfo.serialNumber);
- enforceSerialNumber(userDir, userInfo.serialNumber);
- } catch (IOException e) {
- Log.wtf(LOG_TAG, "Failed to create user directory on " + volumeUuid, e);
+ userInfo.profileGroupId = parent.profileGroupId;
+ } else if (isRestricted) {
+ if (parent.restrictedProfileParentId == UserInfo.NO_PROFILE_GROUP_ID) {
+ parent.restrictedProfileParentId = parent.id;
+ writeUserLP(parent);
}
- }
- mPm.createNewUserLILPw(userId);
- userInfo.partial = false;
- scheduleWriteUser(userInfo);
- updateUserIds();
- Bundle restrictions = new Bundle();
- synchronized (mRestrictionsLock) {
- mBaseUserRestrictions.append(userId, restrictions);
+ userInfo.restrictedProfileParentId = parent.restrictedProfileParentId;
}
}
}
- mPm.newUserCreated(userId);
- if (userInfo != null) {
- Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED);
- addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userInfo.id);
- mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL,
- android.Manifest.permission.MANAGE_USERS);
+ final StorageManager storage = mContext.getSystemService(StorageManager.class);
+ storage.createUserKey(userId, userInfo.serialNumber);
+ for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
+ final String volumeUuid = vol.getFsUuid();
+ try {
+ final File userDir = Environment.getDataUserDirectory(volumeUuid, userId);
+ storage.prepareUserStorage(volumeUuid, userId, userInfo.serialNumber);
+ enforceSerialNumber(userDir, userInfo.serialNumber);
+ } catch (IOException e) {
+ Log.wtf(LOG_TAG, "Failed to create user directory on " + volumeUuid, e);
+ }
}
+ mPm.createNewUser(userId);
+ userInfo.partial = false;
+ synchronized (mPackagesLock) {
+ writeUserLP(userInfo);
+ }
+ updateUserIds();
+ Bundle restrictions = new Bundle();
+ synchronized (mRestrictionsLock) {
+ mBaseUserRestrictions.append(userId, restrictions);
+ }
+ mPm.newUserCreated(userId);
+ Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED);
+ addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+ mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL,
+ android.Manifest.permission.MANAGE_USERS);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -1782,11 +1760,7 @@ public class UserManagerService extends IUserManager.Stub {
// Clean up any ActivityManager state
LocalServices.getService(ActivityManagerInternal.class)
.onUserRemoved(userHandle);
- synchronized (mInstallLock) {
- synchronized (mPackagesLock) {
- removeUserStateLILP(userHandle);
- }
- }
+ removeUserState(userHandle);
}
}.start();
}
@@ -1798,10 +1772,10 @@ public class UserManagerService extends IUserManager.Stub {
}
}
- private void removeUserStateLILP(final int userHandle) {
+ private void removeUserState(final int userHandle) {
mContext.getSystemService(StorageManager.class).destroyUserKey(userHandle);
// Cleanup package manager settings
- mPm.cleanUpUserLILPw(this, userHandle);
+ mPm.cleanUpUser(this, userHandle);
// Remove this user from the list
synchronized (mUsersLock) {
@@ -1811,7 +1785,9 @@ public class UserManagerService extends IUserManager.Stub {
AtomicFile userFile = new AtomicFile(new File(mUsersDir, userHandle + XML_SUFFIX));
userFile.delete();
// Update the user list
- writeUserListLILP();
+ synchronized (mPackagesLock) {
+ writeUserListLP();
+ }
updateUserIds();
removeDirectoryRecursive(Environment.getUserSystemDirectory(userHandle));
}
@@ -2146,17 +2122,15 @@ public class UserManagerService extends IUserManager.Stub {
* @param userId the user that was just foregrounded
*/
public void onUserForeground(int userId) {
- synchronized (mPackagesLock) {
- UserInfo user = getUserInfoNoChecks(userId);
- long now = System.currentTimeMillis();
- if (user == null || user.partial) {
- Slog.w(LOG_TAG, "userForeground: unknown user #" + userId);
- return;
- }
- if (now > EPOCH_PLUS_30_YEARS) {
- user.lastLoggedInTime = now;
- scheduleWriteUser(user);
- }
+ UserInfo user = getUserInfoNoChecks(userId);
+ if (user == null || user.partial) {
+ Slog.w(LOG_TAG, "userForeground: unknown user #" + userId);
+ return;
+ }
+ long now = System.currentTimeMillis();
+ if (now > EPOCH_PLUS_30_YEARS) {
+ user.lastLoggedInTime = now;
+ scheduleWriteUser(user);
}
}
@@ -2164,7 +2138,6 @@ public class UserManagerService extends IUserManager.Stub {
* Returns the next available user id, filling in any holes in the ids.
* TODO: May not be a good idea to recycle ids, in case it results in confusion
* for data and battery stats collection, or unexpected cross-talk.
- * @return
*/
private int getNextAvailableId() {
synchronized (mUsersLock) {
@@ -2348,7 +2321,9 @@ public class UserManagerService extends IUserManager.Stub {
}
pw.println();
pw.println("Guest restrictions:");
- UserRestrictionsUtils.dumpRestrictions(pw, " ", mGuestRestrictions);
+ synchronized (mGuestRestrictions) {
+ UserRestrictionsUtils.dumpRestrictions(pw, " ", mGuestRestrictions);
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index d713751d3786..c2466091bb91 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -409,6 +409,7 @@ final class AccessibilityController {
private final Region mMagnifiedBounds = new Region();
private final Region mOldMagnifiedBounds = new Region();
+ private final Region mOldAvailableBounds = new Region();
private final Path mCircularPath;
@@ -537,29 +538,39 @@ final class AccessibilityController {
screenWidth - mDrawBorderInset, screenHeight - mDrawBorderInset,
Region.Op.INTERSECT);
- if (!mOldMagnifiedBounds.equals(magnifiedBounds)) {
- Region bounds = Region.obtain();
- bounds.set(magnifiedBounds);
- mHandler.obtainMessage(MyHandler.MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED,
- bounds).sendToTarget();
-
- mWindow.setBounds(magnifiedBounds);
- Rect dirtyRect = mTempRect1;
- if (mFullRedrawNeeded) {
- mFullRedrawNeeded = false;
- dirtyRect.set(mDrawBorderInset, mDrawBorderInset,
- screenWidth - mDrawBorderInset, screenHeight - mDrawBorderInset);
- mWindow.invalidate(dirtyRect);
- } else {
- Region dirtyRegion = mTempRegion3;
- dirtyRegion.set(magnifiedBounds);
- dirtyRegion.op(mOldMagnifiedBounds, Region.Op.UNION);
- dirtyRegion.op(nonMagnifiedBounds, Region.Op.INTERSECT);
- dirtyRegion.getBounds(dirtyRect);
- mWindow.invalidate(dirtyRect);
+ final boolean magnifiedChanged = !mOldMagnifiedBounds.equals(magnifiedBounds);
+ final boolean availableChanged = !mOldAvailableBounds.equals(availableBounds);
+ if (magnifiedChanged || availableChanged) {
+ if (magnifiedChanged) {
+ mWindow.setBounds(magnifiedBounds);
+ Rect dirtyRect = mTempRect1;
+ if (mFullRedrawNeeded) {
+ mFullRedrawNeeded = false;
+ dirtyRect.set(mDrawBorderInset, mDrawBorderInset,
+ screenWidth - mDrawBorderInset,
+ screenHeight - mDrawBorderInset);
+ mWindow.invalidate(dirtyRect);
+ } else {
+ Region dirtyRegion = mTempRegion3;
+ dirtyRegion.set(magnifiedBounds);
+ dirtyRegion.op(mOldMagnifiedBounds, Region.Op.UNION);
+ dirtyRegion.op(nonMagnifiedBounds, Region.Op.INTERSECT);
+ dirtyRegion.getBounds(dirtyRect);
+ mWindow.invalidate(dirtyRect);
+ }
+
+ mOldMagnifiedBounds.set(magnifiedBounds);
+ }
+
+ if (availableChanged) {
+ mOldAvailableBounds.set(availableBounds);
}
- mOldMagnifiedBounds.set(magnifiedBounds);
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = Region.obtain(magnifiedBounds);
+ args.arg2 = Region.obtain(availableBounds);
+ mHandler.obtainMessage(
+ MyHandler.MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED, args).sendToTarget();
}
}
@@ -867,9 +878,12 @@ final class AccessibilityController {
public void handleMessage(Message message) {
switch (message.what) {
case MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED: {
- Region bounds = (Region) message.obj;
- mCallbacks.onMagnifedBoundsChanged(bounds);
- bounds.recycle();
+ final SomeArgs args = (SomeArgs) message.obj;
+ final Region magnifiedBounds = (Region) args.arg1;
+ final Region availableBounds = (Region) args.arg2;
+ mCallbacks.onMagnifiedBoundsChanged(magnifiedBounds, availableBounds);
+ magnifiedBounds.recycle();
+ availableBounds.recycle();
} break;
case MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED: {
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 89f565816a62..943c9ed39262 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -139,7 +139,7 @@ public class AppTransition implements Dump {
private static final long APP_TRANSITION_TIMEOUT_MS = 5000;
private final Context mContext;
- private final Handler mH;
+ private final WindowManagerService mService;
private int mNextAppTransition = TRANSIT_UNSET;
@@ -208,15 +208,10 @@ public class AppTransition implements Dump {
private final ArrayList<AppTransitionListener> mListeners = new ArrayList<>();
private final ExecutorService mDefaultExecutor = Executors.newSingleThreadExecutor();
- private final Object mServiceLock;
- private final WindowSurfacePlacer mWindowSurfacePlacer;
- AppTransition(Context context, Handler h, Object serviceLock,
- WindowSurfacePlacer windowSurfacePlacer) {
+ AppTransition(Context context, WindowManagerService service) {
mContext = context;
- mH = h;
- mServiceLock = serviceLock;
- mWindowSurfacePlacer = windowSurfacePlacer;
+ mService = service;
mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
com.android.internal.R.interpolator.linear_out_slow_in);
mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
@@ -971,7 +966,7 @@ public class AppTransition implements Dump {
@Override
public void onAnimationEnd(Animation animation) {
- mH.obtainMessage(H.DO_ANIMATION_CALLBACK, callback).sendToTarget();
+ mService.mH.obtainMessage(H.DO_ANIMATION_CALLBACK, callback).sendToTarget();
}
@Override
@@ -1326,7 +1321,8 @@ public class AppTransition implements Dump {
void postAnimationCallback() {
if (mNextAppTransitionCallback != null) {
- mH.sendMessage(mH.obtainMessage(H.DO_ANIMATION_CALLBACK, mNextAppTransitionCallback));
+ mService.mH.sendMessage(mService.mH.obtainMessage(H.DO_ANIMATION_CALLBACK,
+ mNextAppTransitionCallback));
mNextAppTransitionCallback = null;
}
}
@@ -1478,14 +1474,15 @@ public class AppTransition implements Dump {
} catch (RemoteException e) {
Slog.w(TAG, "Failed to fetch app transition specs: " + e);
}
- synchronized (mServiceLock) {
+ synchronized (mService.mWindowMap) {
mNextAppTransitionAnimationsSpecsPending = false;
overridePendingAppTransitionMultiThumb(specs,
mNextAppTransitionFutureCallback, null /* finishedCallback */,
mNextAppTransitionScaleUp);
mNextAppTransitionFutureCallback = null;
- mWindowSurfacePlacer.requestTraversal();
+ mService.prolongAnimationsFromSpecs(specs, mNextAppTransitionScaleUp);
}
+ mService.requestTraversal();
}
});
}
@@ -1672,8 +1669,8 @@ public class AppTransition implements Dump {
}
boolean prepared = prepare();
if (isTransitionSet()) {
- mH.removeMessages(H.APP_TRANSITION_TIMEOUT);
- mH.sendEmptyMessageDelayed(H.APP_TRANSITION_TIMEOUT, APP_TRANSITION_TIMEOUT_MS);
+ mService.mH.removeMessages(H.APP_TRANSITION_TIMEOUT);
+ mService.mH.sendEmptyMessageDelayed(H.APP_TRANSITION_TIMEOUT, APP_TRANSITION_TIMEOUT_MS);
}
return prepared;
}
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index 2905269eb21c..dfd01efe36d0 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -37,6 +37,10 @@ import java.util.ArrayList;
public class AppWindowAnimator {
static final String TAG = "AppWindowAnimator";
+ private static final int PROLONG_ANIMATION_DISABLED = 0;
+ static final int PROLONG_ANIMATION_AT_END = 1;
+ static final int PROLONG_ANIMATION_AT_START = 2;
+
final AppWindowToken mAppToken;
final WindowManagerService mService;
final WindowAnimator mAnimator;
@@ -85,7 +89,7 @@ public class AppWindowAnimator {
// If true when the animation hits the last frame, it will keep running on that last frame.
// This is used to synchronize animation with Recents and we wait for Recents to tell us to
// finish or for a new animation be set as fail-safe mechanism.
- private boolean mProlongAnimation;
+ private int mProlongAnimation;
// Whether the prolong animation can be removed when animation is set. The purpose of this is
// that if recents doesn't tell us to remove the prolonged animation, we will get rid of it
// when new animation is set.
@@ -142,7 +146,7 @@ public class AppWindowAnimator {
anim.setBackgroundColor(0);
}
if (mClearProlongedAnimation) {
- mProlongAnimation = false;
+ mProlongAnimation = PROLONG_ANIMATION_DISABLED;
} else {
mClearProlongedAnimation = true;
}
@@ -266,6 +270,10 @@ public class AppWindowAnimator {
return false;
}
transformation.clear();
+ if (mProlongAnimation == PROLONG_ANIMATION_AT_START) {
+ animation.setStartTime(currentTime);
+ currentTime += 1;
+ }
boolean hasMoreFrames = animation.getTransformation(currentTime, transformation);
if (!hasMoreFrames) {
if (deferThumbnailDestruction && !deferFinalFrameCleanup) {
@@ -278,7 +286,7 @@ public class AppWindowAnimator {
"Stepped animation in " + mAppToken + ": more=" + hasMoreFrames +
", xform=" + transformation + ", mProlongAnimation=" + mProlongAnimation);
deferFinalFrameCleanup = false;
- if (mProlongAnimation) {
+ if (mProlongAnimation == PROLONG_ANIMATION_AT_END) {
hasMoreFrames = true;
} else {
animation = null;
@@ -434,13 +442,13 @@ public class AppWindowAnimator {
}
}
- void startProlongAnimation() {
- mProlongAnimation = true;
+ void startProlongAnimation(int prolongType) {
+ mProlongAnimation = prolongType;
mClearProlongedAnimation = false;
}
void endProlongedAnimation() {
- mProlongAnimation = false;
+ mProlongAnimation = PROLONG_ANIMATION_DISABLED;
}
// This is an animation that does nothing: it just immediately finishes
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 425ff9b93b1c..3f4eaace838e 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -128,8 +128,6 @@ class AppWindowToken extends WindowToken {
boolean mWillReplaceWindow;
// If true, the replaced window was already requested to be removed.
boolean mReplacingRemoveRequested;
- // Whether the replacement of the window should trigger app transition animation.
- boolean mAnimateReplacingWindow;
// If not null, the window that will be used to replace the old one. This is being set when
// the window is added and unset when this window reports its first draw.
WindowState mReplacingWindow;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e264c437241c..328c0436e2a0 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -22,7 +22,6 @@ import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY;
import static com.android.server.wm.WindowManagerService.TAG;
import static com.android.server.wm.WindowState.RESIZE_HANDLE_WIDTH_IN_DP;
-import static com.android.server.wm.WindowState.BOUNDS_FOR_TOUCH;
import android.app.ActivityManager.StackId;
import android.graphics.Rect;
@@ -55,17 +54,6 @@ class DisplayContent {
* from mDisplayWindows; */
private final WindowList mWindows = new WindowList();
- // This protects the following display size properties, so that
- // getDisplaySize() doesn't need to acquire the global lock. This is
- // needed because the window manager sometimes needs to use ActivityThread
- // while it has its global state locked (for example to load animation
- // resources), but the ActivityThread also needs get the current display
- // size sometimes when it has its package lock held.
- //
- // These will only be modified with both mWindowMap and mDisplaySizeLock
- // held (in that order) so the window manager doesn't need to acquire this
- // lock when needing these values in its normal operation.
- final Object mDisplaySizeLock = new Object();
int mInitialDisplayWidth = 0;
int mInitialDisplayHeight = 0;
int mInitialDisplayDensity = 0;
@@ -202,18 +190,16 @@ class DisplayContent {
}
void initializeDisplayBaseInfo() {
- synchronized(mDisplaySizeLock) {
- // Bootstrap the default logical display from the display manager.
- final DisplayInfo newDisplayInfo =
- mService.mDisplayManagerInternal.getDisplayInfo(mDisplayId);
- if (newDisplayInfo != null) {
- mDisplayInfo.copyFrom(newDisplayInfo);
- }
- mBaseDisplayWidth = mInitialDisplayWidth = mDisplayInfo.logicalWidth;
- mBaseDisplayHeight = mInitialDisplayHeight = mDisplayInfo.logicalHeight;
- mBaseDisplayDensity = mInitialDisplayDensity = mDisplayInfo.logicalDensityDpi;
- mBaseDisplayRect.set(0, 0, mBaseDisplayWidth, mBaseDisplayHeight);
+ // Bootstrap the default logical display from the display manager.
+ final DisplayInfo newDisplayInfo =
+ mService.mDisplayManagerInternal.getDisplayInfo(mDisplayId);
+ if (newDisplayInfo != null) {
+ mDisplayInfo.copyFrom(newDisplayInfo);
}
+ mBaseDisplayWidth = mInitialDisplayWidth = mDisplayInfo.logicalWidth;
+ mBaseDisplayHeight = mInitialDisplayHeight = mDisplayInfo.logicalHeight;
+ mBaseDisplayDensity = mInitialDisplayDensity = mDisplayInfo.logicalDensityDpi;
+ mBaseDisplayRect.set(0, 0, mBaseDisplayWidth, mBaseDisplayHeight);
}
void getLogicalDisplayRect(Rect out) {
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 2be7ab8516b6..4926352829a0 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import static com.android.server.wm.WindowState.BOUNDS_FOR_TOUCH;
import com.android.server.input.InputApplicationHandle;
import com.android.server.input.InputWindowHandle;
import com.android.server.wm.WindowManagerService.DragInputEventReceiver;
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 3c3123ff0ba3..1f351cb94225 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -171,10 +171,10 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks {
private void addInputWindowHandleLw(final InputWindowHandle inputWindowHandle,
final WindowState child, int flags, final int type, final boolean isVisible,
- final boolean hasFocus, final boolean hasWallpaper, DisplayContent displayContent) {
+ final boolean hasFocus, final boolean hasWallpaper) {
// Add a window to our list of input windows.
inputWindowHandle.name = child.toString();
- flags = child.getTouchableRegion(inputWindowHandle.touchableRegion, flags, this);
+ flags = child.getTouchableRegion(inputWindowHandle.touchableRegion, flags);
inputWindowHandle.layoutParamsFlags = flags;
inputWindowHandle.layoutParamsType = type;
inputWindowHandle.dispatchingTimeoutNanos = child.getInputDispatchingTimeoutNanos();
@@ -308,8 +308,8 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks {
mService.mDragState.sendDragStartedIfNeededLw(child);
}
- addInputWindowHandleLw(inputWindowHandle, child, flags, type, isVisible, hasFocus,
- hasWallpaper, displayContent);
+ addInputWindowHandleLw(
+ inputWindowHandle, child, flags, type, isVisible, hasFocus, hasWallpaper);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 92eacd6b750f..a9bd71ff703b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -50,10 +50,12 @@ import static android.view.WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-import static com.android.server.wm.WindowState.BOUNDS_FOR_TOUCH;
+import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_END;
+import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_START;
import android.Manifest;
import android.animation.ValueAnimator;
+import android.annotation.Nullable;
import android.app.ActivityManagerNative;
import android.app.AppOpsManager;
import android.app.IActivityManager;
@@ -910,7 +912,7 @@ public class WindowManagerService extends IWindowManager.Stub
PowerManager.PARTIAL_WAKE_LOCK, "SCREEN_FROZEN");
mScreenFrozenLock.setReferenceCounted(false);
- mAppTransition = new AppTransition(context, mH, mWindowMap, mWindowPlacerLocked);
+ mAppTransition = new AppTransition(context, this);
mAppTransition.registerListenerLocked(mActivityManagerAppTransitionNotifier);
mActivityManager = ActivityManagerNative.getDefault();
@@ -2077,7 +2079,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
private void prepareWindowReplacementTransition(AppWindowToken atoken) {
- if (atoken == null || !atoken.mWillReplaceWindow || !atoken.mAnimateReplacingWindow) {
+ if (atoken == null || !atoken.mWillReplaceWindow) {
return;
}
atoken.allDrawn = false;
@@ -3693,22 +3695,26 @@ public class WindowManagerService extends IWindowManager.Stub
synchronized (mWindowMap) {
mAppTransition.overridePendingAppTransitionMultiThumb(specs, onAnimationStartedCallback,
onAnimationFinishedCallback, scaleUp);
- if (!scaleUp) {
- // This is used by freeform to recents windows transition. We need to synchronize
- // the animation with the appearance of the content of recents, so we will make
- // animation stay on the last frame a little longer.
- mTmpTaskIds.clear();
- for (int i = specs.length - 1; i >= 0; i--) {
- mTmpTaskIds.put(specs[i].taskId, 0);
- }
- for (final WindowState win : mWindowMap.values()) {
- final Task task = win.getTask();
- if (task != null && mTmpTaskIds.get(task.mTaskId, -1) != -1) {
- final AppWindowToken appToken = win.mAppToken;
- if (appToken != null && appToken.mAppAnimator != null) {
- appToken.mAppAnimator.startProlongAnimation();
- }
- }
+ prolongAnimationsFromSpecs(specs, scaleUp);
+
+ }
+ }
+
+ void prolongAnimationsFromSpecs(AppTransitionAnimationSpec[] specs, boolean scaleUp) {
+ // This is used by freeform <-> recents windows transition. We need to synchronize
+ // the animation with the appearance of the content of recents, so we will make
+ // animation stay on the first or last frame a little longer.
+ mTmpTaskIds.clear();
+ for (int i = specs.length - 1; i >= 0; i--) {
+ mTmpTaskIds.put(specs[i].taskId, 0);
+ }
+ for (final WindowState win : mWindowMap.values()) {
+ final Task task = win.getTask();
+ if (task != null && mTmpTaskIds.get(task.mTaskId, -1) != -1) {
+ final AppWindowToken appToken = win.mAppToken;
+ if (appToken != null && appToken.mAppAnimator != null) {
+ appToken.mAppAnimator.startProlongAnimation(scaleUp ?
+ PROLONG_ANIMATION_AT_START : PROLONG_ANIMATION_AT_END);
}
}
}
@@ -6908,27 +6914,25 @@ public class WindowManagerService extends IWindowManager.Stub
final int appWidth = mPolicy.getNonDecorDisplayWidth(dw, dh, mRotation);
final int appHeight = mPolicy.getNonDecorDisplayHeight(dw, dh, mRotation);
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
- synchronized(displayContent.mDisplaySizeLock) {
- displayInfo.rotation = mRotation;
- displayInfo.logicalWidth = dw;
- displayInfo.logicalHeight = dh;
- displayInfo.logicalDensityDpi = displayContent.mBaseDisplayDensity;
- displayInfo.appWidth = appWidth;
- displayInfo.appHeight = appHeight;
- displayInfo.getLogicalMetrics(mRealDisplayMetrics,
- CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
- displayInfo.getAppMetrics(mDisplayMetrics);
- if (displayContent.mDisplayScalingDisabled) {
- displayInfo.flags |= Display.FLAG_SCALING_DISABLED;
- } else {
- displayInfo.flags &= ~Display.FLAG_SCALING_DISABLED;
- }
+ displayInfo.rotation = mRotation;
+ displayInfo.logicalWidth = dw;
+ displayInfo.logicalHeight = dh;
+ displayInfo.logicalDensityDpi = displayContent.mBaseDisplayDensity;
+ displayInfo.appWidth = appWidth;
+ displayInfo.appHeight = appHeight;
+ displayInfo.getLogicalMetrics(mRealDisplayMetrics,
+ CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+ displayInfo.getAppMetrics(mDisplayMetrics);
+ if (displayContent.mDisplayScalingDisabled) {
+ displayInfo.flags |= Display.FLAG_SCALING_DISABLED;
+ } else {
+ displayInfo.flags &= ~Display.FLAG_SCALING_DISABLED;
+ }
- mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(
- displayContent.getDisplayId(), displayInfo);
+ mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(
+ displayContent.getDisplayId(), displayInfo);
- displayContent.mBaseDisplayRect.set(0, 0, dw, dh);
- }
+ displayContent.mBaseDisplayRect.set(0, 0, dw, dh);
if (false) {
Slog.i(TAG, "Set app display size: " + appWidth + " x " + appHeight);
}
@@ -8060,10 +8064,8 @@ public class WindowManagerService extends IWindowManager.Stub
synchronized (mWindowMap) {
final DisplayContent displayContent = getDisplayContentLocked(displayId);
if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) {
- synchronized(displayContent.mDisplaySizeLock) {
- size.x = displayContent.mInitialDisplayWidth;
- size.y = displayContent.mInitialDisplayHeight;
- }
+ size.x = displayContent.mInitialDisplayWidth;
+ size.y = displayContent.mInitialDisplayHeight;
}
}
}
@@ -8073,10 +8075,8 @@ public class WindowManagerService extends IWindowManager.Stub
synchronized (mWindowMap) {
final DisplayContent displayContent = getDisplayContentLocked(displayId);
if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) {
- synchronized(displayContent.mDisplaySizeLock) {
- size.x = displayContent.mBaseDisplayWidth;
- size.y = displayContent.mBaseDisplayHeight;
- }
+ size.x = displayContent.mBaseDisplayWidth;
+ size.y = displayContent.mBaseDisplayHeight;
}
}
}
@@ -8145,13 +8145,9 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- private void setForcedDisplayScalingModeLocked(DisplayContent displayContent,
- int mode) {
+ private void setForcedDisplayScalingModeLocked(DisplayContent displayContent, int mode) {
Slog.i(TAG, "Using display scaling mode: " + (mode == 0 ? "auto" : "off"));
-
- synchronized(displayContent.mDisplaySizeLock) {
- displayContent.mDisplayScalingDisabled = (mode != 0);
- }
+ displayContent.mDisplayScalingDisabled = (mode != 0);
reconfigureDisplayLocked(displayContent);
}
@@ -8169,13 +8165,11 @@ public class WindowManagerService extends IWindowManager.Stub
try {
width = Integer.parseInt(sizeStr.substring(0, pos));
height = Integer.parseInt(sizeStr.substring(pos+1));
- synchronized(displayContent.mDisplaySizeLock) {
- if (displayContent.mBaseDisplayWidth != width
- || displayContent.mBaseDisplayHeight != height) {
- Slog.i(TAG, "FORCED DISPLAY SIZE: " + width + "x" + height);
- displayContent.mBaseDisplayWidth = width;
- displayContent.mBaseDisplayHeight = height;
- }
+ if (displayContent.mBaseDisplayWidth != width
+ || displayContent.mBaseDisplayHeight != height) {
+ Slog.i(TAG, "FORCED DISPLAY SIZE: " + width + "x" + height);
+ displayContent.mBaseDisplayWidth = width;
+ displayContent.mBaseDisplayHeight = height;
}
} catch (NumberFormatException ex) {
}
@@ -8192,11 +8186,9 @@ public class WindowManagerService extends IWindowManager.Stub
int density;
try {
density = Integer.parseInt(densityStr);
- synchronized(displayContent.mDisplaySizeLock) {
- if (displayContent.mBaseDisplayDensity != density) {
- Slog.i(TAG, "FORCED DISPLAY DENSITY: " + density);
- displayContent.mBaseDisplayDensity = density;
- }
+ if (displayContent.mBaseDisplayDensity != density) {
+ Slog.i(TAG, "FORCED DISPLAY DENSITY: " + density);
+ displayContent.mBaseDisplayDensity = density;
}
} catch (NumberFormatException ex) {
}
@@ -8206,21 +8198,16 @@ public class WindowManagerService extends IWindowManager.Stub
int mode = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.DISPLAY_SCALING_FORCE, 0);
if (mode != 0) {
- synchronized(displayContent.mDisplaySizeLock) {
- Slog.i(TAG, "FORCED DISPLAY SCALING DISABLED");
- displayContent.mDisplayScalingDisabled = true;
- }
+ Slog.i(TAG, "FORCED DISPLAY SCALING DISABLED");
+ displayContent.mDisplayScalingDisabled = true;
}
}
// displayContent must not be null
private void setForcedDisplaySizeLocked(DisplayContent displayContent, int width, int height) {
Slog.i(TAG, "Using new display size: " + width + "x" + height);
-
- synchronized(displayContent.mDisplaySizeLock) {
- displayContent.mBaseDisplayWidth = width;
- displayContent.mBaseDisplayHeight = height;
- }
+ displayContent.mBaseDisplayWidth = width;
+ displayContent.mBaseDisplayHeight = height;
reconfigureDisplayLocked(displayContent);
}
@@ -8256,9 +8243,7 @@ public class WindowManagerService extends IWindowManager.Stub
synchronized (mWindowMap) {
final DisplayContent displayContent = getDisplayContentLocked(displayId);
if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) {
- synchronized(displayContent.mDisplaySizeLock) {
- return displayContent.mInitialDisplayDensity;
- }
+ return displayContent.mInitialDisplayDensity;
}
}
return -1;
@@ -8269,9 +8254,7 @@ public class WindowManagerService extends IWindowManager.Stub
synchronized (mWindowMap) {
final DisplayContent displayContent = getDisplayContentLocked(displayId);
if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) {
- synchronized(displayContent.mDisplaySizeLock) {
- return displayContent.mBaseDisplayDensity;
- }
+ return displayContent.mBaseDisplayDensity;
}
}
return -1;
@@ -8306,10 +8289,7 @@ public class WindowManagerService extends IWindowManager.Stub
// displayContent must not be null
private void setForcedDisplayDensityLocked(DisplayContent displayContent, int density) {
Slog.i(TAG, "Using new display density: " + density);
-
- synchronized(displayContent.mDisplaySizeLock) {
- displayContent.mBaseDisplayDensity = density;
- }
+ displayContent.mBaseDisplayDensity = density;
reconfigureDisplayLocked(displayContent);
}
@@ -8400,12 +8380,10 @@ public class WindowManagerService extends IWindowManager.Stub
private void setOverscanLocked(DisplayContent displayContent,
int left, int top, int right, int bottom) {
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
- synchronized (displayContent.mDisplaySizeLock) {
- displayInfo.overscanLeft = left;
- displayInfo.overscanTop = top;
- displayInfo.overscanRight = right;
- displayInfo.overscanBottom = bottom;
- }
+ displayInfo.overscanLeft = left;
+ displayInfo.overscanTop = top;
+ displayInfo.overscanRight = right;
+ displayInfo.overscanBottom = bottom;
mDisplaySettings.setOverscanLocked(displayInfo.uniqueId, displayInfo.name, left, top,
right, bottom);
@@ -8584,8 +8562,7 @@ public class WindowManagerService extends IWindowManager.Stub
} else if (wtoken != null) {
winAnimator.mAnimLayer =
w.mLayer + wtoken.mAppAnimator.animLayerAdjustment;
- if (wtoken.mWillReplaceWindow && wtoken.mAnimateReplacingWindow &&
- wtoken.mReplacingWindow != w) {
+ if (wtoken.mWillReplaceWindow && wtoken.mReplacingWindow != w) {
// We know that we will be animating a relaunching window in the near future,
// which will receive a z-order increase. We want the replaced window to
// immediately receive the same treatment, e.g. to be above the dock divider.
@@ -9992,14 +9969,11 @@ public class WindowManagerService extends IWindowManager.Stub
DisplayInfo displayInfo = displayContent.getDisplayInfo();
final Rect rect = new Rect();
mDisplaySettings.getOverscanLocked(displayInfo.name, displayInfo.uniqueId, rect);
- synchronized (displayContent.mDisplaySizeLock) {
- displayInfo.overscanLeft = rect.left;
- displayInfo.overscanTop = rect.top;
- displayInfo.overscanRight = rect.right;
- displayInfo.overscanBottom = rect.bottom;
- mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(
- displayId, displayInfo);
- }
+ displayInfo.overscanLeft = rect.left;
+ displayInfo.overscanTop = rect.top;
+ displayInfo.overscanRight = rect.right;
+ displayInfo.overscanBottom = rect.bottom;
+ mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(displayId, displayInfo);
configureDisplayPolicyLocked(displayContent);
// TODO: Create an input channel for each display with touch capability.
@@ -10125,9 +10099,8 @@ public class WindowManagerService extends IWindowManager.Stub
* Hint to a token that its activity will relaunch, which will trigger removal and addition of
* a window.
* @param token Application token for which the activity will be relaunched.
- * @param animate Whether to animate the addition of the new window.
*/
- public void setReplacingWindow(IBinder token, boolean animate) {
+ public void setReplacingWindow(IBinder token) {
synchronized (mWindowMap) {
AppWindowToken appWindowToken = findAppWindowToken(token);
if (appWindowToken == null) {
@@ -10138,7 +10111,13 @@ public class WindowManagerService extends IWindowManager.Stub
+ " as replacing window.");
appWindowToken.mWillReplaceWindow = true;
appWindowToken.mHasReplacedWindow = false;
- appWindowToken.mAnimateReplacingWindow = animate;
+
+ // Set-up dummy animation so we can start treating windows associated with this token
+ // like they are in transition before the new app window is ready for us to run the
+ // real transition animation.
+ if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
+ "setReplacingWindow() Setting dummy animation on: " + appWindowToken);
+ appWindowToken.mAppAnimator.setDummyAnimation();
}
}
@@ -10207,7 +10186,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
- public void setMagnificationCallbacks(MagnificationCallbacks callbacks) {
+ public void setMagnificationCallbacks(@Nullable MagnificationCallbacks callbacks) {
synchronized (mWindowMap) {
if (mAccessibilityController == null) {
mAccessibilityController = new AccessibilityController(
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 673c21ff9931..c6f7f4c343fb 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -16,66 +16,35 @@
package com.android.server.wm;
-import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
-import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
-import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
-import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
-import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
-import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
-import static android.view.WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
-import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
-import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
-import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
-import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
-import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
-import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
-import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
-import static com.android.server.wm.WindowManagerService.DEBUG_ADD_REMOVE;
-import static com.android.server.wm.WindowManagerService.DEBUG_ANIM;
-import static com.android.server.wm.WindowManagerService.DEBUG_APP_TRANSITIONS;
-import static com.android.server.wm.WindowManagerService.DEBUG_CONFIGURATION;
-import static com.android.server.wm.WindowManagerService.DEBUG_FOCUS_LIGHT;
-import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT;
-import static com.android.server.wm.WindowManagerService.DEBUG_ORIENTATION;
-import static com.android.server.wm.WindowManagerService.DEBUG_POWER;
-import static com.android.server.wm.WindowManagerService.DEBUG_RESIZE;
-import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY;
+import com.android.server.input.InputWindowHandle;
import android.app.ActivityManager;
import android.app.AppOpsManager;
-import android.graphics.Point;
-import android.os.PowerManager;
-import android.os.RemoteCallbackList;
-import android.os.SystemClock;
-import android.os.Trace;
-import android.os.WorkSource;
-import android.util.DisplayMetrics;
-import android.util.TimeUtils;
-import android.view.Display;
-import android.view.IWindowFocusObserver;
-import android.view.IWindowId;
-
-import com.android.server.input.InputWindowHandle;
-
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Matrix;
import android.graphics.PixelFormat;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.IBinder;
+import android.os.PowerManager;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.Trace;
import android.os.UserHandle;
+import android.os.WorkSource;
+import android.util.DisplayMetrics;
import android.util.Slog;
+import android.util.TimeUtils;
+import android.view.Display;
import android.view.DisplayInfo;
import android.view.Gravity;
import android.view.IApplicationToken;
import android.view.IWindow;
+import android.view.IWindowFocusObserver;
+import android.view.IWindowId;
import android.view.InputChannel;
import android.view.InputEvent;
import android.view.InputEventReceiver;
@@ -87,6 +56,40 @@ import android.view.WindowManagerPolicy;
import java.io.PrintWriter;
import java.util.ArrayList;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT;
+import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
+import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
+import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE;
+import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
+import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
+import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
+import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
+import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
+import static android.view.WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
+import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static com.android.server.wm.WindowManagerService.DEBUG_ADD_REMOVE;
+import static com.android.server.wm.WindowManagerService.DEBUG_CONFIGURATION;
+import static com.android.server.wm.WindowManagerService.DEBUG_FOCUS_LIGHT;
+import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT;
+import static com.android.server.wm.WindowManagerService.DEBUG_ORIENTATION;
+import static com.android.server.wm.WindowManagerService.DEBUG_POWER;
+import static com.android.server.wm.WindowManagerService.DEBUG_RESIZE;
+import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY;
+
class WindowList extends ArrayList<WindowState> {
}
@@ -106,8 +109,6 @@ final class WindowState implements WindowManagerPolicy.WindowState {
// to capture touch events in that area.
static final int RESIZE_HANDLE_WIDTH_IN_DP = 30;
- static final boolean BOUNDS_FOR_TOUCH = true;
-
static final int DRAG_RESIZE_MODE_FREEFORM = 0;
static final int DRAG_RESIZE_MODE_DOCKED_DIVIDER = 1;
@@ -1388,7 +1389,6 @@ final class WindowState implements WindowManagerPolicy.WindowState {
&& token.mHasReplacedWindow) {
if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Removing replacing window: " + this);
token.mWillReplaceWindow = false;
- token.mAnimateReplacingWindow = false;
token.mReplacingRemoveRequested = false;
token.mReplacingWindow = null;
token.mHasReplacedWindow = false;
@@ -1411,12 +1411,11 @@ final class WindowState implements WindowManagerPolicy.WindowState {
return mAppToken != null && mAppToken.mTask != null && mAppToken.mTask.inDockedWorkspace();
}
- int getTouchableRegion(Region region, int flags, InputMonitor inputMonitor) {
- final boolean modal = (flags & (WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)) == 0;
+ int getTouchableRegion(Region region, int flags) {
+ final boolean modal = (flags & (FLAG_NOT_TOUCH_MODAL | FLAG_NOT_FOCUSABLE)) == 0;
if (modal && mAppToken != null) {
// Limit the outer touch to the activity stack region.
- flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+ flags |= FLAG_NOT_TOUCH_MODAL;
// If this is a modal window we need to dismiss it if it's not full screen and the
// touch happens outside of the frame that displays the content. This means we
// need to intercept touches outside of that window. The dim layer user
@@ -1437,6 +1436,7 @@ final class WindowState implements WindowManagerPolicy.WindowState {
mTmpRect.inset(-delta, -delta);
}
region.set(mTmpRect);
+ cropRegionToStackBoundsIfNeeded(region);
} else {
// Not modal or full screen modal
getTouchableRegion(region);
@@ -1772,26 +1772,41 @@ final class WindowState implements WindowManagerPolicy.WindowState {
frame.right - inset.right, frame.bottom - inset.bottom);
}
- public void getTouchableRegion(Region outRegion) {
+ void getTouchableRegion(Region outRegion) {
final Rect frame = mFrame;
switch (mTouchableInsets) {
default:
- case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME:
+ case TOUCHABLE_INSETS_FRAME:
outRegion.set(frame);
break;
- case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT:
+ case TOUCHABLE_INSETS_CONTENT:
applyInsets(outRegion, frame, mGivenContentInsets);
break;
- case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE:
+ case TOUCHABLE_INSETS_VISIBLE:
applyInsets(outRegion, frame, mGivenVisibleInsets);
break;
- case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION: {
+ case TOUCHABLE_INSETS_REGION: {
final Region givenTouchableRegion = mGivenTouchableRegion;
outRegion.set(givenTouchableRegion);
outRegion.translate(frame.left, frame.top);
break;
}
}
+ cropRegionToStackBoundsIfNeeded(outRegion);
+ }
+
+ void cropRegionToStackBoundsIfNeeded(Region region) {
+ if (mAppToken == null || !mAppToken.mCropWindowsToStack) {
+ return;
+ }
+
+ final TaskStack stack = getStack();
+ if (stack == null) {
+ return;
+ }
+
+ stack.getDimBounds(mTmpRect);
+ region.op(mTmpRect, Region.Op.INTERSECT);
}
WindowList getWindowList() {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index f7805b1ab9ec..6faf3a703431 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -44,7 +44,6 @@ import android.graphics.Region;
import android.os.Debug;
import android.os.RemoteException;
import android.util.Slog;
-import android.view.Display;
import android.view.DisplayInfo;
import android.view.MagnificationSpec;
import android.view.Surface.OutOfResourcesException;
@@ -124,16 +123,12 @@ class WindowStateAnimator {
Rect mLastClipRect = new Rect();
Rect mTmpStackBounds = new Rect();
- // Used to save animation distances between the time they are calculated and when they are
- // used.
- int mAnimDw;
- int mAnimDh;
+ // Used to save animation distances between the time they are calculated and when they are used.
+ private int mAnimDx;
+ private int mAnimDy;
/** Is the next animation to be started a window move animation? */
- boolean mAnimateMove = false;
-
- /** Are we currently running a window move animation? */
- boolean mAnimatingMove = false;
+ private boolean mAnimateMove = false;
float mDsDx=1, mDtDx=0, mDsDy=0, mDtDy=1;
float mLastDsDx=1, mLastDtDx=0, mLastDsDy=0, mLastDtDy=1;
@@ -198,8 +193,8 @@ class WindowStateAnimator {
final DisplayContent displayContent = win.getDisplayContent();
if (displayContent != null) {
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
- mAnimDw = displayInfo.appWidth;
- mAnimDh = displayInfo.appHeight;
+ mAnimDx = displayInfo.appWidth;
+ mAnimDy = displayInfo.appHeight;
} else {
Slog.w(TAG, "WindowStateAnimator ctor: Display has been removed");
// This is checked on return and dealt with.
@@ -299,19 +294,19 @@ class WindowStateAnimator {
TAG, "Starting animation in " + this +
" @ " + currentTime + ": ww=" + mWin.mFrame.width() +
" wh=" + mWin.mFrame.height() +
- " dw=" + mAnimDw + " dh=" + mAnimDh +
+ " dx=" + mAnimDx + " dy=" + mAnimDy +
" scale=" + mService.getWindowAnimationScaleLocked());
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
if (mAnimateMove) {
mAnimateMove = false;
mAnimation.initialize(mWin.mFrame.width(), mWin.mFrame.height(),
- mAnimDw, mAnimDh);
+ mAnimDx, mAnimDy);
} else {
mAnimation.initialize(mWin.mFrame.width(), mWin.mFrame.height(),
displayInfo.appWidth, displayInfo.appHeight);
}
- mAnimDw = displayInfo.appWidth;
- mAnimDh = displayInfo.appHeight;
+ mAnimDx = displayInfo.appWidth;
+ mAnimDy = displayInfo.appHeight;
mAnimation.setStartTime(mAnimationStartTime != -1
? mAnimationStartTime
: currentTime);
@@ -368,7 +363,6 @@ class WindowStateAnimator {
mAnimating = false;
mKeyguardGoingAwayAnimation = false;
- mAnimatingMove = false;
mLocalAnimating = false;
if (mAnimation != null) {
mAnimation.cancel();
@@ -773,7 +767,14 @@ class WindowStateAnimator {
mPendingDestroySurface = mSurfaceController;
}
} else {
- WindowManagerService.logSurface(mWin, "DESTROY", null);
+ if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) {
+ RuntimeException e = null;
+ if (!WindowManagerService.HIDE_STACK_CRAWLS) {
+ e = new RuntimeException();
+ e.fillInStackTrace();
+ }
+ WindowManagerService.logSurface(mWin, "DESTROY", null);
+ }
destroySurface();
}
// Don't hide wallpaper if we're deferring the surface destroy
@@ -1606,7 +1607,7 @@ class WindowStateAnimator {
fadeOut.setDuration(fadeDuration);
fadeOut.setStartOffset(elapsed);
newAnimation.addAnimation(fadeOut);
- newAnimation.initialize(mWin.mFrame.width(), mWin.mFrame.height(), mAnimDw, mAnimDh);
+ newAnimation.initialize(mWin.mFrame.width(), mWin.mFrame.height(), mAnimDx, mAnimDy);
mAnimation = newAnimation;
}
@@ -1680,4 +1681,13 @@ class WindowStateAnimator {
mSurfaceController.destroyInTransaction();
mSurfaceController = null;
}
+
+ void setMoveAnimation(int left, int top) {
+ final Animation a = AnimationUtils.loadAnimation(mContext,
+ com.android.internal.R.anim.window_move_from_decor);
+ setAnimation(a);
+ mAnimDx = mWin.mLastFrame.left - left;
+ mAnimDy = mWin.mLastFrame.top - top;
+ mAnimateMove = true;
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index ae96658e9264..979f55baa369 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -17,7 +17,6 @@ import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.WindowManagerService.DEBUG;
import static com.android.server.wm.WindowManagerService.DEBUG_ADD_REMOVE;
-import static com.android.server.wm.WindowManagerService.DEBUG_ANIM;
import static com.android.server.wm.WindowManagerService.DEBUG_APP_TRANSITIONS;
import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT;
import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT_REPEATS;
@@ -57,7 +56,6 @@ import android.view.SurfaceControl;
import android.view.View;
import android.view.WindowManager;
import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -679,20 +677,17 @@ class WindowSurfacePlacer {
final WindowStateAnimator winAnimator = w.mWinAnimator;
// If the window has moved due to its containing content frame changing, then
- // notify the listeners and optionally animate it.
+ // notify the listeners and optionally animate it. Simply checking a change of
+ // position is not enough, because being move due to dock divider is not a trigger
+ // for animation.
if (w.hasMoved()) {
// Frame has moved, containing content frame has also moved, and we're not
// currently animating... let's do something.
final int left = w.mFrame.left;
final int top = w.mFrame.top;
- if ((w.mAttrs.privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0) {
- Animation a = AnimationUtils.loadAnimation(mService.mContext,
- com.android.internal.R.anim.window_move_from_decor);
- winAnimator.setAnimation(a);
- winAnimator.mAnimDw = w.mLastFrame.left - left;
- winAnimator.mAnimDh = w.mLastFrame.top - top;
- winAnimator.mAnimateMove = true;
- winAnimator.mAnimatingMove = true;
+ if ((w.mAttrs.privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0
+ && !w.isDragResizing()) {
+ winAnimator.setMoveAnimation(left, top);
}
//TODO (multidisplay): Accessibility supported only for the default display.
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index f2a1878d0dc5..d292f623a33e 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -747,7 +747,13 @@ bool ResourceParser::parseAttrImpl(XmlPullParser* parser, ParsedResource* outRes
}
}
- std::vector<Attribute::Symbol> items;
+ struct SymbolComparator {
+ bool operator()(const Attribute::Symbol& a, const Attribute::Symbol& b) {
+ return a.symbol.name.value() < b.symbol.name.value();
+ }
+ };
+
+ std::set<Attribute::Symbol, SymbolComparator> items;
std::u16string comment;
bool error = false;
@@ -785,15 +791,27 @@ bool ResourceParser::parseAttrImpl(XmlPullParser* parser, ParsedResource* outRes
}
if (Maybe<Attribute::Symbol> s = parseEnumOrFlagItem(parser, elementName)) {
+ Attribute::Symbol& symbol = s.value();
ParsedResource childResource;
- childResource.name = s.value().symbol.name.value();
+ childResource.name = symbol.symbol.name.value();
childResource.source = itemSource;
childResource.value = util::make_unique<Id>();
outResource->childResources.push_back(std::move(childResource));
- s.value().symbol.setComment(std::move(comment));
- s.value().symbol.setSource(itemSource);
- items.push_back(std::move(s.value()));
+ symbol.symbol.setComment(std::move(comment));
+ symbol.symbol.setSource(itemSource);
+
+ auto insertResult = items.insert(std::move(symbol));
+ if (!insertResult.second) {
+ const Attribute::Symbol& existingSymbol = *insertResult.first;
+ mDiag->error(DiagMessage(itemSource)
+ << "duplicate symbol '" << existingSymbol.symbol.name.value().entry
+ << "'");
+
+ mDiag->note(DiagMessage(existingSymbol.symbol.getSource())
+ << "first defined here");
+ error = true;
+ }
} else {
error = true;
}
@@ -810,7 +828,7 @@ bool ResourceParser::parseAttrImpl(XmlPullParser* parser, ParsedResource* outRes
}
std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(weak);
- attr->symbols.swap(items);
+ attr->symbols = std::vector<Attribute::Symbol>(items.begin(), items.end());
attr->typeMask = typeMask ? typeMask : uint32_t(android::ResTable_map::TYPE_ANY);
outResource->value = std::move(attr);
return true;
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index d3c3c1044ae7..b1a4c7dee73f 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -23,22 +23,28 @@
namespace aapt {
namespace ResourceUtils {
-void extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
+bool extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
StringPiece16* outType, StringPiece16* outEntry) {
+ bool hasPackageSeparator = false;
+ bool hasTypeSeparator = false;
const char16_t* start = str.data();
const char16_t* end = start + str.size();
const char16_t* current = start;
while (current != end) {
if (outType->size() == 0 && *current == u'/') {
+ hasTypeSeparator = true;
outType->assign(start, current - start);
start = current + 1;
} else if (outPackage->size() == 0 && *current == u':') {
+ hasPackageSeparator = true;
outPackage->assign(start, current - start);
start = current + 1;
}
current++;
}
outEntry->assign(start, end - start);
+
+ return !(hasPackageSeparator && outPackage->empty()) && !(hasTypeSeparator && outType->empty());
}
bool tryParseReference(const StringPiece16& str, ResourceNameRef* outRef, bool* outCreate,
@@ -62,26 +68,34 @@ bool tryParseReference(const StringPiece16& str, ResourceNameRef* outRef, bool*
StringPiece16 package;
StringPiece16 type;
StringPiece16 entry;
- extractResourceName(trimmedStr.substr(offset, trimmedStr.size() - offset), &package, &type,
- &entry);
+ if (!extractResourceName(trimmedStr.substr(offset, trimmedStr.size() - offset),
+ &package, &type, &entry)) {
+ return false;
+ }
const ResourceType* parsedType = parseResourceType(type);
if (!parsedType) {
return false;
}
+ if (entry.empty()) {
+ return false;
+ }
+
if (create && *parsedType != ResourceType::kId) {
return false;
}
- if (outRef != nullptr) {
+ if (outRef) {
outRef->package = package;
outRef->type = *parsedType;
outRef->entry = entry;
}
+
if (outCreate) {
*outCreate = create;
}
+
if (outPrivate) {
*outPrivate = priv;
}
@@ -104,20 +118,33 @@ bool tryParseAttributeReference(const StringPiece16& str, ResourceNameRef* outRe
StringPiece16 package;
StringPiece16 type;
StringPiece16 entry;
- extractResourceName(trimmedStr.substr(1, trimmedStr.size() - 1), &package, &type, &entry);
+ if (!extractResourceName(trimmedStr.substr(1, trimmedStr.size() - 1),
+ &package, &type, &entry)) {
+ return false;
+ }
if (!type.empty() && type != u"attr") {
return false;
}
- outRef->package = package;
- outRef->type = ResourceType::kAttr;
- outRef->entry = entry;
+ if (entry.empty()) {
+ return false;
+ }
+
+ if (outRef) {
+ outRef->package = package;
+ outRef->type = ResourceType::kAttr;
+ outRef->entry = entry;
+ }
return true;
}
return false;
}
+bool isAttributeReference(const StringPiece16& str) {
+ return tryParseAttributeReference(str, nullptr);
+}
+
/*
* Style parent's are a bit different. We accept the following formats:
*
diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h
index 851edc89fa90..34daa66b1546 100644
--- a/tools/aapt2/ResourceUtils.h
+++ b/tools/aapt2/ResourceUtils.h
@@ -34,8 +34,9 @@ namespace ResourceUtils {
*
* where the package can be empty. Validation must be performed on each
* individual extracted piece to verify that the pieces are valid.
+ * Returns false if there was no package but a ':' was present.
*/
-void extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
+bool extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
StringPiece16* outType, StringPiece16* outEntry);
/*
@@ -54,12 +55,17 @@ bool tryParseReference(const StringPiece16& str, ResourceNameRef* outReference,
bool isReference(const StringPiece16& str);
/*
- * Returns true if the string was parsed as an attribute reference (?[package:]type/name),
+ * Returns true if the string was parsed as an attribute reference (?[package:][type/]name),
* with `outReference` set to the parsed reference.
*/
bool tryParseAttributeReference(const StringPiece16& str, ResourceNameRef* outReference);
/**
+ * Returns true if the string is in the form of an attribute reference(?[package:][type/]name).
+ */
+bool isAttributeReference(const StringPiece16& str);
+
+/**
* Returns true if the value is a boolean, putting the result in `outValue`.
*/
bool tryParseBool(const StringPiece16& str, bool* outValue);
diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp
index 7de8f4130316..3d2a6e18e2bc 100644
--- a/tools/aapt2/ResourceUtils_test.cpp
+++ b/tools/aapt2/ResourceUtils_test.cpp
@@ -90,6 +90,26 @@ TEST(ResourceUtilsTest, FailToParseAutoCreateNonIdReference) {
&privateRef));
}
+TEST(ResourceUtilsTest, ParseAttributeReferences) {
+ EXPECT_TRUE(ResourceUtils::isAttributeReference(u"?android"));
+ EXPECT_TRUE(ResourceUtils::isAttributeReference(u"?android:foo"));
+ EXPECT_TRUE(ResourceUtils::isAttributeReference(u"?attr/foo"));
+ EXPECT_TRUE(ResourceUtils::isAttributeReference(u"?android:attr/foo"));
+}
+
+TEST(ResourceUtilsTest, FailParseIncompleteReference) {
+ EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?style/foo"));
+ EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?android:style/foo"));
+ EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?android:"));
+ EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?android:attr/"));
+ EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?:attr/"));
+ EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?:attr/foo"));
+ EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?:/"));
+ EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?:/foo"));
+ EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?attr/"));
+ EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?/foo"));
+}
+
TEST(ResourceUtilsTest, ParseStyleParentReference) {
const ResourceName kAndroidStyleFooName = { u"android", ResourceType::kStyle, u"foo" };
const ResourceName kStyleFooName = { {}, ResourceType::kStyle, u"foo" };
diff --git a/tools/aapt2/XmlDom.h b/tools/aapt2/XmlDom.h
index 9a46bcb9fc5c..721bf5bd6320 100644
--- a/tools/aapt2/XmlDom.h
+++ b/tools/aapt2/XmlDom.h
@@ -215,10 +215,13 @@ public:
for (auto iter = mPackageDecls.rbegin(); iter != rend; ++iter) {
if (name.package == iter->prefix) {
if (iter->package.empty()) {
- return ResourceName{ localPackage.toString(), name.type, name.entry };
- } else {
+ if (localPackage != name.package) {
+ return ResourceName{ localPackage.toString(), name.type, name.entry };
+ }
+ } else if (iter->package != name.package) {
return ResourceName{ iter->package, name.type, name.entry };
}
+ break;
}
}
return {};
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index 93f2dc6f04cd..97be774acaf1 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -50,7 +50,7 @@ struct LinkOptions {
std::vector<std::string> includePaths;
std::vector<std::string> overlayFiles;
Maybe<std::string> generateJavaClassPath;
- std::vector<std::string> extraJavaPackages;
+ std::set<std::string> extraJavaPackages;
Maybe<std::string> generateProguardRulesPath;
bool noAutoVersion = false;
bool staticLib = false;
@@ -485,7 +485,7 @@ public:
}
}
- mFilesToProcess[resName.toResourceName()] = FileToProcess{ Source(input), std::move(file) };
+ mFilesToProcess.insert(FileToProcess{ std::move(file), Source(input) });
return true;
}
@@ -640,8 +640,7 @@ public:
}
}
- for (auto& pair : mFilesToProcess) {
- FileToProcess& file = pair.second;
+ for (const FileToProcess& file : mFilesToProcess) {
if (file.file.name.type != ResourceType::kRaw &&
util::stringEndsWith<char>(file.source.path, ".xml.flat")) {
if (mOptions.verbose) {
@@ -760,7 +759,7 @@ public:
return 1;
}
- for (std::string& extraPackage : mOptions.extraJavaPackages) {
+ for (const std::string& extraPackage : mOptions.extraJavaPackages) {
if (!writeJavaFile(&mFinalTable, actualPackage, util::utf8ToUtf16(extraPackage),
options)) {
return 1;
@@ -795,16 +794,24 @@ private:
std::unique_ptr<TableMerger> mTableMerger;
struct FileToProcess {
- Source source;
ResourceFile file;
+ Source source;
+ };
+
+ struct FileToProcessComparator {
+ bool operator()(const FileToProcess& a, const FileToProcess& b) {
+ return std::tie(a.file.name, a.file.config) < std::tie(b.file.name, b.file.config);
+ }
};
- std::map<ResourceName, FileToProcess> mFilesToProcess;
+
+ std::set<FileToProcess, FileToProcessComparator> mFilesToProcess;
};
int link(const std::vector<StringPiece>& args) {
LinkOptions options;
Maybe<std::string> privateSymbolsPackage;
Maybe<std::string> minSdkVersion, targetSdkVersion;
+ std::vector<std::string> extraJavaPackages;
Flags flags = Flags()
.requiredFlag("-o", "Output path", &options.outputPath)
.requiredFlag("--manifest", "Path to the Android manifest to build",
@@ -833,7 +840,7 @@ int link(const std::vector<StringPiece>& args) {
"If not specified, public and private symbols will use the application's "
"package name", &privateSymbolsPackage)
.optionalFlagList("--extra-packages", "Generate the same R.java but with different "
- "package names", &options.extraJavaPackages)
+ "package names", &extraJavaPackages)
.optionalSwitch("-v", "Enables verbose logging", &options.verbose);
if (!flags.parse("aapt2 link", args, &std::cerr)) {
@@ -852,6 +859,14 @@ int link(const std::vector<StringPiece>& args) {
options.targetSdkVersionDefault = util::utf8ToUtf16(targetSdkVersion.value());
}
+ // Populate the set of extra packages for which to generate R.java.
+ for (std::string& extraPackage : extraJavaPackages) {
+ // A given package can actually be a colon separated list of packages.
+ for (StringPiece package : util::split(extraPackage, ':')) {
+ options.extraJavaPackages.insert(package.toString());
+ }
+ }
+
LinkCommand cmd(options);
return cmd.run(flags.getArgs());
}
diff --git a/tools/aapt2/link/ReferenceLinkerVisitor.h b/tools/aapt2/link/ReferenceLinkerVisitor.h
index c70531ba8296..a4cb596eb5ec 100644
--- a/tools/aapt2/link/ReferenceLinkerVisitor.h
+++ b/tools/aapt2/link/ReferenceLinkerVisitor.h
@@ -80,7 +80,7 @@ public:
return;
}
- DiagMessage errorMsg;
+ DiagMessage errorMsg(reference->getSource());
errorMsg << "reference to " << reference->name.value();
if (realName) {
errorMsg << " (aka " << realName.value() << ")";
@@ -92,7 +92,7 @@ public:
}
if (!mSymbols->findById(reference->id.value())) {
- mContext->getDiagnostics()->error(DiagMessage()
+ mContext->getDiagnostics()->error(DiagMessage(reference->getSource())
<< "reference to " << reference->id.value()
<< " was not found");
mError = true;
diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp
index 147b9bf16248..caab9b8a4684 100644
--- a/tools/aapt2/link/XmlReferenceLinker.cpp
+++ b/tools/aapt2/link/XmlReferenceLinker.cpp
@@ -33,6 +33,7 @@ class XmlReferenceLinkerVisitor : public xml::PackageAwareVisitor {
private:
IAaptContext* mContext;
ISymbolTable* mSymbols;
+ Source mSource;
std::set<int>* mSdkLevelsFound;
ReferenceLinkerVisitor mReferenceLinkerVisitor;
bool mError = false;
@@ -40,13 +41,14 @@ private:
public:
using xml::PackageAwareVisitor::visit;
- XmlReferenceLinkerVisitor(IAaptContext* context, ISymbolTable* symbols,
+ XmlReferenceLinkerVisitor(IAaptContext* context, ISymbolTable* symbols, const Source& source,
std::set<int>* sdkLevelsFound) :
- mContext(context), mSymbols(symbols), mSdkLevelsFound(sdkLevelsFound),
+ mContext(context), mSymbols(symbols), mSource(source), mSdkLevelsFound(sdkLevelsFound),
mReferenceLinkerVisitor(context, symbols, this) {
}
void visit(xml::Element* el) override {
+ const Source source = mSource.withLine(el->lineNumber);
for (xml::Attribute& attr : el->attributes) {
Maybe<std::u16string> maybePackage =
util::extractPackageFromNamespace(attr.namespaceUri);
@@ -76,15 +78,16 @@ public:
!(attribute->typeMask & android::ResTable_map::TYPE_STRING)) {
// We won't be able to encode this as a string.
mContext->getDiagnostics()->error(
- DiagMessage() << "'" << attr.value << "' "
- << "is incompatible with attribute "
- << package << ":" << attr.name << " " << *attribute);
+ DiagMessage(source) << "'" << attr.value << "' "
+ << "is incompatible with attribute "
+ << package << ":" << attr.name << " "
+ << *attribute);
mError = true;
}
} else {
- mContext->getDiagnostics()->error(
- DiagMessage() << "attribute '" << package << ":" << attr.name
- << "' was not found");
+ mContext->getDiagnostics()->error(DiagMessage(source)
+ << "attribute '" << package << ":"
+ << attr.name << "' was not found");
mError = true;
}
@@ -95,6 +98,7 @@ public:
if (attr.compiledValue) {
// With a compiledValue, we must resolve the reference and assign it an ID.
+ attr.compiledValue->setSource(source);
attr.compiledValue->accept(&mReferenceLinkerVisitor);
}
}
@@ -123,7 +127,8 @@ public:
bool XmlReferenceLinker::consume(IAaptContext* context, XmlResource* resource) {
mSdkLevelsFound.clear();
- XmlReferenceLinkerVisitor visitor(context, context->getExternalSymbols(), &mSdkLevelsFound);
+ XmlReferenceLinkerVisitor visitor(context, context->getExternalSymbols(), resource->file.source,
+ &mSdkLevelsFound);
if (resource->root) {
resource->root->accept(&visitor);
return !visitor.hasError();
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index 9a8b263f5f97..bb33ea78f496 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -77,16 +77,8 @@ const ISymbolTable::Symbol* SymbolTableWrapper::findByName(const ResourceName& n
}
-static std::shared_ptr<ISymbolTable::Symbol> lookupIdInTable(const android::ResTable& table,
- ResourceId id) {
- android::Res_value val = {};
- ssize_t block = table.getResource(id.id, &val, true);
- if (block >= 0) {
- std::shared_ptr<ISymbolTable::Symbol> s = std::make_shared<ISymbolTable::Symbol>();
- s->id = id;
- return s;
- }
-
+static std::shared_ptr<ISymbolTable::Symbol> lookupAttributeInTable(const android::ResTable& table,
+ ResourceId id) {
// Try as a bag.
const android::ResTable::bag_entry* entry;
ssize_t count = table.lockBag(id.id, &entry);
@@ -148,14 +140,23 @@ const ISymbolTable::Symbol* AssetManagerSymbolTableBuilder::AssetManagerSymbolTa
for (const auto& asset : mAssets) {
const android::ResTable& table = asset->getResources(false);
StringPiece16 typeStr = toString(name.type);
+ uint32_t typeSpecFlags = 0;
ResourceId resId = table.identifierForName(name.entry.data(), name.entry.size(),
typeStr.data(), typeStr.size(),
- name.package.data(), name.package.size());
+ name.package.data(), name.package.size(),
+ &typeSpecFlags);
if (!resId.isValid()) {
continue;
}
- std::shared_ptr<Symbol> s = lookupIdInTable(table, resId);
+ std::shared_ptr<Symbol> s;
+ if (name.type == ResourceType::kAttr) {
+ s = lookupAttributeInTable(table, resId);
+ } else {
+ s = std::make_shared<Symbol>();
+ s->id = resId;
+ }
+
if (s) {
mCache.put(name, s);
return s.get();
@@ -173,7 +174,28 @@ const ISymbolTable::Symbol* AssetManagerSymbolTableBuilder::AssetManagerSymbolTa
for (const auto& asset : mAssets) {
const android::ResTable& table = asset->getResources(false);
- std::shared_ptr<Symbol> s = lookupIdInTable(table, id);
+ android::ResTable::resource_name name;
+ if (!table.getResourceName(id.id, true, &name)) {
+ continue;
+ }
+
+ bool isAttr = false;
+ if (name.type) {
+ if (const ResourceType* t = parseResourceType(StringPiece16(name.type, name.typeLen))) {
+ isAttr = (*t == ResourceType::kAttr);
+ }
+ } else if (name.type8) {
+ isAttr = (StringPiece(name.type8, name.typeLen) == "attr");
+ }
+
+ std::shared_ptr<Symbol> s;
+ if (isAttr) {
+ s = lookupAttributeInTable(table, id);
+ } else {
+ s = std::make_shared<Symbol>();
+ s->id = id;
+ }
+
if (s) {
mIdCache.put(id, s);
return s.get();
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp
index 0d17e8467d32..304833481be0 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.cpp
+++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp
@@ -304,6 +304,11 @@ bool BinaryResourceParser::parsePackage(const ResChunk_header* chunk) {
return false;
}
+ // There can be multiple packages in a table, so
+ // clear the type and key pool in case they were set from a previous package.
+ mTypePool.uninit();
+ mKeyPool.uninit();
+
ResChunkPullParser parser(getChunkData(&packageHeader->header),
getChunkDataLen(&packageHeader->header));
while (ResChunkPullParser::isGoodEvent(parser.next())) {
diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h
index 80552a540ec3..324afb3c7de3 100644
--- a/tools/aapt2/util/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -229,11 +229,12 @@ public:
private:
friend class Tokenizer<Char>;
- iterator(BasicStringPiece<Char> s, Char sep, BasicStringPiece<Char> tok);
+ iterator(BasicStringPiece<Char> s, Char sep, BasicStringPiece<Char> tok, bool end);
- BasicStringPiece<Char> str;
- Char separator;
- BasicStringPiece<Char> token;
+ BasicStringPiece<Char> mStr;
+ Char mSeparator;
+ BasicStringPiece<Char> mToken;
+ bool mEnd;
};
Tokenizer(BasicStringPiece<Char> str, Char sep);
@@ -252,36 +253,38 @@ inline Tokenizer<Char> tokenize(BasicStringPiece<Char> str, Char sep) {
template <typename Char>
typename Tokenizer<Char>::iterator& Tokenizer<Char>::iterator::operator++() {
- const Char* start = token.end();
- const Char* end = str.end();
+ const Char* start = mToken.end();
+ const Char* end = mStr.end();
if (start == end) {
- token.assign(token.end(), 0);
+ mEnd = true;
+ mToken.assign(mToken.end(), 0);
return *this;
}
start += 1;
const Char* current = start;
while (current != end) {
- if (*current == separator) {
- token.assign(start, current - start);
+ if (*current == mSeparator) {
+ mToken.assign(start, current - start);
return *this;
}
++current;
}
- token.assign(start, end - start);
+ mToken.assign(start, end - start);
return *this;
}
template <typename Char>
inline BasicStringPiece<Char> Tokenizer<Char>::iterator::operator*() {
- return token;
+ return mToken;
}
template <typename Char>
inline bool Tokenizer<Char>::iterator::operator==(const iterator& rhs) const {
// We check equality here a bit differently.
// We need to know that the addresses are the same.
- return token.begin() == rhs.token.begin() && token.end() == rhs.token.end();
+ return mToken.begin() == rhs.mToken.begin() && mToken.end() == rhs.mToken.end() &&
+ mEnd == rhs.mEnd;
}
template <typename Char>
@@ -291,8 +294,8 @@ inline bool Tokenizer<Char>::iterator::operator!=(const iterator& rhs) const {
template <typename Char>
inline Tokenizer<Char>::iterator::iterator(BasicStringPiece<Char> s, Char sep,
- BasicStringPiece<Char> tok) :
- str(s), separator(sep), token(tok) {
+ BasicStringPiece<Char> tok, bool end) :
+ mStr(s), mSeparator(sep), mToken(tok), mEnd(end) {
}
template <typename Char>
@@ -307,8 +310,8 @@ inline typename Tokenizer<Char>::iterator Tokenizer<Char>::end() {
template <typename Char>
inline Tokenizer<Char>::Tokenizer(BasicStringPiece<Char> str, Char sep) :
- mBegin(++iterator(str, sep, BasicStringPiece<Char>(str.begin() - 1, 0))),
- mEnd(str, sep, BasicStringPiece<Char>(str.end(), 0)) {
+ mBegin(++iterator(str, sep, BasicStringPiece<Char>(str.begin() - 1, 0), false)),
+ mEnd(str, sep, BasicStringPiece<Char>(str.end(), 0), true) {
}
inline uint16_t hostToDevice16(uint16_t value) {
diff --git a/tools/aapt2/util/Util_test.cpp b/tools/aapt2/util/Util_test.cpp
index 9db9fb7f112a..9208e07e635b 100644
--- a/tools/aapt2/util/Util_test.cpp
+++ b/tools/aapt2/util/Util_test.cpp
@@ -101,6 +101,15 @@ TEST(UtilTest, TokenizeInput) {
ASSERT_EQ(tokenizer.end(), iter);
}
+TEST(UtilTest, TokenizeEmptyString) {
+ auto tokenizer = util::tokenize(StringPiece16(u""), u'|');
+ auto iter = tokenizer.begin();
+ ASSERT_NE(tokenizer.end(), iter);
+ ASSERT_EQ(StringPiece16(), *iter);
+ ++iter;
+ ASSERT_EQ(tokenizer.end(), iter);
+}
+
TEST(UtilTest, TokenizeAtEnd) {
auto tokenizer = util::tokenize(StringPiece16(u"one."), u'.');
auto iter = tokenizer.begin();
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
index 868c6d328e8e..b67afebf9a89 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
@@ -16,10 +16,13 @@
package com.android.layoutlib.bridge.bars;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.LayoutlibCallback;
import com.android.ide.common.rendering.api.RenderResources;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.SessionParams;
import com.android.ide.common.rendering.api.StyleResourceValue;
+import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.impl.ResourceHelper;
import com.android.resources.ResourceType;
@@ -45,6 +48,8 @@ public class AppCompatActionBar extends BridgeActionBar {
private Object mWindowDecorActionBar;
private static final String WINDOW_ACTION_BAR_CLASS = "android.support.v7.internal.app.WindowDecorActionBar";
+ // This is used on v23.1.1 and later.
+ private static final String WINDOW_ACTION_BAR_CLASS_NEW = "android.support.v7.app.WindowDecorActionBar";
private Class<?> mWindowActionBarClass;
/**
@@ -70,14 +75,26 @@ public class AppCompatActionBar extends BridgeActionBar {
try {
Class[] constructorParams = {View.class};
Object[] constructorArgs = {getDecorContent()};
- mWindowDecorActionBar = params.getLayoutlibCallback().loadView(WINDOW_ACTION_BAR_CLASS,
- constructorParams, constructorArgs);
+ LayoutlibCallback callback = params.getLayoutlibCallback();
+ // First try to load the class as was available before appcompat v23.1.1, without
+ // logging warnings.
+ try {
+ mWindowDecorActionBar = callback.loadClass(WINDOW_ACTION_BAR_CLASS,
+ constructorParams, constructorArgs);
+ } catch (ClassNotFoundException ignore) {
+ }
+ if (mWindowDecorActionBar == null) {
+ // If failed, load the new class, while logging warnings.
+ mWindowDecorActionBar = callback.loadView(WINDOW_ACTION_BAR_CLASS_NEW,
+ constructorParams, constructorArgs);
+ }
mWindowActionBarClass = mWindowDecorActionBar == null ? null :
mWindowDecorActionBar.getClass();
setupActionBar();
} catch (Exception e) {
- e.printStackTrace();
+ Bridge.getLog().warning(LayoutLog.TAG_BROKEN,
+ "Failed to load AppCompat ActionBar with unknown error.", e);
}
}
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index e611ea448497..6e423911f09c 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -737,7 +737,9 @@ public class WifiEnterpriseConfig implements Parcelable {
public String toString() {
StringBuffer sb = new StringBuffer();
for (String key : mFields.keySet()) {
- sb.append(key).append(" ").append(mFields.get(key)).append("\n");
+ // Don't display password in toString().
+ String value = (key == PASSWORD_KEY) ? "<removed>" : mFields.get(key);
+ sb.append(key).append(" ").append(value).append("\n");
}
return sb.toString();
}
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index c26ca6ed2aff..5534cad16872 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -168,6 +168,22 @@ public class WifiScanner {
* to wake up at fixed interval
*/
public int maxScansToCache;
+ /**
+ * if maxPeriodInMs is non zero or different than period, then this bucket is
+ * an exponential backoff bucket and the scan period will grow exponentially
+ * as per formula: actual_period(N) = period ^ (N/(step_count+1))
+ * to a maximum period of max_period.
+ */
+ public int maxPeriodInMs;
+ /**
+ * for exponential back off bucket: multiplier: new_period=old_period*exponent
+ */
+ public int exponent;
+ /**
+ * for exponential back off bucket, number of scans performed at a given
+ * period and until the exponent is applied
+ */
+ public int stepCount;
/** Implement the Parcelable interface {@hide} */
public int describeContents() {
@@ -181,6 +197,9 @@ public class WifiScanner {
dest.writeInt(reportEvents);
dest.writeInt(numBssidsPerScan);
dest.writeInt(maxScansToCache);
+ dest.writeInt(maxPeriodInMs);
+ dest.writeInt(exponent);
+ dest.writeInt(stepCount);
if (channels != null) {
dest.writeInt(channels.length);
@@ -206,6 +225,9 @@ public class WifiScanner {
settings.reportEvents = in.readInt();
settings.numBssidsPerScan = in.readInt();
settings.maxScansToCache = in.readInt();
+ settings.maxPeriodInMs = in.readInt();
+ settings.exponent = in.readInt();
+ settings.stepCount = in.readInt();
int num_channels = in.readInt();
settings.channels = new ChannelSpec[num_channels];
for (int i = 0; i < num_channels; i++) {