diff options
| -rw-r--r-- | api/current.txt | 14 | ||||
| -rw-r--r-- | cmds/pm/src/com/android/commands/pm/Pm.java | 332 | ||||
| -rw-r--r-- | core/java/android/app/EnterTransitionCoordinator.java | 9 | ||||
| -rw-r--r-- | core/java/android/provider/Settings.java | 3 | ||||
| -rw-r--r-- | core/java/android/view/View.java | 134 | ||||
| -rw-r--r-- | core/java/android/view/accessibility/AccessibilityNodeInfo.java | 168 | ||||
| -rw-r--r-- | core/java/android/widget/RemoteViews.java | 20 | ||||
| -rw-r--r-- | core/java/com/android/internal/app/ResolverActivity.java | 4 | ||||
| -rw-r--r-- | core/java/com/android/internal/widget/ResolverDrawerLayout.java | 80 | ||||
| -rw-r--r-- | core/jni/android/graphics/pdf/PdfEditor.cpp | 204 | ||||
| -rw-r--r-- | core/res/res/values/attrs.xml | 12 | ||||
| -rw-r--r-- | core/res/res/values/public.xml | 2 | ||||
| -rw-r--r-- | graphics/java/android/graphics/pdf/PdfEditor.java | 155 | ||||
| -rw-r--r-- | packages/PrintSpooler/src/com/android/printspooler/renderer/IPdfEditor.aidl | 2 | ||||
| -rw-r--r-- | packages/PrintSpooler/src/com/android/printspooler/renderer/PdfManipulationService.java | 109 | ||||
| -rw-r--r-- | packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java | 38 |
16 files changed, 1073 insertions, 213 deletions
diff --git a/api/current.txt b/api/current.txt index 3fdb1a3..4445e3f 100644 --- a/api/current.txt +++ b/api/current.txt @@ -239,6 +239,8 @@ package android { field public static final int accessibilityFeedbackType = 16843650; // 0x1010382 field public static final int accessibilityFlags = 16843652; // 0x1010384 field public static final int accessibilityLiveRegion = 16843758; // 0x10103ee + field public static final int accessibilityTraversalAfter = 16844036; // 0x1010504 + field public static final int accessibilityTraversalBefore = 16844035; // 0x1010503 field public static final int accountPreferences = 16843423; // 0x101029f field public static final int accountType = 16843407; // 0x101028f field public static final int action = 16842797; // 0x101002d @@ -33611,6 +33613,8 @@ package android.view { method public static int generateViewId(); method public int getAccessibilityLiveRegion(); method public android.view.accessibility.AccessibilityNodeProvider getAccessibilityNodeProvider(); + method public int getAccessibilityTraversalAfter(); + method public int getAccessibilityTraversalBefore(); method public float getAlpha(); method public android.view.animation.Animation getAnimation(); method public android.os.IBinder getApplicationWindowToken(); @@ -33887,6 +33891,8 @@ package android.view { method public void sendAccessibilityEventUnchecked(android.view.accessibility.AccessibilityEvent); method public void setAccessibilityDelegate(android.view.View.AccessibilityDelegate); method public void setAccessibilityLiveRegion(int); + method public void setAccessibilityTraversalAfter(int); + method public void setAccessibilityTraversalBefore(int); method public void setActivated(boolean); method public void setAlpha(float); method public void setAnimation(android.view.animation.Animation); @@ -35166,6 +35172,8 @@ package android.view.accessibility { method public java.lang.CharSequence getText(); method public int getTextSelectionEnd(); method public int getTextSelectionStart(); + method public android.view.accessibility.AccessibilityNodeInfo getTraversalAfter(); + method public android.view.accessibility.AccessibilityNodeInfo getTraversalBefore(); method public java.lang.String getViewIdResourceName(); method public android.view.accessibility.AccessibilityWindowInfo getWindow(); method public int getWindowId(); @@ -35236,6 +35244,10 @@ package android.view.accessibility { method public void setSource(android.view.View, int); method public void setText(java.lang.CharSequence); method public void setTextSelection(int, int); + method public void setTraversalAfter(android.view.View); + method public void setTraversalAfter(android.view.View, int); + method public void setTraversalBefore(android.view.View); + method public void setTraversalBefore(android.view.View, int); method public void setViewIdResourceName(java.lang.String); method public void setVisibleToUser(boolean); method public void writeToParcel(android.os.Parcel, int); @@ -38433,6 +38445,8 @@ package android.widget { method public boolean onLoadClass(java.lang.Class); method public void reapply(android.content.Context, android.view.View); method public void removeAllViews(int); + method public void setAccessibilityTraversalAfter(int, int); + method public void setAccessibilityTraversalBefore(int, int); method public void setBitmap(int, java.lang.String, android.graphics.Bitmap); method public void setBoolean(int, java.lang.String, boolean); method public void setBundle(int, java.lang.String, android.os.Bundle); diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java index 5e9d8f7..7fd586f 100644 --- a/cmds/pm/src/com/android/commands/pm/Pm.java +++ b/cmds/pm/src/com/android/commands/pm/Pm.java @@ -96,8 +96,9 @@ public final class Pm { "Error: Could not access the Package Manager. Is the system running?"; public static void main(String[] args) { + int exitCode = 1; try { - new Pm().run(args); + exitCode = new Pm().run(args); } catch (Exception e) { Log.e(TAG, "Error", e); System.err.println("Error: " + e); @@ -105,20 +106,20 @@ public final class Pm { System.err.println(PM_NOT_RUNNING_ERR); } } + System.exit(exitCode); } - public void run(String[] args) throws IOException, RemoteException { + public int run(String[] args) throws IOException, RemoteException { boolean validCommand = false; if (args.length < 1) { - showUsage(); - return; + return showUsage(); } mUm = IUserManager.Stub.asInterface(ServiceManager.getService("user")); mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package")); if (mPm == null) { System.err.println(PM_NOT_RUNNING_ERR); - return; + return 1; } mInstaller = mPm.getPackageInstaller(); @@ -127,155 +128,129 @@ public final class Pm { mNextArg = 1; if ("list".equals(op)) { - runList(); - return; + return runList(); } if ("path".equals(op)) { - runPath(); - return; + return runPath(); } if ("dump".equals(op)) { - runDump(); - return; + return runDump(); } if ("install".equals(op)) { - runInstall(); - return; + return runInstall(); } if ("install-create".equals(op)) { - runInstallCreate(); - return; + return runInstallCreate(); } if ("install-write".equals(op)) { - runInstallWrite(); - return; + return runInstallWrite(); } if ("install-commit".equals(op)) { - runInstallCommit(); - return; + return runInstallCommit(); } if ("install-abandon".equals(op) || "install-destroy".equals(op)) { - runInstallAbandon(); - return; + return runInstallAbandon(); } if ("set-installer".equals(op)) { - runSetInstaller(); - return; + return runSetInstaller(); } if ("uninstall".equals(op)) { - runUninstall(); - return; + return runUninstall(); } if ("clear".equals(op)) { - runClear(); - return; + return runClear(); } if ("enable".equals(op)) { - runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED); - return; + return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED); } if ("disable".equals(op)) { - runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED); - return; + return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED); } if ("disable-user".equals(op)) { - runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER); - return; + return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER); } if ("disable-until-used".equals(op)) { - runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED); - return; + return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED); } if ("hide".equals(op)) { - runSetHiddenSetting(true); - return; + return runSetHiddenSetting(true); } if ("unhide".equals(op)) { - runSetHiddenSetting(false); - return; + return runSetHiddenSetting(false); } if ("grant".equals(op)) { - runGrantRevokePermission(true); - return; + return runGrantRevokePermission(true); } if ("revoke".equals(op)) { - runGrantRevokePermission(false); - return; + return runGrantRevokePermission(false); } if ("set-permission-enforced".equals(op)) { - runSetPermissionEnforced(); - return; + return runSetPermissionEnforced(); } if ("set-install-location".equals(op)) { - runSetInstallLocation(); - return; + return runSetInstallLocation(); } if ("get-install-location".equals(op)) { - runGetInstallLocation(); - return; + return runGetInstallLocation(); } if ("trim-caches".equals(op)) { - runTrimCaches(); - return; + return runTrimCaches(); } if ("create-user".equals(op)) { - runCreateUser(); - return; + return runCreateUser(); } if ("remove-user".equals(op)) { - runRemoveUser(); - return; + return runRemoveUser(); } if ("get-max-users".equals(op)) { - runGetMaxUsers(); - return; + return runGetMaxUsers(); } if ("force-dex-opt".equals(op)) { - runForceDexOpt(); - return; + return runForceDexOpt(); } try { if (args.length == 1) { if (args[0].equalsIgnoreCase("-l")) { validCommand = true; - runListPackages(false); + return runListPackages(false); } else if (args[0].equalsIgnoreCase("-lf")){ validCommand = true; - runListPackages(true); + return runListPackages(true); } } else if (args.length == 2) { if (args[0].equalsIgnoreCase("-p")) { validCommand = true; - displayPackageFilePath(args[1]); + return displayPackageFilePath(args[1]); } } + return 1; } finally { if (validCommand == false) { if (op != null) { @@ -296,35 +271,36 @@ public final class Pm { * pm list libraries * pm list instrumentation */ - private void runList() { + private int runList() { String type = nextArg(); if (type == null) { System.err.println("Error: didn't specify type of data to list"); - return; + return 1; } if ("package".equals(type) || "packages".equals(type)) { - runListPackages(false); + return runListPackages(false); } else if ("permission-groups".equals(type)) { - runListPermissionGroups(); + return runListPermissionGroups(); } else if ("permissions".equals(type)) { - runListPermissions(); + return runListPermissions(); } else if ("features".equals(type)) { - runListFeatures(); + return runListFeatures(); } else if ("libraries".equals(type)) { - runListLibraries(); + return runListLibraries(); } else if ("instrumentation".equals(type)) { - runListInstrumentation(); + return runListInstrumentation(); } else if ("users".equals(type)) { - runListUsers(); + return runListUsers(); } else { System.err.println("Error: unknown list type '" + type + "'"); + return 1; } } /** * Lists all the installed packages. */ - private void runListPackages(boolean showApplicationPackage) { + private int runListPackages(boolean showApplicationPackage) { int getFlags = 0; boolean listDisabled = false, listEnabled = false; boolean listSystem = false, listThirdParty = false; @@ -355,12 +331,12 @@ public final class Pm { getFlags |= PackageManager.GET_UNINSTALLED_PACKAGES; } else { System.err.println("Error: Unknown option: " + opt); - return; + return 1; } } } catch (RuntimeException ex) { System.err.println("Error: " + ex.toString()); - return; + return 1; } String filter = nextArg(); @@ -393,9 +369,11 @@ public final class Pm { System.out.println(); } } + return 0; } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(PM_NOT_RUNNING_ERR); + return 1; } } @@ -411,7 +389,7 @@ public final class Pm { * * pm list features */ - private void runListFeatures() { + private int runListFeatures() { try { List<FeatureInfo> list = new ArrayList<FeatureInfo>(); FeatureInfo[] rawList = mPm.getSystemAvailableFeatures(); @@ -438,9 +416,11 @@ public final class Pm { else System.out.println("reqGlEsVersion=0x" + Integer.toHexString(fi.reqGlEsVersion)); } + return 0; } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(PM_NOT_RUNNING_ERR); + return 1; } } @@ -449,7 +429,7 @@ public final class Pm { * * pm list libraries */ - private void runListLibraries() { + private int runListLibraries() { try { List<String> list = new ArrayList<String>(); String[] rawList = mPm.getSystemSharedLibraryNames(); @@ -474,9 +454,11 @@ public final class Pm { System.out.print("library:"); System.out.println(lib); } + return 0; } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(PM_NOT_RUNNING_ERR); + return 1; } } @@ -485,7 +467,7 @@ public final class Pm { * * pm list instrumentation [package] [-f] */ - private void runListInstrumentation() { + private int runListInstrumentation() { int flags = 0; // flags != 0 is only used to request meta-data boolean showPackage = false; String targetPackage = null; @@ -499,12 +481,12 @@ public final class Pm { targetPackage = opt; } else { System.err.println("Error: Unknown option: " + opt); - return; + return 1; } } } catch (RuntimeException ex) { System.err.println("Error: " + ex.toString()); - return; + return 1; } try { @@ -531,16 +513,18 @@ public final class Pm { System.out.print(ii.targetPackage); System.out.println(")"); } + return 0; } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(PM_NOT_RUNNING_ERR); + return 1; } } /** * Lists all the known permission groups. */ - private void runListPermissionGroups() { + private int runListPermissionGroups() { try { List<PermissionGroupInfo> pgs = mPm.getAllPermissionGroups(0); @@ -550,9 +534,11 @@ public final class Pm { System.out.print("permission group:"); System.out.println(pgi.name); } + return 0; } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(PM_NOT_RUNNING_ERR); + return 1; } } @@ -572,7 +558,7 @@ public final class Pm { /** * Lists all the permissions in a group. */ - private void runListPermissions() { + private int runListPermissions() { try { boolean labels = false; boolean groups = false; @@ -595,7 +581,7 @@ public final class Pm { dangerousOnly = true; } else { System.err.println("Error: Unknown option: " + opt); - return; + return 1; } } @@ -637,9 +623,11 @@ public final class Pm { doListPermissions(groupList, groups, labels, summary, -10000, 10000); } + return 0; } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(PM_NOT_RUNNING_ERR); + return 1; } } @@ -739,22 +727,23 @@ public final class Pm { } } - private void runPath() { + private int runPath() { String pkg = nextArg(); if (pkg == null) { System.err.println("Error: no package specified"); - return; + return 1; } - displayPackageFilePath(pkg); + return displayPackageFilePath(pkg); } - private void runDump() { + private int runDump() { String pkg = nextArg(); if (pkg == null) { System.err.println("Error: no package specified"); - return; + return 1; } ActivityManager.dumpPackageStateStatic(FileDescriptor.out, pkg); + return 0; } class LocalPackageInstallObserver extends PackageInstallObserver { @@ -822,31 +811,34 @@ public final class Pm { return Integer.toString(result); } - private void runSetInstallLocation() { + private int runSetInstallLocation() { int loc; String arg = nextArg(); if (arg == null) { System.err.println("Error: no install location specified."); - return; + return 1; } try { loc = Integer.parseInt(arg); } catch (NumberFormatException e) { System.err.println("Error: install location has to be a number."); - return; + return 1; } try { if (!mPm.setInstallLocation(loc)) { System.err.println("Error: install location has to be a number."); + return 1; } + return 0; } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(PM_NOT_RUNNING_ERR); + return 1; } } - private void runGetInstallLocation() { + private int runGetInstallLocation() { try { int loc = mPm.getInstallLocation(); String locStr = "invalid"; @@ -858,13 +850,15 @@ public final class Pm { locStr = "external"; } System.out.println(loc + "[" + locStr + "]"); + return 0; } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(PM_NOT_RUNNING_ERR); + return 1; } } - private void runInstall() { + private int runInstall() { int installFlags = 0; int userId = UserHandle.USER_ALL; String installerPackageName = null; @@ -884,7 +878,7 @@ public final class Pm { installerPackageName = nextOptionData(); if (installerPackageName == null) { System.err.println("Error: no value specified for -i"); - return; + return 1; } } else if (opt.equals("-t")) { installFlags |= PackageManager.INSTALL_ALLOW_TEST; @@ -900,13 +894,13 @@ public final class Pm { originatingUriString = nextOptionData(); if (originatingUriString == null) { System.err.println("Error: must supply argument for --originating-uri"); - return; + return 1; } } else if (opt.equals("--referrer")) { referrer = nextOptionData(); if (referrer == null) { System.err.println("Error: must supply argument for --referrer"); - return; + return 1; } } else if (opt.equals("--abi")) { abi = checkAbiArgument(nextOptionData()); @@ -914,7 +908,7 @@ public final class Pm { userId = Integer.parseInt(nextOptionData()); } else { System.err.println("Error: Unknown option: " + opt); - return; + return 1; } } @@ -944,7 +938,7 @@ public final class Pm { System.err.println("\tpkg: " + apkFilePath); if (apkFilePath == null) { System.err.println("Error: no package specified"); - return; + return 1; } // Populate verificationURI, optionally present @@ -973,19 +967,22 @@ public final class Pm { } if (obs.result == PackageManager.INSTALL_SUCCEEDED) { System.out.println("Success"); + return 0; } else { System.err.println("Failure [" + installFailureToString(obs) + "]"); + return 1; } } } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(PM_NOT_RUNNING_ERR); + return 1; } } - private void runInstallCreate() throws RemoteException { + private int runInstallCreate() throws RemoteException { int userId = UserHandle.USER_ALL; String installerPackageName = null; @@ -1040,9 +1037,10 @@ public final class Pm { // NOTE: adb depends on parsing this string System.out.println("Success: created install session [" + sessionId + "]"); + return 0; } - private void runInstallWrite() throws IOException, RemoteException { + private int runInstallWrite() throws IOException, RemoteException { long sizeBytes = -1; String opt; @@ -1097,6 +1095,7 @@ public final class Pm { session.fsync(out); System.out.println("Success: streamed " + total + " bytes"); + return 0; } finally { IoUtils.closeQuietly(out); IoUtils.closeQuietly(in); @@ -1104,7 +1103,7 @@ public final class Pm { } } - private void runInstallCommit() throws RemoteException { + private int runInstallCommit() throws RemoteException { final int sessionId = Integer.parseInt(nextArg()); PackageInstaller.Session session = null; @@ -1119,18 +1118,19 @@ public final class Pm { PackageInstaller.STATUS_FAILURE); if (status == PackageInstaller.STATUS_SUCCESS) { System.out.println("Success"); + return 0; } else { Log.e(TAG, "Failure details: " + result.getExtras()); - System.out.println("Failure [" + System.err.println("Failure [" + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]"); - return; + return 1; } } finally { IoUtils.closeQuietly(session); } } - private void runInstallAbandon() throws RemoteException { + private int runInstallAbandon() throws RemoteException { final int sessionId = Integer.parseInt(nextArg()); PackageInstaller.Session session = null; @@ -1138,12 +1138,13 @@ public final class Pm { session = new PackageInstaller.Session(mInstaller.openSession(sessionId)); session.abandon(); System.out.println("Success"); + return 0; } finally { IoUtils.closeQuietly(session); } } - private void runSetInstaller() throws RemoteException { + private int runSetInstaller() throws RemoteException { final String targetPackage = nextArg(); final String installerPackageName = nextArg(); @@ -1154,9 +1155,10 @@ public final class Pm { mPm.setInstallerPackageName(targetPackage, installerPackageName); System.out.println("Success"); + return 0; } - public void runCreateUser() { + public int runCreateUser() { String name; int userId = -1; int flags = 0; @@ -1167,7 +1169,7 @@ public final class Pm { if (optionData == null || !isNumber(optionData)) { System.err.println("Error: no USER_ID specified"); showUsage(); - return; + return 1; } else { userId = Integer.parseInt(optionData); } @@ -1176,13 +1178,13 @@ public final class Pm { } else { System.err.println("Error: unknown option " + opt); showUsage(); - return; + return 1; } } String arg = nextArg(); if (arg == null) { System.err.println("Error: no user name specified."); - return; + return 1; } name = arg; try { @@ -1194,75 +1196,85 @@ public final class Pm { } if (info != null) { System.out.println("Success: created user id " + info.id); + return 1; } else { System.err.println("Error: couldn't create User."); + return 1; } } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(PM_NOT_RUNNING_ERR); + return 1; } - } - public void runRemoveUser() { + public int runRemoveUser() { int userId; String arg = nextArg(); if (arg == null) { System.err.println("Error: no user id specified."); - return; + return 1; } try { userId = Integer.parseInt(arg); } catch (NumberFormatException e) { System.err.println("Error: user id '" + arg + "' is not a number."); - return; + return 1; } try { if (mUm.removeUser(userId)) { System.out.println("Success: removed user"); + return 0; } else { System.err.println("Error: couldn't remove user id " + userId); + return 1; } } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(PM_NOT_RUNNING_ERR); + return 1; } } - public void runListUsers() { + public int runListUsers() { try { IActivityManager am = ActivityManagerNative.getDefault(); List<UserInfo> users = mUm.getUsers(false); if (users == null) { System.err.println("Error: couldn't get users"); + return 1; } else { System.out.println("Users:"); for (int i = 0; i < users.size(); i++) { String running = am.isUserRunning(users.get(i).id, false) ? " running" : ""; System.out.println("\t" + users.get(i).toString() + running); } + return 0; } } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(PM_NOT_RUNNING_ERR); + return 1; } } - public void runGetMaxUsers() { + public int runGetMaxUsers() { System.out.println("Maximum supported users: " + UserManager.getMaxSupportedUsers()); + return 0; } - public void runForceDexOpt() { + public int runForceDexOpt() { final String packageName = nextArg(); try { mPm.forceDexOpt(packageName); + return 0; } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } } - private void runUninstall() throws RemoteException { + private int runUninstall() throws RemoteException { int flags = 0; int userId = UserHandle.USER_ALL; @@ -1277,11 +1289,11 @@ public final class Pm { } else { showUsage(); System.err.println("Error: Invalid user: " + param); - return; + return 1; } } else { System.err.println("Error: Unknown option: " + opt); - return; + return 1; } } @@ -1289,7 +1301,7 @@ public final class Pm { if (pkg == null) { System.err.println("Error: no package specified"); showUsage(); - return; + return 1; } if (userId == UserHandle.USER_ALL) { @@ -1302,11 +1314,11 @@ public final class Pm { } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(PM_NOT_RUNNING_ERR); - return; + return 1; } if (info == null) { System.err.println("Failure - not installed for " + userId); - return; + return 1; } final boolean isSystem = (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; @@ -1326,10 +1338,12 @@ public final class Pm { PackageInstaller.STATUS_FAILURE); if (status == PackageInstaller.STATUS_SUCCESS) { System.out.println("Success"); + return 0; } else { Log.e(TAG, "Failure details: " + result.getExtras()); - System.out.println("Failure [" + System.err.println("Failure [" + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]"); + return 1; } } @@ -1347,7 +1361,7 @@ public final class Pm { } } - private void runClear() { + private int runClear() { int userId = 0; String option = nextOption(); if (option != null && option.equals("--user")) { @@ -1355,7 +1369,7 @@ public final class Pm { if (optionData == null || !isNumber(optionData)) { System.err.println("Error: no USER_ID specified"); showUsage(); - return; + return 1; } else { userId = Integer.parseInt(optionData); } @@ -1365,7 +1379,7 @@ public final class Pm { if (pkg == null) { System.err.println("Error: no package specified"); showUsage(); - return; + return 1; } ClearDataObserver obs = new ClearDataObserver(); @@ -1381,13 +1395,16 @@ public final class Pm { } if (obs.result) { - System.err.println("Success"); + System.out.println("Success"); + return 0; } else { System.err.println("Failed"); + return 1; } } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(PM_NOT_RUNNING_ERR); + return 1; } } @@ -1416,7 +1433,7 @@ public final class Pm { return true; } - private void runSetEnabledSetting(int state) { + private int runSetEnabledSetting(int state) { int userId = 0; String option = nextOption(); if (option != null && option.equals("--user")) { @@ -1424,7 +1441,7 @@ public final class Pm { if (optionData == null || !isNumber(optionData)) { System.err.println("Error: no USER_ID specified"); showUsage(); - return; + return 1; } else { userId = Integer.parseInt(optionData); } @@ -1434,34 +1451,38 @@ public final class Pm { if (pkg == null) { System.err.println("Error: no package or component specified"); showUsage(); - return; + return 1; } ComponentName cn = ComponentName.unflattenFromString(pkg); if (cn == null) { try { mPm.setApplicationEnabledSetting(pkg, state, 0, userId, "shell:" + android.os.Process.myUid()); - System.err.println("Package " + pkg + " new state: " + System.out.println("Package " + pkg + " new state: " + enabledSettingToString( mPm.getApplicationEnabledSetting(pkg, userId))); + return 0; } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(PM_NOT_RUNNING_ERR); + return 1; } } else { try { mPm.setComponentEnabledSetting(cn, state, 0, userId); - System.err.println("Component " + cn.toShortString() + " new state: " + System.out.println("Component " + cn.toShortString() + " new state: " + enabledSettingToString( mPm.getComponentEnabledSetting(cn, userId))); + return 0; } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(PM_NOT_RUNNING_ERR); + return 1; } } } - private void runSetHiddenSetting(boolean state) { + private int runSetHiddenSetting(boolean state) { int userId = 0; String option = nextOption(); if (option != null && option.equals("--user")) { @@ -1469,7 +1490,7 @@ public final class Pm { if (optionData == null || !isNumber(optionData)) { System.err.println("Error: no USER_ID specified"); showUsage(); - return; + return 1; } else { userId = Integer.parseInt(optionData); } @@ -1479,30 +1500,32 @@ public final class Pm { if (pkg == null) { System.err.println("Error: no package or component specified"); showUsage(); - return; + return 1; } try { mPm.setApplicationHiddenSettingAsUser(pkg, state, userId); - System.err.println("Package " + pkg + " new hidden state: " + System.out.println("Package " + pkg + " new hidden state: " + mPm.getApplicationHiddenSettingAsUser(pkg, userId)); + return 0; } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(PM_NOT_RUNNING_ERR); + return 1; } } - private void runGrantRevokePermission(boolean grant) { + private int runGrantRevokePermission(boolean grant) { String pkg = nextArg(); if (pkg == null) { System.err.println("Error: no package specified"); showUsage(); - return; + return 1; } String perm = nextArg(); if (perm == null) { System.err.println("Error: no permission specified"); showUsage(); - return; + return 1; } try { if (grant) { @@ -1510,41 +1533,49 @@ public final class Pm { } else { mPm.revokePermission(pkg, perm); } + return 0; } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(PM_NOT_RUNNING_ERR); + return 1; } catch (IllegalArgumentException e) { System.err.println("Bad argument: " + e.toString()); showUsage(); + return 1; } catch (SecurityException e) { System.err.println("Operation not allowed: " + e.toString()); + return 1; } } - private void runSetPermissionEnforced() { + private int runSetPermissionEnforced() { final String permission = nextArg(); if (permission == null) { System.err.println("Error: no permission specified"); showUsage(); - return; + return 1; } final String enforcedRaw = nextArg(); if (enforcedRaw == null) { System.err.println("Error: no enforcement specified"); showUsage(); - return; + return 1; } final boolean enforced = Boolean.parseBoolean(enforcedRaw); try { mPm.setPermissionEnforced(permission, enforced); + return 0; } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(PM_NOT_RUNNING_ERR); + return 1; } catch (IllegalArgumentException e) { System.err.println("Bad argument: " + e.toString()); showUsage(); + return 1; } catch (SecurityException e) { System.err.println("Operation not allowed: " + e.toString()); + return 1; } } @@ -1563,12 +1594,12 @@ public final class Pm { } - private void runTrimCaches() { + private int runTrimCaches() { String size = nextArg(); if (size == null) { System.err.println("Error: no size specified"); showUsage(); - return; + return 1; } int len = size.length(); long multiplier = 1; @@ -1583,7 +1614,7 @@ public final class Pm { } else { System.err.println("Invalid suffix: " + c); showUsage(); - return; + return 1; } size = size.substring(0, len-1); } @@ -1593,7 +1624,7 @@ public final class Pm { } catch (NumberFormatException e) { System.err.println("Error: expected number at: " + size); showUsage(); - return; + return 1; } ClearDataObserver obs = new ClearDataObserver(); try { @@ -1606,14 +1637,18 @@ public final class Pm { } } } + return 0; } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(PM_NOT_RUNNING_ERR); + return 1; } catch (IllegalArgumentException e) { System.err.println("Bad argument: " + e.toString()); showUsage(); + return 1; } catch (SecurityException e) { System.err.println("Operation not allowed: " + e.toString()); + return 1; } } @@ -1621,7 +1656,7 @@ public final class Pm { * Displays the package file for a package. * @param pckg */ - private void displayPackageFilePath(String pckg) { + private int displayPackageFilePath(String pckg) { try { PackageInfo info = mPm.getPackageInfo(pckg, 0, 0); if (info != null && info.applicationInfo != null) { @@ -1632,12 +1667,14 @@ public final class Pm { System.out.print("package:"); System.out.println(splitSourceDir); } + return 0; } } } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(PM_NOT_RUNNING_ERR); } + return 1; } private Resources getResources(PackageItemInfo pii) { @@ -1752,7 +1789,7 @@ public final class Pm { return arg; } - private static void showUsage() { + private static int showUsage() { System.err.println("usage: pm list packages [-f] [-d] [-e] [-s] [-3] [-i] [-u] [--user USER_ID] [FILTER]"); System.err.println(" pm list permission-groups"); System.err.println(" pm list permissions [-g] [-f] [-d] [-u] [GROUP]"); @@ -1873,5 +1910,6 @@ public final class Pm { System.err.println("pm remove-user: remove the user with the given USER_IDENTIFIER,"); System.err.println(" deleting all data associated with that user"); System.err.println(""); + return 1; } } diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java index 7894887..ecf19c7 100644 --- a/core/java/android/app/EnterTransitionCoordinator.java +++ b/core/java/android/app/EnterTransitionCoordinator.java @@ -133,16 +133,17 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { return; } mAreViewsReady = true; + final ViewGroup decor = getDecor(); // Ensure the views have been laid out before capturing the views -- we need the epicenter. - if (sharedElements.isEmpty() || !sharedElements.valueAt(0).isLayoutRequested()) { + if (decor == null || (decor.isAttachedToWindow() && + (sharedElements.isEmpty() || !sharedElements.valueAt(0).isLayoutRequested()))) { viewsReady(sharedElements); } else { - final View sharedElement = sharedElements.valueAt(0); - sharedElement.getViewTreeObserver() + decor.getViewTreeObserver() .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { - sharedElement.getViewTreeObserver().removeOnPreDrawListener(this); + decor.getViewTreeObserver().removeOnPreDrawListener(this); viewsReady(sharedElements); return true; } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 369efac..c54a5ba 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -6629,7 +6629,8 @@ public final class Settings { WIFI_NUM_OPEN_NETWORKS_KEPT, EMERGENCY_TONE, CALL_AUTO_RETRY, - DOCK_AUDIO_MEDIA_ENABLED + DOCK_AUDIO_MEDIA_ENABLED, + LOW_POWER_MODE_TRIGGER_LEVEL }; // Populated lazily, guarded by class object: diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index e4f95a4..1d09696 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -3109,6 +3109,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private MatchLabelForPredicate mMatchLabelForPredicate; /** + * Specifies a view before which this one is visited in accessibility traversal. + */ + private int mAccessibilityTraversalBeforeId = NO_ID; + + /** + * Specifies a view after which this one is visited in accessibility traversal. + */ + private int mAccessibilityTraversalAfterId = NO_ID; + + /** * Predicate for matching a view by its id. */ private MatchIdPredicate mMatchIdPredicate; @@ -3888,6 +3898,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, case com.android.internal.R.styleable.View_contentDescription: setContentDescription(a.getString(attr)); break; + case com.android.internal.R.styleable.View_accessibilityTraversalBefore: + setAccessibilityTraversalBefore(a.getResourceId(attr, NO_ID)); + break; + case com.android.internal.R.styleable.View_accessibilityTraversalAfter: + setAccessibilityTraversalAfter(a.getResourceId(attr, NO_ID)); + break; case com.android.internal.R.styleable.View_labelFor: setLabelFor(a.getResourceId(attr, NO_ID)); break; @@ -5611,6 +5627,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (rootView == null) { rootView = this; } + View label = rootView.findLabelForView(this, mID); if (label != null) { info.setLabeledBy(label); @@ -5639,6 +5656,30 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } + if (mAccessibilityTraversalBeforeId != View.NO_ID) { + View rootView = getRootView(); + if (rootView == null) { + rootView = this; + } + View next = rootView.findViewInsideOutShouldExist(this, + mAccessibilityTraversalBeforeId); + if (next != null) { + info.setTraversalBefore(next); + } + } + + if (mAccessibilityTraversalAfterId != View.NO_ID) { + View rootView = getRootView(); + if (rootView == null) { + rootView = this; + } + View next = rootView.findViewInsideOutShouldExist(this, + mAccessibilityTraversalAfterId); + if (next != null) { + info.setTraversalAfter(next); + } + } + info.setVisibleToUser(isVisibleToUser()); info.setPackageName(mContext.getPackageName()); @@ -6043,6 +6084,94 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Sets the id of a view before which this one is visited in accessibility traversal. + * A screen-reader must visit the content of this view before the content of the one + * it precedes. For example, if view B is set to be before view A, then a screen-reader + * will traverse the entire content of B before traversing the entire content of A, + * regardles of what traversal strategy it is using. + * <p> + * Views that do not have specified before/after relationships are traversed in order + * determined by the screen-reader. + * </p> + * <p> + * Setting that this view is before a view that is not important for accessibility + * or if this view is not important for accessibility will have no effect as the + * screen-reader is not aware of unimportant views. + * </p> + * + * @param beforeId The id of a view this one precedes in accessibility traversal. + * + * @attr ref android.R.styleable#View_accessibilityTraversalBefore + * + * @see #setImportantForAccessibility(int) + */ + @RemotableViewMethod + public void setAccessibilityTraversalBefore(int beforeId) { + if (mAccessibilityTraversalBeforeId == beforeId) { + return; + } + mAccessibilityTraversalBeforeId = beforeId; + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); + } + + /** + * Gets the id of a view before which this one is visited in accessibility traversal. + * + * @return The id of a view this one precedes in accessibility traversal if + * specified, otherwise {@link #NO_ID}. + * + * @see #setAccessibilityTraversalBefore(int) + */ + public int getAccessibilityTraversalBefore() { + return mAccessibilityTraversalBeforeId; + } + + /** + * Sets the id of a view after which this one is visited in accessibility traversal. + * A screen-reader must visit the content of the other view before the content of this + * one. For example, if view B is set to be after view A, then a screen-reader + * will traverse the entire content of A before traversing the entire content of B, + * regardles of what traversal strategy it is using. + * <p> + * Views that do not have specified before/after relationships are traversed in order + * determined by the screen-reader. + * </p> + * <p> + * Setting that this view is after a view that is not important for accessibility + * or if this view is not important for accessibility will have no effect as the + * screen-reader is not aware of unimportant views. + * </p> + * + * @param afterId The id of a view this one succedees in accessibility traversal. + * + * @attr ref android.R.styleable#View_accessibilityTraversalAfter + * + * @see #setImportantForAccessibility(int) + */ + @RemotableViewMethod + public void setAccessibilityTraversalAfter(int afterId) { + if (mAccessibilityTraversalAfterId == afterId) { + return; + } + mAccessibilityTraversalAfterId = afterId; + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); + } + + /** + * Gets the id of a view after which this one is visited in accessibility traversal. + * + * @return The id of a view this one succeedes in accessibility traversal if + * specified, otherwise {@link #NO_ID}. + * + * @see #setAccessibilityTraversalAfter(int) + */ + public int getAccessibilityTraversalAfter() { + return mAccessibilityTraversalAfterId; + } + + /** * Gets the id of a view for which this view serves as a label for * accessibility purposes. * @@ -6061,11 +6190,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ @RemotableViewMethod public void setLabelFor(int id) { + if (mLabelForId == id) { + return; + } mLabelForId = id; if (mLabelForId != View.NO_ID && mID == View.NO_ID) { mID = generateViewId(); } + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } /** diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 3987fbc..b5afdf7 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -547,6 +547,8 @@ public class AccessibilityNodeInfo implements Parcelable { private long mParentNodeId = ROOT_NODE_ID; private long mLabelForId = ROOT_NODE_ID; private long mLabeledById = ROOT_NODE_ID; + private long mTraversalBefore = ROOT_NODE_ID; + private long mTraversalAfter = ROOT_NODE_ID; private int mBooleanProperties; private final Rect mBoundsInParent = new Rect(); @@ -1046,6 +1048,126 @@ public class AccessibilityNodeInfo implements Parcelable { } /** + * Gets the node before which this one is visited during traversal. A screen-reader + * must visit the content of this node before the content of the one it precedes. + * + * @return The succeeding node if such or <code>null</code>. + * + * @see #setTraversalBefore(android.view.View) + * @see #setTraversalBefore(android.view.View, int) + */ + public AccessibilityNodeInfo getTraversalBefore() { + enforceSealed(); + return getNodeForAccessibilityId(mTraversalBefore); + } + + /** + * Sets the view before whose node this one should be visited during traversal. A + * screen-reader must visit the content of this node before the content of the one + * it precedes. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param view The view providing the preceding node. + * + * @see #getTraversalBefore() + */ + public void setTraversalBefore(View view) { + setTraversalBefore(view, UNDEFINED_ITEM_ID); + } + + /** + * Sets the node before which this one is visited during traversal. A screen-reader + * must visit the content of this node before the content of the one it precedes. + * The successor is a virtual descendant of the given <code>root</code>. If + * <code>virtualDescendantId</code> equals to {@link View#NO_ID} the root is set + * as the successor. + * <p> + * A virtual descendant is an imaginary View that is reported as a part of the view + * hierarchy for accessibility purposes. This enables custom views that draw complex + * content to report them selves as a tree of virtual views, thus conveying their + * logical structure. + * </p> + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param root The root of the virtual subtree. + * @param virtualDescendantId The id of the virtual descendant. + */ + public void setTraversalBefore(View root, int virtualDescendantId) { + enforceNotSealed(); + final int rootAccessibilityViewId = (root != null) + ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID; + mTraversalBefore = makeNodeId(rootAccessibilityViewId, virtualDescendantId); + } + + /** + * Gets the node after which this one is visited in accessibility traversal. + * A screen-reader must visit the content of the other node before the content + * of this one. + * + * @return The succeeding node if such or <code>null</code>. + * + * @see #setTraversalAfter(android.view.View) + * @see #setTraversalAfter(android.view.View, int) + */ + public AccessibilityNodeInfo getTraversalAfter() { + enforceSealed(); + return getNodeForAccessibilityId(mTraversalAfter); + } + + /** + * Sets the view whose node is visited after this one in accessibility traversal. + * A screen-reader must visit the content of the other node before the content + * of this one. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param view The previous view. + * + * @see #getTraversalAfter() + */ + public void setTraversalAfter(View view) { + setTraversalAfter(view, UNDEFINED_ITEM_ID); + } + + /** + * Sets the node after which this one is visited in accessibility traversal. + * A screen-reader must visit the content of the other node before the content + * of this one. If <code>virtualDescendantId</code> equals to {@link View#NO_ID} + * the root is set as the predecessor. + * <p> + * A virtual descendant is an imaginary View that is reported as a part of the view + * hierarchy for accessibility purposes. This enables custom views that draw complex + * content to report them selves as a tree of virtual views, thus conveying their + * logical structure. + * </p> + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param root The root of the virtual subtree. + * @param virtualDescendantId The id of the virtual descendant. + */ + public void setTraversalAfter(View root, int virtualDescendantId) { + enforceNotSealed(); + final int rootAccessibilityViewId = (root != null) + ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID; + mTraversalAfter = makeNodeId(rootAccessibilityViewId, virtualDescendantId); + } + + /** * Sets the maximum text length, or -1 for no limit. * <p> * Typically used to indicate that an editable text field has a limit on @@ -1229,13 +1351,7 @@ public class AccessibilityNodeInfo implements Parcelable { */ public AccessibilityNodeInfo getParent() { enforceSealed(); - if (!canPerformRequestOverConnection(mParentNodeId)) { - return null; - } - AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); - return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, - mWindowId, mParentNodeId, false, FLAG_PREFETCH_PREDECESSORS - | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS); + return getNodeForAccessibilityId(mParentNodeId); } /** @@ -2055,13 +2171,7 @@ public class AccessibilityNodeInfo implements Parcelable { */ public AccessibilityNodeInfo getLabelFor() { enforceSealed(); - if (!canPerformRequestOverConnection(mLabelForId)) { - return null; - } - AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); - return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, - mWindowId, mLabelForId, false, FLAG_PREFETCH_PREDECESSORS - | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS); + return getNodeForAccessibilityId(mLabelForId); } /** @@ -2113,13 +2223,7 @@ public class AccessibilityNodeInfo implements Parcelable { */ public AccessibilityNodeInfo getLabeledBy() { enforceSealed(); - if (!canPerformRequestOverConnection(mLabeledById)) { - return null; - } - AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); - return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, - mWindowId, mLabeledById, false, FLAG_PREFETCH_PREDECESSORS - | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS); + return getNodeForAccessibilityId(mLabeledById); } /** @@ -2453,6 +2557,9 @@ public class AccessibilityNodeInfo implements Parcelable { parcel.writeLong(mParentNodeId); parcel.writeLong(mLabelForId); parcel.writeLong(mLabeledById); + parcel.writeLong(mTraversalBefore); + parcel.writeLong(mTraversalAfter); + parcel.writeInt(mConnectionId); final LongArray childIds = mChildNodeIds; @@ -2571,6 +2678,8 @@ public class AccessibilityNodeInfo implements Parcelable { mParentNodeId = other.mParentNodeId; mLabelForId = other.mLabelForId; mLabeledById = other.mLabeledById; + mTraversalBefore = other.mTraversalBefore; + mTraversalAfter = other.mTraversalAfter; mWindowId = other.mWindowId; mConnectionId = other.mConnectionId; mBoundsInParent.set(other.mBoundsInParent); @@ -2633,6 +2742,9 @@ public class AccessibilityNodeInfo implements Parcelable { mParentNodeId = parcel.readLong(); mLabelForId = parcel.readLong(); mLabeledById = parcel.readLong(); + mTraversalBefore = parcel.readLong(); + mTraversalAfter = parcel.readLong(); + mConnectionId = parcel.readInt(); final int childrenSize = parcel.readInt(); @@ -2725,6 +2837,8 @@ public class AccessibilityNodeInfo implements Parcelable { mParentNodeId = ROOT_NODE_ID; mLabelForId = ROOT_NODE_ID; mLabeledById = ROOT_NODE_ID; + mTraversalBefore = ROOT_NODE_ID; + mTraversalAfter = ROOT_NODE_ID; mWindowId = UNDEFINED_ITEM_ID; mConnectionId = UNDEFINED_CONNECTION_ID; mMaxTextLength = -1; @@ -2911,6 +3025,8 @@ public class AccessibilityNodeInfo implements Parcelable { builder.append("; accessibilityViewId: " + getAccessibilityViewId(mSourceNodeId)); builder.append("; virtualDescendantId: " + getVirtualDescendantId(mSourceNodeId)); builder.append("; mParentNodeId: " + mParentNodeId); + builder.append("; traversalBefore: ").append(mTraversalBefore); + builder.append("; traversalAfter: ").append(mTraversalAfter); int granularities = mMovementGranularities; builder.append("; MovementGranularities: ["); @@ -2963,6 +3079,16 @@ public class AccessibilityNodeInfo implements Parcelable { return builder.toString(); } + private AccessibilityNodeInfo getNodeForAccessibilityId(long accessibilityId) { + if (!canPerformRequestOverConnection(accessibilityId)) { + return null; + } + AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); + return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, + mWindowId, accessibilityId, false, FLAG_PREFETCH_PREDECESSORS + | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS); + } + /** * A class defining an action that can be performed on an {@link AccessibilityNodeInfo}. * Each action has a unique id that is mandatory and optional data. diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 80f364b..dd7fa18 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -2528,6 +2528,26 @@ public class RemoteViews implements Parcelable, Filter { } /** + * Equivalent to calling {@link android.view.View#setAccessibilityTraversalBefore(int)}. + * + * @param viewId The id of the view whose before view in accessibility traversal to set. + * @param nextId The id of the next in the accessibility traversal. + **/ + public void setAccessibilityTraversalBefore(int viewId, int nextId) { + setInt(viewId, "setAccessibilityTraversalBefore", nextId); + } + + /** + * Equivalent to calling {@link android.view.View#setAccessibilityTraversalAfter(int)}. + * + * @param viewId The id of the view whose after view in accessibility traversal to set. + * @param nextId The id of the next in the accessibility traversal. + **/ + public void setAccessibilityTraversalAfter(int viewId, int nextId) { + setInt(viewId, "setAccessibilityTraversalAfter", nextId); + } + + /** * Equivalent to calling View.setLabelFor(int). * * @param viewId The id of the view whose property to set. diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index ccffa19..7df76e5 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -278,9 +278,9 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic final ResolverDrawerLayout rdl = (ResolverDrawerLayout) findViewById(R.id.contentPanel); if (rdl != null) { - rdl.setOnClickOutsideListener(new View.OnClickListener() { + rdl.setOnDismissedListener(new ResolverDrawerLayout.OnDismissedListener() { @Override - public void onClick(View v) { + public void onDismissed() { finish(); } }); diff --git a/core/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java index 375822f..25b4945 100644 --- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java +++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java @@ -63,18 +63,22 @@ public class ResolverDrawerLayout extends ViewGroup { private float mCollapseOffset; private int mCollapsibleHeight; + private int mUncollapsibleHeight; private int mTopOffset; private boolean mIsDragging; private boolean mOpenOnClick; private boolean mOpenOnLayout; + private boolean mDismissOnScrollerFinished; private final int mTouchSlop; private final float mMinFlingVelocity; private final OverScroller mScroller; private final VelocityTracker mVelocityTracker; - private OnClickListener mClickOutsideListener; + private OnDismissedListener mOnDismissedListener; + private RunOnDismissedListener mRunOnDismissedListener; + private float mInitialTouchX; private float mInitialTouchY; private float mLastTouchY; @@ -143,8 +147,8 @@ public class ResolverDrawerLayout extends ViewGroup { return isSmallCollapsed() ? mMaxCollapsedHeightSmall : mMaxCollapsedHeight; } - public void setOnClickOutsideListener(OnClickListener listener) { - mClickOutsideListener = listener; + public void setOnDismissedListener(OnDismissedListener listener) { + mOnDismissedListener = listener; } @Override @@ -194,7 +198,7 @@ public class ResolverDrawerLayout extends ViewGroup { } if (mIsDragging) { - mScroller.abortAnimation(); + abortAnimation(); } return mIsDragging || mOpenOnClick; } @@ -213,12 +217,9 @@ public class ResolverDrawerLayout extends ViewGroup { mInitialTouchX = x; mInitialTouchY = mLastTouchY = y; mActivePointerId = ev.getPointerId(0); - if (findChildUnder(mInitialTouchX, mInitialTouchY) == null && - mClickOutsideListener != null) { - mIsDragging = handled = true; - } - handled |= mCollapsibleHeight > 0; - mScroller.abortAnimation(); + mIsDragging = findChildUnder(mInitialTouchX, mInitialTouchY) != null; + handled = (!mIsDragging && mOnDismissedListener != null) || mCollapsibleHeight > 0; + abortAnimation(); } break; @@ -264,11 +265,12 @@ public class ResolverDrawerLayout extends ViewGroup { break; case MotionEvent.ACTION_UP: { + final boolean wasDragging = mIsDragging; mIsDragging = false; - if (!mIsDragging && findChildUnder(mInitialTouchX, mInitialTouchY) == null && + if (!wasDragging && findChildUnder(mInitialTouchX, mInitialTouchY) == null && findChildUnder(ev.getX(), ev.getY()) == null) { - if (mClickOutsideListener != null) { - mClickOutsideListener.onClick(this); + if (mOnDismissedListener != null) { + dispatchOnDismissed(); resetTouch(); return true; } @@ -281,7 +283,13 @@ public class ResolverDrawerLayout extends ViewGroup { mVelocityTracker.computeCurrentVelocity(1000); final float yvel = mVelocityTracker.getYVelocity(mActivePointerId); if (Math.abs(yvel) > mMinFlingVelocity) { - smoothScrollTo(yvel < 0 ? 0 : mCollapsibleHeight, yvel); + if (mOnDismissedListener != null + && yvel > 0 && mCollapseOffset > mCollapsibleHeight) { + smoothScrollTo(mCollapsibleHeight + mUncollapsibleHeight, yvel); + mDismissOnScrollerFinished = true; + } else { + smoothScrollTo(yvel < 0 ? 0 : mCollapsibleHeight, yvel); + } } else { smoothScrollTo( mCollapseOffset < mCollapsibleHeight / 2 ? 0 : mCollapsibleHeight, 0); @@ -327,17 +335,27 @@ public class ResolverDrawerLayout extends ViewGroup { @Override public void computeScroll() { super.computeScroll(); - if (!mScroller.isFinished()) { - final boolean keepGoing = mScroller.computeScrollOffset(); + if (mScroller.computeScrollOffset()) { + final boolean keepGoing = !mScroller.isFinished(); performDrag(mScroller.getCurrY() - mCollapseOffset); if (keepGoing) { postInvalidateOnAnimation(); + } else if (mDismissOnScrollerFinished && mOnDismissedListener != null) { + mRunOnDismissedListener = new RunOnDismissedListener(); + post(mRunOnDismissedListener); } } } + private void abortAnimation() { + mScroller.abortAnimation(); + mRunOnDismissedListener = null; + mDismissOnScrollerFinished = false; + } + private float performDrag(float dy) { - final float newPos = Math.max(0, Math.min(mCollapseOffset + dy, mCollapsibleHeight)); + final float newPos = Math.max(0, Math.min(mCollapseOffset + dy, + mCollapsibleHeight + mUncollapsibleHeight)); if (newPos != mCollapseOffset) { dy = newPos - mCollapseOffset; final int childCount = getChildCount(); @@ -356,11 +374,18 @@ public class ResolverDrawerLayout extends ViewGroup { return 0; } - private void smoothScrollTo(int yOffset, float velocity) { - if (getMaxCollapsedHeight() == 0) { - return; + void dispatchOnDismissed() { + if (mOnDismissedListener != null) { + mOnDismissedListener.onDismissed(); } - mScroller.abortAnimation(); + if (mRunOnDismissedListener != null) { + removeCallbacks(mRunOnDismissedListener); + mRunOnDismissedListener = null; + } + } + + private void smoothScrollTo(int yOffset, float velocity) { + abortAnimation(); final int sy = (int) mCollapseOffset; int dy = yOffset - sy; if (dy == 0) { @@ -490,6 +515,7 @@ public class ResolverDrawerLayout extends ViewGroup { protected void onDetachedFromWindow() { super.onDetachedFromWindow(); getViewTreeObserver().removeOnTouchModeChangeListener(mTouchModeChangeListener); + abortAnimation(); } @Override @@ -585,6 +611,7 @@ public class ResolverDrawerLayout extends ViewGroup { mCollapsibleHeight = Math.max(0, heightUsed - alwaysShowHeight - getMaxCollapsedHeight()); + mUncollapsibleHeight = heightUsed - mCollapsibleHeight; if (isLaidOut()) { mCollapseOffset = Math.min(mCollapseOffset, mCollapsibleHeight); @@ -734,4 +761,15 @@ public class ResolverDrawerLayout extends ViewGroup { } }; } + + public interface OnDismissedListener { + public void onDismissed(); + } + + private class RunOnDismissedListener implements Runnable { + @Override + public void run() { + dispatchOnDismissed(); + } + } } diff --git a/core/jni/android/graphics/pdf/PdfEditor.cpp b/core/jni/android/graphics/pdf/PdfEditor.cpp index 5f60c9e..2b756e2 100644 --- a/core/jni/android/graphics/pdf/PdfEditor.cpp +++ b/core/jni/android/graphics/pdf/PdfEditor.cpp @@ -19,6 +19,9 @@ #include "fpdfview.h" #include "fpdfedit.h" #include "fpdfsave.h" +#include "fsdk_rendercontext.h" +#include "fpdf_transformpage.h" +#include "SkMatrix.h" #include <android_runtime/AndroidRuntime.h> #include <vector> @@ -29,6 +32,20 @@ namespace android { +enum PageBox {PAGE_BOX_MEDIA, PAGE_BOX_CROP}; + +static struct { + jfieldID x; + jfieldID y; +} gPointClassInfo; + +static struct { + jfieldID left; + jfieldID top; + jfieldID right; + jfieldID bottom; +} gRectClassInfo; + static Mutex sLock; static int sUnmatchedInitRequestCount = 0; @@ -144,18 +161,201 @@ static void nativeWrite(JNIEnv* env, jclass thiz, jlong documentPtr, jint fd) { } } +static void nativeSetTransformAndClip(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex, + jlong transformPtr, jint clipLeft, jint clipTop, jint clipRight, jint clipBottom) { + FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr); + + CPDF_Page* page = (CPDF_Page*) FPDF_LoadPage(document, pageIndex); + if (!page) { + jniThrowException(env, "java/lang/IllegalStateException", + "cannot open page"); + return; + } + + double width = 0; + double height = 0; + + const int result = FPDF_GetPageSizeByIndex(document, pageIndex, &width, &height); + if (!result) { + jniThrowException(env, "java/lang/IllegalStateException", + "cannot get page size"); + return; + } + + CFX_AffineMatrix matrix; + + SkMatrix* skTransform = reinterpret_cast<SkMatrix*>(transformPtr); + + SkScalar transformValues[6]; + skTransform->asAffine(transformValues); + + // PDF's coordinate system origin is left-bottom while in graphics it + // is the top-left. So, translate the PDF coordinates to ours. + matrix.Set(1, 0, 0, -1, 0, page->GetPageHeight()); + + // Apply the transformation what was created in our coordinates. + matrix.Concat(transformValues[SkMatrix::kAScaleX], transformValues[SkMatrix::kASkewY], + transformValues[SkMatrix::kASkewX], transformValues[SkMatrix::kAScaleY], + transformValues[SkMatrix::kATransX], transformValues[SkMatrix::kATransY]); + + // Translate the result back to PDF coordinates. + matrix.Concat(1, 0, 0, -1, 0, page->GetPageHeight()); + + FS_MATRIX transform = {matrix.a, matrix.b, matrix.c, matrix.d, matrix.e, matrix.f}; + FS_RECTF clip = {(float) clipLeft, (float) clipTop, (float) clipRight, (float) clipBottom}; + + FPDFPage_TransFormWithClip(page, &transform, &clip); + + FPDF_ClosePage(page); +} + +static void nativeGetPageSize(JNIEnv* env, jclass thiz, jlong documentPtr, + jint pageIndex, jobject outSize) { + FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr); + + FPDF_PAGE page = FPDF_LoadPage(document, pageIndex); + if (!page) { + jniThrowException(env, "java/lang/IllegalStateException", + "cannot open page"); + return; + } + + double width = 0; + double height = 0; + + const int result = FPDF_GetPageSizeByIndex(document, pageIndex, &width, &height); + if (!result) { + jniThrowException(env, "java/lang/IllegalStateException", + "cannot get page size"); + return; + } + + env->SetIntField(outSize, gPointClassInfo.x, width); + env->SetIntField(outSize, gPointClassInfo.y, height); + + FPDF_ClosePage(page); +} + +static jboolean nativeScaleForPrinting(JNIEnv* env, jclass thiz, jlong documentPtr) { + FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr); + FPDF_BOOL success = FPDF_VIEWERREF_GetPrintScaling(document); + return success ? JNI_TRUE : JNI_FALSE; +} + +static bool nativeGetPageBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex, + PageBox pageBox, jobject outBox) { + FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr); + + FPDF_PAGE page = FPDF_LoadPage(document, pageIndex); + if (!page) { + jniThrowException(env, "java/lang/IllegalStateException", + "cannot open page"); + return false; + } + + float left; + float top; + float right; + float bottom; + + const FPDF_BOOL success = (pageBox == PAGE_BOX_MEDIA) + ? FPDFPage_GetMediaBox(page, &left, &top, &right, &bottom) + : FPDFPage_GetCropBox(page, &left, &top, &right, &bottom); + + FPDF_ClosePage(page); + + if (!success) { + return false; + } + + env->SetIntField(outBox, gRectClassInfo.left, (int) left); + env->SetIntField(outBox, gRectClassInfo.top, (int) top); + env->SetIntField(outBox, gRectClassInfo.right, (int) right); + env->SetIntField(outBox, gRectClassInfo.bottom, (int) bottom); + + return true; +} + +static jboolean nativeGetPageMediaBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex, + jobject outMediaBox) { + const bool success = nativeGetPageBox(env, thiz, documentPtr, pageIndex, PAGE_BOX_MEDIA, + outMediaBox); + return success ? JNI_TRUE : JNI_FALSE; +} + +static jboolean nativeGetPageCropBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex, + jobject outMediaBox) { + const bool success = nativeGetPageBox(env, thiz, documentPtr, pageIndex, PAGE_BOX_CROP, + outMediaBox); + return success ? JNI_TRUE : JNI_FALSE; +} + +static void nativeSetPageBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex, + PageBox pageBox, jobject box) { + FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr); + + FPDF_PAGE page = FPDF_LoadPage(document, pageIndex); + if (!page) { + jniThrowException(env, "java/lang/IllegalStateException", + "cannot open page"); + return; + } + + const int left = env->GetIntField(box, gRectClassInfo.left); + const int top = env->GetIntField(box, gRectClassInfo.top); + const int right = env->GetIntField(box, gRectClassInfo.right); + const int bottom = env->GetIntField(box, gRectClassInfo.bottom); + + if (pageBox == PAGE_BOX_MEDIA) { + FPDFPage_SetMediaBox(page, left, top, right, bottom); + } else { + FPDFPage_SetCropBox(page, left, top, right, bottom); + } + + FPDF_ClosePage(page); +} + +static void nativeSetPageMediaBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex, + jobject mediaBox) { + nativeSetPageBox(env, thiz, documentPtr, pageIndex, PAGE_BOX_MEDIA, mediaBox); +} + +static void nativeSetPageCropBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex, + jobject mediaBox) { + nativeSetPageBox(env, thiz, documentPtr, pageIndex, PAGE_BOX_CROP, mediaBox); +} + static JNINativeMethod gPdfEditor_Methods[] = { {"nativeOpen", "(IJ)J", (void*) nativeOpen}, {"nativeClose", "(J)V", (void*) nativeClose}, {"nativeGetPageCount", "(J)I", (void*) nativeGetPageCount}, {"nativeRemovePage", "(JI)I", (void*) nativeRemovePage}, - {"nativeWrite", "(JI)V", (void*) nativeWrite} + {"nativeWrite", "(JI)V", (void*) nativeWrite}, + {"nativeSetTransformAndClip", "(JIJIIII)V", (void*) nativeSetTransformAndClip}, + {"nativeGetPageSize", "(JILandroid/graphics/Point;)V", (void*) nativeGetPageSize}, + {"nativeScaleForPrinting", "(J)Z", (void*) nativeScaleForPrinting}, + {"nativeGetPageMediaBox", "(JILandroid/graphics/Rect;)Z", (void*) nativeGetPageMediaBox}, + {"nativeSetPageMediaBox", "(JILandroid/graphics/Rect;)V", (void*) nativeSetPageMediaBox}, + {"nativeGetPageCropBox", "(JILandroid/graphics/Rect;)Z", (void*) nativeGetPageCropBox}, + {"nativeSetPageCropBox", "(JILandroid/graphics/Rect;)V", (void*) nativeSetPageCropBox} }; int register_android_graphics_pdf_PdfEditor(JNIEnv* env) { - return android::AndroidRuntime::registerNativeMethods( + const int result = android::AndroidRuntime::registerNativeMethods( env, "android/graphics/pdf/PdfEditor", gPdfEditor_Methods, NELEM(gPdfEditor_Methods)); + + jclass pointClass = env->FindClass("android/graphics/Point"); + gPointClassInfo.x = env->GetFieldID(pointClass, "x", "I"); + gPointClassInfo.y = env->GetFieldID(pointClass, "y", "I"); + + jclass rectClass = env->FindClass("android/graphics/Rect"); + gRectClassInfo.left = env->GetFieldID(rectClass, "left", "I"); + gRectClassInfo.top = env->GetFieldID(rectClass, "top", "I"); + gRectClassInfo.right = env->GetFieldID(rectClass, "right", "I"); + gRectClassInfo.bottom = env->GetFieldID(rectClass, "bottom", "I"); + + return result; }; }; diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index c4131b3..91a8598 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -2363,6 +2363,18 @@ representation this attribute can be used for providing such. --> <attr name="contentDescription" format="string" localization="suggested" /> + <!-- Sets the id of a view before which this one is visited in accessibility traversal. + A screen-reader must visit the content of this view before the content of the one + it precedes. + @see android.view.View#setAccessibilityTraversalBefore(int)} --> + <attr name="accessibilityTraversalBefore" format="integer" /> + + <!-- Sets the id of a view after which this one is visited in accessibility traversal. + A screen-reader must visit the content of the other view before the content of + this one. + @see android.view.View#setAccessibilityTraversalAfter(int)} --> + <attr name="accessibilityTraversalAfter" format="integer" /> + <!-- Name of the method in this View's context to invoke when the view is clicked. This name must correspond to a public method that takes exactly one parameter of type View. For instance, if you specify diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index c0b7fd0..c0a5ab2 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2597,5 +2597,7 @@ <public type="attr" name="resizeClip"/> <public type="attr" name="collapseContentDescription"/> + <public type="attr" name="accessibilityTraversalBefore" /> + <public type="attr" name="accessibilityTraversalAfter" /> </resources> diff --git a/graphics/java/android/graphics/pdf/PdfEditor.java b/graphics/java/android/graphics/pdf/PdfEditor.java index 9837139..0b84d29 100644 --- a/graphics/java/android/graphics/pdf/PdfEditor.java +++ b/graphics/java/android/graphics/pdf/PdfEditor.java @@ -17,6 +17,10 @@ package android.graphics.pdf; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Matrix; +import android.graphics.Point; +import android.graphics.Rect; import android.os.ParcelFileDescriptor; import android.system.ErrnoException; import android.system.OsConstants; @@ -98,6 +102,109 @@ public final class PdfEditor { } /** + * Sets a transformation and clip for a given page. The transformation matrix if + * non-null must be affine as per {@link android.graphics.Matrix#isAffine()}. If + * the clip is null, then no clipping is performed. + * + * @param pageIndex The page whose transform to set. + * @param transform The transformation to apply. + * @param clip The clip to apply. + */ + public void setTransformAndClip(int pageIndex, @Nullable Matrix transform, + @Nullable Rect clip) { + throwIfClosed(); + throwIfPageNotInDocument(pageIndex); + throwIfNotNullAndNotAfine(transform); + if (transform == null) { + transform = Matrix.IDENTITY_MATRIX; + } + if (clip == null) { + Point size = new Point(); + getPageSize(pageIndex, size); + nativeSetTransformAndClip(mNativeDocument, pageIndex, transform.native_instance, + 0, 0, size.x, size.y); + } else { + nativeSetTransformAndClip(mNativeDocument, pageIndex, transform.native_instance, + clip.left, clip.top, clip.right, clip.bottom); + } + } + + /** + * Gets the size of a given page in mils (1/72"). + * + * @param pageIndex The page index. + * @param outSize The size output. + */ + public void getPageSize(int pageIndex, @NonNull Point outSize) { + throwIfClosed(); + throwIfOutSizeNull(outSize); + throwIfPageNotInDocument(pageIndex); + nativeGetPageSize(mNativeDocument, pageIndex, outSize); + } + + /** + * Gets the media box of a given page in mils (1/72"). + * + * @param pageIndex The page index. + * @param outMediaBox The media box output. + */ + public boolean getPageMediaBox(int pageIndex, @NonNull Rect outMediaBox) { + throwIfClosed(); + throwIfOutMediaBoxNull(outMediaBox); + throwIfPageNotInDocument(pageIndex); + return nativeGetPageMediaBox(mNativeDocument, pageIndex, outMediaBox); + } + + /** + * Sets the media box of a given page in mils (1/72"). + * + * @param pageIndex The page index. + * @param mediaBox The media box. + */ + public void setPageMediaBox(int pageIndex, @NonNull Rect mediaBox) { + throwIfClosed(); + throwIfMediaBoxNull(mediaBox); + throwIfPageNotInDocument(pageIndex); + nativeSetPageMediaBox(mNativeDocument, pageIndex, mediaBox); + } + + /** + * Gets the crop box of a given page in mils (1/72"). + * + * @param pageIndex The page index. + * @param outCropBox The crop box output. + */ + public boolean getPageCropBox(int pageIndex, @NonNull Rect outCropBox) { + throwIfClosed(); + throwIfOutCropBoxNull(outCropBox); + throwIfPageNotInDocument(pageIndex); + return nativeGetPageCropBox(mNativeDocument, pageIndex, outCropBox); + } + + /** + * Sets the crop box of a given page in mils (1/72"). + * + * @param pageIndex The page index. + * @param cropBox The crop box. + */ + public void setPageCropBox(int pageIndex, @NonNull Rect cropBox) { + throwIfClosed(); + throwIfCropBoxNull(cropBox); + throwIfPageNotInDocument(pageIndex); + nativeSetPageCropBox(mNativeDocument, pageIndex, cropBox); + } + + /** + * Gets whether the document prefers to be scaled for printing. + * + * @return Whether to scale the document. + */ + public boolean shouldScaleForPrinting() { + throwIfClosed(); + return nativeScaleForPrinting(mNativeDocument); + } + + /** * Writes the PDF file to the provided destination. * <p> * <strong>Note:</strong> This method takes ownership of the passed in file @@ -154,9 +261,57 @@ public final class PdfEditor { } } + private void throwIfNotNullAndNotAfine(Matrix matrix) { + if (matrix != null && !matrix.isAffine()) { + throw new IllegalStateException("Matrix must be afine"); + } + } + + private void throwIfOutSizeNull(Point outSize) { + if (outSize == null) { + throw new NullPointerException("outSize cannot be null"); + } + } + + private void throwIfOutMediaBoxNull(Rect outMediaBox) { + if (outMediaBox == null) { + throw new NullPointerException("outMediaBox cannot be null"); + } + } + + private void throwIfMediaBoxNull(Rect mediaBox) { + if (mediaBox == null) { + throw new NullPointerException("mediaBox cannot be null"); + } + } + + private void throwIfOutCropBoxNull(Rect outCropBox) { + if (outCropBox == null) { + throw new NullPointerException("outCropBox cannot be null"); + } + } + + private void throwIfCropBoxNull(Rect cropBox) { + if (cropBox == null) { + throw new NullPointerException("cropBox cannot be null"); + } + } + private static native long nativeOpen(int fd, long size); private static native void nativeClose(long documentPtr); private static native int nativeGetPageCount(long documentPtr); private static native int nativeRemovePage(long documentPtr, int pageIndex); private static native void nativeWrite(long documentPtr, int fd); + private static native void nativeSetTransformAndClip(long documentPtr, int pageIndex, + long transformPtr, int clipLeft, int clipTop, int clipRight, int clipBottom); + private static native void nativeGetPageSize(long documentPtr, int pageIndex, Point outSize); + private static native boolean nativeGetPageMediaBox(long documentPtr, int pageIndex, + Rect outMediaBox); + private static native void nativeSetPageMediaBox(long documentPtr, int pageIndex, + Rect mediaBox); + private static native boolean nativeGetPageCropBox(long documentPtr, int pageIndex, + Rect outMediaBox); + private static native void nativeSetPageCropBox(long documentPtr, int pageIndex, + Rect mediaBox); + private static native boolean nativeScaleForPrinting(long documentPtr); } diff --git a/packages/PrintSpooler/src/com/android/printspooler/renderer/IPdfEditor.aidl b/packages/PrintSpooler/src/com/android/printspooler/renderer/IPdfEditor.aidl index b450ccb..01cabe1 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/renderer/IPdfEditor.aidl +++ b/packages/PrintSpooler/src/com/android/printspooler/renderer/IPdfEditor.aidl @@ -18,6 +18,7 @@ package com.android.printspooler.renderer; import android.os.ParcelFileDescriptor; import android.print.PageRange; +import android.print.PrintAttributes; /** * Interface for communication with a remote pdf editor. @@ -25,6 +26,7 @@ import android.print.PageRange; interface IPdfEditor { int openDocument(in ParcelFileDescriptor source); void removePages(in PageRange[] pages); + void applyPrintAttributes(in PrintAttributes attributes); void write(in ParcelFileDescriptor destination); void closeDocument(); } diff --git a/packages/PrintSpooler/src/com/android/printspooler/renderer/PdfManipulationService.java b/packages/PrintSpooler/src/com/android/printspooler/renderer/PdfManipulationService.java index 00e5051..0462e4d 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/renderer/PdfManipulationService.java +++ b/packages/PrintSpooler/src/com/android/printspooler/renderer/PdfManipulationService.java @@ -87,7 +87,7 @@ public final class PdfManipulationService extends Service { } mRenderer = new PdfRenderer(source); return mRenderer.getPageCount(); - } catch (IOException|IllegalStateException e) { + } catch (IOException | IllegalStateException e) { IoUtils.closeQuietly(source); Log.e(LOG_TAG, "Cannot open file", e); return MALFORMED_PDF_FILE_ERROR; @@ -217,7 +217,7 @@ public final class PdfManipulationService extends Service { } mEditor = new PdfEditor(source); return mEditor.getPageCount(); - } catch (IOException|IllegalStateException e) { + } catch (IOException | IllegalStateException e) { IoUtils.closeQuietly(source); Log.e(LOG_TAG, "Cannot open file", e); throw new RemoteException(e.toString()); @@ -246,6 +246,111 @@ public final class PdfManipulationService extends Service { } @Override + public void applyPrintAttributes(PrintAttributes attributes) { + synchronized (mLock) { + throwIfNotOpened(); + if (DEBUG) { + Log.i(LOG_TAG, "applyPrintAttributes()"); + } + + Rect mediaBox = new Rect(); + Rect cropBox = new Rect(); + Matrix transform = new Matrix(); + + final boolean contentPortrait = attributes.getMediaSize().isPortrait(); + + final boolean layoutDirectionRtl = getResources().getConfiguration() + .getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; + + // We do not want to rotate the media box, so take into account orientation. + final int dstWidthPts = contentPortrait + ? pointsFromMils(attributes.getMediaSize().getWidthMils()) + : pointsFromMils(attributes.getMediaSize().getHeightMils()); + final int dstHeightPts = contentPortrait + ? pointsFromMils(attributes.getMediaSize().getHeightMils()) + : pointsFromMils(attributes.getMediaSize().getWidthMils()); + + final boolean scaleForPrinting = mEditor.shouldScaleForPrinting(); + + final int pageCount = mEditor.getPageCount(); + for (int i = 0; i < pageCount; i++) { + if (!mEditor.getPageMediaBox(i, mediaBox)) { + Log.e(LOG_TAG, "Malformed PDF file"); + return; + } + + final int srcWidthPts = mediaBox.width(); + final int srcHeightPts = mediaBox.height(); + + // Update the media box with the desired size. + mediaBox.right = dstWidthPts; + mediaBox.bottom = dstHeightPts; + mEditor.setPageMediaBox(i, mediaBox); + + // Make sure content is top-left after media box resize. + transform.setTranslate(0, srcHeightPts - dstHeightPts); + + // Rotate the content if in landscape. + if (!contentPortrait) { + transform.postRotate(270); + transform.postTranslate(0, dstHeightPts); + } + + // Scale the content if document allows it. + final float scale; + if (scaleForPrinting) { + if (contentPortrait) { + scale = Math.min((float) dstWidthPts / srcWidthPts, + (float) dstHeightPts / srcHeightPts); + transform.postScale(scale, scale); + } else { + scale = Math.min((float) dstWidthPts / srcHeightPts, + (float) dstHeightPts / srcWidthPts); + transform.postScale(scale, scale, mediaBox.left, mediaBox.bottom); + } + } else { + scale = 1.0f; + } + + // Update the crop box relatively to the media box change, if needed. + if (mEditor.getPageCropBox(i, cropBox)) { + cropBox.left = (int) (cropBox.left * scale + 0.5f); + cropBox.top = (int) (cropBox.top * scale + 0.5f); + cropBox.right = (int) (cropBox.right * scale + 0.5f); + cropBox.bottom = (int) (cropBox.bottom * scale + 0.5f); + cropBox.intersect(mediaBox); + mEditor.setPageCropBox(i, cropBox); + } + + // If in RTL mode put the content in the logical top-right corner. + if (layoutDirectionRtl) { + final float dx = contentPortrait + ? dstWidthPts - (int) (srcWidthPts * scale + 0.5f) : 0; + final float dy = contentPortrait + ? 0 : - (dstHeightPts - (int) (srcWidthPts * scale + 0.5f)); + transform.postTranslate(dx, dy); + } + + // Adjust the physical margins if needed. + Margins minMargins = attributes.getMinMargins(); + final int paddingLeftPts = pointsFromMils(minMargins.getLeftMils()); + final int paddingTopPts = pointsFromMils(minMargins.getTopMils()); + final int paddingRightPts = pointsFromMils(minMargins.getRightMils()); + final int paddingBottomPts = pointsFromMils(minMargins.getBottomMils()); + + Rect clip = new Rect(mediaBox); + clip.left += paddingLeftPts; + clip.top += paddingTopPts; + clip.right -= paddingRightPts; + clip.bottom -= paddingBottomPts; + + // Apply the accumulated transforms. + mEditor.setTransformAndClip(i, transform, clip); + } + } + } + + @Override public void write(ParcelFileDescriptor destination) throws RemoteException { synchronized (mLock) { try { diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java index 15ea9a7..f361884 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java +++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java @@ -592,7 +592,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat mDestinationSpinner.post(new Runnable() { @Override public void run() { - shredPagesAndFinish(uri); + transformDocumentAndFinish(uri); } }); } else if (resultCode == RESULT_CANCELED) { @@ -922,7 +922,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat if (mCurrentPrinter == mDestinationSpinnerAdapter.getPdfPrinter()) { startCreateDocumentActivity(); } else { - shredPagesAndFinish(null); + transformDocumentAndFinish(null); } } @@ -1597,8 +1597,11 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat return true; } - private void shredPagesAndFinish(final Uri writeToUri) { - new PageShredder(this, mPrintJob, mFileProvider, new Runnable() { + private void transformDocumentAndFinish(final Uri writeToUri) { + // If saving to PDF, apply the attibutes as we are acting as a print service. + PrintAttributes attributes = mDestinationSpinnerAdapter.getPdfPrinter() == mCurrentPrinter + ? mPrintJob.getAttributes() : null; + new DocumentTransformer(this, mPrintJob, mFileProvider, attributes, new Runnable() { @Override public void run() { if (writeToUri != null) { @@ -1606,7 +1609,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat } doFinish(); } - }).shred(); + }).transform(); } private void doFinish() { @@ -2329,7 +2332,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat } } - private static final class PageShredder implements ServiceConnection { + private static final class DocumentTransformer implements ServiceConnection { private static final String TEMP_FILE_PREFIX = "print_job"; private static final String TEMP_FILE_EXTENSION = ".pdf"; @@ -2341,20 +2344,24 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat private final PageRange[] mPagesToShred; + private final PrintAttributes mAttributesToApply; + private final Runnable mCallback; - public PageShredder(Context context, PrintJobInfo printJob, - MutexFileProvider fileProvider, Runnable callback) { + public DocumentTransformer(Context context, PrintJobInfo printJob, + MutexFileProvider fileProvider, PrintAttributes attributes, + Runnable callback) { mContext = context; mPrintJob = printJob; mFileProvider = fileProvider; mCallback = callback; mPagesToShred = computePagesToShred(mPrintJob); + mAttributesToApply = attributes; } - public void shred() { + public void transform() { // If we have only the pages we want, done. - if (mPagesToShred.length <= 0) { + if (mPagesToShred.length <= 0 && mAttributesToApply == null) { mCallback.run(); return; } @@ -2376,14 +2383,14 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat // final and this code is the last one to touch // them as shredding is the very last step, so the // UI is not interactive at this point. - shredPages(editor); + doTransform(editor); updatePrintJob(); return null; } @Override protected void onPostExecute(Void aVoid) { - mContext.unbindService(PageShredder.this); + mContext.unbindService(DocumentTransformer.this); mCallback.run(); } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); @@ -2394,7 +2401,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat /* do nothing */ } - private void shredPages(IPdfEditor editor) { + private void doTransform(IPdfEditor editor) { File tempFile = null; ParcelFileDescriptor src = null; ParcelFileDescriptor dst = null; @@ -2413,6 +2420,11 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat // Drop the pages. editor.removePages(mPagesToShred); + // Apply print attributes if needed. + if (mAttributesToApply != null) { + editor.applyPrintAttributes(mAttributesToApply); + } + // Write the modified PDF to a temp file. tempFile = File.createTempFile(TEMP_FILE_PREFIX, TEMP_FILE_EXTENSION, mContext.getCacheDir()); |
